]> jfr.im git - irc/quakenet/newserv.git/blobdiff - glines/glines_buf.c
CHANSERV: remove accidental sendemail from SETEMAIL command.
[irc/quakenet/newserv.git] / glines / glines_buf.c
index 1a867d3d5db138589e265e6e13e4f5fc9e8b2576..01b85c6b958897b49257936a7a071524c811aee8 100644 (file)
@@ -1,18 +1,32 @@
+#include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
+#include <assert.h>
+#include "../lib/array.h"
 #include "../lib/irc_string.h"
 #include "../irc/irc.h"
 #include "../control/control.h"
 #include "../trusts/trusts.h"
 #include "glines.h"
 
-void glinebufinit(glinebuf *gbuf, int merge) {
-  gbuf->head = NULL;
-  gbuf->merge = merge;
+static int nextglinebufid = 1;
+glinebuf *glinebuflog[MAXGLINELOG] = {};
+int glinebuflogoffset = 0;
+
+void glinebufinit(glinebuf *gbuf, int id) {
+  gbuf->id = id;
+  gbuf->comment = NULL;
+  gbuf->commit = 0;
+  gbuf->amend = 0;
+  gbuf->glines = NULL;
+  gbuf->hitsvalid = 1;
+  gbuf->userhits = 0;
+  gbuf->channelhits = 0;
+  array_init(&gbuf->hits, sizeof(sstring *));
 }
 
 gline *glinebufadd(glinebuf *gbuf, const char *mask, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
-  gline *gl, *sgl, **pnext;
+  gline *gl;
 
   gl = makegline(mask); /* sets up nick,user,host,node and flags */
 
@@ -22,29 +36,6 @@ gline *glinebufadd(glinebuf *gbuf, const char *mask, const char *creator, const
     return 0;
   }
 
-  if (gbuf->merge) {
-    /* Check if an existing gline supercedes this mask */
-    for (sgl = gbuf->head; sgl; sgl = sgl->next) {
-      if (gline_match_mask(sgl, gl)) {
-        freegline(gl);
-        return sgl;
-      }
-    }
-
-    /* Remove older glines this gline matches */
-    for (pnext = &gbuf->head; *pnext; pnext = &((*pnext)->next)) {
-      sgl = *pnext;
-
-      if (gline_match_mask(gl, sgl)) {
-        *pnext = sgl->next;
-        freegline(sgl);
-        
-        if (!*pnext)
-          break;
-      }
-    }
-  }
-
   gl->creator = getsstring(creator, 255);
 
   /* it's not unreasonable to assume gline is active, if we're adding a deactivated gline, we can remove this later */
@@ -55,15 +46,17 @@ gline *glinebufadd(glinebuf *gbuf, const char *mask, const char *creator, const
   gl->lastmod = lastmod;
   gl->lifetime = lifetime;
 
-  gl->next = gbuf->head;
-  gbuf->head = gl;
+  gl->next = gbuf->glines;
+  gbuf->glines = gl;
+
+  gbuf->hitsvalid = 0;
 
   return gl;
 }
 
-void glinebufaddbyip(glinebuf *gbuf, const char *user, struct irc_in_addr *ip, unsigned char bits, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
+char *glinebufaddbyip(glinebuf *gbuf, const char *user, struct irc_in_addr *ip, unsigned char bits, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
   trusthost *th, *oth;
-  char mask[512];
+  static char mask[512];
   unsigned char nodebits;
 
   nodebits = getnodebits(ip);
@@ -75,7 +68,8 @@ void glinebufaddbyip(glinebuf *gbuf, const char *user, struct irc_in_addr *ip, u
       for (oth = th->group->hosts; oth; oth = oth->next)
         glinebufaddbyip(gbuf, user, &oth->ip, oth->bits, flags | GLINE_ALWAYS_USER | GLINE_IGNORE_TRUST, creator, reason, expire, lastmod, lifetime);
 
-      return;
+      snprintf(mask, sizeof(mask), "%s@%s/tg%u", user, (bits == 128) ? IPtostr(*ip) : CIDRtostr(*ip, bits), th->group->id);
+      return mask;
     }
   }
 
@@ -86,126 +80,251 @@ void glinebufaddbyip(glinebuf *gbuf, const char *user, struct irc_in_addr *ip, u
   if (nodebits < bits)
     bits = nodebits;
 
-  snprintf(mask, sizeof(mask), "%s@%s", user, trusts_cidr2str(ip, bits));
+  snprintf(mask, sizeof(mask), "%s@%s", user, (bits == 128) ? IPtostr(*ip) : CIDRtostr(*ip, bits));
 
   glinebufadd(gbuf, mask, creator, reason, expire, lastmod, lifetime);
+  return mask;
 }
 
-void glinebufaddbynick(glinebuf *gbuf, nick *np, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
+char *glinebufaddbynick(glinebuf *gbuf, nick *np, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
+  if (flags & GLINE_ALWAYS_NICK) {
+    static char mask[512];
+    snprintf(mask, sizeof(mask), "%s!*@*", np->nick);
+    glinebufadd(gbuf, mask, creator, reason, expire, lastmod, lifetime);
+    return mask;
+  } else {
+    return glinebufaddbyip(gbuf, np->ident, &np->ipaddress, 128, flags, creator, reason, expire, lastmod, lifetime);
+  }
+}
+
+void glinebufaddbywhowas(glinebuf *gbuf, whowas *ww, int flags, const char *creator, const char *reason, time_t expire, time_t lastmod, time_t lifetime) {
+  nick *np = &ww->nick;
+
   if (flags & GLINE_ALWAYS_NICK) {
     char mask[512];
     snprintf(mask, sizeof(mask), "%s!*@*", np->nick);
     glinebufadd(gbuf, mask, creator, reason, expire, lastmod, lifetime);
   } else {
-    glinebufaddbyip(gbuf, np->ident, &np->p_ipaddr, 128, flags, creator, reason, expire, lastmod, lifetime);
+    glinebufaddbyip(gbuf, np->ident, &np->ipaddress, 128, flags, creator, reason, expire, lastmod, lifetime);
   }
 }
 
 void glinebufcounthits(glinebuf *gbuf, int *users, int *channels) {
   gline *gl;
-  int i, hit;
+  int i, hit, slot;
   chanindex *cip;
   channel *cp;
   nick *np;
+  char uhmask[512];
 
-  if (users)
-    *users = 0;
+#if 0 /* Let's just do a new hit check anyway. */
+  if (gbuf->hitsvalid)
+    return;
+#endif
 
-  if (channels)
-    *channels = 0;
+  gbuf->userhits = 0;
+  gbuf->channelhits = 0;
+
+  for (i = 0; i < gbuf->hits.cursi; i++)
+    freesstring(((sstring **)gbuf->hits.content)[i]);
+
+  array_free(&gbuf->hits);
+  array_init(&gbuf->hits, sizeof(sstring *));
 
-  if (channels) {
-    for (i = 0; i<CHANNELHASHSIZE; i++) {
-      for (cip = chantable[i]; cip; cip = cip->next) {
-        cp = cip->channel;
+  for (i = 0; i<CHANNELHASHSIZE; i++) {
+    for (cip = chantable[i]; cip; cip = cip->next) {
+      cp = cip->channel;
 
-        if (!cp)
-          continue;
+      if (!cp)
+        continue;
 
-        hit = 0;
+      hit = 0;
 
-        for (gl = gbuf->head; gl; gl = gl->next) {
-          if (gline_match_channel(gl, cp)) {
-            hit = 1;
-            break;
-          }
+      for (gl = gbuf->glines; gl; gl = gl->next) {
+        if (gline_match_channel(gl, cp)) {
+          hit = 1;
+          break;
         }
+      }
 
-        if (hit)
-          (*channels)++;
+      if (hit) {
+        snprintf(uhmask, sizeof(uhmask), "channel: %s", cip->name->content);
+
+        gbuf->channelhits++;
+
+        slot = array_getfreeslot(&gbuf->hits);
+        ((sstring **)gbuf->hits.content)[slot] = getsstring(uhmask, 512);
       }
     }
   }
 
-  if (users) {
-    for (i = 0; i < NICKHASHSIZE; i++) {
-      for (np = nicktable[i]; np; np = np->next) {
-        hit = 0;
+  for (i = 0; i < NICKHASHSIZE; i++) {
+    for (np = nicktable[i]; np; np = np->next) {
+      hit = 0;
 
-        for (gl = gbuf->head; gl; gl = gl->next) {
-          if (gline_match_nick(gl, np)) {
-            hit = 1;
-            break;
-          }
+      for (gl = gbuf->glines; gl; gl = gl->next) {
+        if (gline_match_nick(gl, np)) {
+          hit = 1;
+          break;
         }
+      }
+
+      if (hit) {
+        snprintf(uhmask, sizeof(uhmask), "user: %s!%s@%s%s%s r(%s)", np->nick, np->ident, np->host->name->content,
+          (np->auth) ? "/" : "", (np->auth) ? np->authname : "", np->realname->name->content);
 
-        if (hit)
-          (*users)++;
+        gbuf->userhits++;
+
+        slot = array_getfreeslot(&gbuf->hits);
+        ((sstring **)gbuf->hits.content)[slot] = getsstring(uhmask, 512);
       }
     }
   }
+
+  gbuf->hitsvalid = 1;  
+
+  if (users)
+    *users = gbuf->userhits;
+  
+  if (channels)
+    *channels = gbuf->channelhits;
 }
 
-int glinebufsanitize(glinebuf *gbuf) {
-  gline *gl, **pnext;
-  int skipped;
+int glinebufchecksane(glinebuf *gbuf, nick *spewto, int overridesanity, int overridelimit) {
+  gline *gl;
+  int users, channels, good;
   const char *hint;
 
-  skipped = 0;
-
-  /* Remove glines that fail the sanity check */
-  for (pnext = &gbuf->head; *pnext; pnext = &((*pnext)->next)) {
-    gl = *pnext;
+  glinebufcounthits(gbuf, &users, &channels);
 
-    if (!isglinesane(gl, &hint)) {
-      controlwall(NO_OPER, NL_GLINES, "Sanity check failed for G-Line on '%s' lasting %s created by %s with reason '%s' - Skipping: %s",
-        glinetostring(gl), longtoduration(gl->expire-getnettime(), 0),
-        gl->reason->content, gl->creator->content, hint);
+  if (!overridelimit) {
+    if (channels > MAXUSERGLINECHANNELHITS) {
+      controlreply(spewto, "G-Lines would hit %d channels. Limit is %d. Not setting G-Lines.", channels, MAXUSERGLINECHANNELHITS);
+      return 0;
+    } else if (users > MAXUSERGLINEUSERHITS) {
+      controlreply(spewto, "G-Lines would hit %d users. Limit is %d. Not setting G-Lines.", users, MAXUSERGLINEUSERHITS);
+      return 0;
+    }
+  }
 
-      *pnext = gl->next;
-      freegline(gl);
+  good = 1;
 
-      skipped++;
-      
-      if (!*pnext)
-        break;
+  if (!overridesanity) {
+    /* Remove glines that fail the sanity check */
+    for (gl = gbuf->glines; gl; gl = gl->next) {
+      if (!isglinesane(gl, &hint)) {
+        controlreply(spewto, "Sanity check failed for G-Line on '%s' - Skipping: %s",
+          glinetostring(gl), hint);
+        good = 0;
+      }
     }
   }
 
-  return skipped;
+  return good;
 }
 
-void glinebufspew(glinebuf *gbuf, nick *np) {
+void glinebufspew(glinebuf *gbuf, nick *spewto) {
   gline *gl;
+  int i;
+  char timebuf[30], lastmod[30];
+
+  if (!gbuf->hitsvalid)
+    glinebufcounthits(gbuf, NULL, NULL);
 
-  for (gl = gbuf->head; gl; gl = gl->next)
-    controlreply(np, "mask: %s", glinetostring(gl));
+  if (gbuf->id == 0)
+    controlreply(spewto, "Uncommitted G-Line transaction.");
+  else
+    controlreply(spewto, "G-Line transaction ID %d", gbuf->id);
+
+  controlreply(spewto, "Comment: %s", (gbuf->comment) ? gbuf->comment->content : "(no comment)");
+
+  if (gbuf->commit) {
+    strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gbuf->commit));
+    controlreply(spewto, "Committed at: %s", timebuf);
+  }
+
+  if (gbuf->amend) {
+    strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gbuf->amend));
+    controlreply(spewto, "Amended at: %s", timebuf);
+  }
+
+  controlreply(spewto, "Mask                                     Expiry               Last modified        Creator                   Reason");
+  
+  for (gl = gbuf->glines; gl; gl = gl->next) {
+    strftime(timebuf, sizeof(timebuf), "%d/%m/%y %H:%M:%S", localtime(&gl->expire));
+    if (gl->lastmod == 0)
+      strncpy(lastmod, "<ulined>", sizeof(lastmod));
+    else
+      strftime(lastmod, sizeof(lastmod), "%d/%m/%y %H:%M:%S", localtime(&gl->lastmod));
+
+    controlreply(spewto, "%-40s %-20s %-20s %-25s %s", glinetostring(gl), timebuf, lastmod, gl->creator->content, gl->reason ? gl->reason->content : "");
+  }
+
+  controlreply(spewto, "Hits");
+
+  for (i = 0; i < gbuf->hits.cursi; i++) {
+    controlreply(spewto, "%s", ((sstring **)gbuf->hits.content)[i]->content);
+
+    if (i >= 500) {
+      controlreply(spewto, "More than 500 hits, list truncated.");
+      break;
+    }
+  }
+
+  if (i == 0)
+    controlreply(spewto, "(no hits)");
 }
 
-void glinebufflush(glinebuf *gbuf, int propagate) {
+void glinebufmerge(glinebuf *gbuf) {
+  /* TODO: reimplement */
+
+/*
+  if (gbuf->merge) {
+    /-* Check if an existing gline supercedes this mask *-/
+    for (sgl = gbuf->glines; sgl; sgl = sgl->next) {
+      if (gline_match_mask(sgl, gl)) {
+        freegline(gl);
+        return sgl;
+      }
+    }
+
+    /-* Remove older glines this gline matches *-/
+    for (pnext = &gbuf->glines; *pnext; pnext = &((*pnext)->next)) {
+      sgl = *pnext;
+
+      if (gline_match_mask(gl, sgl)) {
+        *pnext = sgl->next;
+        freegline(sgl);
+
+        if (!*pnext)
+          break;
+      }
+    }
+  }
+*/
+}
+
+int glinebufcommit(glinebuf *gbuf, int propagate) {
   gline *gl, *next, *sgl;
-  int users, channels;
+  int users, channels, id;
 
   /* Sanity check */
   glinebufcounthits(gbuf, &users, &channels);
 
   if (propagate && (users > MAXGLINEUSERHITS || channels > MAXGLINECHANNELHITS)) {
-    controlwall(NO_OPER, NL_GLINES, "G-Line buffer would hit %d users/%d channels. Not setting G-Lines.");
-    glinebufabandon(gbuf);
-    return;
+    controlwall(NO_OPER, NL_GLINES_AUTO, "G-Line buffer would hit %d users/%d channels. Not setting G-Lines.");
+    glinebufabort(gbuf);
+    return 0;
   }
 
-  for (gl = gbuf->head; gl; gl = next) {
+  /* Record the commit time */
+  time(&gbuf->commit);
+
+  id = glinebufwritelog(gbuf, propagate);
+
+  /* Move glines to the global gline list */
+  for (gl = gbuf->glines; gl; gl = next) {
     next = gl->next;
 
     sgl = findgline(glinetostring(gl));
@@ -220,6 +339,16 @@ void glinebufflush(glinebuf *gbuf, int propagate) {
       else if (!(gl->flags & GLINE_ACTIVE) && sgl->flags & GLINE_ACTIVE)
         gline_deactivate(sgl, 0, 0);
 
+#if SNIRCD_VERSION >= 140
+      sgl->expire = gl->expire;
+
+      if (gl->lifetime > sgl->lifetime) 
+        sgl->lifetime = gl->lifetime;
+
+      freesstring(sgl->reason);
+      sgl->reason = getsstring(gl->reason, 512);
+#endif
+
       freegline(gl);
       gl = sgl;
     } else {
@@ -227,17 +356,196 @@ void glinebufflush(glinebuf *gbuf, int propagate) {
       glinelist = gl;
     }
 
+    gl->glinebufid = id;
+
     if (propagate)
       gline_propagate(gl);
   }
+
+  /* We've moved all glines to the global gline list. Clear glines link in the glinebuf. */  
+  gbuf->glines = NULL;
+
+  glinebufabort(gbuf);
+
+  return id;
 }
 
-void glinebufabandon(glinebuf *gbuf) {
+void glinebufabort(glinebuf *gbuf) {
   gline *gl, *next;
+  int i;
 
-  for (gl = gbuf->head; gl; gl = next) {
+  for (gl = gbuf->glines; gl; gl = next) {
     next = gl->next;
 
     freegline(gl);
   }
+  
+  freesstring(gbuf->comment);
+
+  for (i = 0; i < gbuf->hits.cursi; i++)
+    freesstring(((sstring **)gbuf->hits.content)[i]);
+
+  array_free(&gbuf->hits);
+}
+
+int glinebufundo(int id) {
+  glinebuf *gbl;
+  gline *gl, *sgl;
+  int i;
+
+  for (i = 0; i < MAXGLINELOG; i++) {
+    gbl = glinebuflog[i];
+    
+    if (!gbl || gbl->id != id)
+      continue;
+
+    for (gl = gbl->glines; gl; gl = gl->next) {
+      sgl = findgline(glinetostring(gl));
+      
+      if (!sgl)
+        continue;
+
+      sgl->glinebufid = 0;
+
+      gline_deactivate(sgl, 0, 1);
+    }
+    
+    glinebufabort(gbl);
+    glinebuflog[i] = NULL;
+
+    return 1;
+  }
+  
+  return 0;
+}
+
+void glinebufcommentf(glinebuf *gbuf, const char *format, ...) {
+  char comment[512];
+  va_list va;
+
+  va_start(va, format);
+  vsnprintf(comment, 511, format, va);
+  comment[511] = '\0';
+  va_end(va);
+  
+  gbuf->comment = getsstring(comment, 512);
+}
+
+void glinebufcommentv(glinebuf *gbuf, const char *prefix, int cargc, char **cargv) {
+  char comment[512];
+  int i;
+
+  if (prefix)
+    strncpy(comment, prefix, sizeof(comment));
+  else
+    comment[0] = '\0';
+  
+  for (i = 0; i < cargc; i++) {
+    if (comment[0])
+      strncat(comment, " ", sizeof(comment));
+
+    strncat(comment, cargv[i], sizeof(comment));
+  }
+  
+  comment[511] = '\0';
+  
+  gbuf->comment = getsstring(comment, 512);
+}
+
+int glinebufwritelog(glinebuf *gbuf, int propagating) {
+  int i, slot;
+  gline *gl, *sgl;
+  glinebuf *gbl;
+
+  if (!gbuf->glines)
+    return 0; /* Don't waste log IDs on empty gline buffers */
+
+  gbl = NULL;
+
+  /* Find an existing log buffer with the same id */
+  if (gbuf->id != 0) {
+    for (i = 0; i < MAXGLINELOG; i++) {
+      if (!glinebuflog[i])
+        continue;
+
+      if (glinebuflog[i]->id == gbuf->id) {
+        gbl = glinebuflog[i];
+        gbl->amend = gbuf->commit;
+
+        /* We're going to re-insert this glinebuf, so remove it for now */
+        glinebuflog[i] = NULL;
+
+        break;
+      }
+    }
+  }
+
+  /* Find a recent glinebuf that's a close match */
+  if (!gbl && !propagating) {
+    for (i = 0; i < MAXGLINELOG; i++) {
+      if (!glinebuflog[i])
+        continue;
+
+      if (glinebuflog[i]->commit < getnettime() - 5 && glinebuflog[i]->amend < getnettime() - 5)
+        continue;
+
+      assert(glinebuflog[i]->glines);
+
+      if (strcmp(glinebuflog[i]->glines->creator->content, gbuf->glines->creator->content) != 0)
+        continue;
+
+      gbl = glinebuflog[i];
+      gbl->amend = gbuf->commit;
+
+      /* We're going to re-insert this glinebuf, so remove it for now */
+      glinebuflog[i] = NULL;
+
+      break;
+    }
+  }
+
+  /* Make a new glinebuf for the log */
+  if (!gbl) {
+    gbl = malloc(sizeof(glinebuf));
+    glinebufinit(gbl, (gbuf->id == 0) ? nextglinebufid++ : gbuf->id);
+
+    assert(gbl->hitsvalid);
+
+    if (gbuf->comment)
+      glinebufcommentf(gbl, "%s", gbuf->comment->content);
+    else if (!propagating)
+      glinebufcommentf(gbl, "G-Lines set by %s", gbuf->glines->creator->content);
+
+    gbl->commit = gbuf->commit;
+  }
+
+  /* Save a duplicate of the glines in the log buffer */
+  for (gl = gbuf->glines; gl; gl = gl->next) {
+    sgl = glinedup(gl);
+    sgl->next = gbl->glines;
+    gbl->glines = sgl;
+  }
+
+  gbl->userhits += gbuf->userhits;
+  gbl->channelhits += gbuf->channelhits;
+
+  assert(gbuf->userhits + gbuf->channelhits == gbuf->hits.cursi);
+
+  for (i = 0; i < gbuf->hits.cursi; i++) {
+    slot = array_getfreeslot(&gbl->hits);
+    ((sstring **)gbl->hits.content)[slot] = getsstring(((sstring **)gbuf->hits.content)[i]->content, 512);
+  }
+
+  /* Log the transaction */
+  glinebuflogoffset++;
+
+  if (glinebuflogoffset >= MAXGLINELOG)
+    glinebuflogoffset = 0;
+
+  if (glinebuflog[glinebuflogoffset])
+    glinebufabort(glinebuflog[glinebuflogoffset]);
+
+  glinebuflog[glinebuflogoffset] = gbl;
+
+  return gbl->id;
 }