Author: Kev <klmitch@mit.edu>
authorKevin L. Mitchell <klmitch@mit.edu>
Sat, 17 Mar 2007 22:45:35 +0000 (22:45 +0000)
committerKevin L. Mitchell <klmitch@mit.edu>
Sat, 17 Mar 2007 22:45:35 +0000 (22:45 +0000)
Log message:

Finish changes to G-line.  These changes enable "/stats g" and "/gline" to
show G-line lastmod, lifetime, and local activation status, as well as
allowing G-lines to be modified in virtually any way imaginable.  Note that
overlapping G-lines are now to be kept, as a potentially masking G-line may
actually be disabled and so not actually be masking a new G-line...

git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1780 c9e4aea6-c8fd-4c43-8297-357d70d61c8c

ChangeLog
doc/readme.gline
include/gline.h
ircd/gline.c
ircd/m_gline.c
ircd/s_err.c

index d8bd643b73130bdb930b2fdfd8cbc38d197bbf5b..908bc5fd9ed6c3fa8637de79ff4578874620f04d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,50 @@
+2007-03-17  Kevin L. Mitchell  <klmitch@mit.edu>
+
+       * ircd/s_err.c: update replies to handle new fields in
+       RPL_STATSGLINE and RPL_GLIST--new fields indicate G-line lastmod,
+       G-line lifetime, and local activation status
+
+       * ircd/m_gline.c: update function documentation for ms_gline();
+       move test for server to before mask processing; don't look up
+       remote server too early; add code to process local activations and
+       deactivations early in ms_gline(); implement adding and destroying
+       local G-lines; don't try to locally activate or deactivate G-lines
+       that don't exist; add code to keep track of which fields were
+       available to ms_gline(); implement G-line modification and
+       creation; remove old ms_gline() code; convert
+       sendwallto_group_butone() calls to Debug() calls; reimplement
+       mo_gline() to take into account new syntax
+
+       * ircd/gline.c: change gl_rexpire to gl_lifetime to better reflect
+       its meaning; make sure to set gl_state to GLOCAL_GLOBAL when
+       G-line expires; add lifetime parameter to make_gline(); disable
+       overlapping G-line check; initialize gl_lifetime from lifetime
+       parameter; initialize gl_state to GLOCAL_GLOBAL; update
+       gline_propagate() to send lifetime parameter; add lifetime
+       parameter to gline_add(); remove some old code in gline_add();
+       figure out lifetime to set on new G-line; remove test for
+       make_gline() returning NULL, since it should never do so now; add
+       modify_gline() for modifying global G-lines; add gline_destroy()
+       for destroying local G-lines; update gline_burst() to send
+       lifetime parameter; update gline_resend() to send lifetime
+       parameter; update gline_list() to add lastmod, lifetime, and local
+       status indicators; update gline_stats() to send lastmod, lifetime,
+       and local status indicators; count BADCHANs in
+       gline_memory_count()
+
+       * include/gline.h: add enum GlineLocalState for keeping track of
+       local state changes to global G-lines; store state in struct
+       Gline; document enum GlineAction; add GLINE_EXPIRE,
+       GLINE_LIFETIME, and GLINE_REASON flags to indicate the presence of
+       those fields to gline_modify(); add GLINE_UPDATE mask for checking
+       for the above flags; update GlineIsActive() to take into account
+       new gl_state field in struct Gline; add lifetime to gline_add();
+       add gline_modify() for modifying existing global G-lines, and
+       gline_destroy() for destroying local G-lines
+
+       * doc/readme.gline: update documentation to reflect changes made
+       to G-line command syntax
+
 2007-03-17  Michael Poole <mdpoole@troilus.org>
 
        * ircd/umkpasswd.c (parse_arguments): Exit cleanly rather than
index 0526898392ff985db282d8455920d2175c149af6..bbc00b4908f5195e2bd9fde4e9bdde46a3339741 100644 (file)
@@ -1,4 +1,4 @@
-GLINE documentation, last updated on 18 Mar 2000
+GLINE documentation, last updated on 17 Mar 2007
 
 For an ordinary user, the syntax is:
 
@@ -10,66 +10,115 @@ an error is returned.
 
 For an operator, the syntax is:
 
-  GLINE [[!][+|-]<mask> [[<target>] <expiration> :<reason>]]
-
-If <mask> is not given, or if it is not prefixed by "+" or "-", the
-operation is exactly the same as if it were issued by an ordinary
-user, except that a list of all G-lines may be returned.  If the "+"
-or "-" prefixes are used, the arguments <target>, <expiration>, and
-<reason> must be given, even if the G-line already exists.  If
-<target> is "*" and the currently existing G-line is a local G-line,
-the local G-line will be erased and recreated with the parameters
-given, as described below.  Otherwise, if the G-line currently exists,
-a prefix of "+" will cause an inactive G-line to be activated, whereas
-a prefix of "-" will cause an active G-line to be deactivated.  If an
-attempt is made to modify a G-line set by a U-lined service such as
-Uworld, the change will be forced to be local.  If the mask would not
-be permitted due to it being too wide or affecting too many users
-(governed by the GLINEMAXUSERCOUNT feature), the "!" prefix may be
-used to force the G-line to be set anyway.
-
-If the G-line does not already exist, it is created. The <target>
-parameter is used to select whether the G-line is only to apply to a
-single server (which need not be the local server) or to the whole
-network; if <target> is not given, it is assumed to be the local
-server.  This could be useful if a single particular link is having
-problems, for instance.  The <expiration> parameter is a number of
-seconds, not to exceed 7 days, for the G-line to exist.  The <reason>
-argument is mandatory and should describe why this particular G-line
-was placed.  The <mask> parameter must be a user@host mask; the host
-component must contain at least 2 non-wildcarded subdomains or, if it
-is an IP address, at least 16 bits.  Normally, the host component may
-not contain *any* wildcards, but that can be overridden with the "!"
-prefix, as indicated above, if the operator has the WIDE_GLINE
-privilege.
+  GLINE [[!][+|-|>|<]<mask> [<target>] [<expiration> [:<reason>]]]
+
+There are a total of 10 basic forms of the GLINE command.  If no
+arguments are given, all existing G-lines will be listed; if only
+<mask> is given, the behavior is the same as for an ordinary user.
+The remaining forms allow G-lines to be set, manipulated, or possibly
+destroyed.
+
+* Local G-lines.
+
+Opers may set or remove G-lines that only apply to a specific server.
+When the <target> parameter is not given, the specific server will be
+the local server; otherwise, it will be taken to be a remote server,
+and the G-line operations will take place there, if the oper has the
+GLINE privilege.  When <mask> is preceded with the '+' character, the
+G-line will be added, and <expiration> and <reason> are required; when
+<mask> is preceded with the '-' character, the G-line will be removed,
+and <expiration> and <reason> are not required.  The '<' and '>'
+character prefixes are not valid for local G-lines.
+
+* Local modifications to global G-lines.
+
+Opers may locally activate or deactivate global G-lines.  In this
+mode, <mask> is interpreted as referencing an existing G-line, and
+will be preceded by either '<' (to locally deactivate the G-line) or
+'>' (to locally activate the G-line).  This local state overrides the
+global state of the G-line, and persists until there is a global state
+change to the G-line, or until the G-line expires.  The <expiration>
+and <reason> arguments are not required, but <target> may be given if
+the oper desires to make the appropriate change on a remote
+server--note that the oper will need the GLINE privilege for this.
+
+* Global G-lines.
+
+Opers may, if they have the GLINE privilege, set and manipulate global
+G-lines on the network.  To create a new G-line, the oper must prefix
+the <mask> with either '+' (for globally activated G-lines) or '-'
+(for globally deactivated G-lines).  Additionally, <target> must be
+given as "*", and the <expiration> and <reason> parameters are
+required.  If the G-line already exists, it will be modified to match
+the new global status, <expiration>, and <reason>.
+
+When the G-line already exists, an oper may activate or deactivate it
+simply by setting <target> to "*" and prefixing the <mask> with either
+"+" (to activate the G-line) or "-" (to deactivate it).  If it is
+desired to simply modify the expiration time or reason, without
+changing the activation status, specify <mask> without any prefix, set
+<target> to "*", and provide the updated <expire> and optionally an
+updated <reason>.
+
+* Privilege notes.
+
+Note that, for all locally-restricted G-line changes, such as locally
+activating a G-line or creating a local G-line, the oper must have the
+LOCAL_GLINE privilege.  For any other G-line change, including
+locally-restricted changes on remote servers, the server's
+CONFIG_OPERCMDS privilege must be enabled and the oper must have the
+GLINE privilege.  There are also restrictions to prevent an oper from
+setting a G-line that is too wide; in some cases, those restrictions
+may be overridden by prefixing the <mask> parameter with the "!"
+character, IF the operator has the WIDE_GLINE privilege.
 
 For a server, the syntax is:
 
