Support G-line lifetimes.
authorMichael Poole <mdpoole@troilus.org>
Thu, 18 Mar 2010 02:11:00 +0000 (22:11 -0400)
committerMichael Poole <mdpoole@troilus.org>
Thu, 18 Mar 2010 02:11:00 +0000 (22:11 -0400)
This involves using the old "expiration" as when the G-line becomes
inactive, and adding a new "lifetime" that is the latest of all the
expiration times that the G-line has ever had.  For convenience,
gline_add() will compare its new "lifetime" argument with its calculated
expiration time -- so callers can safely pass 0 as the lifetime.

src/gline.h (struct gline): Add the lifetime field.  Add Doxygen comments
    for future reference.
  (struct gline_discrim): Add bounds for the lifetime field.
  (gline_add): Add lifetime argument.

src/gline.c (KEY_LIFETIME): New database key.
  (delete_gline_for_p): Delete.
  (gline_expire): Use lifetime to determine whether a G-line expired.
  (gline_remove): Simplify greatly, because we don't actually remove
    G-lines any more (we just mark them immediately expired).
  (gline_add): Add parameter for lifetime, and use it.
  (gline_add_record): Parse lifetime field if it is present.  Simplify
    the parsing of the issued field.
  (gline_write_entry): Write the lifetime.
  (gline_discrim_create): Use ULONG_MAX rather than INT_MAX for
    maximum timestamps.  Parse "lifetime" arguments.
  (gline_discrim_match): Check the lifetime bounds.

src/mod-blacklist.c (dnsbl_hit): Pass 0 lifetime for gline_add().
  (blacklist_check_user): Likewise.

src/mod-sockcheck.c (sockcheck_issue_gline): Likewise.

src/opserv.c (OSMSG_GTRACE_FORMAT): Add lifetime field.
  (opserv_block): Pass 0 lifetime for gline_add().
  (cmd_gline): Likewise.
  (opserv_new_user_check): Likewise.
  (gtrace_print_func): Format the lifetime.

src/proto-p10.c (irc_gline): Include the lifetime in the correct place.
  (irc_ungline): Include the current timestamp (last modified time).
  (cmd_num_gline): When the lifetime argument is given, parse it.
  (cmd_gline): Likewise.  Also implement the normal ircu check for
    expiration as an absolute versus relative time.

src/gline.c
src/gline.h
src/mod-blacklist.c
src/mod-sockcheck.c
src/opserv.c
src/proto-p10.c

index 3ca7e1a682760be649f126eb2dfb236a21cd30f3..0b2ed9bb36f25d4ea6e1c5ef137f0aee7a8beb6a 100644 (file)
@@ -43,6 +43,7 @@
 #define KEY_LASTMOD "lastmod"
 #define KEY_ISSUER "issuer"
 #define KEY_ISSUED "issued"
+#define KEY_LIFETIME "lifetime"
 
 static heap_t gline_heap; /* key: expiry time, data: struct gline_entry* */
 static dict_t gline_dict; /* key: target, data: struct gline_entry* */
@@ -77,19 +78,6 @@ gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
     return !irccasecmp(ge->target, extra);
 }
 