-  <prefix> GL <target> (+|-)<mask> <expiration> <lastmod> :<reason>
-
-The <target> may be a server numeric or the character "*", for a
-globally scoped G-line.  The <mask> argument is a server name, and
-must be prefixed by one of "+" (to indicate an active G-line) or "-"
-(to indicate an inactive G-line).  The parameter <expiration> is a
-total number of seconds the G-line is to live for, and <lastmod> is
-used for versioning.  Since GLINEs are propagated during netbursts,
-there must be some way of resolving conflicting states, which is the
-reason for this argument, and is also the reason G-lines cannot be
-deleted, only deactivated.  The <reason> parameter indicates the
-reason the G-line was placed.
-
-If a GLINE is received with a <target> of "*", any G-lines with local
-scope are deleted, in preference for the globally scoped version.  If
-the G-line already exists, the values of <lastmod> are compared; if
-the received <lastmod> is less than the stored <lastmod>, the existing
-G-line is resent to the server from which the GLINE message was
-received; otherwise, the G-line is activated or deactivated, depending
-on the <mask> prefix.  If the G-line does not currently exist, it is
-created with the parameters given.
-
-For a U-lined server, this syntax should be used:
-
-  <prefix> GL <target> +<mask> <expiration> :<reason>
-  <prefix> GL <target> -<mask>
-
-The <lastmod> parameter will be assumed to be 0.
+  <prefix> GL <target> [!][+|-|>|<]<mask> [<expiration>] [<lastmod>]
+       [<lifetime>] [:<reason>]
+
+There are a total of 8 basic forms of the GL command.  The primary
+innovation is the addition of the <lifetime> parameter, which
+specifies a lifetime for the G-line record which may be longer than
+the expiration time.  <lifetime> will be monotonically increasing,
+enabling <expiration> to be modified in any way desirable.
+
+* Local G-lines.
+
+Remote servers, or opers on them, may remotely set local G-lines on
+the local server.  To create a local G-line, <target> will be set to
+the numeric of the local server, and <mask> must be preceded by '+'
+(optionally preceded by '!' if the origin desires to override some
+safety settings).  The <expiration> and <reason> parameters are
+required.  The <lastmod> and <lifetime> parameters will be ignored if
+present, allowing backwards compatibility with ircu2.10.12.10 and
+prior versions.  Removing local G-lines is similar--<mask> must be
+preceded by '-', and all other parameters are ignored to allow
+backwards compatibility.
+
+* Local modifications to global G-lines.
+
+Remote servers, or opers on them, may also locally activate or
+deactivate a global G-line on the local server.  The <target> must be
+set to the numeric of the local server, and <mask> must be preceded by
+either '<' (to locally deactivate the G-line) or '>' (to locally
+activate the G-line).  This local state overrides the global state of
+the G-line, and persists until there is a global state change to the
+G-line, or until the G-line expires.  No other parameters are
+necessary in this mode, and will be ignored if present.
+
+* Global G-lines.
+
+For creation and manipulation of global G-lines, the <target>
+parameter must be set to "*".  If the G-line does not exist, and if
+<expiration> is given, the G-line will be created with the specified
+expiration and <reason> (the latter defaulting to "No reason" if not
+present).  Otherwise, the G-line will be updated according to the
+available parameters.  The rules are similar to those for oper-issued
+global G-lines, with the addition of a <lastmod> parameter, which is a
+monotonically increasing serial number for the G-line, and an optional
+<lifetime> parameter that specifies a monotonically increasing
+lifetime for the G-line record.  Note that, for existing G-lines where
+only state changes (global activation or deactivation) are necessary,
+only <lastmod> is required; <expiration> must be specified for all
+other forms of the GL command.
index 0fa3aba7fb6edb8c6f27381bacca82285ede4357..a2acdd245e52477c1ff5924a2720807622cc56c5 100644 (file)
@@ -38,27 +38,36 @@ struct StatDesc;
 
 #define GLINE_MAX_EXPIRE 604800        /**< max expire: 7 days */
 
+/** Local state of a G-line. */
+enum GlineLocalState {
+  GLOCAL_GLOBAL,               /**< G-line state unmodified locally. */
+  GLOCAL_ACTIVATED,            /**< G-line state locally activated. */
+  GLOCAL_DEACTIVATED           /**< G-line state locally deactivated. */
+};
+
 /** Description of a G-line. */
 struct Gline {
-  struct Gline *gl_next;      /**< Next G-line in linked list. */
-  struct Gline**gl_prev_p;    /**< Previous pointer to this G-line. */
-  char        *gl_user;      /**< Username mask (or channel/realname mask). */
-  char        *gl_host;      /**< Host prtion of mask. */
-  char        *gl_reason;    /**< Reason for G-line. */
-  time_t       gl_expire;    /**< Expiration timestamp. */
-  time_t       gl_lastmod;   /**< Last modification timestamp. */
-  time_t       gl_rexpire;   /**< Record expiration timestamp. */
-  struct irc_in_addr gl_addr; /**< IP address (for IP-based G-lines). */
-  unsigned char gl_bits;      /**< Usable bits in gl_addr. */
-  unsigned int gl_flags;     /**< G-line status flags. */
+  struct Gline *gl_next;       /**< Next G-line in linked list. */
+  struct Gline**gl_prev_p;     /**< Previous pointer to this G-line. */
+  char        *gl_user;        /**< Username mask (or channel/realname mask). */
+  char        *gl_host;        /**< Host prtion of mask. */
+  char        *gl_reason;      /**< Reason for G-line. */
+  time_t       gl_expire;      /**< Expiration timestamp. */
+  time_t       gl_lastmod;     /**< Last modification timestamp. */
+  time_t       gl_lifetime;    /**< Record expiration timestamp. */
+  struct irc_in_addr gl_addr;  /**< IP address (for IP-based G-lines). */
+  unsigned char gl_bits;       /**< Usable bits in gl_addr. */
+  unsigned int gl_flags;       /**< G-line status flags. */
+  enum GlineLocalState gl_state;/**< G-line local state. */
 };
 
+/** Action to perform on a G-line. */
 enum GlineAction {
-  GLINE_ACTIVATE,
-  GLINE_DEACTIVATE,
-  GLINE_LOCAL_ACTIVATE,
-  GLINE_LOCAL_DEACTIVATE,
-  GLINE_MODIFY
+  GLINE_ACTIVATE,              /**< G-line should be activated. */
+  GLINE_DEACTIVATE,            /**< G-line should be deactivated. */
+  GLINE_LOCAL_ACTIVATE,                /**< G-line should be locally activated. */
+  GLINE_LOCAL_DEACTIVATE,      /**< G-line should be locally deactivated. */
+  GLINE_MODIFY                 /**< G-line should be modified. */
 };
 
 #define GLINE_ACTIVE   0x0001  /**< G-line is active. */
@@ -74,14 +83,22 @@ enum GlineAction {
 #define GLINE_OPERFORCE        0x0400  /**< Oper forcing G-line to be set. */
 #define GLINE_REALNAME  0x0800  /**< G-line matches only the realname field. */
 
+#define GLINE_EXPIRE   0x1000  /**< Expiration time update */
+#define GLINE_LIFETIME 0x2000  /**< Record lifetime update */
+#define GLINE_REASON   0x4000  /**< Reason update */
+
 /** Controllable flags that can be set on an actual G-line. */
 #define GLINE_MASK     (GLINE_ACTIVE | GLINE_BADCHAN | GLINE_LOCAL | GLINE_REALNAME)
 /** Mask for G-line activity flags. */
 #define GLINE_ACTMASK  (GLINE_ACTIVE | GLINE_LDEACT)
 
+/** Mask for G-line update flags. */
+#define GLINE_UPDATE   (GLINE_EXPIRE | GLINE_LIFETIME | GLINE_REASON)
+
 /** Test whether \a g is active. */
-#define GlineIsActive(g)       (((g)->gl_flags & GLINE_ACTMASK) == \
-                                GLINE_ACTIVE)
+#define GlineIsActive(g)       ((((g)->gl_flags & GLINE_ACTIVE) &&       \
+                                 (g)->gl_state != GLOCAL_DEACTIVATED) || \
+                                (g)->gl_state == GLOCAL_ACTIVATED)
 /** Test whether \a g is remotely (globally) active. */
 #define GlineIsRemActive(g)    ((g)->gl_flags & GLINE_ACTIVE)
 /** Test whether \a g is an IP-based G-line. */
@@ -106,13 +123,19 @@ extern int gline_propagate(struct Client *cptr, struct Client *sptr,
                           struct Gline *gline);
 extern int gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
                     char *reason, time_t expire, time_t lastmod,
-                    unsigned int flags);
+                    time_t lifetime, unsigned int flags);
 extern int gline_activate(struct Client *cptr, struct Client *sptr,
                          struct Gline *gline, time_t lastmod,
                          unsigned int flags);
 extern int gline_deactivate(struct Client *cptr, struct Client *sptr,
                            struct Gline *gline, time_t lastmod,
                            unsigned int flags);
+extern int gline_modify(struct Client *cptr, struct Client *sptr,
+                       struct Gline *gline, enum GlineAction action,
+                       char *reason, time_t expire, time_t lastmod,
+                       time_t lifetime, unsigned int flags);
+extern int gline_destroy(struct Client *cptr, struct Client *sptr,
+                        struct Gline *gline);
 extern struct Gline *gline_find(char *userhost, unsigned int flags);
 extern struct Gline *gline_lookup(struct Client *cptr, unsigned int flags);
 extern void gline_free(struct Gline *gline);