-static int
-delete_gline_for_p(UNUSED_ARG(void *key), void *data, void *extra)
-{
-    struct gline *ge = data;
-
-    if (!irccasecmp(ge->target, extra)) {
-        free_gline(ge);
-        return 1;
-    } else {
-        return 0;
-    }
-}
-
 static void
 gline_expire(UNUSED_ARG(void *data))
 {
@@ -99,7 +87,7 @@ gline_expire(UNUSED_ARG(void *data))
     stopped = 0;
     while (heap_size(gline_heap)) {
         heap_peek(gline_heap, 0, &wraa);
-        stopped = ((struct gline*)wraa)->expires;
+        stopped = ((struct gline*)wraa)->lifetime;
         if (stopped > now)
             break;
         heap_pop(gline_heap);
@@ -112,58 +100,61 @@ gline_expire(UNUSED_ARG(void *data))
 int
 gline_remove(const char *target, int announce)
 {
-    int res = dict_find(gline_dict, target, NULL) ? 1 : 0;
-    if (heap_remove_pred(gline_heap, delete_gline_for_p, (char*)target)) {
-        void *argh;
-        struct gline *new_first;
-        heap_peek(gline_heap, 0, &argh);
-        if (argh) {
-            new_first = argh;
-            timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
-            timeq_add(new_first->expires, gline_expire, 0);
-        }
-    }
-#ifdef WITH_PROTOCOL_BAHAMUT
-    /* Bahamut is sort of lame: It permanently remembers any AKILLs
-     * with durations longer than a day, and will never auto-expire
-     * them.  So when the time comes, we'd better remind it.  */
-    announce = 1;
-#endif
+    struct gline *gl;
+
+    gl = dict_find(gline_dict, target, NULL);
+    if (gl != NULL)
+        gl->expires = now;
     if (announce)
         irc_ungline(target);
-    return res;
+    return gl != NULL;
 }
 
 struct gline *
-gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, int announce)
+gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, unsigned long lifetime, int announce)
 {
     struct gline *ent;
     struct gline *prev_first;
     void *argh;
+    unsigned long expires;
 
     heap_peek(gline_heap, 0, &argh);
     prev_first = argh;
+    expires = now + duration;
+    if (lifetime < expires)
+        lifetime = expires;
     ent = dict_find(gline_dict, target, NULL);
     if (ent) {
         heap_remove_pred(gline_heap, gline_for_p, (char*)target);
-        if (ent->expires < now + duration)
-            ent->expires = now + duration;
+        if (ent->expires != expires)
+            ent->expires = expires;
+        if (ent->lifetime < lifetime)
+            ent->lifetime = lifetime;
         if (ent->lastmod < lastmod)
             ent->lastmod = lastmod;
+        if (strcmp(ent->issuer, issuer)) {
+            free(ent->issuer);
+            ent->issuer = strdup(issuer);
+        }
+        if (strcmp(ent->reason, reason)) {
+            free(ent->reason);
+            ent->reason = strdup(reason);
+        }
     } else {
         ent = malloc(sizeof(*ent));
         ent->issued = issued;
         ent->lastmod = lastmod;
         ent->issuer = strdup(issuer);
         ent->target = strdup(target);
-        ent->expires = now + duration;
+        ent->expires = expires;
+        ent->lifetime = lifetime;
         ent->reason = strdup(reason);
         dict_insert(gline_dict, ent->target, ent);
     }
     heap_insert(gline_heap, ent, ent);
-    if (!prev_first || (ent->expires < prev_first->expires)) {
+    if (!prev_first || (ent->lifetime < prev_first->lifetime)) {
         timeq_del(0, gline_expire, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
-        timeq_add(ent->expires, gline_expire, 0);
+        timeq_add(ent->lifetime, gline_expire, 0);
     }
     if (announce)
         irc_gline(NULL, ent);
@@ -255,7 +246,7 @@ gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
 {
     struct record_data *rd = data;
     const char *issuer, *reason, *dstr;
-    unsigned long issued, expiration, lastmod;
+    unsigned long issued, expiration, lastmod, lifetime;
 
     if (!(reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING))) {
         log_module(MAIN_LOG, LOG_ERROR, "Missing reason for gline %s", key);
@@ -266,18 +257,16 @@ gline_add_record(const char *key, void *data, UNUSED_ARG(void *extra))
         return 0;
     }
     expiration = strtoul(dstr, NULL, 0);
+    dstr = database_get_data(rd->d.object, KEY_LIFETIME, RECDB_QSTRING);
+    lifetime = dstr ? strtoul(dstr, NULL, 0) : expiration;
     dstr = database_get_data(rd->d.object, KEY_LASTMOD, RECDB_QSTRING);
     lastmod = dstr ? strtoul(dstr, NULL, 0) : 0;
-    if ((dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING))) {
-        issued = strtoul(dstr, NULL, 0);
-    } else {
-        issued = now;
-    }
-    if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING))) {
+    dstr = database_get_data(rd->d.object, KEY_ISSUED, RECDB_QSTRING);
+    issued = dstr ? strtoul(dstr, NULL, 0) : now;
+    if (!(issuer = database_get_data(rd->d.object, KEY_ISSUER, RECDB_QSTRING)))
         issuer = "<unknown>";
-    }
-    if (expiration > now)
-        gline_add(issuer, key, expiration - now, reason, issued, lastmod, 0);
+    if (lifetime > now)
+        gline_add(issuer, key, expiration - now, reason, issued, lastmod, lifetime, 0);
     return 0;
 }
 
@@ -296,6 +285,7 @@ gline_write_entry(UNUSED_ARG(void *key), void *data, void *extra)
     saxdb_start_record(ctx, ent->target, 0);
     saxdb_write_int(ctx, KEY_EXPIRES, ent->expires);
     saxdb_write_int(ctx, KEY_ISSUED, ent->issued);
+    saxdb_write_int(ctx, KEY_LIFETIME, ent->lifetime);
     if (ent->lastmod)
         saxdb_write_int(ctx, KEY_LASTMOD, ent->lastmod);
     saxdb_write_string(ctx, KEY_REASON, ent->reason);
@@ -336,8 +326,9 @@ gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int a
 
     discrim = calloc(1, sizeof(*discrim));
     discrim->limit = 50;
-    discrim->max_issued = INT_MAX;
-    discrim->max_lastmod = INT_MAX;
+    discrim->max_issued = ULONG_MAX;
+    discrim->max_lastmod = ULONG_MAX;
+    discrim->max_lifetime = ULONG_MAX;
 
     for (i=0; i<argc; i++) {
         if (i + 2 > argc) {
@@ -389,6 +380,23 @@ gline_discrim_create(struct userNode *user, struct userNode *src, unsigned int a
             } else {
                 discrim->min_lastmod = now - ParseInterval(cmp + 2);
             }
+        } else if (!irccasecmp(argv[i], "lifetime")) {
+            const char *cmp = argv[++i];
+            if (cmp[0] == '<') {
+                if (cmp[1] == '=') {
+                    discrim->min_lifetime = now - ParseInterval(cmp + 2);
+                } else {
+                    discrim->min_lifetime = now - (ParseInterval(cmp + 1) - 1);
+                }
+            } else if (cmp[0] == '>') {
+                if (cmp[1] == '=') {
+                    discrim->max_lifetime = now - ParseInterval(cmp + 2);
+                } else {
+                    discrim->max_lifetime = now - (ParseInterval(cmp + 1) - 1);
+                }
+            } else {
+                discrim->min_lifetime = now - ParseInterval(cmp + 2);
+            }
         } else {
             send_message(user, src, "MSG_INVALID_CRITERIA", argv[i]);
             goto fail;
@@ -429,7 +437,9 @@ gline_discrim_match(struct gline *gline, struct gline_discrim *discrim)
         || (discrim->max_issued < gline->issued)
         || (discrim->min_expire > gline->expires)
         || (discrim->min_lastmod > gline->lastmod)
-        || (discrim->max_lastmod < gline->lastmod)) {
+        || (discrim->max_lastmod < gline->lastmod)
+        || (discrim->min_lifetime > gline->lifetime)
+        || (discrim->max_lifetime < gline->lifetime)) {
         return 0;
     }
     return 1;
index d81a2e673e2c50f7f89c5edd14df0f5e9b5e236d..65f17a216d41cc59ba0978df2dc5153e04abae7e 100644 (file)
 #include "hash.h"
 
 struct gline {
+    /** When the G-line was first created. */
     unsigned long issued;
+    /** When the G-line was last modified. */
     unsigned long lastmod;
+    /** When the G-line becomes ineffective. */
     unsigned long expires;
+    /** When the G-line may be forgotten (the maximum "expires" value
+     * from any modification period).
+     */
+    unsigned long lifetime;
+    /** Account or nick name of the person creating the G-line. */
     char *issuer;
+    /** user@host mask covered by the G-line. */
     char *target;
+    /** What to tell affected users. */
     char *reason;
 };
 
@@ -43,10 +53,12 @@ struct gline_discrim {
     unsigned long min_expire;
     unsigned long min_lastmod;
     unsigned long max_lastmod;
+    unsigned long min_lifetime;
+    unsigned long max_lifetime;
 };
 
 void gline_init(void);
-struct gline *gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, int announce);
+struct gline *gline_add(const char *issuer, const char *target, unsigned long duration, const char *reason, unsigned long issued, unsigned long lastmod, unsigned long lifetime, int announce);
 struct gline *gline_find(const char *target);
 int gline_remove(const char *target, int announce);
 void gline_refresh_server(struct server *srv);