index f7cb6192c79a61ce3de1cb600e3dd87da65e9281..5ae577e785423b82785191bf4f5fc4d0f001cbea 100644 (file)
@@ -40,6 +40,7 @@
 #include "s_stats.h"
 #include "send.h"
 #include "struct.h"
+#include "sys.h"
 #include "msg.h"
 #include "numnicks.h"
 #include "numeric.h"
@@ -82,12 +83,13 @@ struct Gline* BadChanGlineList = 0;
     /* Figure out the next pointer in list... */       \
     if ((((next) = (gl)->gl_next) || 1) &&             \
        /* Then see if it's expired */                  \
-       (gl)->gl_rexpire <= CurrentTime)                \
+       (gl)->gl_lifetime <= CurrentTime)               \
       /* Record has expired, so free the G-line */     \
       gline_free((gl));                                        \
     /* See if we need to expire the G-line */          \
     else if (((gl)->gl_expire > CurrentTime ||         \
-             ((gl)->gl_flags &= ~GLINE_ACTIVE)) && 0)  \
+             ((gl)->gl_flags &= ~GLINE_ACTIVE) ||      \
+             ((gl)->gl_state = GLOCAL_GLOBAL)) && 0)   \
       ; /* empty statement */                          \
     else
 
@@ -133,41 +135,49 @@ canon_userhost(char *userhost, char **user_p, char **host_p, char *def_user)
  */
 static struct Gline *
 make_gline(char *user, char *host, char *reason, time_t expire, time_t lastmod,
-          unsigned int flags)
+          time_t lifetime, unsigned int flags)
 {
-  struct Gline *gline, *sgline, *after = 0;
-
-  if (!(flags & GLINE_BADCHAN)) { /* search for overlapping glines first */
-
-    for (gline = GlobalGlineList; gline; gline = sgline) {
-      sgline = gline->gl_next;
-
-      if (gline->gl_expire <= CurrentTime)
-       gline_free(gline);
-      else if (((gline->gl_flags & GLINE_LOCAL) != (flags & GLINE_LOCAL)) ||
-               (gline->gl_host && !host) || (!gline->gl_host && host))
-       continue;
-      else if (!mmatch(gline->gl_user, user) /* gline contains new mask */
-              && (gline->gl_host == NULL || !mmatch(gline->gl_host, host))) {
-       if (expire <= gline->gl_expire) /* will expire before wider gline */
-         return 0;
-       else
-         after = gline; /* stick new gline after this one */
-      } else if (!mmatch(user, gline->gl_user) /* new mask contains gline */
-                && (gline->gl_host==NULL || !mmatch(host, gline->gl_host)) 
-                && gline->gl_expire <= expire) /* old expires before new */
-       gline_free(gline); /* save some memory */
-    }
-  }
+  struct Gline *gline, *after = 0;
+
+  /* Disable checking for overlapping G-lines.  The problem is that we
+   * may have a wide-mask, long lifetime G-line that we've
+   * deactivated--maybe it was a mistake?--and someone comes along and
+   * wants to set a narrower overlapping G-line with a shorter
+   * lifetime.  If we were to leave this logic enabled, there would be
+   * no way to set that narrower G-line.
+   */
+/*   if (!(flags & GLINE_BADCHAN)) { /\* search for overlapping glines first *\/ */
+
+/*     for (gline = GlobalGlineList; gline; gline = sgline) { */
+/*       sgline = gline->gl_next; */
+
+/*       if (gline->gl_expire <= CurrentTime) */
+/*     gline_free(gline); */
+/*       else if (((gline->gl_flags & GLINE_LOCAL) != (flags & GLINE_LOCAL)) || */
+/*                (gline->gl_host && !host) || (!gline->gl_host && host)) */
+/*     continue; */
+/*       else if (!mmatch(gline->gl_user, user) /\* gline contains new mask *\/ */
+/*            && (gline->gl_host == NULL || !mmatch(gline->gl_host, host))) { */
+/*     if (expire <= gline->gl_expire) /\* will expire before wider gline *\/ */
+/*       return 0; */
+/*     else */
+/*       after = gline; /\* stick new gline after this one *\/ */
+/*       } else if (!mmatch(user, gline->gl_user) /\* new mask contains gline *\/ */
+/*              && (gline->gl_host==NULL || !mmatch(host, gline->gl_host))  */
+/*              && gline->gl_expire <= expire) /\* old expires before new *\/ */
+/*     gline_free(gline); /\* save some memory *\/ */
+/*     } */
+/*   } */
 
   gline = (struct Gline *)MyMalloc(sizeof(struct Gline)); /* alloc memory */
   assert(0 != gline);
 
   DupString(gline->gl_reason, reason); /* initialize gline... */
   gline->gl_expire = expire;
-  gline->gl_rexpire = expire;
+  gline->gl_lifetime = lifetime;
   gline->gl_lastmod = lastmod;
   gline->gl_flags = flags & GLINE_MASK;
+  gline->gl_state = GLOCAL_GLOBAL; /* not locally modified */
 
   if (flags & GLINE_BADCHAN) { /* set a BADCHAN gline */
     DupString(gline->gl_user, user); /* first, remember channel */
@@ -355,24 +365,17 @@ gline_checkmask(char *mask)
 int
 gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline)
 {
-  if (GlineIsLocal(gline) || (IsUser(sptr) && !gline->gl_lastmod))
+  if (GlineIsLocal(gline))
     return 0;
 
-  if (gline->gl_lastmod)
-    sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s",
-                         GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
-                         gline->gl_host ? "@" : "",
-                         gline->gl_host ? gline->gl_host : "",
-                         gline->gl_expire - CurrentTime, gline->gl_lastmod,
-                         gline->gl_reason);
-  else
-    sendcmdto_serv_butone(sptr, CMD_GLINE, cptr,
-                         (GlineIsRemActive(gline) ?
-                          "* +%s%s%s %Tu :%s" : "* -%s%s%s"),
-                         gline->gl_user, 
-                         gline->gl_host ? "@" : "",
-                         gline->gl_host ? gline->gl_host : "",
-                         gline->gl_expire - CurrentTime, gline->gl_reason);
+  assert(gline->gl_lastmod);
+
+  sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
+                       GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
+                       gline->gl_host ? "@" : "",
+                       gline->gl_host ? gline->gl_host : "",
+                       gline->gl_expire - CurrentTime, gline->gl_lastmod,
+                       gline->gl_lifetime - CurrentTime, gline->gl_reason);
 
   return 0;
 }
@@ -390,12 +393,14 @@ gline_propagate(struct Client *cptr, struct Client *sptr, struct Gline *gline)
  * @param[in] reason Reason for G-line.
  * @param[in] expire Duration of G-line in seconds.
  * @param[in] lastmod Last modification time of G-line.
+ * @param[in] lifetime Lifetime of G-line.
  * @param[in] flags Bitwise combination of GLINE_* flags.
  * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal.
  */
 int
 gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
-         char *reason, time_t expire, time_t lastmod, unsigned int flags)
+         char *reason, time_t expire, time_t lastmod, time_t lifetime,
+         unsigned int flags)
 {
   struct Gline *agline;
   char uhmask[USERLEN + HOSTLEN + 2];
@@ -405,6 +410,10 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
   assert(0 != userhost);
   assert(0 != reason);
 
+  Debug((DEBUG_DEBUG, "gline_add(\"%s\", \"%s\", \"%s\", \"%s\", %Tu, %Tu "
+        "%Tu, 0x%04x)", cli_name(cptr), cli_name(sptr), userhost, reason,
+        expire, lastmod, lifetime, flags));
+
   if (*userhost == '#' || *userhost == '&') {
     if ((flags & GLINE_LOCAL) && !HasPriv(sptr, PRIV_LOCAL_BADCHAN))
       return send_reply(sptr, ERR_NOPRIVILEGES);
@@ -413,7 +422,7 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
     user = userhost;
     host = 0;
   } else if (*userhost == '$') {
-    switch (*userhost == '$' ? userhost[1] : userhost[3]) {
+    switch (userhost[1]) {
       case 'R': flags |= GLINE_REALNAME; break;
       default:
         /* uh, what to do here? */
@@ -427,7 +436,7 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
         }
         break;
     }
-     user = (*userhost =='$' ? userhost : userhost+2);
+     user = userhost;
      host = 0;
   } else {
     canon_userhost(userhost, &user, &host, "*");
@@ -463,6 +472,11 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
 
   expire += CurrentTime; /* convert from lifetime to timestamp */
 
+  if (!lifetime) /* no lifetime set, use expiration time */
+    lifetime = expire;
+  else /* convert lifetime into timestamp */
+    lifetime += CurrentTime;
+
   /* Inform ops... */
   sendto_opmask_butone(0, ircd_strncmp(reason, "AUTO", 4) ? SNO_GLINE :
                        SNO_AUTO, "%s adding %s %s for %s%s%s, expiring at "
@@ -486,10 +500,14 @@ gline_add(struct Client *cptr, struct Client *sptr, char *userhost,
            expire + TSoffset, reason);
 
   /* make the gline */
-  agline = make_gline(user, host, reason, expire, lastmod, flags);
+  agline = make_gline(user, host, reason, expire, lastmod, lifetime, flags);
 
-  if (!agline) /* if it overlapped, silently return */
-    return 0;
+  /* since we've disabled overlapped G-line checking, agline should
+   * never be NULL...
+   */
+  assert(agline);
+/*   if (!agline) /\* if it overlapped, silently return *\/ */
+/*     return 0; */
 
   gline_propagate(cptr, sptr, agline);
 
@@ -626,6 +644,240 @@ gline_deactivate(struct Client *cptr, struct Client *sptr, struct Gline *gline,
   return 0;
 }
 
+/** Modify a global G-line.
+ * @param[in] cptr Client that sent us the G-line modification.
+ * @param[in] sptr Client that originated the G-line modification.
+ * @param[in] gline G-line being modified.
+ * @param[in] action Resultant status of the G-line.
+ * @param[in] reason Reason for G-line.
+ * @param[in] expire Duration of G-line in seconds.
+ * @param[in] lastmod Last modification time of G-line.
+ * @param[in] lifetime Lifetime of G-line.
+ * @param[in] flags Bitwise combination of GLINE_* flags.
+ * @return Zero or CPTR_KILLED, depending on whether \a sptr is suicidal.
+ */
+int
+gline_modify(struct Client *cptr, struct Client *sptr, struct Gline *gline,
+            enum GlineAction action, char *reason, time_t expire,
+            time_t lastmod, time_t lifetime, unsigned int flags)
+{
+  char buf[BUFSIZE], *op = "";
+  int pos = 0;
+
+  assert(gline);
+  assert(!GlineIsLocal(gline));
+
+  Debug((DEBUG_DEBUG,  "gline_modify(\"%s\", \"%s\", \"%s%s%s\", %s, \"%s\", "
+        "%Tu, %Tu, %Tu, 0x%04x)", cli_name(cptr), cli_name(sptr),
+        gline->gl_user, gline->gl_host ? "@" : "",
+        gline->gl_host ? gline->gl_host : "",
+        action == GLINE_ACTIVATE ? "GLINE_ACTIVATE" :
+        (action == GLINE_DEACTIVATE ? "GLINE_DEACTIVATE" :
+         (action == GLINE_LOCAL_ACTIVATE ? "GLINE_LOCAL_ACTIVATE" :
+          (action == GLINE_LOCAL_DEACTIVATE ? "GLINE_LOCAL_DEACTIVATE" :
+           (action == GLINE_MODIFY ? "GLINE_MODIFY" : "<UNKNOWN>")))),
+        reason, expire, lastmod, lifetime, flags));
+
+  /* First, let's check lastmod... */
+  if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE) {
+    if (GlineLastMod(gline) > lastmod) { /* we have a more recent version */
+      if (IsBurstOrBurstAck(cptr))
+       return 0; /* middle of a burst, it'll resync on its own */
+      return gline_resend(cptr, gline); /* resync the server */
+    } else if (GlineLastMod(gline) == lastmod)
+      return 0; /* we have that version of the G-line... */
+  }
+
+  /* All right, we know that there's a change of some sort.  What is it? */
+  /* first, check out the expiration time... */
+  if ((flags & GLINE_EXPIRE) && expire) {
+    if (!(flags & GLINE_FORCE) && (expire <= 0 || expire > GLINE_MAX_EXPIRE)) {
+      if (!IsServer(sptr) && MyConnect(sptr)) /* bad expiration time */
+       send_reply(sptr, ERR_BADEXPIRE, expire);
+      return 0;
+    }
+
+    /* convert to a timestamp... */
+    expire += CurrentTime;
+  } else
+    flags &= ~GLINE_EXPIRE;
+
+  /* Now check to see if there's any change... */
+  if ((flags & GLINE_EXPIRE) && expire == gline->gl_expire) {
+    flags &= ~GLINE_EXPIRE; /* no change to expiration time... */
+    expire = 0;
+  }
+
+  /* Next, check out lifetime--this one's a bit trickier... */
+  if ((flags & GLINE_LIFETIME) && lifetime)
+    lifetime += CurrentTime; /* convert to a timestamp */
+  else
+    lifetime = gline->gl_lifetime; /* use G-line lifetime */
+
+  lifetime = IRCD_MAX(lifetime, expire); /* set lifetime to the max */
+
+  /* OK, let's see which is greater... */
+  if (lifetime > gline->gl_lifetime)
+    flags |= GLINE_LIFETIME; /* have to update lifetime */
+  else {
+    flags &= ~GLINE_LIFETIME; /* no change to lifetime */
+    lifetime = 0;
+  }
+
+  /* Finally, let's see if the reason needs to be updated */
+  if ((flags & GLINE_REASON) && reason &&
+      !ircd_strcmp(gline->gl_reason, reason))
+    flags &= ~GLINE_REASON; /* no changes to the reason */
+
+  /* OK, now let's take a look at the action... */
+  if ((action == GLINE_ACTIVATE && (gline->gl_flags & GLINE_ACTIVE)) ||
+      (action == GLINE_DEACTIVATE && !(gline->gl_flags & GLINE_ACTIVE)) ||
+      (action == GLINE_LOCAL_ACTIVATE &&
+       (gline->gl_state == GLOCAL_ACTIVATED)) ||
+      (action == GLINE_LOCAL_DEACTIVATE &&
+       (gline->gl_state == GLOCAL_DEACTIVATED)) ||
+      /* can't activate an expired G-line */
+      IRCD_MAX(gline->gl_expire, expire) <= CurrentTime)
+    action = GLINE_MODIFY; /* no activity state modifications */
+
+  Debug((DEBUG_DEBUG,  "About to perform changes; flags 0x%04x, action %s",
+        flags, action == GLINE_ACTIVATE ? "GLINE_ACTIVATE" :
+        (action == GLINE_DEACTIVATE ? "GLINE_DEACTIVATE" :
+         (action == GLINE_LOCAL_ACTIVATE ? "GLINE_LOCAL_ACTIVATE" :
+          (action == GLINE_LOCAL_DEACTIVATE ? "GLINE_LOCAL_DEACTIVATE" :
+           (action == GLINE_MODIFY ? "GLINE_MODIFY" : "<UNKNOWN>"))))));
+
+  /* If there are no changes to perform, do no changes */
+  if (!(flags & GLINE_UPDATE) && action == GLINE_MODIFY)
+    return 0;
+
+  /* Now we know what needs to be changed, so let's process the changes... */
+
+  /* Start by updating lastmod, if indicated... */
+  if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE)
+    gline->gl_lastmod = lastmod;
+
+  /* Then move on to activity status changes... */
+  switch (action) {
+  case GLINE_ACTIVATE: /* Globally activating G-line */
+    gline->gl_flags |= GLINE_ACTIVE; /* make it active... */
+    gline->gl_state = GLOCAL_GLOBAL; /* reset local activity state */
+    pos += ircd_snprintf(0, buf, sizeof(buf), " globally activating G-line");
+    op = "+"; /* operation for G-line propagation */
+    break;
+
+  case GLINE_DEACTIVATE: /* Globally deactivating G-line */
+    gline->gl_flags &= ~GLINE_ACTIVE; /* make it inactive... */
+    gline->gl_state = GLOCAL_GLOBAL; /* reset local activity state */
+    pos += ircd_snprintf(0, buf, sizeof(buf), " globally deactivating G-line");
+    op = "-"; /* operation for G-line propagation */
+    break;
+
+  case GLINE_LOCAL_ACTIVATE: /* Locally activating G-line */
+    gline->gl_state = GLOCAL_ACTIVATED; /* make it locally active */
+    pos += ircd_snprintf(0, buf, sizeof(buf), " locally activating G-line");
+    break;
+
+  case GLINE_LOCAL_DEACTIVATE: /* Locally deactivating G-line */
+    gline->gl_state = GLOCAL_DEACTIVATED; /* make it locally inactive */
+    pos += ircd_snprintf(0, buf, sizeof(buf), " locally deactivating G-line");
+    break;
+
+  case GLINE_MODIFY: /* no change to activity status */
+    break;
+  }
+
+  /* Handle expiration changes... */
+  if (flags & GLINE_EXPIRE) {
+    gline->gl_expire = expire; /* save new expiration time */
+    if (pos < BUFSIZE)
+      pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
+                          "%s%s changing expiration time to %Tu",
+                          pos ? ";" : "",
+                          pos && !(flags & (GLINE_LIFETIME | GLINE_REASON)) ?
+                          " and" : "", expire);
+  }
+
+  /* Next, handle lifetime changes... */
+  if (flags & GLINE_LIFETIME) {
+    gline->gl_lifetime = lifetime; /* save new lifetime */
+    if (pos < BUFSIZE)
+      pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
+                          "%s%s extending record lifetime to %Tu",
+                          pos ? ";" : "", pos && !(flags & GLINE_REASON) ?
+                          " and" : "", lifetime);
+  }
+
+  /* Now, handle reason changes... */
+  if (flags & GLINE_REASON) {
+    MyFree(gline->gl_reason); /* release old reason */
+    DupString(gline->gl_reason, reason); /* store new reason */
+    if (pos < BUFSIZE)
+      pos += ircd_snprintf(0, buf + pos, sizeof(buf) - pos,
+                          "%s%s changing reason to \"%s\"",
+                          pos ? ";" : "", pos ? " and" : "", reason);
+  }
+
+  /* All right, inform ops... */
+  sendto_opmask_butone(0, SNO_GLINE, "%s modifying global %s for %s%s%s:%s",
+                      (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
+                      cli_name(sptr) : cli_name((cli_user(sptr))->server),
+                      GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
+                      gline->gl_user, gline->gl_host ? "@" : "",
+                      gline->gl_host ? gline->gl_host : "", buf);
+
+  /* and log the change */
+  log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
+           "%#C modifying global %s for %s%s%s:%s", sptr,
+           GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
+           gline->gl_host ? "@" : "", gline->gl_host ? gline->gl_host : "",
+           buf);
+
+  /* We'll be simple for this release, but we can update this to change
+   * the propagation syntax on future updates
+   */
+  if (action != GLINE_LOCAL_ACTIVATE && action != GLINE_LOCAL_DEACTIVATE)
+    sendcmdto_serv_butone(sptr, CMD_GLINE, cptr,
+                         "* %s%s%s%s%s %Tu %Tu %Tu :%s",
+                         flags & GLINE_OPERFORCE ? "!" : "", op,
+                         gline->gl_user, gline->gl_host ? "@" : "",
+                         gline->gl_host ? gline->gl_host : "",
+                         gline->gl_expire - CurrentTime, gline->gl_lastmod,
+                         gline->gl_lifetime - CurrentTime, gline->gl_reason);
+
+  /* OK, let's do the G-line... */
+  return do_gline(cptr, sptr, gline);
+}
+
+/** Destroy a local G-line.
+ * @param[in] cptr Peer that gave us the message.
+ * @param[in] sptr Client that initiated the destruction.
+ * @param[in] gline G-line to destroy.
+ * @return Zero.
+ */
+int
+gline_destroy(struct Client *cptr, struct Client *sptr, struct Gline *gline)
+{
+  assert(gline);
+  assert(GlineIsLocal(gline));
+
+  /* Inform ops and log it */
+  sendto_opmask_butone(0, SNO_GLINE, "%s removing local %s for %s%s%s",
+                      (feature_bool(FEAT_HIS_SNOTICES) || IsServer(sptr)) ?
+                      cli_name(sptr) : cli_name((cli_user(sptr))->server),
+                      GlineIsBadChan(gline) ? "BADCHAN" : "GLINE",
+                      gline->gl_user, gline->gl_host ? "@" : "",
+                      gline->gl_host ? gline->gl_host : "");
+  log_write(LS_GLINE, L_INFO, LOG_NOSNOTICE,
+           "%#C removing local %s for %s%s%s", sptr,
+           GlineIsBadChan(gline) ? "BADCHAN" : "GLINE", gline->gl_user,
+           gline->gl_host ? "@" : "", gline->gl_host ? gline->gl_host : "");
+
+  gline_free(gline); /* get rid of the G-line */
+
+  return 0; /* convenience return */
+}
+
 /** Find a G-line for a particular mask, guided by certain flags.
  * Certain bits in \a flags are interpreted specially:
  * <dl>
@@ -764,20 +1016,20 @@ gline_burst(struct Client *cptr)
 
   gliter(GlobalGlineList, gline, sgline) {
     if (!GlineIsLocal(gline) && gline->gl_lastmod)
-      sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s",
+      sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
                    GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
                     gline->gl_host ? "@" : "",
                     gline->gl_host ? gline->gl_host : "",
                    gline->gl_expire - CurrentTime, gline->gl_lastmod,
-                    gline->gl_reason);
+                    gline->gl_lifetime - CurrentTime, gline->gl_reason);
   }
 
   gliter(BadChanGlineList, gline, sgline) {
     if (!GlineIsLocal(gline) && gline->gl_lastmod)
-      sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu :%s",
+      sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s %Tu %Tu %Tu :%s",
                    GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
                    gline->gl_expire - CurrentTime, gline->gl_lastmod,
-                   gline->gl_reason);
+                   gline->gl_lifetime - CurrentTime, gline->gl_reason);
   }
 }
 
@@ -792,12 +1044,12 @@ gline_resend(struct Client *cptr, struct Gline *gline)
   if (GlineIsLocal(gline) || !gline->gl_lastmod)
     return 0;
 
-  sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu :%s",
+  sendcmdto_one(&me, CMD_GLINE, cptr, "* %c%s%s%s %Tu %Tu %Tu :%s",
                GlineIsRemActive(gline) ? '+' : '-', gline->gl_user,
                gline->gl_host ? "@" : "",
                 gline->gl_host ? gline->gl_host : "",
                gline->gl_expire - CurrentTime, gline->gl_lastmod,
-               gline->gl_reason);
+               gline->gl_lifetime - CurrentTime, gline->gl_reason);
 
   return 0;
 }
@@ -823,24 +1075,33 @@ gline_list(struct Client *sptr, char *userhost)
     send_reply(sptr, RPL_GLIST, gline->gl_user,
                gline->gl_host ? "@" : "",
                gline->gl_host ? gline->gl_host : "",
-              gline->gl_expire + TSoffset,
+              gline->gl_expire + TSoffset, gline->gl_lastmod,
+              gline->gl_lifetime + TSoffset,
               GlineIsLocal(gline) ? cli_name(&me) : "*",
-              GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
+              gline->gl_state == GLOCAL_ACTIVATED ? ">" :
+              (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
+              GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
   } else {
     gliter(GlobalGlineList, gline, sgline) {
       send_reply(sptr, RPL_GLIST, gline->gl_user,
                 gline->gl_host ? "@" : "",
                 gline->gl_host ? gline->gl_host : "",
-                gline->gl_expire + TSoffset,
+                gline->gl_expire + TSoffset, gline->gl_lastmod,
+                gline->gl_lifetime + TSoffset,
                 GlineIsLocal(gline) ? cli_name(&me) : "*",
-                GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
+                gline->gl_state == GLOCAL_ACTIVATED ? ">" :
+                (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
+                GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
     }
 
     gliter(BadChanGlineList, gline, sgline) {
       send_reply(sptr, RPL_GLIST, gline->gl_user, "", "",
-                gline->gl_expire + TSoffset,
+                gline->gl_expire + TSoffset, gline->gl_lastmod,
+                gline->gl_lifetime + TSoffset,
                 GlineIsLocal(gline) ? cli_name(&me) : "*",
-                GlineIsActive(gline) ? '+' : '-', gline->gl_reason);
+                gline->gl_state == GLOCAL_ACTIVATED ? ">" :
+                (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
+                GlineIsRemActive(gline) ? '+' : '-', gline->gl_reason);
     }
   }
 
@@ -864,8 +1125,11 @@ gline_stats(struct Client *sptr, const struct StatDesc *sd,
     send_reply(sptr, RPL_STATSGLINE, 'G', gline->gl_user,
               gline->gl_host ? "@" : "",
               gline->gl_host ? gline->gl_host : "",
-              gline->gl_expire + TSoffset,
-              GlineIsActive(gline) ? '+' : '-',
+              gline->gl_expire + TSoffset, gline->gl_lastmod,
+              gline->gl_lifetime + TSoffset,
+              gline->gl_state == GLOCAL_ACTIVATED ? ">" :
+              (gline->gl_state == GLOCAL_DEACTIVATED ? "<" : ""),
+              GlineIsRemActive(gline) ? '+' : '-',
               gline->gl_reason);
   }
 }
@@ -880,13 +1144,21 @@ gline_memory_count(size_t *gl_size)
   struct Gline *gline;
   unsigned int gl = 0;
 
-  for (gline = GlobalGlineList; gline; gline = gline->gl_next)
-  {
+  for (gline = GlobalGlineList; gline; gline = gline->gl_next) {
+    gl++;
+    *gl_size += sizeof(struct Gline);
+    *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0;
+    *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0;
+    *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0;
+  }
+
+  for (gline = BadChanGlineList; gline; gline = gline->gl_next) {
     gl++;
     *gl_size += sizeof(struct Gline);
     *gl_size += gline->gl_user ? (strlen(gline->gl_user) + 1) : 0;
     *gl_size += gline->gl_host ? (strlen(gline->gl_host) + 1) : 0;
     *gl_size += gline->gl_reason ? (strlen(gline->gl_reason) + 1) : 0;
   }
+
   return gl;
 }
index 546c1b1c6cf1f0cdbb01a804ee394c796f33be40..f3b436762d781aa2afa60cf7c034bdb436763054 100644 (file)
@@ -94,6 +94,7 @@
 #include "numeric.h"
 #include "numnicks.h"
 #include "s_conf.h"
+#include "s_debug.h"
 #include "s_misc.h"
 #include "send.h"
 
  * parv[0] = Sender prefix
  * parv[1] = Target: server numeric
  * parv[2] = (+|-)<G-line mask>
- * parv[3] = G-line lifetime
- *
- * From Uworld:
- *
- * parv[4] = Comment
- *
- * From somewhere else:
- *
- * parv[4] = Last modification time
- * parv[5] = Comment
  *
+ * For other parameters, see doc/readme.gline.
  */
 int
 ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