index 0e4b58b0d3233758d3eac04f0d63648805f7a316..e202f739302e9de2a8c4a90fb338eb406d762520 100644 (file)
@@ -155,7 +155,7 @@ dnsbl_hit(struct sar_request *req, struct dns_header *hdr, struct dns_rr *rr, un
             target[0] = '*';
             target[1] = '@';
             strcpy(target + 2, data->client_ip);
-            gline_add(self->name, target, zone->duration, reason, now, now, 1);
+            gline_add(self->name, target, zone->duration, reason, now, now, 0, 1);
         }
     }
     free(txt);
@@ -194,7 +194,7 @@ blacklist_check_user(struct userNode *user)
         target[1] = '@';
         strcpy(target + 2, host);
         /* We do not prepend AUTO here so it can be done in the blacklist file. */
-        gline_add(self->name, target, conf.gline_duration, reason, now, now, 1);
+        gline_add(self->name, target, conf.gline_duration, reason, now, now, 0, 1);
     }
 
     /* Figure out the base part of a DNS blacklist hostname. */
index 665252813c685e3cc46240517b3165c2e6e4d8f8..ab68da07cd558aed9f41112bd544617eee4cd928 100644 (file)
@@ -206,7 +206,7 @@ sockcheck_issue_gline(sockcheck_cache_info sci)
     char addr[IRC_NTOP_MAX_SIZE + 2] = {'*', '@', '\0'};
     irc_ntop(addr + 2, sizeof(addr) - 2, &sci->addr);
     log_module(PC_LOG, LOG_INFO, "Issuing gline for client at %s: %s", addr + 2, sci->reason);
-    gline_add("ProxyCheck", addr, sockcheck_conf.gline_duration, sci->reason, now, now, 1);
+    gline_add("ProxyCheck", addr, sockcheck_conf.gline_duration, sci->reason, now, now, 0, 1);
 }
 
 static struct sockcheck_client *
index d7bddd5213044ce0036a73a2691216daae7a0021..4bc6be2791ba476c1bc0991a0d0b81460598e2ee 100644 (file)
@@ -201,7 +201,7 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_GLINE_SEARCH_RESULTS", "The following glines were found:" },
     { "OSMSG_LOG_SEARCH_RESULTS", "The following log entries were found:" },
     { "OSMSG_GSYNC_RUNNING", "Synchronizing glines from %s." },
-    { "OSMSG_GTRACE_FORMAT", "%s (issued %s by %s, lastmod %s, expires %s): %s" },
+    { "OSMSG_GTRACE_FORMAT", "%1$s (issued %2$s ago by %3$s, lastmod %4$s ago, expires %5$s, lifetime %7$s): %6$s" },
     { "OSMSG_GAG_APPLIED", "Gagged $b%s$b, affecting %d users." },
     { "OSMSG_GAG_ADDED", "Gagged $b%s$b." },
     { "OSMSG_REDUNDANT_GAG", "Gag $b%s$b is redundant." },
@@ -789,7 +789,7 @@ opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned l
                  "G-line requested by %s.", src_handle);
     if (!duration)
         duration = opserv_conf.block_gline_duration;
-    return gline_add(src_handle, mask, duration, reason, now, now, 1);
+    return gline_add(src_handle, mask, duration, reason, now, now, 0, 1);
 }
 
 static MODCMD_FUNC(cmd_block)
@@ -863,7 +863,7 @@ static MODCMD_FUNC(cmd_gline)
         reply("MSG_INVALID_DURATION", argv[2]);
         return 0;
     }
-    gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, now, 1);
+    gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, now, 0, 1);
     reply("OSMSG_GLINE_ISSUED", gline->target);
     return 1;
 }
@@ -1882,7 +1882,7 @@ opserv_new_user_check(struct userNode *user)
         } else if (ohi->clients.used > limit) {
             char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' };
             strcpy(target + 2, addr);
-            gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "AUTO Excessive connections from a single host.", now, now, 1);
+            gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "AUTO Excessive connections from a single host.", now, now, 0, 1);
         }
     }
 }