@@ -132,11 +124,13 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   if (parc < 3)
     return need_more_params(sptr, "GLINE");
 
+  if (IsServer(sptr))
+    flags |= GLINE_FORCE;
+
   if (*mask == '!') {
     mask++;
     flags |= GLINE_OPERFORCE; /* assume oper had WIDE_GLINE */
-  } else if (IsServer(sptr))
-    flags |= GLINE_FORCE;
+  }
 
   switch (*mask) { /* handle +, -, <, and > */
   case '+': /* activate the G-line */
@@ -164,19 +158,35 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   if (action == GLINE_LOCAL_ACTIVATE || action == GLINE_LOCAL_DEACTIVATE ||
       (target[0] == '*' && target[1] == '\0'))
     flags |= GLINE_GLOBAL;
-  else {
-    if (!(acptr = FindNServer(target)))
-      return 0; /* no such server, jump out */
-
+  else
     flags |= GLINE_LOCAL;
+
+  /* now figure out if we need to resolve a server */
+  if ((action == GLINE_LOCAL_ACTIVATE || action == GLINE_LOCAL_DEACTIVATE ||
+       (flags & GLINE_LOCAL)) && !(acptr = FindNServer(target)))
+    return 0; /* no such server, jump out */
+
+  /* If it's a local activate/deactivate and server isn't me, propagate it */
+  if ((action == GLINE_LOCAL_ACTIVATE || action == GLINE_LOCAL_DEACTIVATE) &&
+      !IsMe(acptr)) {
+    Debug((DEBUG_DEBUG, "I am forwarding a local change to a global gline "
+          "to a remote server; target %s, mask %s, operforce %s, action %s",
+          target, mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
+          action == GLINE_LOCAL_ACTIVATE ? ">" : "<"));
+
+    sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s", acptr,
+                 flags & GLINE_OPERFORCE ? "!" : "",
+                 action == GLINE_LOCAL_ACTIVATE ? ">" : "<", mask);
+
+    return 0; /* all done */
   }
 
   /* Next, try to find the G-line... */
   if ((flags & GLINE_GLOBAL) || IsMe(acptr)) /* don't bother if it's not me! */
     agline = gline_find(mask, flags | GLINE_ANY | GLINE_EXACT);
 
-  /* We now have all the pieces to tell us what we've got; let's
-   * put it all together and convert the rest of the arguments.
+  /* We now have all the pieces to tell us what we've got; let's put
+   * it all together and convert the rest of the arguments.
    */
 
   /* Handle the local G-lines first... */
@@ -197,22 +207,29 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       reason = parv[parc - 1]; /* and reason */
 
       if (IsMe(acptr)) {
-       /* XXX and create the local G-line */
-       sendwallto_group_butone(&me, WALL_DESYNCH, NULL,
-                               "I would create a local G-line here; target "
-                               "%s, mask %s, operforce %s, action %s, "
-                               "expire %Tu, reason: %s", target, mask,
-                               flags & GLINE_OPERFORCE ? "YES" : "NO",
-                               action == GLINE_ACTIVATE ? "+" : "-",
-                               expire_off, reason);
+       if (agline) /* G-line already exists, so let's ignore it... */
+         return 0;
+
+       /* OK, create the local G-line */
+       Debug((DEBUG_DEBUG, "I am creating a local G-line here; target %s, "
+              "mask %s, operforce %s, action %s, expire %Tu, reason: %s",
+              target, mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
+              action == GLINE_ACTIVATE ? "+" : "-", expire_off, reason));
+
+       return gline_add(cptr, sptr, mask, reason, expire_off, lastmod,
+                        lifetime, flags | GLINE_ACTIVE);
       }
     } else if (IsMe(acptr)) { /* destroying a local G-line */
-      /* XXX destroy the G-line */;
-      sendwallto_group_butone(&me, WALL_DESYNCH, NULL,
-                             "I would destroy a local G-line here; target "
-                             "%s, mask %s, operforce %s, action %s", target,
-                             mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
-                             action == GLINE_ACTIVATE ? "+" : "-");
+      if (!agline) /* G-line doesn't exist, so let's complain... */
+       return send_reply(sptr, ERR_NOSUCHGLINE, mask);
+
+      /* Let's now destroy the G-line */;
+      Debug((DEBUG_DEBUG, "I am destroying a local G-line here; target %s, "
+            "mask %s, operforce %s, action %s", target, mask,
+            flags & GLINE_OPERFORCE ? "YES" : "NO",
+            action == GLINE_ACTIVATE ? "+" : "-"));
+
+      return gline_destroy(cptr, sptr, agline);
     }
 
     /* OK, we've converted arguments; if it's not for us, forward */
@@ -221,20 +238,19 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
      * <lastmod> for GLINE_ACTIVATE and to omit <expire>, <lastmod>,
      * and <reason> for GLINE_DEACTIVATE.
      */
-    if (!IsMe(acptr)) {
-      sendwallto_group_butone(&me, WALL_DESYNCH, NULL,
-                             "I am forwarding a local G-line to a remote "
-                             "server; target %s, mask %s, operforce %s, "
-                             "action %s, expire %Tu, lastmod %Tu, reason: %s",
-                             target, mask,
-                             flags & GLINE_OPERFORCE ? "YES" : "NO",
-                             action == GLINE_ACTIVATE ? "+" :  "-",
-                             expire_off, CurrentTime, reason);
-      sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s %Tu %Tu :%s",
-                   acptr, flags & GLINE_OPERFORCE ? "!" : "",
-                   action == GLINE_ACTIVATE ? '+' : '-', mask, expire_off,
-                   CurrentTime, reason);
-    }
+    assert(!IsMe(acptr));
+
+    Debug((DEBUG_DEBUG, "I am forwarding a local G-line to a remote server; "
+          "target %s, mask %s, operforce %s, action %s, expire %Tu, "
+          "lastmod %Tu, reason: %s", target, mask,
+          flags & GLINE_OPERFORCE ? "YES" : "NO",
+          action == GLINE_ACTIVATE ? "+" :  "-", expire_off, CurrentTime,
+          reason));
+
+    sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s %Tu %Tu :%s",
+                 acptr, flags & GLINE_OPERFORCE ? "!" : "",
+                 action == GLINE_ACTIVATE ? '+' : '-', mask, expire_off,
+                 CurrentTime, reason);
 
     return 0; /* all done */
   }
@@ -247,6 +263,8 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   switch (action) {
   case GLINE_LOCAL_ACTIVATE: /* locally activating a G-line */
   case GLINE_LOCAL_DEACTIVATE: /* locally deactivating a G-line */
+    if (!agline) /* no G-line to locally activate or deactivate? */
+      return send_reply(sptr, ERR_NOSUCHGLINE, mask);
     break; /* no additional parameters to manipulate */
 
   case GLINE_ACTIVATE: /* activating a G-line */
@@ -266,9 +284,13 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       expire_off = atoi(parv[3]); /* convert expiration and lastmod */
       lastmod = atoi(parv[4]);
 
+      flags |= GLINE_EXPIRE; /* we have an expiration time update */
+
       if (parc > 6) { /* no question, have a lifetime and reason */
        lifetime = atoi(parv[5]);
        reason = parv[parc - 1];
+
+       flags |= GLINE_LIFETIME | GLINE_REASON;
       } else if (parc == 6) { /* either a lifetime or a reason */
        if (!agline || /* gline creation, has to be the reason */
            /* trial-convert as lifetime, and if it doesn't fully convert,
@@ -276,111 +298,42 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
            ((lifetime = strtoul(parv[5], &tmp, 10)) && !*tmp)) {
          lifetime = 0;
          reason = parv[5];
-       }
+
+         flags |= GLINE_REASON; /* have a reason update */
+       } else if (lifetime)
+         flags |= GLINE_LIFETIME; /* have a lifetime update */
       }
     }
   }
 
-  sendwallto_group_butone(&me, WALL_DESYNCH, NULL,
-                         "I have a global G-line I would act upon now; "
-                         "target %s, mask %s, operforce %s, action %s, "
-                         "expire %Tu, lastmod %Tu, lifetime %Tu, "
-                         "reason: %s; gline %s!",
-                         target, mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
-                         action == GLINE_ACTIVATE ? "+" :
-                         (action == GLINE_DEACTIVATE ? "-" :
-                          (action == GLINE_LOCAL_ACTIVATE ? ">" :
-                           (action == GLINE_LOCAL_DEACTIVATE ? "<" :
-                            "(MODIFY)"))), expire_off, lastmod, lifetime,
-                         reason, agline ? "EXISTS" : "does not exist");
+  Debug((DEBUG_DEBUG, "I have a global G-line I am acting upon now; "
+        "target %s, mask %s, operforce %s, action %s, expire %Tu, "
+        "lastmod %Tu, lifetime %Tu, reason: %s; gline %s!  (fields "
+        "present: %s %s %s)", target, mask,
+        flags & GLINE_OPERFORCE ? "YES" : "NO",
+        action == GLINE_ACTIVATE ? "+" :
+        (action == GLINE_DEACTIVATE ? "-" :
+         (action == GLINE_LOCAL_ACTIVATE ? ">" :
+          (action == GLINE_LOCAL_DEACTIVATE ? "<" : "(MODIFY)"))),
+        expire_off, lastmod, lifetime, reason,
+        agline ? "EXISTS" : "does not exist",
+        flags & GLINE_EXPIRE ? "expire" : "",
+        flags & GLINE_LIFETIME ? "lifetime" : "",
+        flags & GLINE_REASON ? "reason" : ""));
 
   /* OK, at this point, we have converted all available parameters.
    * Let's actually do the action!
    */
   if (agline)