@@ -3801,6 +3801,7 @@ gtrace_print_func(struct gline *gline, void *extra)
     char issued[INTERVALLEN];
     char lastmod[INTERVALLEN];
     char expires[INTERVALLEN];
+    char lifetime[INTERVALLEN];
 
     intervalString(issued, now - gline->issued, xtra->user->handle_info);
     if (gline->lastmod)
@@ -3811,7 +3812,8 @@ gtrace_print_func(struct gline *gline, void *extra)
         intervalString(expires, gline->expires - now, xtra->user->handle_info);
     else
         strcpy(expires, "never");
-    send_message(xtra->user, opserv, "OSMSG_GTRACE_FORMAT", gline->target, issued, gline->issuer, lastmod, expires, gline->reason);
+    intervalString(lifetime, gline->lifetime - now, xtra->user->handle_info);
+    send_message(xtra->user, opserv, "OSMSG_GTRACE_FORMAT", gline->target, issued, gline->issuer, lastmod, expires, gline->reason, lifetime);
 }
 
 static MODCMD_FUNC(cmd_stats_glines) {
index 3d291a5bbd50594ab728e9eb9e89c9ff2fc367ab..bb62c0b5c5c1af6f99b73f975f512245794bc36d 100644 (file)
@@ -655,11 +655,11 @@ void
 irc_gline(struct server *srv, struct gline *gline)
 {
     if (gline->lastmod)
-        putsock("%s " P10_GLINE " %s +%s %lu %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, (unsigned long)(gline->expires-now), (unsigned long)gline->lastmod, gline->reason);
+        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s",
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason);
     else
         putsock("%s " P10_GLINE " %s +%s %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, (unsigned long)(gline->expires-now), gline->reason);
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason);
 }
 
 void
@@ -674,7 +674,7 @@ irc_settime(const char *srv_name_mask, unsigned long new_time)
 void
 irc_ungline(const char *mask)
 {
-    putsock("%s " P10_GLINE " * -%s", self->numeric, mask);
+    putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
 }
 
 /* Return negative if *(struct modeNode**)pa is "less than" pb,
@@ -1494,10 +1494,13 @@ static CMD_FUNC(cmd_num_topic)
 static CMD_FUNC(cmd_num_gline)
 {
     unsigned long lastmod;
+    unsigned long lifetime;
+
     if (argc < 6)
         return 0;
     lastmod = (argc > 5) ? strtoul(argv[5], NULL, 0) : 0;
-    gline_add(origin, argv[3], atoi(argv[4])-now, argv[argc - 1], now, lastmod, 0);
+    lifetime = (argc > 6) ? strtoul(argv[6], NULL, 0) : 0;
+    gline_add(origin, argv[3], atoi(argv[4])-now, argv[argc - 1], now, lastmod, lifetime, 0);
     return 1;
 }
 
@@ -1612,15 +1615,22 @@ static CMD_FUNC(cmd_away)
 
 static CMD_FUNC(cmd_gline)
 {
+#define PASTWATCH (5*365*24*3600)
     unsigned long lastmod;
+    unsigned long lifetime;
+    unsigned long expiration;
 
     if (argc < 3)
         return 0;
     if (argv[2][0] == '+') {
         if (argc < 5)
             return 0;
-        lastmod = (argc > 5) ? strtoul(argv[5], NULL, 0) : 0;
-        gline_add(origin, argv[2]+1, strtoul(argv[3], NULL, 0), argv[argc-1], now, lastmod, 0);
+        expiration = strtoul(argv[3], NULL, 10);
+        if (expiration < now - PASTWATCH)
+            expiration += now;
+        lastmod = (argc > 5) ? strtoul(argv[4], NULL, 10) : 0;
+        lifetime = (argc > 6) ? strtoul(argv[5], NULL, 10) : 0;
+        gline_add(origin, argv[2]+1, expiration - now, argv[argc-1], now, lastmod, lifetime, 0);
         return 1;
     } else if (argv[2][0] == '-') {
         gline_remove(argv[2]+1, 0);