-    /* XXX modify the G-line */;
-
-  /* XXX create the G-line */return 0;
-
-
-
-
-
-/*   if ((parc == 3 && *mask == '-') || parc == 5) */
-/*   { */
-/*     if (!find_conf_byhost(cli_confs(cptr), cli_name(sptr), CONF_UWORLD)) */
-/*       return need_more_params(sptr, "GLINE"); */
-
-/*     flags |= GLINE_FORCE; */
-/*   } */
-/*   else if (parc > 5) */
-/*     lastmod = atoi(parv[4]); */
-/*   else */
-/*     return need_more_params(sptr, "GLINE"); */
-
-/*   if (parc > 4) */
-/*     reason = parv[parc - 1]; */
-
-/*   if (IsServer(sptr)) */
-/*     flags |= GLINE_FORCE; */
-
-/*   if (!(target[0] == '*' && target[1] == '\0')) { */
-/*     if (!(acptr = FindNServer(target))) */
-/*       return 0; /\* no such server *\/ */
-
-/*     if (!IsMe(acptr)) { /\* manually propagate *\/ */
-/*       if (!lastmod) */
-/*     sendcmdto_one(sptr, CMD_GLINE, acptr, */
-/*                   (parc == 3) ? "%C %s" : "%C %s %s :%s", acptr, mask, */
-/*                   parv[3], reason); */
-/*       else */
-/*     sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%s %s %s :%s", acptr, */
-/*                   flags & GLINE_OPERFORCE ? "!" : "", mask, parv[3], */
-/*                   parv[4], reason); */
-
-/*       return 0; */
-/*     } */
-
-/*     flags |= GLINE_LOCAL; */
-/*   } */
-
-/*   if (*mask == '-') */
-/*     mask++; */
-/*   else if (*mask == '+') { */
-/*     flags |= GLINE_ACTIVE; */
-/*     mask++; */
-/*   } else */
-/*     flags |= GLINE_ACTIVE; */
-
-/*   expire_off = parc < 5 ? 0 : atoi(parv[3]); */
-
-/*   agline = gline_find(mask, GLINE_ANY | GLINE_EXACT); */
-
-/*   if (agline) { */
-/*     if (GlineIsLocal(agline) && !(flags & GLINE_LOCAL)) /\* global over local *\/ */
-/*       gline_free(agline); */
-/*     else if (!lastmod && ((flags & GLINE_ACTIVE) == GlineIsRemActive(agline))) */
-/*       return gline_propagate(cptr, sptr, agline); */
-/*     else if (!lastmod || GlineLastMod(agline) < lastmod) { /\* new mod *\/ */
-/*       if (flags & GLINE_ACTIVE) */
-/*     return gline_activate(cptr, sptr, agline, lastmod, flags); */
-/*       else */
-/*     return gline_deactivate(cptr, sptr, agline, lastmod, flags); */
-/*     } else if (GlineLastMod(agline) == lastmod || IsBurstOrBurstAck(cptr)) */
-/*       return 0; */
-/*     else */
-/*       return gline_resend(cptr, agline); /\* other server desynched WRT gline *\/ */
-/*   } else if (parc == 3 && !(flags & GLINE_ACTIVE)) { */
-/*     /\* U-lined server removing a G-line we don't have; propagate the removal */
-/*      * anyway. */
-/*      *\/ */
-/*     if (!(flags & GLINE_LOCAL)) */
-/*       sendcmdto_serv_butone(sptr, CMD_GLINE, cptr, "* -%s", mask); */
-/*     return 0; */
-/*   } else if (parc < 5) */
-/*     return need_more_params(sptr, "GLINE"); */
-
-/*   return gline_add(cptr, sptr, mask, reason, expire_off, lastmod, flags); */
+    return gline_modify(cptr, sptr, agline, action, reason, expire_off,
+                       lastmod, lifetime, flags);
+
+  assert(action != GLINE_LOCAL_ACTIVATE);
+  assert(action != GLINE_LOCAL_DEACTIVATE);
+  assert(action != GLINE_MODIFY);
+
+  return gline_add(cptr, sptr, mask, reason, expire_off, lastmod, lifetime,
+                  flags | ((action == GLINE_ACTIVATE) ? GLINE_ACTIVE : 0));
 }
 
 /*
@@ -389,26 +342,17 @@ ms_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
  * parv[0] = Sender prefix
  * parv[1] = [[+|-]<G-line mask>]
  *
- * Local (to me) style:
- *
- * parv[2] = [Expiration offset]
- * parv[3] = [Comment]
- *
- * Global (or remote local) style:
- *
- * parv[2] = [target]
- * parv[3] = [Expiration offset]
- * parv[4] = [Comment]
- *
+ * For other parameters, see doc/readme.gline.
  */
 int
 mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
 {
   struct Client *acptr = 0;
-  struct Gline *agline;
+  struct Gline *agline = 0;
   unsigned int flags = 0;
-  time_t expire_off;
-  char *mask = parv[1], *target = 0, *reason;
+  enum GlineAction action = GLINE_MODIFY;
+  time_t expire_off = 0;
+  char *mask = parv[1], *target = 0, *reason = 0;
 
   if (parc < 2)
     return gline_list(sptr, 0);
@@ -420,77 +364,210 @@ mo_gline(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       flags |= GLINE_OPERFORCE;
   }
 
-  if (*mask == '+') {
-    flags |= GLINE_ACTIVE;
+  switch (*mask) { /* handle +, -, <, and > */
+  case '+': /* activate the G-line */
+    action = GLINE_ACTIVATE;
     mask++;
+    break;
 
-  } else if (*mask == '-')
+  case '-': /* deactivate the G-line */
+    action = GLINE_DEACTIVATE;
     mask++;
-  else
-    return gline_list(sptr, mask);
+    break;
 
-  if (parc == 4) {
-    expire_off = atoi(parv[2]);
-    reason = parv[3];
-    flags |= GLINE_LOCAL;
-  } else if (parc > 4) {
-    target = parv[2];
-    expire_off = atoi(parv[3]);
-    reason = parv[4];
-  } else
-    return need_more_params(sptr, "GLINE");
+  case '>': /* locally activate the G-line */
+    action = GLINE_LOCAL_ACTIVATE;
+    mask++;
+    break;
 
-  if (target)
-  {
-    if (!(target[0] == '*' && target[1] == '\0'))
-    {
-      if (!(acptr = find_match_server(target)))
-       return send_reply(sptr, ERR_NOSUCHSERVER, target);
-
-      /* manually propagate, since we don't set it */
-      if (!IsMe(acptr))
-      {
-       if (!feature_bool(FEAT_CONFIG_OPERCMDS))
-         return send_reply(sptr, ERR_DISABLED, "GLINE");
-
-       if (!HasPriv(sptr, PRIV_GLINE))
-         return send_reply(sptr, ERR_NOPRIVILEGES);
-
-       sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s %s %Tu :%s", acptr,
-                     flags & GLINE_OPERFORCE ? "!" : "",
-                     flags & GLINE_ACTIVE ? '+' : '-', mask, parv[3],
-                     TStime(), reason);
-       return 0;
-      }
-      flags |= GLINE_LOCAL;
+  case '<': /* locally deactivate the G-line */
+    action = GLINE_LOCAL_DEACTIVATE;
+    mask++;
+    break;
+  }
+
+  /* OK, let's figure out the parameters... */
+  switch (action) {
+  case GLINE_MODIFY: /* no specific action on the G-line... */
+    if (parc == 2) /* user wants a listing of a specific G-line */
+      return gline_list(sptr, mask);
+    else if (parc < 4) /* must have target and expire, minimum */
+      return need_more_params(sptr, "GLINE");
+
+    target = parv[2]; /* get the target... */
+    expire_off = atoi(parv[3]); /* and the expiration */
+
+    flags |= GLINE_EXPIRE; /* remember that we got an expire time */
+
+    if (parc > 4) { /* also got a reason... */
+      reason = parv[4];
+      flags |= GLINE_REASON;
+    }
+
+    /* target is not global, interpolate action and require reason */
+    if (target[0] != '*' || target[1] != '\0') {
+      if (!reason) /* have to have a reason for this */
+       return need_more_params(sptr, "GLINE");
+
+      action = GLINE_ACTIVATE;
+    }
+    break;
+
+  case GLINE_LOCAL_ACTIVATE: /* locally activate a G-line */
+  case GLINE_LOCAL_DEACTIVATE: /* locally deactivate a G-line */
+    if (parc > 2) /* if target is available, pick it */
+      target = parv[2];
+    break;
+
+  case GLINE_ACTIVATE: /* activating/adding a G-line */
+  case GLINE_DEACTIVATE: /* deactivating/removing a G-line */
+    if (parc < 3)
+      return need_more_params(sptr, "GLINE");
+
+    if (parc > 3) {
+      /* get expiration and target */
+      expire_off = atoi(parv[parc - 2]);
+      reason = parv[parc - 1];
+
+      flags |= GLINE_EXPIRE | GLINE_REASON; /* remember that we got 'em */
+
+      if (parc > 4) /* also have a target! */
+       target = parv[2];
+    } else {
+      target = parv[2]; /* target has to be present, and has to be '*' */
+
+      if (target[0] != '*' || target[1] != '\0')
+       return need_more_params(sptr, "GLINE");
     }
+    break;
   }
 
-  if (!(flags & GLINE_LOCAL) && !feature_bool(FEAT_CONFIG_OPERCMDS))
-    return send_reply(sptr, ERR_DISABLED, "GLINE");
+  /* Now let's figure out which is the target server */
+  if (!target) /* no target, has to be me... */
+    acptr = &me;
+  /* if it's not '*', look up the server */
+  else if ((target[0] != '*' || target[1] != '\0') &&
+          !(acptr = find_match_server(target)))
+    return send_reply(sptr, ERR_NOSUCHSERVER, target);
 
-  if (!HasPriv(sptr, (flags & GLINE_LOCAL ? PRIV_LOCAL_GLINE : PRIV_GLINE)))
-    return send_reply(sptr, ERR_NOPRIVILEGES);
+  /* Now, is the G-line local or global? */
+  if (action == GLINE_LOCAL_ACTIVATE || action == GLINE_LOCAL_DEACTIVATE ||
+      !acptr)
+    flags |= GLINE_GLOBAL;
+  else /* it's some form of local G-line */
+    flags |= GLINE_LOCAL;
+
+  /* If it's a local activate/deactivate and server isn't me, propagate it */
+  if ((action == GLINE_LOCAL_ACTIVATE || action == GLINE_LOCAL_DEACTIVATE) &&
+      !IsMe(acptr)) {
+    Debug((DEBUG_DEBUG, "I am forwarding a local change to a global gline "
+          "to a remote server; target %s, mask %s, operforce %s, action %s",
+          cli_name(acptr), mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
+          action == GLINE_LOCAL_ACTIVATE ? ">" : "<"));
 
-  agline = gline_find(mask, GLINE_ANY | GLINE_EXACT);
+    sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s", acptr,
+                 flags & GLINE_OPERFORCE ? "!" : "",
+                 action == GLINE_LOCAL_ACTIVATE ? ">" : "<", mask);
 
-  if (agline) {
-    if (GlineIsLocal(agline) && !(flags & GLINE_LOCAL)) /* global over local */
-      gline_free(agline);
-    else {
-      if (!GlineLastMod(agline)) /* force mods to Uworld-set G-lines local */
-       flags |= GLINE_LOCAL;
+    return 0; /* all done */
+  }
+
+  /* Next, try to find the G-line... */
+  if ((flags & GLINE_GLOBAL) || IsMe(acptr)) /* don't bother if it's not me! */
+    agline = gline_find(mask, flags | GLINE_ANY | GLINE_EXACT);
+
+  /* We now have all the pieces to tell us what we've got; let's put
+   * it all together and convert the rest of the arguments.
+   */
+
+  /* Handle the local G-lines first... */
+  if (flags & GLINE_LOCAL) {
+    assert(acptr);
+
+    /* normalize the action, first */
+    if (action == GLINE_LOCAL_ACTIVATE || action == GLINE_MODIFY)
+      action = GLINE_ACTIVATE;
+    else if (action == GLINE_LOCAL_DEACTIVATE)
+      action = GLINE_DEACTIVATE;
+
+    /* If it's not for us, forward */
+    /* UPDATE NOTE: Once all servers are updated to u2.10.12.11, the
+     * format string in this sendcmdto_one() may be updated to omit
+     * <lastmod> for GLINE_ACTIVATE and to omit <expire>, <lastmod>,
+     * and <reason> for GLINE_DEACTIVATE.
+     */
+
+    if (!IsMe(acptr)) {
+      Debug((DEBUG_DEBUG, "I am forwarding a local G-line to a remote "
+            "server; target %s, mask %s, operforce %s, action %s, "
+            "expire %Tu, reason %s", target, mask,
+            flags & GLINE_OPERFORCE ? "YES" : "NO",
+            action == GLINE_ACTIVATE ? "+" : "-", expire_off, reason));
+
+      sendcmdto_one(sptr, CMD_GLINE, acptr, "%C %s%c%s %Tu %Tu :%s",
+                   acptr, flags & GLINE_OPERFORCE ? "!" : "",
+                   action == GLINE_ACTIVATE ? "+" : "-", mask, expire_off,
+                   CurrentTime, reason);
+
+      return 0; /* all done */
+    }
+
+    /* let's handle activation... */
+    if (action == GLINE_ACTIVATE) {
+      if (agline) /* G-line already exists, so let's ignore it... */
+       return 0;
 
-      if (flags & GLINE_ACTIVE)
-       return gline_activate(cptr, sptr, agline,
-                             GlineLastMod(agline) ? TStime() : 0, flags);
-      else
-       return gline_deactivate(cptr, sptr, agline,
-                               GlineLastMod(agline) ? TStime() : 0, flags);
+      /* OK, create the local G-line */
+      Debug((DEBUG_DEBUG, "I am creating a local G-line here; target %s, "
+            "mask %s, operforce %s, action  %s, expire %Tu, reason: %s",
+            target, mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
+            action == GLINE_ACTIVATE ? "+" : "-", expire_off, reason));
+
+      return gline_add(cptr, sptr, mask, reason, expire_off, 0, 0,
+                      flags | GLINE_ACTIVE);
+    } else { /* OK, it's a deactivation/destruction */
+      if (!agline) /* G-line doesn't exist, so let's complain... */
+       return send_reply(sptr, ERR_NOSUCHGLINE, mask);
+
+      /* Let's now destroy the G-line */
+      Debug((DEBUG_DEBUG, "I am destroying a local G-line here; target %s, "
+            "mask %s, operforce %s, action %s", target, mask,
+            flags & GLINE_OPERFORCE ? "YES" : "NO",
+            action == GLINE_ACTIVATE ? "+" : "-"));
+
+      return gline_destroy(cptr, sptr, agline);
     }
   }
 
-  return gline_add(cptr, sptr, mask, reason, expire_off, TStime(), flags);
+  /* can't modify a G-line that doesn't exist... */
+  if (!agline &&
+      (action == GLINE_MODIFY || action == GLINE_LOCAL_ACTIVATE ||
+       action == GLINE_LOCAL_DEACTIVATE))
+    return send_reply(sptr, ERR_NOSUCHGLINE, mask);
+
+  Debug((DEBUG_DEBUG, "I have a global G-line I am acting upon now; "
+        "target %s, mask %s, operforce %s, action %s, expire %Tu, "
+        "reason: %s; gline %s!  (fields present: %s %s)", target, 
+        mask, flags & GLINE_OPERFORCE ? "YES" : "NO",
+        action == GLINE_ACTIVATE ? "+" :
+        (action == GLINE_DEACTIVATE ? "-" :
+         (action == GLINE_LOCAL_ACTIVATE ? ">" :
+          (action == GLINE_LOCAL_DEACTIVATE ? "<" : "(MODIFY)"))),
+        expire_off, reason, agline ? "EXISTS" : "does not exist",
+        flags & GLINE_EXPIRE ? "expire" : "",
+        flags & GLINE_REASON ? "reason" : ""));
+
+  if (agline) /* modifying an existing G-line */
+    return gline_modify(cptr, sptr, agline, action, reason, expire_off,
+                       CurrentTime, 0, flags);
+
+  assert(action != GLINE_LOCAL_ACTIVATE);
+  assert(action != GLINE_LOCAL_DEACTIVATE);
+  assert(action != GLINE_MODIFY);
+
+  /* create a new G-line */
+  return gline_add(cptr, sptr, mask, reason, expire_off, CurrentTime, 0,
+                  flags | ((action == GLINE_ACTIVATE) ? GLINE_ACTIVE : 0));
 }
 
 /*
index dd36800845d709e77fb43536303c7cd23e05ea99..cbe6b8d695bb803128ca08b8173cf432f2a81f34 100644 (file)
@@ -526,7 +526,7 @@ static Numeric replyTable[] = {
 /* 246 */
   { RPL_STATSTLINE, "%c %s %s", "246" },
 /* 247 */
-  { RPL_STATSGLINE, "%c %s%s%s %Tu %c :%s", "247" },
+  { RPL_STATSGLINE, "%c %s%s%s %Tu %Tu %Tu %s%c :%s", "247" },
 /* 248 */
   { RPL_STATSULINE, "U %s", "248" },
 /* 249 */
@@ -592,7 +592,7 @@ static Numeric replyTable[] = {
 /* 279 */
   { 0 },
 /* 280 */
-  { RPL_GLIST, "%s%s%s %Tu %%c :%s", "280" },
+  { RPL_GLIST, "%s%s%s %Tu %Tu %Tu %s %s%c :%s", "280" },
 /* 281 */
   { RPL_ENDOFGLIST, ":End of G-line List", "281" },
 /* 282 */