Răsfoiți Sursa

Init: stats.mod 1.4.0 dev20

Florian Sander 13 ani în urmă
părinte
comite
9316b6a99b
100 a modificat fișierele cu 13941 adăugiri și 121 ștergeri
  1. 164 77
      DATAHANDLING.~C
  2. 13 8
      Makefile
  3. 3 2
      README
  4. 14 4
      UPDATES
  5. 183 0
      addons/anticheat_buggy_dont_use_it.tcl
  6. 17 0
      addons/backup.tcl
  7. 214 0
      addons/stats.php
  8. 31 0
      backup/Makefile
  9. BIN
      bin/00BAFF copy.bmp
  10. BIN
      bin/00BAFF copy.tif
  11. BIN
      bin/00BAFF.png
  12. BIN
      bin/6500E1.png
  13. BIN
      bin/6B54C0.gif
  14. BIN
      bin/6B54C0.jpg
  15. BIN
      bin/6B54C0.png
  16. BIN
      bin/dev/arrow_up.gif
  17. BIN
      bin/dev/email_supersimple.gif
  18. BIN
      bin/dev/homepage_supersimple.gif
  19. BIN
      bin/dev/homepage_supersimple2.gif
  20. BIN
      bin/dev/homepage_supersimple3.gif
  21. BIN
      bin/dev/homepage_supersimple4.gif
  22. BIN
      bin/email.gif
  23. 0 0
      bin/graph.gif
  24. BIN
      bin/homepage.gif
  25. BIN
      bin/vertical_blue_bar.gif
  26. BIN
      bin/vertical_green_bar.gif
  27. 16 0
      compat/CVS/Entries
  28. 1 0
      compat/CVS/Repository
  29. 1 0
      compat/CVS/Root
  30. 92 0
      compat/Makefile
  31. 92 0
      compat/Makefile.in
  32. 35 0
      compat/compat.h
  33. 1257 0
      compat/gnu_strftime.c
  34. 186 0
      compat/inet_aton.c
  35. 40 0
      compat/inet_aton.h
  36. BIN
      compat/inet_aton.o
  37. 35 0
      compat/memcpy.c
  38. 38 0
      compat/memcpy.h
  39. BIN
      compat/memcpy.o
  40. 35 0
      compat/memset.c
  41. 42 0
      compat/memset.h
  42. BIN
      compat/memset.o
  43. 720 0
      compat/snprintf.c
  44. 49 0
      compat/snprintf.h
  45. BIN
      compat/snprintf.o
  46. 50 0
      compat/strcasecmp.c
  47. 46 0
      compat/strcasecmp.h
  48. BIN
      compat/strcasecmp.o
  49. 35 0
      compat/strftime.c
  50. 42 0
      compat/strftime.h
  51. BIN
      compat/strftime.o
  52. 6 0
      core/Makefile
  53. 34 0
      core/compat/noegg.c
  54. 36 0
      core/compat/noegg.h
  55. 114 0
      core/core.c
  56. 166 0
      core/data.h
  57. 412 0
      core/data_sorting.c
  58. 1364 0
      core/datahandling.c
  59. 227 0
      core/dynamic_mem_debug.c
  60. 156 0
      core/generic_linked_list.c
  61. 85 0
      core/global_vars.c
  62. 335 0
      core/http_processing.c
  63. 120 0
      core/llists.c
  64. 1010 0
      core/mini_httpd.c
  65. 71 0
      core/mini_httpd.h
  66. 17 0
      core/mini_httpd_misc.c
  67. 18 0
      core/mini_httpd_net.c
  68. 309 0
      core/misc.c
  69. 31 0
      core/misc.h
  70. 42 0
      core/net.c
  71. 112 0
      core/schan.c
  72. 28 0
      core/schan.h
  73. 74 0
      core/schan_interface.c
  74. 177 0
      core/schan_members.c
  75. 32 0
      core/schan_members.h
  76. 283 0
      core/sensors.c
  77. 364 0
      core/slang.c
  78. 63 30
      core/slang.h
  79. 117 0
      core/slang_chanlang.c
  80. 82 0
      core/slang_duration.c
  81. 163 0
      core/slang_facts.c
  82. 214 0
      core/slang_facts_places.c
  83. 106 0
      core/slang_ids.c
  84. 151 0
      core/slang_multitext.c
  85. 339 0
      core/slang_stats_commands.c
  86. 202 0
      core/slang_text.c
  87. 98 0
      core/slang_types.c
  88. 171 0
      core/templates.c
  89. 76 0
      core/templates.h
  90. 89 0
      core/templates_commands.c
  91. 258 0
      core/templates_content.c
  92. 47 0
      core/templates_httpd_commands.c
  93. 104 0
      core/templates_skin.c
  94. 101 0
      core/templates_standard_commands.c
  95. 1483 0
      core/templates_stats_commands.c
  96. 82 0
      core/templates_template.c
  97. 217 0
      core/user.c
  98. 559 0
      core/userrec.c
  99. 74 0
      core/userrec.h
  100. 71 0
      core/vars.c

+ 164 - 77
datahandling.c → DATAHANDLING.~C

@@ -41,23 +41,8 @@ static void incrstats(char *user, char *chan, int type, int value, int set)
     while (gs2 && gs2->next)
     while (gs2 && gs2->next)
       gs2 = gs2->next;
       gs2 = gs2->next;
     gs = nmalloc(sizeof(globstats));
     gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
     gs->started = now;
     gs->started = now;
-    gs->peak[S_TOTAL] = gs->peak[S_DAILY] = gs->peak[S_WEEKLY] = gs->peak[S_MONTHLY] = 0;
-    for (i = 0; i < 24; i++) {
-      gs->users[S_USERSUM][i] = 0;
-      gs->users[S_USERCOUNTS][i] = -1;
-    }
-    for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
-      gs->slocal[S_TOTAL][i] = gs->slocal[S_DAILY][i] = gs->slocal[S_WEEKLY][i] = gs->slocal[S_MONTHLY][i] = NULL;
-    gs->next = NULL;
-    gs->local = NULL;
-    gs->topics = NULL;
-    gs->hosts = NULL;
-    gs->urls = NULL;
-    gs->log = gs->lastlog = NULL;
-    gs->log_length = 0;
-    gs->kicks = NULL;
-    gs->words = NULL;
     gs->chan = nmalloc(strlen(chan) + 1);
     gs->chan = nmalloc(strlen(chan) + 1);
     strcpy(gs->chan, chan);
     strcpy(gs->chan, chan);
     if (gs2)
     if (gs2)
@@ -82,23 +67,10 @@ static void incrstats(char *user, char *chan, int type, int value, int set)
     while (ls2 && ls2->next)
     while (ls2 && ls2->next)
       ls2 = ls2->next;
       ls2 = ls2->next;
     ls = nmalloc(sizeof(locstats));
     ls = nmalloc(sizeof(locstats));
+    locstats_init(ls);
     ls->started = now;
     ls->started = now;
-    ls->next = NULL;
-    ls->words = NULL;
-    ls->tree = NULL;
-    ls->quotes = NULL;
-    ls->quotefr = 0;
-    ls->flag = 0;
-    for (i = 0; i < TOTAL_TYPES; i++) {
-      ls->values[S_TOTAL][i] = 0;
-      ls->values[S_TODAY][i] = 0;
-      ls->values[S_WEEKLY][i] = 0;
-      ls->values[S_MONTHLY][i] = 0;
-    }
     ls->user = nmalloc(strlen(user) + 1);
     ls->user = nmalloc(strlen(user) + 1);
     strcpy(ls->user, user);
     strcpy(ls->user, user);
-    // we'll initialize this later, if it's needed
-    ls->u = NULL;
     if (ls2)
     if (ls2)
       ls2->next = ls;
       ls2->next = ls;
     else
     else
@@ -158,25 +130,10 @@ static locstats *initstats(char *chan, char *user)
     while (gs2 && gs2->next)
     while (gs2 && gs2->next)
       gs2 = gs2->next;
       gs2 = gs2->next;
     gs = nmalloc(sizeof(globstats));
     gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
     gs->started = now;
     gs->started = now;
-    gs->peak[S_TOTAL] = gs->peak[S_DAILY] = gs->peak[S_WEEKLY] = gs->peak[S_MONTHLY] = 0;
-    for (i = 0; i < 24; i++) {
-      gs->users[S_USERSUM][i] = 0;
-      gs->users[S_USERCOUNTS][i] = -1;
-    }
-    gs->next = NULL;
-    gs->local = NULL;
-    gs->words = NULL;
-    gs->topics = NULL;
-    gs->hosts = NULL;
-    gs->urls = NULL;
-    gs->log = gs->lastlog = NULL;
-    gs->log_length = 0;
-    gs->kicks = NULL;
     gs->chan = nmalloc(strlen(chan) + 1);
     gs->chan = nmalloc(strlen(chan) + 1);
     strcpy(gs->chan, chan);
     strcpy(gs->chan, chan);
-    for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
-      gs->slocal[S_TOTAL][i] = gs->slocal[S_DAILY][i] = gs->slocal[S_WEEKLY][i] = gs->slocal[S_MONTHLY][i] = NULL;
     if (gs2)
     if (gs2)
       gs2->next = gs;
       gs2->next = gs;
     else
     else
@@ -191,23 +148,10 @@ static locstats *initstats(char *chan, char *user)
     while (ls2 && ls2->next)
     while (ls2 && ls2->next)
       ls2 = ls2->next;
       ls2 = ls2->next;
     ls = nmalloc(sizeof(locstats));
     ls = nmalloc(sizeof(locstats));
+    locstats_init(ls);
     ls->started = now;
     ls->started = now;
-    ls->next = NULL;
-    ls->words = NULL;
-    ls->tree = NULL;
-    ls->quotes = NULL;
-    ls->quotefr = 0;
-    ls->flag = 0;
-    for (i = 0; i < TOTAL_TYPES; i++) {
-      ls->values[S_TOTAL][i] = 0;
-      ls->values[S_TODAY][i] = 0;
-      ls->values[S_WEEKLY][i] = 0;
-      ls->values[S_MONTHLY][i] = 0;
-    }
     ls->user = nmalloc(strlen(user) + 1);
     ls->user = nmalloc(strlen(user) + 1);
     strcpy(ls->user, user);
     strcpy(ls->user, user);
-    // we'll initialize this later, if it's needed
-    ls->u = NULL;
     if (ls2)
     if (ls2)
       ls2->next = ls;
       ls2->next = ls;
     else
     else
@@ -229,6 +173,58 @@ static locstats *initstats(char *chan, char *user)
   return ls;
   return ls;
 }
 }
 
 
+static void locstats_init(locstats *ls)
+{
+  int i;
+  
+  Assert(ls);
+  ls->started = now;
+  ls->next = NULL;
+  ls->words = NULL;
+  ls->tree = NULL;
+  ls->quotes = NULL;
+  ls->quotefr = 0;
+  ls->flag = 0;
+  for (i = 0; i < TOTAL_TYPES; i++) {
+    ls->values[S_TOTAL][i] = 0;
+    ls->values[S_TODAY][i] = 0;
+    ls->values[S_WEEKLY][i] = 0;
+    ls->values[S_MONTHLY][i] = 0;
+  }
+  ls->user = NULL;
+  ls->u = NULL;
+  ls->lastspoke = 0;
+  for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+    ls->snext[S_TOTAL][i] = ls->snext[S_DAILY][i] = ls->snext[S_WEEKLY][i] = ls->snext[S_MONTHLY][i] = NULL;
+}
+
+static void globstats_init(globstats *gs)
+{
+  int i;
+  
+  Assert(gs);
+  gs->next = NULL;
+  gs->chan = NULL;
+  gs->started = now;
+  gs->peak[S_TOTAL] = gs->peak[S_DAILY] = gs->peak[S_WEEKLY] = gs->peak[S_MONTHLY] = 0;
+  for (i = 0; i < 24; i++) {
+    gs->users[S_USERSUM][i] = 0;
+    gs->users[S_USERCOUNTS][i] = -1;
+  }
+  for (i = 0; i < 24; i++)
+    gs->activity[i] = 0;
+  gs->local = NULL;
+  for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+    gs->slocal[S_TOTAL][i] = gs->slocal[S_DAILY][i] = gs->slocal[S_WEEKLY][i] = gs->slocal[S_MONTHLY][i] = NULL;
+  gs->words = NULL;
+  gs->topics = NULL;
+  gs->hosts = NULL;
+  gs->urls = NULL;
+  gs->log = gs->lastlog = NULL;
+  gs->log_length = 0;
+  gs->kicks = NULL;
+}
+
 static int getstats(char *user, char *chan, char *type, int today)
 static int getstats(char *user, char *chan, char *type, int today)
 {
 {
   struct stats_global *gs = sdata;
   struct stats_global *gs = sdata;
@@ -259,6 +255,7 @@ static void sortstats(struct stats_global *gs, int itype, int today)
   int a, b, pitype;
   int a, b, pitype;
 
 
   Context;
   Context;
+  Assert(gs);
   again = 1;
   again = 1;
   last = NULL;
   last = NULL;
   if (itype < 0) {
   if (itype < 0) {
@@ -335,11 +332,11 @@ static void sortstats_wpl(struct stats_global *gs, int today)
       if (!c || !n)
       if (!c || !n)
         a = b = 0;
         a = b = 0;
       else {
       else {
-        if (c->values[today][T_LINES])
+        if (c->values[today][T_LINES] >= min_lines)
           a = (int) (((float) c->values[today][T_WORDS] / (float) c->values[today][T_LINES]) * 100.0);
           a = (int) (((float) c->values[today][T_WORDS] / (float) c->values[today][T_LINES]) * 100.0);
         else
         else
           a = 0;
           a = 0;
-        if (n->values[today][T_LINES])
+        if (n->values[today][T_LINES] >= min_lines)
           b = (int) (((float) n->values[today][T_WORDS] / (float) n->values[today][T_LINES]) * 100.0);
           b = (int) (((float) n->values[today][T_WORDS] / (float) n->values[today][T_LINES]) * 100.0);
         else
         else
           b = 0;
           b = 0;
@@ -412,9 +409,11 @@ static void sortstats_word(struct stats_global *gs, int today)
   int a, b, pitype;
   int a, b, pitype;
 
 
   Context;
   Context;
+  debug1("sortstats_word: today == %d", today);
   again = 1;
   again = 1;
   last = NULL;
   last = NULL;
   pitype = (T_WORD * (-1)) + TOTAL_TYPES - 1;
   pitype = (T_WORD * (-1)) + TOTAL_TYPES - 1;
+  debug1("pitype: %d", pitype);
   while ((gs->slocal[today][pitype] != last) && (again)) {
   while ((gs->slocal[today][pitype] != last) && (again)) {
     p = NULL;
     p = NULL;
     c = gs->slocal[today][pitype];
     c = gs->slocal[today][pitype];
@@ -472,11 +471,11 @@ static void sortstats_idle(struct stats_global *gs, int today)
       if (!c || !n)
       if (!c || !n)
         a = b = 0;
         a = b = 0;
       else {
       else {
-        if (c->values[today][T_LINES])
+        if (c->values[today][T_LINES] >= min_lines)
           a = (int) (((float) c->values[today][T_MINUTES] / (float) c->values[today][T_LINES]) * 100.0);
           a = (int) (((float) c->values[today][T_MINUTES] / (float) c->values[today][T_LINES]) * 100.0);
         else
         else
           a = 0;
           a = 0;
-        if (n->values[today][T_LINES])
+        if (n->values[today][T_LINES] >= min_lines)
           b = (int) (((float) n->values[today][T_MINUTES] / (float) n->values[today][T_LINES]) * 100.0);
           b = (int) (((float) n->values[today][T_MINUTES] / (float) n->values[today][T_LINES]) * 100.0);
         else
         else
           b = 0;
           b = 0;
@@ -656,6 +655,33 @@ static int typetoi(char *type)
   }
   }
 }
 }
 
 
+/* itotype():
+ * similar to typetoi(), but returns the string that describes
+ * the given type
+ */
+static char *itotype(int type)
+{
+  switch (type) {
+    case T_WORDS: return "words";
+    case T_LETTERS: return "letters";
+    case T_MINUTES: return "minutes";
+    case T_TOPICS: return "topics";
+    case T_LINES: return "lines";
+    case T_ACTIONS: return "actions";
+    case T_MODES: return "modes";
+    case T_BANS: return "bans";
+    case T_KICKS: return "kicks";
+    case T_NICKS: return "nicks";
+    case T_JOINS: return "joins";
+    case T_SMILEYS: return "smileys";
+    case T_QUESTIONS: return "questions";
+    case T_WPL: return "w/l";
+    case T_IDLE: return "idle";
+    case T_VOCABLES: return "vocables";
+  }
+  return "!!!ERROR!!!";
+}
+
 static locstats *findlocstats(char *chan, char *user)
 static locstats *findlocstats(char *chan, char *user)
 {
 {
   globstats *gl;
   globstats *gl;
@@ -706,7 +732,7 @@ static void write_stats()
     return;
     return;
   }
   }
   fprintf(f, "@ # Statistics from %s.\n", botnetnick);
   fprintf(f, "@ # Statistics from %s.\n", botnetnick);
-  fprintf(f, "@ filever 1\n");
+  fprintf(f, "@ filever 3\n");
   fprintf(f, "@ month %d\n", getmonth());
   fprintf(f, "@ month %d\n", getmonth());
   for (gs = sdata; gs; gs = gs->next) {
   for (gs = sdata; gs; gs = gs->next) {
     fprintf(f, "%s ! %d\n", gs->chan, (int) gs->started);
     fprintf(f, "%s ! %d\n", gs->chan, (int) gs->started);
@@ -718,6 +744,7 @@ static void write_stats()
       for (i = 0; i < TOTAL_TYPES; i++)
       for (i = 0; i < TOTAL_TYPES; i++)
         fprintf(f, " %ld", ls->values[S_TOTAL][i]);
         fprintf(f, " %ld", ls->values[S_TOTAL][i]);
       fprintf(f, "\n");
       fprintf(f, "\n");
+      fprintf(f, "@ lastspoke %d\n", (int) ls->lastspoke);
       fprintf(f, "@ daily %s %s", gs->chan, ls->user);
       fprintf(f, "@ daily %s %s", gs->chan, ls->user);
       for (i = 0; i < TOTAL_TYPES; i++)
       for (i = 0; i < TOTAL_TYPES; i++)
         fprintf(f, " %ld", ls->values[S_DAILY][i]);
         fprintf(f, " %ld", ls->values[S_DAILY][i]);
@@ -734,17 +761,21 @@ static void write_stats()
     }
     }
   }
   }
   for (u = suserlist; u; u = u->next) {
   for (u = suserlist; u; u = u->next) {
-    fprintf(f, "@ user %s %d %d", u->user, u->list, u->addhosts);
+    fprintf(f, "@ user %s %d %lu %lu", u->user, u->flags, u->created, u->laston);
     for (h = u->hosts; h; h = h->next) {
     for (h = u->hosts; h; h = h->next) {
-      fprintf(f, " %s %lu", h->mask, h->lastused);
+      fprintf(f, " %s %lu %lu", h->mask, h->lastused, h->created);
     }
     }
     fprintf(f, "\n");
     fprintf(f, "\n");
-    if (u->email || u->homepage) {
+    if (u->password || u->email || u->homepage || u->icqnr) {
       fprintf(f, "@ uxtra %s", u->user);
       fprintf(f, "@ uxtra %s", u->user);
       if (u->email)
       if (u->email)
         fprintf(f, " e %s", u->email);
         fprintf(f, " e %s", u->email);
       if (u->homepage)
       if (u->homepage)
         fprintf(f, " h %s", u->homepage);
         fprintf(f, " h %s", u->homepage);
+      if (u->icqnr)
+        fprintf(f, " i %d", u->icqnr);
+      if (u->password)
+        fprintf(f, " p %s", u->password);
       fprintf(f, "\n");
       fprintf(f, "\n");
     }
     }
   }
   }
@@ -760,11 +791,12 @@ static void read_stats()
   FILE *f;
   FILE *f;
   char buf[SAVESTATSLENGTH + 1];
   char buf[SAVESTATSLENGTH + 1];
   char *s, *chan, *user, *cmd, *host, *tmp;
   char *s, *chan, *user, *cmd, *host, *tmp;
-  int i, version, range, month, list, addhosts;
+  int i, version, range, month, list, addhosts, flags;
   struct stats_userlist *u;
   struct stats_userlist *u;
   time_t lastused;
   time_t lastused;
   locstats *ls;
   locstats *ls;
   globstats *gs;
   globstats *gs;
+  time_t created, laston;
 
 
   Context;
   Context;
   ls = NULL;
   ls = NULL;
@@ -828,15 +860,41 @@ static void read_stats()
           ls = initstats(chan, user);
           ls = initstats(chan, user);
         for (i = 0; i < TOTAL_TYPES; i++)
         for (i = 0; i < TOTAL_TYPES; i++)
           ls->values[range][i] = atoi(newsplit(&s));
           ls->values[range][i] = atoi(newsplit(&s));
+      } else if (!strcmp(cmd, "lastspoke")) {
+	if (ls) {
+	  ls->lastspoke = atoi(newsplit(&s));
+	} else {
+	  putlog(LOG_MISC, "*", "ERROR: Can't load lastspoke info. No locstats.");
+	}
       } else if (!strcmp(cmd, "user")) {
       } else if (!strcmp(cmd, "user")) {
 	user = newsplit(&s);
 	user = newsplit(&s);
-	list = atoi(newsplit(&s));
-	addhosts = atoi(newsplit(&s));
-	u = addsuser(user, list, addhosts);
+	flags = 0;
+	if (version < 2) {
+	  list = atoi(newsplit(&s));
+	  addhosts = atoi(newsplit(&s));
+	  if (list)
+	    flags |= S_LIST;
+	  if (addhosts)
+	    flags |= S_ADDHOSTS;
+	} else
+	  flags = atoi(newsplit(&s));
+	if (version >= 3) {
+	  created = atoi(newsplit(&s));
+	  laston = atoi(newsplit(&s));
+	} else {
+	  created = get_creation_time_from_locstats(user);
+	  laston = get_laston_time_from_hosts(user);
+	}
+	u = addsuser(user, created, laston);
+	u->flags = flags;
 	while (s[0]) {
 	while (s[0]) {
 	  host = newsplit(&s);
 	  host = newsplit(&s);
 	  lastused = (time_t) atoi(newsplit(&s));
 	  lastused = (time_t) atoi(newsplit(&s));
-	  saddhost(u, host, lastused);
+	  if (version >= 3)
+	    created = (time_t) atoi(newsplit(&s));
+	  else
+	    created = lastused;
+	  saddhost(u, host, lastused, created);
 	}
 	}
       } else if (!strcmp(cmd, "uxtra")) {
       } else if (!strcmp(cmd, "uxtra")) {
         user = newsplit(&s);
         user = newsplit(&s);
@@ -845,8 +903,12 @@ static void read_stats()
           tmp = newsplit(&s);
           tmp = newsplit(&s);
           if (!strcmp(tmp, "e"))
           if (!strcmp(tmp, "e"))
             setemail(u, newsplit(&s));
             setemail(u, newsplit(&s));
-          else
+          else if (!strcmp(tmp, "h"))
             sethomepage(u, newsplit(&s));
             sethomepage(u, newsplit(&s));
+          else if (!strcmp(tmp, "i"))
+            u->icqnr = atoi(newsplit(&s));
+          else if (!strcmp(tmp, "p"))
+            setpassword(u, newsplit(&s));
         }
         }
       }
       }
     } else {
     } else {
@@ -1112,11 +1174,16 @@ static void nincrwordstats(globstats *gs, char *word, int value)
   l->nr += value;
   l->nr += value;
 }
 }
 
 
+static time_t glob_lastglobwordstats;
 static void do_globwordstats(globstats *gs)
 static void do_globwordstats(globstats *gs)
 {
 {
   wordstats *l;
   wordstats *l;
   locstats *ls;
   locstats *ls;
 
 
+  if (glob_lastglobwordstats == now)
+    return; /* don't recalculate everything if we already did it in this second */
+  debug0("calculating global wordstats");
+  glob_lastglobwordstats = now;
   for (l = gs->words; l; l = l->next)
   for (l = gs->words; l; l = l->next)
     l->nr = 0;
     l->nr = 0;
   for (ls = gs->local; ls; ls = ls->next)
   for (ls = gs->local; ls; ls = ls->next)
@@ -1376,8 +1443,8 @@ static void add_chanlog(globstats *gs, char *nick, char *text, int type)
   newlog = nmalloc(sizeof(quotestr));
   newlog = nmalloc(sizeof(quotestr));
   newlog->next = NULL;
   newlog->next = NULL;
   if (type == SL_PRIVMSG) {
   if (type == SL_PRIVMSG) {
-    newlog->quote = nmalloc(strlen(nick) + strlen(ts) + strlen(text) + 11);
-    sprintf(newlog->quote, "%s &lt;%s&gt; %s", ts, nick, text);
+    newlog->quote = nmalloc(strlen(nick) + strlen(ts) + strlen(text) + 5);
+    sprintf(newlog->quote, "%s <%s> %s", ts, nick, text);
   } else if (type == SL_KICK) {
   } else if (type == SL_KICK) {
     newlog->quote = nmalloc(strlen(text) + strlen(ts) + 2);
     newlog->quote = nmalloc(strlen(text) + strlen(ts) + 2);
     sprintf(newlog->quote, "%s %s", ts, text);
     sprintf(newlog->quote, "%s %s", ts, text);
@@ -1398,7 +1465,8 @@ static void add_chanlog(globstats *gs, char *nick, char *text, int type)
     sprintf(newlog->quote, "%s %s has quit IRC (%s)", ts, nick, text);
     sprintf(newlog->quote, "%s %s has quit IRC (%s)", ts, nick, text);
   } else {
   } else {
     debug1("Unknown log-type: %d !!!", type);
     debug1("Unknown log-type: %d !!!", type);
-    newlog->quote = NULL;
+    newlog->quote = nmalloc(1);
+    newlog->quote[0] = 0;
   }
   }
   if (gs->lastlog)
   if (gs->lastlog)
     gs->lastlog->next = newlog;
     gs->lastlog->next = newlog;
@@ -1408,6 +1476,7 @@ static void add_chanlog(globstats *gs, char *nick, char *text, int type)
   gs->log_length++;
   gs->log_length++;
   while ((gs->log_length > kick_context) && (gs->log_length > 0)) {
   while ((gs->log_length > kick_context) && (gs->log_length > 0)) {
     l = gs->log->next;
     l = gs->log->next;
+    Assert(gs->log->quote);
     nfree(gs->log->quote);
     nfree(gs->log->quote);
     if (gs->lastlog == gs->log)
     if (gs->lastlog == gs->log)
       gs->lastlog = NULL;
       gs->lastlog = NULL;
@@ -1455,3 +1524,21 @@ static void save_kick(globstats *gs, char *kick)
     gs->kicks = nk;
     gs->kicks = nk;
   debug1("Logged kick in %s.", gs->chan);
   debug1("Logged kick in %s.", gs->chan);
 }
 }
+
+static int getplace(globstats *gs, int today, int itype, char *user)
+{
+  locstats *ls;
+  int place = 0;
+
+  // if itype is < 0, get the modified itype that we need for accessing the data
+  if (itype < 0)
+    itype = (TOTAL_TYPES - 1) + (itype * -1);
+  for (ls = gs->slocal[today][itype]; ls; ls = ls->snext[today][itype]) {
+    if (!listsuser(ls, gs->chan))
+      continue;
+    place++;
+    if (!rfc_casecmp(ls->user, user))
+      return place;
+  }
+  return 0;
+}

+ 13 - 8
Makefile

@@ -7,21 +7,26 @@ doofus:
 	@cd ../../../; make
 	@cd ../../../; make
 
 
 clean:
 clean:
-	@rm -f *.o *.so *~
+	@rm -f *.o *.$(MOD_EXT) *~
 
 
 static: ../stats.o
 static: ../stats.o
 
 
-modules: ../../../stats.so
+modules: ../../../stats.$(MOD_EXT)
 
 
-../stats.o: ../module.h ../modvals.h ../../eggdrop.h datahandling.c \
- stats.c sensors.c dcccmds.c misc.c pubcmds.c msgcmds.c webfiles.c \
- user.c livestats.c userrec.c tclstats.c slang.c slang.h stats.h
+../stats.o: ../module.h ../modvals.h ../../eggdrop.h \
+ egg_chancontrol.c pubcmds.c \
+ core/sensors.c core/datahandling.c core/global_vars.c core/schan_members.c \
+ stats.c tclstats.c core/misc.c core/dynamic_mem_debug.c core/userrec.c stats.h
 	$(CC) $(CFLAGS) $(CPPFLAGS) -DMAKING_MODS -c stats.c
 	$(CC) $(CFLAGS) $(CPPFLAGS) -DMAKING_MODS -c stats.c
 	rm -f ../stats.o
 	rm -f ../stats.o
 	mv stats.o ../
 	mv stats.o ../
 
 
-../../../stats.so: ../stats.o
-	$(LD) -o ../../../stats.so ../stats.o
-	$(STRIP) ../../../stats.so
+../../../stats.$(MOD_EXT): ../stats.o
+	$(LD) -o ../../../stats.$(MOD_EXT) ../stats.o $(XLIBS)
+
+core: core.o
+
+core.o: core/core.c
+	gcc -pipe -g -O2 -I. -g3 -DNO_EGG core/core.c
 
 
 #safety hash
 #safety hash

+ 3 - 2
README

@@ -146,7 +146,8 @@ Livestats:
 ----------
 ----------
 
 
 If you activate livestats, your bot will listen on a specified port for
 If you activate livestats, your bot will listen on a specified port for
-http connections. (accessable via http://your.shell.com:8033/, for example)
+http connections. (accessable via http://your.shell.com:8033/, for example
+(depending on your configuration, you might have to use your vhost instead))
 The webfiles will then be generated on the fly. There will also be a little
 The webfiles will then be generated on the fly. There will also be a little
 channel and user list which makes it easier for your users to browse through
 channel and user list which makes it easier for your users to browse through
 the several channel statistics of your bot.
 the several channel statistics of your bot.
@@ -238,6 +239,6 @@ Thanks to:
 ----------
 ----------
 
 
 - Fabian for teaching me plenty of things
 - Fabian for teaching me plenty of things
-- Johoho and Fox_Muld for various bug reports and suggestions
+- Johoho, Fox_Muld and many others for various bug reports and suggestions
 - dw for the idea of livestats and for many bug reports
 - dw for the idea of livestats and for many bug reports
 - the eggdev team for developing eggdrop
 - the eggdev team for developing eggdrop

+ 14 - 4
UPDATES

@@ -1,10 +1,20 @@
 Changes in Stats.mod: (since v1.0.1)
 Changes in Stats.mod: (since v1.0.1)
 --------------------
 --------------------
 
 
-1.3.3
-- fixed bug which could crash the bot in certain cases
-- links for weekly/monthly graphs were switched
-- possible length of CSS was a little bit too limited (fix by Vetinari`)
+1.4.0
+- added TEMPLATES!
+- rewrote language system
+- new command: !top (more flexible than !top10, !top20, etc)
+- new command: !last (just like !top, but shows the last x users)
+- !place now also accepts username _and_ stat-type at once as
+  parameter
+- new setting: $autoadd-min-lines
+- users can now login via WWW and change their info
+- added info: icq#
+- added filter for stupid color-codes
+- new way of expireing users
+- new command: !lastspoke
+
 
 
 1.3.2
 1.3.2
 - optimized sorting functions, livestats should be faster now
 - optimized sorting functions, livestats should be faster now

+ 183 - 0
addons/anticheat_buggy_dont_use_it.tcl

@@ -0,0 +1,183 @@
+# AntiStatCheat v1.2.0
+
+# this script is intended for use with stats.mod
+# (http://www.visions-of-fantasy.de/stats.mod/)
+
+# This script should prevent any cheating attempts
+# Depending on the configuration, it either doesn't increase
+# the stats during the cheat attempt, subtracts the words
+# or does nothing at all and calculates the stats as usual.
+
+# Cheating attempts get logged to a certain loglevel (2 by default)
+# set your console to +2 and the channel you want to monitor to
+# watch the bots reaction on cheating attempts.
+
+### CONFIGURATION ###
+
+# if a user talks more than x lines in a row without another
+# user interfering, then he or she is probably trying to cheat
+set acset(monologue) 7
+
+# the following settings control the way how the bot should react
+# on different cheating attempts.
+# 0: do nothing
+# 1: don't increase the stats
+# 2: subtract words
+
+# Repeating: This is usually a real cheating attempt, so the default
+#            treatment is subtracting the words.
+set acset(repeatpunish) 2
+# Monologue: This is also a strong indication of a cheating attempt.
+set acset(monologuepunish) 2
+# Colors: Users who use colors aren't necessarly cheating, so just
+#         don't increase the stats and don't punish them.
+set acset(colorpunish) 1
+
+# if the average wordlength in a line is smaller than this value, then
+# someone probably tried to cheat ("a a a a a a")
+set acset(wlen) 2.0
+# 
+set acset(wlenpunish) 1
+
+# the loglevel where the bot logs the cheating attempts to
+set acset(loglev) 2
+
+# the time in seconds for that the bot remembers what the user has spoken
+# to detect repeating.
+set acset(tracktime) 240
+
+### END OF CONFIGURATION ###
+
+catch "unbind pubm - * *pubm:stat"
+bind pubm - * pubm:stat
+proc pubm:stat {nick uhost hand chan rest} {
+  if {![stat:cheated $nick $hand $chan $rest words]} {
+    *pubm:stat $nick $uhost $hand $chan $rest
+  }
+}
+
+catch "unbind ctcp - ACTION *ctcp:stat"
+bind ctcp - ACTION ctcp:stat
+proc ctcp:stat {nick uhost hand chan key rest} {
+  if {![stat:cheated $nick $hand $chan $rest action]} {
+    *ctcp:stat $nick $uhost $hand $chan $key $rest
+  }
+}
+
+proc stat:cheated {nick hand chan rest type} {
+  global acset
+  stat:resetstuff
+  set hand [nick2suser $nick $chan]
+  if {$hand == "*"} {
+    stat:monologue $hand $chan
+    return 1
+  }
+  if {$acset(repeatpunish) != 0} {
+    if {[stat:repeated $hand $chan $rest]} {
+      if {$acset(repeatpunish) == 2} {
+      	putloglev $acset(loglev) $chan "StatCheat: $hand is repeating. Subtracting words instead of adding."
+	incrstats $hand $chan words -[llength $rest]
+        incrstats $hand $chan lines -1
+	if {$type == "action"} {
+	  incrstats $hand $chan actions -1
+	}
+      } else {
+      	putloglev $acset(loglev) $chan "StatCheat: $hand is repeating. Ignoring..."
+      }
+      return 1
+    }
+  }
+  if {$acset(monologuepunish) != 0} {
+    if {[stat:monologue $hand $chan]} {
+      if {$acset(monologuepunish) == 2} {
+      	putloglev $acset(loglev) $chan "StatCheat: $hand is making a monologue since more than $acset(monologue) lines. Subtracting words instead of adding."
+	incrstats $hand $chan words -[llength $rest]
+        incrstats $hand $chan lines -1
+	if {$type == "action"} {
+	  incrstats $hand $chan actions -1
+	}
+      } else {
+      	putloglev $acset(loglev) $chan "StatCheat: $hand is holding a monologue. Ignoring..."
+      }
+      return 1
+    }
+  }
+  if {[string match "*\003*" $rest]} {
+    if {$acset(colorpunish) == 2} {
+      putloglev $acset(loglev) $chan "StatCheat: $hand is using colors. That's probably a script. Subtracting words instead of adding."
+      incrstats $hand $chan words -[llength $rest]
+      incrstats $hand $chan lines -1
+      if {$type == "action"} {
+	incrstats $hand $chan actions -1
+      }
+    } else {
+      putloglev $acset(loglev) $chan "StatCheat: $hand is using colors. That's probably a script. Ignoring."
+    }
+    return 1
+  }
+  return 0
+}
+
+proc stat:repeated {hand chan text} {
+  global statrepeat
+
+  set idx ""
+  lappend idx $hand
+  lappend idx $chan
+  if {![info exists statrepeat($idx)]} {
+    set statrepeat($idx) ""
+  }
+  if {[lsearch -exact $statrepeat($idx) $text] > -1} {
+    return 1
+  } else {
+    lappend statrepeat($idx) $text
+    return 0
+  }
+}
+
+proc stat:monologue {hand chan} {
+  global statmonologue acset
+
+  if {![info exists statmonologue($chan)]} {
+    set statmonologue($chan) "foo 0"
+  }
+  set lasthand [lindex $statmonologue($chan) 0]
+  set times [lindex $statmonologue($chan) 1]
+  if {$lasthand != $hand} {
+    set newdat ""
+    lappend newdat $hand
+    lappend newdat 1
+    set statmonologue($chan) $newdat
+    return 0
+  } else {
+    incr times
+    set newdat ""
+    lappend newdat $hand
+    lappend newdat $times
+    set statmonologue($chan) $newdat
+  }
+  if {$times >= $acset(monologue)} {
+    return 1
+  } else {
+    return 0
+  }
+}
+
+proc stat:resetstuff {} {
+  global statrepeat statreset acset
+
+  if {![info exists statreset]} {
+    set statreset 0
+  }
+  if {[expr [unixtime] - $statreset] < $acset(tracktime)} {
+    return
+  }
+  set statreset [unixtime]
+  foreach item [array names statrepeat] {
+    unset statrepeat($item)
+  }
+  catch "unbind ctcp - ACTION *ctcp:stat"
+  catch "unbind pubm - * *pubm:stat"
+}
+
+putlog "AntiCheat v1.2.0 loaded."

+ 17 - 0
addons/backup.tcl

@@ -0,0 +1,17 @@
+# Simple script to make a daily backup of certain files
+
+set files-to-backup "statsmod.dat"
+set backup-dir "backup/"
+
+bind time - "00 00 * * *" time:backup
+proc time:backup {a b c d e} {
+  global files-to-backup backup-dir
+
+  putlog "Backing up..."
+  if {[string index ${backup-dir} [expr [string length ${backup-dir}] - 1]] != "/"} {
+    append ${backup-dir} "/"
+  }
+  foreach datei ${files-to-backup} {
+    file copy $datei ${backup-dir}${datei}.[strftime "%Y%m%d" [unixtime]]
+  }
+}

+ 214 - 0
addons/stats.php

@@ -0,0 +1,214 @@
+<?
+#### stats.php
+#
+## a little script to relay stats to your normal homepage
+#
+
+
+# Configuration
+
+# the server where stats.mod is running (you can use "localhost" if
+# the stats.php is located on the same server)
+$STATSERVER = "broken.eggheads.org";
+
+# the port
+$STATPORT = 8033;
+
+# time to wait before the script gives up
+$STATTIMEOUT = 10;
+
+
+
+
+
+
+
+
+
+
+
+
+###################################################################
+###################################################################
+#
+## Don't touch anything below unless you know what you are doing!
+#
+#
+#
+
+
+
+
+
+$MAXLEN = 4096;
+$statpath = $HTTP_GET_VARS["statpath"];
+$password = $HTTP_POST_VARS["password"];
+$languages = $HTTP_SERVER_VARS["HTTP_ACCEPT_LANGUAGE"];
+$useragent = $HTTP_SERVER_VARS["HTTP_USER_AGENT"];
+$xforwardedfor = $HTTP_SERVER_VARS["HTTP_X_FORWARDED_FOR"];
+$remoteaddr = $HTTP_SERVER_VARS["REMOTE_ADDR"];
+$cookies = $HTTP_SERVER_VARS["HTTP_COOKIE"];
+$scriptname = $HTTP_SERVER_VARS["SCRIPT_NAME"];
+$postparams = "";
+
+if ($statpath == "") {
+	$statpath = $HTTP_SERVER_VARS["PATH_INFO"];
+	if ($statpath == "") {
+		$statpath = "/";
+	}
+}
+
+function my_array_keys ($arr, $term="") {
+    $t = array();
+    while (list($k,$v) = each($arr)) {
+        if ($term && $v != $term)
+            continue;
+            $t[] = $k;
+        }
+        return $t;
+}
+
+
+if ($password != "") {
+	$keys = my_array_keys($HTTP_POST_VARS);
+	for ($i = 0; $i < count($keys); $i++) {
+		$param = urlencode($HTTP_POST_VARS[$keys[$i]]);
+		$postparams .= "&$keys[$i]=$param";
+	}
+	$postparams = substr($postparams, 1);
+}
+
+function transform_url($url) {
+	global $scriptname;
+	global $statpath;
+
+	if (!strcasecmp(substr($url, 0, 7), "http://") ||
+		!strcasecmp(substr($url, 0, 7), "mailto:") ||
+		!strcasecmp(substr($url, 0, 6), "ftp://")) {
+		// global URL, don't touch it
+		$retstr = $url;
+	} else if ($url[0] == "/") {
+		// absolute URL, simply remap it
+		$retstr = "$scriptname$url";
+	} else {
+		// relative URL, process any '../'s
+		$parts = explode("/", $url);
+		$backs = 0;
+		$newurl = "";
+		for ($i = 0; $i < count($parts); $i++) {
+			if (!strcasecmp($parts[$i], "..")) {
+				$backs++;
+			} else {
+				$tmp = "$newurl/$parts[$i]";
+				$newurl = $tmp;
+			}
+		}
+		$newurl = substr($newurl, 1);
+		$parts = explode("/", $statpath);
+		$newpath = "";
+		for ($i = 0; $i < (count($parts) - $backs - 1); $i++) {
+			$tmp = "${newpath}/$parts[$i]";
+			$newpath = $tmp;
+		}
+		$newpath = substr($newpath, 1);
+		$retstr = "$scriptname$newpath/$newurl";
+	}
+	return $retstr;
+}
+
+function do_transform($line) {
+	$words = explode(" ", $line);
+	for ($i = 0; $i < count($words); $i++) {
+		if (!strcasecmp(substr($words[$i], 0, 5), "href=")) {
+			$urlq = strstr($words[$i], "\"");
+			$urlq = substr($urlq, 1);
+			$urlparts = explode("\"", $urlq);
+			$rest = $urlparts[1];
+			$newurl = transform_url($urlparts[0]);
+			echo "href=\"$newurl\"$rest ";
+		} else if (!strcasecmp(substr($words[$i], 0, 7), "action=")) {
+			$urlq = strstr($words[$i], "\"");
+			$urlq = substr($urlq, 1);
+			$urlparts = explode("\"", $urlq);
+			$rest = $urlparts[1];
+			$newurl = transform_url($urlparts[0]);
+			echo "action=\"$newurl\"$rest ";
+		} else {
+			echo "$words[$i] ";
+		}
+	}
+}
+
+$httpbody = false;
+
+$idx = fsockopen($STATSERVER, $STATPORT, $errno, $errstr, $STATTIMEOUT);
+if (!$idx) {
+	echo "<html><head><title>$errno: $errstr</title></head>";
+	echo "<body><H1>Connection to statistics server FAILED!</H1><br>";
+	echo "$errno: $errstr";
+} else {
+	if ($postparams == "") {
+		fputs ($idx, "GET $statpath HTTP/1.0\r\n");
+	} else {
+		$len = strlen($postparams);
+		fputs ($idx, "POST $statpath HTTP/1.0\r\n");
+		fputs ($idx, "Content-Length: $len\r\n");
+	}
+	if ($languages != "") {
+		fputs ($idx, "Accept-Language: $languages\r\n");
+	}
+	if ($useragent != "") {
+		fputs ($idx, "User-Agent: $useragent\r\n");
+	}
+	if ($remoteaddr != "") {
+		fputs ($idx, "X-Relayed-For: $remoteaddr\r\n");
+	}
+	if ($xforwardedfor != "") {
+		fputs ($idx, "X-Forwarded-For: $xforwardedfor\r\n");
+	}
+	if ($cookies != "") {
+		fputs ($idx, "Cookie: $cookies\r\n");
+	}
+
+	fputs ($idx, "\r\n");
+
+	if ($postparams != "") {
+		fputs ($idx, $postparams);
+	}
+
+	while (!feof($idx)) {
+		$buf = fgets($idx, $MAXLEN);
+		if ($httpbody) {
+			do_transform($buf);
+		} else if ($buf == "" || $buf == "\n") {
+			$httpbody = true;
+		} else {
+			$buf = eregi_replace("Location: ", "Location: $scriptname?statpath=", $buf);
+			header($buf);
+		}
+	}
+	fclose($idx);
+}
+
+
+########### old regex experiments
+//	echo eregi_replace("href=\"(\/.*\/)*\"/", "relay.php?statpath=", $buf);
+//	echo eregi_replace("href=\"(http\:\/\/{0,0})", "relay.php?statpath=", $buf);
+//        $buf = eregi_replace("href=\"", "href=\"relay.php?statpath=", $buf);
+//	$buf = eregi_replace("(href=\")([^\"]*)(\")", "href=\"$scriptname.php?path=$statpath{transform_remote_url(\\2)}\"", $buf);
+//	$buf = eregi_replace("(href=\"){1,}", "href=\"$scriptname?statpath=$statpath", $buf);
+//	$buf = eregi_replace("(href=\")[[:alpha:]]*[^\ ]*http\:\/\/", "href=\"http://", $buf);
+//	echo $buf;
+//	echo pregi_replace("href=\"(^http)", "href=\"relay.php?statpath=", $buf);
+//      for ($i = 0; $i < strlen($buf); $i++) {
+//        if (!strncasecmp($buf[$i], "href=\"", 6) && !(!strncasecmp($buf[$i], "href=\"http://", 13)) {
+//          echo "href=\"$scriptname?statpath=$statpath/";
+//          $i = $i + 5;
+//        } else {
+//          echo $buf[i];
+//        }
+//      }
+//      echo eregi_replace("(href\=\")(http\:\/\/){,0}", "href=\"$scriptname?statpath=", $buf);
+//      echo $buf;
+
+?>

+ 31 - 0
backup/Makefile

@@ -0,0 +1,31 @@
+# Makefile for src/mod/stats.mod/
+
+doofus:
+	@echo ""
+	@echo "Let's try this from the right directory..."
+	@echo ""
+	@cd ../../../; make
+
+clean:
+	@rm -f *.o *.$(MOD_EXT) *~
+
+static: ../stats.o
+
+modules: ../../../stats.$(MOD_EXT)
+
+../stats.o: ../module.h ../modvals.h ../../eggdrop.h datahandling.c \
+ stats.c sensors.c dcccmds.c misc.c pubcmds.c msgcmds.c \
+ user.c userrec.c tclstats.c slang.c slang.h mini_httpd.c \
+ templates.c http_processing.c slang_text.c slang_chanlang.c \
+ slang_multitext.c slang_ids.c slang_types.c slang_facts.c \
+ slang_facts_places.c slang_duration.c templates_commands.c \
+ templates_skin.c templates_stats_commands.c templates_content.c \
+ slang_stats_commands.c dynamic_mem_debug.c stats.h
+	$(CC) $(CFLAGS) $(CPPFLAGS) -DMAKING_MODS -c stats.c
+	rm -f ../stats.o
+	mv stats.o ../
+
+../../../stats.$(MOD_EXT): ../stats.o
+	$(LD) -o ../../../stats.$(MOD_EXT) ../stats.o $(XLIBS)
+
+#safety hash

BIN
bin/00BAFF copy.bmp


BIN
bin/00BAFF copy.tif


BIN
bin/00BAFF.png


BIN
bin/6500E1.png


BIN
bin/6B54C0.gif


BIN
bin/6B54C0.jpg


BIN
bin/6B54C0.png


BIN
bin/dev/arrow_up.gif


BIN
bin/dev/email_supersimple.gif


BIN
bin/dev/homepage_supersimple.gif


BIN
bin/dev/homepage_supersimple2.gif


BIN
bin/dev/homepage_supersimple3.gif


BIN
bin/dev/homepage_supersimple4.gif


BIN
bin/email.gif


+ 0 - 0
bar.gif → bin/graph.gif


BIN
bin/homepage.gif


BIN
bin/vertical_blue_bar.gif


BIN
bin/vertical_green_bar.gif


+ 16 - 0
compat/CVS/Entries

@@ -0,0 +1,16 @@
+/Makefile.in/1.5/Tue Sep 12 15:26:51 2000//
+/gnu_strftime.c/1.1/Mon Sep 18 10:06:14 2000//
+/strftime.h/1.1/Tue Sep 12 15:34:01 2000//
+/compat.h/1.3/Thu Apr 12 23:00:04 2001//
+/inet_aton.c/1.5/Thu Apr 12 23:00:04 2001//
+/inet_aton.h/1.3/Thu Apr 12 23:00:04 2001//
+/memcpy.c/1.2/Thu Apr 12 23:00:04 2001//
+/memcpy.h/1.3/Thu Apr 12 23:00:04 2001//
+/memset.c/1.3/Thu Apr 12 23:00:04 2001//
+/memset.h/1.3/Thu Apr 12 23:00:04 2001//
+/snprintf.c/1.4/Thu Apr 12 23:00:04 2001//
+/snprintf.h/1.7/Thu Apr 12 23:00:04 2001//
+/strcasecmp.c/1.2/Thu Apr 12 23:00:04 2001//
+/strcasecmp.h/1.3/Thu Apr 12 23:00:04 2001//
+/strftime.c/1.2/Thu Apr 12 23:00:04 2001//
+D

+ 1 - 0
compat/CVS/Repository

@@ -0,0 +1 @@
+eggdrop1.6/src/compat

+ 1 - 0
compat/CVS/Root

@@ -0,0 +1 @@
+:pserver:anonymous@cvs.eggheads.org:/usr/local/cvsroot

+ 92 - 0
compat/Makefile

@@ -0,0 +1,92 @@
+# Generated automatically from Makefile.in by configure.
+# Makefile for src/compat/
+# $Id: Makefile.in,v 1.5 2000/09/12 15:26:51 fabian Exp $
+
+SHELL = /bin/sh
+top_srcdir = ../..
+srcdir = .
+
+
+INSTALL = /usr/bin/install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_DATA = ${INSTALL} -m 644
+INSTALL_SCRIPT = ${INSTALL_PROGRAM}
+
+CC = gcc -pipe -mwin32
+LD = gcc -pipe -mwin32
+STRIP = strip
+CFLAGS = -g -O2 -I../.. -I$(top_srcdir) -I$(top_srcdir)/src -DHAVE_CONFIG_H $(CFLGS)
+CPPFLAGS = 
+
+OBJS = inet_aton.o snprintf.o memset.o memcpy.o strcasecmp.o strftime.o
+
+doofus:
+	@echo ""
+	@echo "Let's try this from the right directory..."
+	@echo ""
+	@cd ../.. && $(MAKE)
+
+depend:
+	$(CC) $(CFLAGS) $(CPPFLAGS) -MM $(srcdir)/*.c > .depend
+
+clean:
+	@rm -f .depend *.o *~
+
+compat: $(OBJS)
+
+.SUFFIXES:
+.SUFFIXES: .c .o .h
+
+.c.o:
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
+
+#safety hash
+gnu_strftime.o: ./gnu_strftime.c
+inet_aton.o: ./inet_aton.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h inet_aton.h
+memcpy.o: ./memcpy.c ../../src/main.h ../../config.h ../../src/lang.h \
+ ../../src/eggdrop.h ../../src/flags.h ../../src/proto.h ../../lush.h \
+ ../../src/misc_file.h ../../src/cmdt.h ../../src/tclegg.h \
+ ../../src/tclhash.h ../../src/chan.h ../../src/users.h \
+ ../../src/compat/compat.h ../../src/compat/inet_aton.h \
+ ../../src/compat/snprintf.h ../../src/compat/memset.h \
+ ../../src/compat/memcpy.h ../../src/compat/strcasecmp.h \
+ ../../src/compat/strftime.h memcpy.h
+memset.o: ./memset.c ../../src/main.h ../../config.h ../../src/lang.h \
+ ../../src/eggdrop.h ../../src/flags.h ../../src/proto.h ../../lush.h \
+ ../../src/misc_file.h ../../src/cmdt.h ../../src/tclegg.h \
+ ../../src/tclhash.h ../../src/chan.h ../../src/users.h \
+ ../../src/compat/compat.h ../../src/compat/inet_aton.h \
+ ../../src/compat/snprintf.h ../../src/compat/memset.h \
+ ../../src/compat/memcpy.h ../../src/compat/strcasecmp.h \
+ ../../src/compat/strftime.h memset.h
+snprintf.o: ./snprintf.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h snprintf.h
+strcasecmp.o: ./strcasecmp.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h memcpy.h
+strftime.o: ./strftime.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h strftime.h

+ 92 - 0
compat/Makefile.in

@@ -0,0 +1,92 @@
+# Makefile for src/compat/
+# $Id: Makefile.in,v 1.5 2000/09/12 15:26:51 fabian Exp $
+
+SHELL = @SHELL@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+@SET_MAKE@
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+
+CC = @CC@
+LD = @CC@
+STRIP = @STRIP@
+CFLAGS = @CFLAGS@ -I../.. -I$(top_srcdir) -I$(top_srcdir)/src @DEFS@ $(CFLGS)
+CPPFLAGS = @CPPFLAGS@
+
+OBJS = inet_aton.o snprintf.o memset.o memcpy.o strcasecmp.o strftime.o
+
+doofus:
+	@echo ""
+	@echo "Let's try this from the right directory..."
+	@echo ""
+	@cd ../.. && $(MAKE)
+
+depend:
+	$(CC) $(CFLAGS) $(CPPFLAGS) -MM $(srcdir)/*.c > .depend
+
+clean:
+	@rm -f .depend *.o *~
+
+compat: $(OBJS)
+
+.SUFFIXES:
+.SUFFIXES: .c .o .h
+
+.c.o:
+	$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
+
+#safety hash
+gnu_strftime.o: ./gnu_strftime.c
+inet_aton.o: ./inet_aton.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h inet_aton.h
+memcpy.o: ./memcpy.c ../../src/main.h ../../config.h ../../src/lang.h \
+ ../../src/eggdrop.h ../../src/flags.h ../../src/proto.h ../../lush.h \
+ ../../src/misc_file.h ../../src/cmdt.h ../../src/tclegg.h \
+ ../../src/tclhash.h ../../src/chan.h ../../src/users.h \
+ ../../src/compat/compat.h ../../src/compat/inet_aton.h \
+ ../../src/compat/snprintf.h ../../src/compat/memset.h \
+ ../../src/compat/memcpy.h ../../src/compat/strcasecmp.h \
+ ../../src/compat/strftime.h memcpy.h
+memset.o: ./memset.c ../../src/main.h ../../config.h ../../src/lang.h \
+ ../../src/eggdrop.h ../../src/flags.h ../../src/proto.h ../../lush.h \
+ ../../src/misc_file.h ../../src/cmdt.h ../../src/tclegg.h \
+ ../../src/tclhash.h ../../src/chan.h ../../src/users.h \
+ ../../src/compat/compat.h ../../src/compat/inet_aton.h \
+ ../../src/compat/snprintf.h ../../src/compat/memset.h \
+ ../../src/compat/memcpy.h ../../src/compat/strcasecmp.h \
+ ../../src/compat/strftime.h memset.h
+snprintf.o: ./snprintf.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h snprintf.h
+strcasecmp.o: ./strcasecmp.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h memcpy.h
+strftime.o: ./strftime.c ../../src/main.h ../../config.h \
+ ../../src/lang.h ../../src/eggdrop.h ../../src/flags.h \
+ ../../src/proto.h ../../lush.h ../../src/misc_file.h ../../src/cmdt.h \
+ ../../src/tclegg.h ../../src/tclhash.h ../../src/chan.h \
+ ../../src/users.h ../../src/compat/compat.h \
+ ../../src/compat/inet_aton.h ../../src/compat/snprintf.h \
+ ../../src/compat/memset.h ../../src/compat/memcpy.h \
+ ../../src/compat/strcasecmp.h ../../src/compat/strftime.h strftime.h

+ 35 - 0
compat/compat.h

@@ -0,0 +1,35 @@
+/*
+ * compat.h
+ *   wrap-around header for all compability functions.
+ *
+ * $Id: compat.h,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_COMPAT_H
+#define _EGG_COMPAT_COMPAT_H
+
+#include "inet_aton.h"
+#include "snprintf.h"
+#include "memset.h"
+#include "memcpy.h"
+#include "strcasecmp.h"
+#include "strftime.h"
+
+#endif	/* !__EGG_COMPAT_COMPAT_H */

+ 1257 - 0
compat/gnu_strftime.c

@@ -0,0 +1,1257 @@
+/* Copyright (C) 1991,92,93,94,95,96,97,98 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef _LIBC
+# define HAVE_LIMITS_H 1
+# define HAVE_MBLEN 1
+# define HAVE_MBRLEN 1
+# define HAVE_STRUCT_ERA_ENTRY 1
+# define HAVE_TM_GMTOFF 1
+# define HAVE_TM_ZONE 1
+# define HAVE_TZNAME 1
+# define HAVE_TZSET 1
+# define MULTIBYTE_IS_FORMAT_SAFE 1
+# define STDC_HEADERS 1
+# include "../locale/localeinfo.h"
+#endif
+
+#if defined emacs && !defined HAVE_BCOPY
+# define HAVE_MEMCPY 1
+#endif
+
+#include <ctype.h>
+#include <sys/types.h>		/* Some systems define `time_t' here.  */
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+#if HAVE_TZNAME
+extern char *tzname[];
+#endif
+
+/* Do multibyte processing if multibytes are supported, unless
+   multibyte sequences are safe in formats.  Multibyte sequences are
+   safe if they cannot contain byte sequences that look like format
+   conversion specifications.  The GNU C Library uses UTF8 multibyte
+   encoding, which is safe for formats, but strftime.c can be used
+   with other C libraries that use unsafe encodings.  */
+#define DO_MULTIBYTE (HAVE_MBLEN && ! MULTIBYTE_IS_FORMAT_SAFE)
+
+#if DO_MULTIBYTE
+# if HAVE_MBRLEN
+#  include <wchar.h>
+# else
+   /* Simulate mbrlen with mblen as best we can.  */
+#  define mbstate_t int
+#  define mbrlen(s, n, ps) mblen (s, n)
+#  define mbsinit(ps) (*(ps) == 0)
+# endif
+  static const mbstate_t mbstate_zero;
+#endif
+
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#if STDC_HEADERS
+# include <stddef.h>
+# include <stdlib.h>
+# include <string.h>
+#else
+# ifndef HAVE_MEMCPY
+#  define memcpy(d, s, n) bcopy ((s), (d), (n))
+# endif
+#endif
+
+#ifdef _LIBC
+# define MEMPCPY(d, s, n) __mempcpy (d, s, n)
+#else
+# ifndef HAVE_MEMPCPY
+#  define MEMPCPY(d, s, n) ((void *) ((char *) memcpy (d, s, n) + (n)))
+# endif
+#endif
+
+#ifndef __P
+# if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
+#  define __P(args) args
+# else
+#  define __P(args) ()
+# endif  /* GCC.  */
+#endif  /* Not __P.  */
+
+#ifndef PTR
+# ifdef __STDC__
+#  define PTR void *
+# else
+#  define PTR char *
+# endif
+#endif
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+
+#ifndef NULL
+# define NULL 0
+#endif
+
+#define TYPE_SIGNED(t) ((t) -1 < 0)
+
+/* Bound on length of the string representing an integer value of type t.
+   Subtract one for the sign bit if t is signed;
+   302 / 1000 is log10 (2) rounded up;
+   add one for integer division truncation;
+   add one more for a minus sign if t is signed.  */
+#define INT_STRLEN_BOUND(t) \
+ ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 + 1 + TYPE_SIGNED (t))
+
+#define TM_YEAR_BASE 1900
+
+#ifndef __isleap
+/* Nonzero if YEAR is a leap year (every 4 years,
+   except every 100th isn't, and every 400th is).  */
+# define __isleap(year)	\
+  ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
+#endif
+
+
+#ifdef _LIBC
+# define my_strftime_gmtime_r __gmtime_r
+# define my_strftime_localtime_r __localtime_r
+# define tzname __tzname
+# define tzset __tzset
+#else
+
+/* If we're a strftime substitute in a GNU program, then prefer gmtime
+   to gmtime_r, since many gmtime_r implementations are buggy.
+   Similarly for localtime_r.  */
+
+# if ! HAVE_TM_GMTOFF
+static struct tm *my_strftime_gmtime_r __P ((const time_t *, struct tm *));
+static struct tm *
+my_strftime_gmtime_r (t, tp)
+     const time_t *t;
+     struct tm *tp;
+{
+  struct tm *l = gmtime (t);
+  if (! l)
+    return 0;
+  *tp = *l;
+  return tp;
+}
+# endif /* ! HAVE_TM_GMTOFF */
+
+static struct tm *my_strftime_localtime_r __P ((const time_t *, struct tm *));
+static struct tm *
+my_strftime_localtime_r (t, tp)
+     const time_t *t;
+     struct tm *tp;
+{
+  struct tm *l = localtime (t);
+  if (! l)
+    return 0;
+  *tp = *l;
+  return tp;
+}
+#endif /* ! defined _LIBC */
+
+
+#if !defined memset && !defined HAVE_MEMSET && !defined _LIBC
+/* Some systems lack the `memset' function and we don't want to
+   introduce additional dependencies.  */
+/* The SGI compiler reportedly barfs on the trailing null
+   if we use a string constant as the initializer.  28 June 1997, rms.  */
+static const char spaces[16] = /* "                " */
+  { ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ' };
+static const char zeroes[16] = /* "0000000000000000" */
+  { '0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0' };
+
+# define memset_space(P, Len) \
+  do {									      \
+    int _len = (Len);							      \
+									      \
+    do									      \
+      {									      \
+	int _this = _len > 16 ? 16 : _len;				      \
+	(P) = MEMPCPY ((P), spaces, _this);				      \
+	_len -= _this;							      \
+      }									      \
+    while (_len > 0);							      \
+  } while (0)
+
+# define memset_zero(P, Len) \
+  do {									      \
+    int _len = (Len);							      \
+									      \
+    do									      \
+      {									      \
+	int _this = _len > 16 ? 16 : _len;				      \
+	(P) = MEMPCPY ((P), zeroes, _this);				      \
+	_len -= _this;							      \
+      }									      \
+    while (_len > 0);							      \
+  } while (0)
+#else
+# define memset_space(P, Len) (memset ((P), ' ', (Len)), (P) += (Len))
+# define memset_zero(P, Len) (memset ((P), '0', (Len)), (P) += (Len))
+#endif
+
+#define add(n, f)							      \
+  do									      \
+    {									      \
+      int _n = (n);							      \
+      int _delta = width - _n;						      \
+      int _incr = _n + (_delta > 0 ? _delta : 0);			      \
+      if (i + _incr >= maxsize)						      \
+	return 0;							      \
+      if (p)								      \
+	{								      \
+	  if (_delta > 0)						      \
+	    {								      \
+	      if (pad == '0')						      \
+		memset_zero (p, _delta);				      \
+	      else							      \
+		memset_space (p, _delta);				      \
+	    }								      \
+	  f;								      \
+	  p += _n;							      \
+	}								      \
+      i += _incr;							      \
+    } while (0)
+
+#define cpy(n, s) \
+    add ((n),								      \
+	 if (to_lowcase)						      \
+	   memcpy_lowcase (p, (s), _n);					      \
+	 else if (to_uppcase)						      \
+	   memcpy_uppcase (p, (s), _n);					      \
+	 else								      \
+	   memcpy ((PTR) p, (PTR) (s), _n))
+
+
+
+#ifdef _LIBC
+# define TOUPPER(Ch) toupper (Ch)
+# define TOLOWER(Ch) tolower (Ch)
+#else
+# define TOUPPER(Ch) (islower (Ch) ? toupper (Ch) : (Ch))
+# define TOLOWER(Ch) (isupper (Ch) ? tolower (Ch) : (Ch))
+#endif
+/* We don't use `isdigit' here since the locale dependent
+   interpretation is not what we want here.  We only need to accept
+   the arabic digits in the ASCII range.  One day there is perhaps a
+   more reliable way to accept other sets of digits.  */
+#define ISDIGIT(Ch) ((unsigned int) (Ch) - '0' <= 9)
+
+static char *memcpy_lowcase __P ((char *dest, const char *src, size_t len));
+
+static char *
+memcpy_lowcase (dest, src, len)
+     char *dest;
+     const char *src;
+     size_t len;
+{
+  while (len-- > 0)
+    dest[len] = TOLOWER ((unsigned char) src[len]);
+  return dest;
+}
+
+static char *memcpy_uppcase __P ((char *dest, const char *src, size_t len));
+
+static char *
+memcpy_uppcase (dest, src, len)
+     char *dest;
+     const char *src;
+     size_t len;
+{
+  while (len-- > 0)
+    dest[len] = TOUPPER ((unsigned char) src[len]);
+  return dest;
+}
+
+
+#if ! HAVE_TM_GMTOFF
+/* Yield the difference between *A and *B,
+   measured in seconds, ignoring leap seconds.  */
+# define tm_diff ftime_tm_diff
+static int tm_diff __P ((const struct tm *, const struct tm *));
+static int
+tm_diff (a, b)
+     const struct tm *a;
+     const struct tm *b;
+{
+  /* Compute intervening leap days correctly even if year is negative.
+     Take care to avoid int overflow in leap day calculations,
+     but it's OK to assume that A and B are close to each other.  */
+  int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+  int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+  int a100 = a4 / 25 - (a4 % 25 < 0);
+  int b100 = b4 / 25 - (b4 % 25 < 0);
+  int a400 = a100 >> 2;
+  int b400 = b100 >> 2;
+  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+  int years = a->tm_year - b->tm_year;
+  int days = (365 * years + intervening_leap_days
+	      + (a->tm_yday - b->tm_yday));
+  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+		+ (a->tm_min - b->tm_min))
+	  + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+
+
+/* The number of days from the first day of the first ISO week of this
+   year to the year day YDAY with week day WDAY.  ISO weeks start on
+   Monday; the first ISO week has the year's first Thursday.  YDAY may
+   be as small as YDAY_MINIMUM.  */
+#define ISO_WEEK_START_WDAY 1 /* Monday */
+#define ISO_WEEK1_WDAY 4 /* Thursday */
+#define YDAY_MINIMUM (-366)
+static int iso_week_days __P ((int, int));
+#ifdef __GNUC__
+__inline__
+#endif
+static int
+iso_week_days (yday, wday)
+     int yday;
+     int wday;
+{
+  /* Add enough to the first operand of % to make it nonnegative.  */
+  int big_enough_multiple_of_7 = (-YDAY_MINIMUM / 7 + 2) * 7;
+  return (yday
+	  - (yday - wday + ISO_WEEK1_WDAY + big_enough_multiple_of_7) % 7
+	  + ISO_WEEK1_WDAY - ISO_WEEK_START_WDAY);
+}
+
+
+#if !(defined _NL_CURRENT || HAVE_STRFTIME)
+static char const weekday_name[][10] =
+  {
+    "Sunday", "Monday", "Tuesday", "Wednesday",
+    "Thursday", "Friday", "Saturday"
+  };
+static char const month_name[][10] =
+  {
+    "January", "February", "March", "April", "May", "June",
+    "July", "August", "September", "October", "November", "December"
+  };
+#endif
+
+
+#ifdef emacs
+# define my_strftime emacs_strftimeu
+# define ut_argument , ut
+# define ut_argument_spec int ut;
+# define ut_argument_spec_iso , int ut
+#else
+# define my_strftime strftime
+# define ut_argument
+# define ut_argument_spec
+# define ut_argument_spec_iso
+/* We don't have this information in general.  */
+# define ut 0
+#endif
+
+#if !defined _LIBC && HAVE_TZNAME && HAVE_TZSET
+  /* Solaris 2.5 tzset sometimes modifies the storage returned by localtime.
+     Work around this bug by copying *tp before it might be munged.  */
+  size_t _strftime_copytm __P ((char *, size_t, const char *,
+			        const struct tm * ut_argument_spec_iso));
+  size_t
+  my_strftime (s, maxsize, format, tp ut_argument)
+      char *s;
+      size_t maxsize;
+      const char *format;
+      const struct tm *tp;
+      ut_argument_spec
+  {
+    struct tm tmcopy;
+    tmcopy = *tp;
+    return _strftime_copytm (s, maxsize, format, &tmcopy ut_argument);
+  }
+# undef my_strftime
+# define my_strftime(S, Maxsize, Format, Tp) \
+  _strftime_copytm (S, Maxsize, Format, Tp)
+#endif
+
+
+/* Write information from TP into S according to the format
+   string FORMAT, writing no more that MAXSIZE characters
+   (including the terminating '\0') and returning number of
+   characters written.  If S is NULL, nothing will be written
+   anywhere, so to determine how many characters would be
+   written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
+size_t
+my_strftime (s, maxsize, format, tp ut_argument)
+      char *s;
+      size_t maxsize;
+      const char *format;
+      const struct tm *tp;
+      ut_argument_spec
+{
+  int hour12 = tp->tm_hour;
+#ifdef _NL_CURRENT
+  /* We cannot make the following values variables since we must delay
+     the evaluation of these values until really needed since some
+     expressions might not be valid in every situation.  The `struct tm'
+     might be generated by a strptime() call that initialized
+     only a few elements.  Dereference the pointers only if the format
+     requires this.  Then it is ok to fail if the pointers are invalid.  */
+# define a_wkday _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday)
+# define f_wkday _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday)
+# define a_month _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon)
+# define f_month _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon)
+# define ampm _NL_CURRENT (LC_TIME, tp->tm_hour > 11 ? PM_STR : AM_STR)
+
+# define aw_len strlen (a_wkday)
+# define am_len strlen (a_month)
+# define ap_len strlen (ampm)
+#else
+# if !HAVE_STRFTIME
+# define f_wkday (weekday_name[tp->tm_wday])
+# define f_month (month_name[tp->tm_mon])
+# define a_wkday f_wkday
+# define a_month f_month
+# define ampm ("AMPM" + 2 * (tp->tm_hour > 11))
+
+  size_t aw_len = 3;
+  size_t am_len = 3;
+  size_t ap_len = 2;
+# endif
+#endif
+  const char *zone;
+  size_t i = 0;
+  char *p = s;
+  const char *f;
+
+  zone = NULL;
+#if HAVE_TM_ZONE
+  /* The POSIX test suite assumes that setting
+     the environment variable TZ to a new value before calling strftime()
+     will influence the result (the %Z format) even if the information in
+     TP is computed with a totally different time zone.
+     This is bogus: though POSIX allows bad behavior like this,
+     POSIX does not require it.  Do the right thing instead.  */
+  zone = (const char *) tp->tm_zone;
+#endif
+#if HAVE_TZNAME
+  if (ut)
+    {
+      if (! (zone && *zone))
+	zone = "GMT";
+    }
+  else
+    {
+      /* POSIX.1 8.1.1 requires that whenever strftime() is called, the
+	 time zone names contained in the external variable `tzname' shall
+	 be set as if the tzset() function had been called.  */
+# if HAVE_TZSET
+      tzset ();
+# endif
+    }
+#endif
+
+  if (hour12 > 12)
+    hour12 -= 12;
+  else
+    if (hour12 == 0)
+      hour12 = 12;
+
+  for (f = format; *f != '\0'; ++f)
+    {
+      int pad = 0;		/* Padding for number ('-', '_', or 0).  */
+      int modifier;		/* Field modifier ('E', 'O', or 0).  */
+      int digits;		/* Max digits for numeric format.  */
+      int number_value; 	/* Numeric value to be printed.  */
+      int negative_number;	/* 1 if the number is negative.  */
+      const char *subfmt;
+      char *bufp;
+      char buf[1 + (sizeof (int) < sizeof (time_t)
+		    ? INT_STRLEN_BOUND (time_t)
+		    : INT_STRLEN_BOUND (int))];
+      int width = -1;
+      int to_lowcase = 0;
+      int to_uppcase = 0;
+      int change_case = 0;
+      int format_char;
+
+#if DO_MULTIBYTE
+
+       switch (*f)
+	{
+	case '%':
+	  break;
+
+	case '\a': case '\b': case '\t': case '\n':
+	case '\v': case '\f': case '\r':
+	case ' ': case '!': case '"': case '#': case '&': case'\'':
+	case '(': case ')': case '*': case '+': case ',': case '-':
+	case '.': case '/': case '0': case '1': case '2': case '3':
+	case '4': case '5': case '6': case '7': case '8': case '9':
+	case ':': case ';': case '<': case '=': case '>': case '?':
+	case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+	case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
+	case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
+	case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
+	case 'Y': case 'Z': case '[': case'\\': case ']': case '^':
+	case '_': case 'a': case 'b': case 'c': case 'd': case 'e':
+	case 'f': case 'g': case 'h': case 'i': case 'j': case 'k':
+	case 'l': case 'm': case 'n': case 'o': case 'p': case 'q':
+	case 'r': case 's': case 't': case 'u': case 'v': case 'w':
+	case 'x': case 'y': case 'z': case '{': case '|': case '}':
+	case '~':
+	  /* The C Standard requires these 98 characters (plus '%') to
+	     be in the basic execution character set.  None of these
+	     characters can start a multibyte sequence, so they need
+	     not be analyzed further.  */
+	  add (1, *p = *f);
+	  continue;
+
+	default:
+	  /* Copy this multibyte sequence until we reach its end, find
+	     an error, or come back to the initial shift state.  */
+	  {
+	    mbstate_t mbstate = mbstate_zero;
+	    size_t len = 0;
+
+	    do
+	      {
+		size_t bytes = mbrlen (f + len, (size_t) -1, &mbstate);
+
+		if (bytes == 0)
+		  break;
+
+		if (bytes == (size_t) -2)
+		  {
+		    len += strlen (f + len);
+		    break;
+		  }
+
+		if (bytes == (size_t) -1)
+		  {
+		    len++;
+		    break;
+		  }
+
+		len += bytes;
+	      }
+	    while (! mbsinit (&mbstate));
+
+	    cpy (len, f);
+	    f += len - 1;
+	    continue;
+	  }
+	}
+
+#else /* ! DO_MULTIBYTE */
+
+      /* Either multibyte encodings are not supported, or they are
+	 safe for formats, so any non-'%' byte can be copied through.  */
+      if (*f != '%')
+	{
+	  add (1, *p = *f);
+	  continue;
+	}
+
+#endif /* ! DO_MULTIBYTE */
+
+      /* Check for flags that can modify a format.  */
+      while (1)
+	{
+	  switch (*++f)
+	    {
+	      /* This influences the number formats.  */
+	    case '_':
+	    case '-':
+	    case '0':
+	      pad = *f;
+	      continue;
+
+	      /* This changes textual output.  */
+	    case '^':
+	      to_uppcase = 1;
+	      continue;
+	    case '#':
+	      change_case = 1;
+	      continue;
+
+	    default:
+	      break;
+	    }
+	  break;
+	}
+
+      /* As a GNU extension we allow to specify the field width.  */
+      if (ISDIGIT (*f))
+	{
+	  width = 0;
+	  do
+	    {
+	      width *= 10;
+	      width += *f - '0';
+	      ++f;
+	    }
+	  while (ISDIGIT (*f));
+	}
+
+      /* Check for modifiers.  */
+      switch (*f)
+	{
+	case 'E':
+	case 'O':
+	  modifier = *f++;
+	  break;
+
+	default:
+	  modifier = 0;
+	  break;
+	}
+
+      /* Now do the specified format.  */
+      format_char = *f;
+      switch (format_char)
+	{
+#define DO_NUMBER(d, v) \
+	  digits = width == -1 ? d : width;				      \
+	  number_value = v; goto do_number
+#define DO_NUMBER_SPACEPAD(d, v) \
+	  digits = width == -1 ? d : width;				      \
+	  number_value = v; goto do_number_spacepad
+
+	case '%':
+	  if (modifier != 0)
+	    goto bad_format;
+	  add (1, *p = *f);
+	  break;
+
+	case 'a':
+	  if (modifier != 0)
+	    goto bad_format;
+	  if (change_case)
+	    {
+	      to_uppcase = 1;
+	      to_lowcase = 0;
+	    }
+#if defined _NL_CURRENT || !HAVE_STRFTIME
+	  cpy (aw_len, a_wkday);
+	  break;
+#else
+	  goto underlying_strftime;
+#endif
+
+	case 'A':
+	  if (modifier != 0)
+	    goto bad_format;
+	  if (change_case)
+	    {
+	      to_uppcase = 1;
+	      to_lowcase = 0;
+	    }
+#if defined _NL_CURRENT || !HAVE_STRFTIME
+	  cpy (strlen (f_wkday), f_wkday);
+	  break;
+#else
+	  goto underlying_strftime;
+#endif
+
+	case 'b':
+	case 'h':		/* POSIX.2 extension.  */
+	  if (modifier != 0)
+	    goto bad_format;
+#if defined _NL_CURRENT || !HAVE_STRFTIME
+	  cpy (am_len, a_month);
+	  break;
+#else
+	  goto underlying_strftime;
+#endif
+
+	case 'B':
+	  if (modifier != 0)
+	    goto bad_format;
+	  if (change_case)
+	    {
+	      to_uppcase = 1;
+	      to_lowcase = 0;
+	    }
+#if defined _NL_CURRENT || !HAVE_STRFTIME
+	  cpy (strlen (f_month), f_month);
+	  break;
+#else
+	  goto underlying_strftime;
+#endif
+
+	case 'c':
+	  if (modifier == 'O')
+	    goto bad_format;
+#ifdef _NL_CURRENT
+	  if (! (modifier == 'E'
+		 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT)) != '\0'))
+	    subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
+#else
+# if HAVE_STRFTIME
+	  goto underlying_strftime;
+# else
+	  subfmt = "%a %b %e %H:%M:%S %Y";
+# endif
+#endif
+
+	subformat:
+	  {
+	    char *old_start = p;
+	    size_t len = my_strftime (NULL, (size_t) -1, subfmt, tp);
+	    add (len, my_strftime (p, maxsize - i, subfmt, tp));
+
+	    if (to_uppcase)
+	      while (old_start < p)
+		{
+		  *old_start = TOUPPER ((unsigned char) *old_start);
+		  ++old_start;
+		}
+	  }
+	  break;
+
+#if HAVE_STRFTIME && ! (defined _NL_CURRENT && HAVE_STRUCT_ERA_ENTRY)
+	underlying_strftime:
+	  {
+	    /* The relevant information is available only via the
+	       underlying strftime implementation, so use that.  */
+	    char ufmt[4];
+	    char *u = ufmt;
+	    char ubuf[1024]; /* enough for any single format in practice */
+	    size_t len;
+	    *u++ = '%';
+	    if (modifier != 0)
+	      *u++ = modifier;
+	    *u++ = format_char;
+	    *u = '\0';
+	    len = strftime (ubuf, sizeof ubuf, ufmt, tp);
+	    if (len == 0 && ubuf[0] != '\0')
+	      return 0;
+	    cpy (len, ubuf);
+	  }
+	  break;
+#endif
+
+	case 'C':		/* POSIX.2 extension.  */
+	  if (modifier == 'O')
+	    goto bad_format;
+	  if (modifier == 'E')
+	    {
+#if HAVE_STRUCT_ERA_ENTRY
+	      struct era_entry *era = _nl_get_era_entry (tp);
+	      if (era)
+		{
+		  size_t len = strlen (era->name_fmt);
+		  cpy (len, era->name_fmt);
+		  break;
+		}
+#else
+# if HAVE_STRFTIME
+	      goto underlying_strftime;
+# endif
+#endif
+	    }
+
+	  {
+	    int year = tp->tm_year + TM_YEAR_BASE;
+	    DO_NUMBER (1, year / 100 - (year % 100 < 0));
+	  }
+
+	case 'x':
+	  if (modifier == 'O')
+	    goto bad_format;
+#ifdef _NL_CURRENT
+	  if (! (modifier == 'E'
+		 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_D_FMT)) != '\0'))
+	    subfmt = _NL_CURRENT (LC_TIME, D_FMT);
+	  goto subformat;
+#else
+# if HAVE_STRFTIME
+	  goto underlying_strftime;
+# else
+	  /* Fall through.  */
+# endif
+#endif
+	case 'D':		/* POSIX.2 extension.  */
+	  if (modifier != 0)
+	    goto bad_format;
+	  subfmt = "%m/%d/%y";
+	  goto subformat;
+
+	case 'd':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, tp->tm_mday);
+
+	case 'e':		/* POSIX.2 extension.  */
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER_SPACEPAD (2, tp->tm_mday);
+
+	  /* All numeric formats set DIGITS and NUMBER_VALUE and then
+	     jump to one of these two labels.  */
+
+	do_number_spacepad:
+	  /* Force `_' flag unless overwritten by `0' flag.  */
+	  if (pad != '0')
+	    pad = '_';
+
+	do_number:
+	  /* Format the number according to the MODIFIER flag.  */
+
+	  if (modifier == 'O' && 0 <= number_value)
+	    {
+#ifdef _NL_CURRENT
+	      /* Get the locale specific alternate representation of
+		 the number NUMBER_VALUE.  If none exist NULL is returned.  */
+	      const char *cp = _nl_get_alt_digit (number_value);
+
+	      if (cp != NULL)
+		{
+		  size_t digitlen = strlen (cp);
+		  if (digitlen != 0)
+		    {
+		      cpy (digitlen, cp);
+		      break;
+		    }
+		}
+#else
+# if HAVE_STRFTIME
+	      goto underlying_strftime;
+# endif
+#endif
+	    }
+	  {
+	    unsigned int u = number_value;
+
+	    bufp = buf + sizeof (buf);
+	    negative_number = number_value < 0;
+
+	    if (negative_number)
+	      u = -u;
+
+	    do
+	      *--bufp = u % 10 + '0';
+	    while ((u /= 10) != 0);
+  	  }
+
+	do_number_sign_and_padding:
+	  if (negative_number)
+	    *--bufp = '-';
+
+	  if (pad != '-')
+	    {
+	      int padding = digits - (buf + sizeof (buf) - bufp);
+
+	      if (pad == '_')
+		{
+		  while (0 < padding--)
+		    *--bufp = ' ';
+		}
+	      else
+		{
+		  bufp += negative_number;
+		  while (0 < padding--)
+		    *--bufp = '0';
+		  if (negative_number)
+		    *--bufp = '-';
+		}
+	    }
+
+	  cpy (buf + sizeof (buf) - bufp, bufp);
+	  break;
+
+	case 'F':
+	  if (modifier != 0)
+	    goto bad_format;
+	  subfmt = "%Y-%m-%d";
+	  goto subformat;
+
+	case 'H':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, tp->tm_hour);
+
+	case 'I':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, hour12);
+
+	case 'k':		/* GNU extension.  */
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER_SPACEPAD (2, tp->tm_hour);
+
+	case 'l':		/* GNU extension.  */
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER_SPACEPAD (2, hour12);
+
+	case 'j':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (3, 1 + tp->tm_yday);
+
+	case 'M':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, tp->tm_min);
+
+	case 'm':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, tp->tm_mon + 1);
+
+	case 'n':		/* POSIX.2 extension.  */
+	  add (1, *p = '\n');
+	  break;
+
+	case 'P':
+	  to_lowcase = 1;
+#if !defined _NL_CURRENT && HAVE_STRFTIME
+	  format_char = 'p';
+#endif
+	  /* FALLTHROUGH */
+
+	case 'p':
+	  if (change_case)
+	    {
+	      to_uppcase = 0;
+	      to_lowcase = 1;
+	    }
+#if defined _NL_CURRENT || !HAVE_STRFTIME
+	  cpy (ap_len, ampm);
+	  break;
+#else
+	  goto underlying_strftime;
+#endif
+
+	case 'R':		/* GNU extension.  */
+	  subfmt = "%H:%M";
+	  goto subformat;
+
+	case 'r':		/* POSIX.2 extension.  */
+#ifdef _NL_CURRENT
+	  if (*(subfmt = _NL_CURRENT (LC_TIME, T_FMT_AMPM)) == '\0')
+#endif
+	    subfmt = "%I:%M:%S %p";
+	  goto subformat;
+
+	case 'S':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, tp->tm_sec);
+
+	case 's':		/* GNU extension.  */
+  	  {
+	    struct tm ltm;
+	    time_t t;
+
+	    ltm = *tp;
+	    t = mktime (&ltm);
+
+	    /* Generate string value for T using time_t arithmetic;
+	       this works even if sizeof (long) < sizeof (time_t).  */
+
+	    bufp = buf + sizeof (buf);
+	    negative_number = t < 0;
+
+	    do
+	      {
+		int d = t % 10;
+		t /= 10;
+
+		if (negative_number)
+		  {
+		    d = -d;
+
+		    /* Adjust if division truncates to minus infinity.  */
+		    if (0 < -1 % 10 && d < 0)
+		      {
+			t++;
+			d += 10;
+		      }
+		  }
+
+		*--bufp = d + '0';
+	      }
+	    while (t != 0);
+
+	    digits = 1;
+	    goto do_number_sign_and_padding;
+	  }
+
+	case 'X':
+	  if (modifier == 'O')
+	    goto bad_format;
+#ifdef _NL_CURRENT
+	  if (! (modifier == 'E'
+		 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_T_FMT)) != '\0'))
+	    subfmt = _NL_CURRENT (LC_TIME, T_FMT);
+	  goto subformat;
+#else
+# if HAVE_STRFTIME
+	  goto underlying_strftime;
+# else
+	  /* Fall through.  */
+# endif
+#endif
+	case 'T':		/* POSIX.2 extension.  */
+	  subfmt = "%H:%M:%S";
+	  goto subformat;
+
+	case 't':		/* POSIX.2 extension.  */
+	  add (1, *p = '\t');
+	  break;
+
+	case 'u':		/* POSIX.2 extension.  */
+	  DO_NUMBER (1, (tp->tm_wday - 1 + 7) % 7 + 1);
+
+	case 'U':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, (tp->tm_yday - tp->tm_wday + 7) / 7);
+
+	case 'V':
+	case 'g':		/* GNU extension.  */
+	case 'G':		/* GNU extension.  */
+	  if (modifier == 'E')
+	    goto bad_format;
+	  {
+	    int year = tp->tm_year + TM_YEAR_BASE;
+	    int days = iso_week_days (tp->tm_yday, tp->tm_wday);
+
+	    if (days < 0)
+	      {
+		/* This ISO week belongs to the previous year.  */
+		year--;
+		days = iso_week_days (tp->tm_yday + (365 + __isleap (year)),
+				      tp->tm_wday);
+	      }
+	    else
+	      {
+		int d = iso_week_days (tp->tm_yday - (365 + __isleap (year)),
+				       tp->tm_wday);
+		if (0 <= d)
+		  {
+		    /* This ISO week belongs to the next year.  */
+		    year++;
+		    days = d;
+		  }
+	      }
+
+	    switch (*f)
+	      {
+	      case 'g':
+		DO_NUMBER (2, (year % 100 + 100) % 100);
+
+	      case 'G':
+		DO_NUMBER (1, year);
+
+	      default:
+		DO_NUMBER (2, days / 7 + 1);
+	      }
+	  }
+
+	case 'W':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (2, (tp->tm_yday - (tp->tm_wday - 1 + 7) % 7 + 7) / 7);
+
+	case 'w':
+	  if (modifier == 'E')
+	    goto bad_format;
+
+	  DO_NUMBER (1, tp->tm_wday);
+
+	case 'Y':
+	  if (modifier == 'E')
+	    {
+#if HAVE_STRUCT_ERA_ENTRY
+	      struct era_entry *era = _nl_get_era_entry (tp);
+	      if (era)
+		{
+		  subfmt = strchr (era->name_fmt, '\0') + 1;
+		  goto subformat;
+		}
+#else
+# if HAVE_STRFTIME
+	      goto underlying_strftime;
+# endif
+#endif
+	    }
+	  if (modifier == 'O')
+	    goto bad_format;
+	  else
+	    DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE);
+
+	case 'y':
+	  if (modifier == 'E')
+	    {
+#if HAVE_STRUCT_ERA_ENTRY
+	      struct era_entry *era = _nl_get_era_entry (tp);
+	      if (era)
+		{
+		  int delta = tp->tm_year - era->start_date[0];
+		  DO_NUMBER (1, (era->offset
+				 + (era->direction == '-' ? -delta : delta)));
+		}
+#else
+# if HAVE_STRFTIME
+	      goto underlying_strftime;
+# endif
+#endif
+	    }
+	  DO_NUMBER (2, (tp->tm_year % 100 + 100) % 100);
+
+	case 'Z':
+	  if (change_case)
+	    {
+	      to_uppcase = 0;
+	      to_lowcase = 1;
+	    }
+
+#if HAVE_TZNAME
+	  /* The tzset() call might have changed the value.  */
+	  if (!(zone && *zone) && tp->tm_isdst >= 0)
+	    zone = tzname[tp->tm_isdst];
+#endif
+	  if (! zone)
+	    zone = "";		/* POSIX.2 requires the empty string here.  */
+
+	  cpy (strlen (zone), zone);
+	  break;
+
+	case 'z':		/* GNU extension.  */
+	  if (tp->tm_isdst < 0)
+	    break;
+
+	  {
+	    int diff;
+#if HAVE_TM_GMTOFF
+	    diff = tp->tm_gmtoff;
+#else
+	    if (ut)
+	      diff = 0;
+	    else
+	      {
+		struct tm gtm;
+		struct tm ltm;
+		time_t lt;
+
+		ltm = *tp;
+		lt = mktime (&ltm);
+
+		if (lt == (time_t) -1)
+		  {
+		    /* mktime returns -1 for errors, but -1 is also a
+		       valid time_t value.  Check whether an error really
+		       occurred.  */
+		    struct tm tm;
+
+		    if (! my_strftime_localtime_r (&lt, &tm)
+			|| ((ltm.tm_sec ^ tm.tm_sec)
+			    | (ltm.tm_min ^ tm.tm_min)
+			    | (ltm.tm_hour ^ tm.tm_hour)
+			    | (ltm.tm_mday ^ tm.tm_mday)
+			    | (ltm.tm_mon ^ tm.tm_mon)
+			    | (ltm.tm_year ^ tm.tm_year)))
+		      break;
+		  }
+
+		if (! my_strftime_gmtime_r (&lt, &gtm))
+		  break;
+
+		diff = tm_diff (&ltm, &gtm);
+	      }
+#endif
+
+	    if (diff < 0)
+	      {
+		add (1, *p = '-');
+		diff = -diff;
+	      }
+	    else
+	      add (1, *p = '+');
+
+	    diff /= 60;
+	    DO_NUMBER (4, (diff / 60) * 100 + diff % 60);
+	  }
+
+	case '\0':		/* GNU extension: % at end of format.  */
+	    --f;
+	    /* Fall through.  */
+	default:
+	  /* Unknown format; output the format, including the '%',
+	     since this is most likely the right thing to do if a
+	     multibyte string has been misparsed.  */
+	bad_format:
+	  {
+	    int flen;
+	    for (flen = 1; f[1 - flen] != '%'; flen++)
+	      continue;
+	    cpy (flen, &f[1 - flen]);
+	  }
+	  break;
+	}
+    }
+
+  if (p && maxsize != 0)
+    *p = '\0';
+  return i;
+}
+
+
+#ifdef emacs
+/* For Emacs we have a separate interface which corresponds to the normal
+   strftime function and does not have the extra information whether the
+   TP arguments comes from a `gmtime' call or not.  */
+size_t
+emacs_strftime (s, maxsize, format, tp)
+      char *s;
+      size_t maxsize;
+      const char *format;
+      const struct tm *tp;
+{
+  return my_strftime (s, maxsize, format, tp, 0);
+}
+#endif

+ 186 - 0
compat/inet_aton.c

@@ -0,0 +1,186 @@
+/*
+ * inet_aton.c -- provides inet_aton() if necessary.
+ *
+ * $Id: inet_aton.c,v 1.5 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Poritions Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "main.h"
+#include "inet_aton.h"
+
+#ifndef HAVE_ISASCII
+/* Let all checks succeed if we don't have isascii(). */
+#  define isascii(x)	1
+#endif
+
+#ifndef HAVE_INET_ATON
+/*
+ * ++Copyright++ 1983, 1990, 1993
+ * -
+ * Copyright (c) 1983, 1990, 1993
+ *    The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ * 	This product includes software developed by the University of
+ * 	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * -
+ * Portions Copyright (c) 1993 by Digital Equipment Corporation.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies, and that
+ * the name of Digital Equipment Corporation not be used in advertising or
+ * publicity pertaining to distribution of the document or software without
+ * specific, written prior permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
+ * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ * --Copyright--
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)inet_addr.c	8.1 (Berkeley) 6/17/93";
+static char rcsid[] = "$-Id: inet_addr.c,v 1.11 1999/04/29 18:19:53 drepper Exp $";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <ctype.h>
+
+/*
+ * Check whether "cp" is a valid ascii representation
+ * of an Internet address and convert to a binary address.
+ * Returns 1 if the address is valid, 0 if not.
+ * This replaces inet_addr, the return value from which
+ * cannot distinguish between failure and a local broadcast address.
+ */
+int
+egg_inet_aton(cp, addr)
+	const char *cp;
+	struct in_addr *addr;
+{
+	static const u_32bit_t max[4] = { 0xffffffff, 0xffffff, 0xffff, 0xff };
+	register u_32bit_t val;	/* changed from u_long --david */
+	register int base;
+	register int n;
+	register char c;
+	u_32bit_t parts[4];
+	register u_32bit_t *pp = parts;
+
+	egg_bzero(parts, sizeof (parts));
+
+	c = *cp;
+	for (;;) {
+		/*
+		 * Collect number up to ``.''.
+		 * Values are specified as for C:
+		 * 0x=hex, 0=octal, isdigit=decimal.
+		 */
+		if (!isdigit(c))
+			goto ret_0;
+		base = 10;
+		if (c == '0') {
+			c = *++cp;
+			if (c == 'x' || c == 'X')
+				base = 16, c = *++cp;
+			else
+				base = 8;
+		}
+		val = 0;
+		for (;;) {
+			if (isascii(c) && isdigit(c)) {
+				val = (val * base) + (c - '0');
+				c = *++cp;
+			} else if (base == 16 && isascii(c) && isxdigit(c)) {
+				val = (val << 4) |
+					(c + 10 - (islower(c) ? 'a' : 'A'));
+				c = *++cp;
+			} else
+				break;
+		}
+		if (c == '.') {
+			/*
+			 * Internet format:
+			 *	a.b.c.d
+			 *	a.b.c	(with c treated as 16 bits)
+			 *	a.b	(with b treated as 24 bits)
+			 */
+			if (pp >= parts + 3)
+				goto ret_0;
+			*pp++ = val;
+			c = *++cp;
+		} else
+			break;
+	}
+	/*
+	 * Check for trailing characters.
+	 */
+	if (c != '\0' && (!isascii(c) || !isspace(c)))
+		goto ret_0;
+	/*
+	 * Concoct the address according to
+	 * the number of parts specified.
+	 */
+	n = pp - parts + 1;
+
+	if (n == 0	/* initial nondigit */
+	    || parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xff
+	    || val > max[n - 1])
+	  goto ret_0;
+
+	val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
+
+	if (addr)
+		addr->s_addr = htonl(val);
+	return (1);
+
+ret_0:
+	return (0);
+}
+#endif /* HAVE_INET_ATON */

+ 40 - 0
compat/inet_aton.h

@@ -0,0 +1,40 @@
+/*
+ * inet_aton.h
+ *   prototypes for inet_aton.c
+ *
+ * $Id: inet_aton.h,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_INET_ATON_H
+#define _EGG_COMPAT_INET_ATON_H
+
+#include "src/main.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#ifndef HAVE_INET_ATON
+/* Use our own implementation. */
+int egg_inet_aton(const char *cp, struct in_addr *addr);
+#else
+#  define egg_inet_aton	inet_aton
+#endif
+
+#endif	/* !__EGG_COMPAT_INET_ATON_H */

BIN
compat/inet_aton.o


+ 35 - 0
compat/memcpy.c

@@ -0,0 +1,35 @@
+/*
+ * memcpy.c -- provides memcpy() if necessary.
+ *
+ * $Id: memcpy.c,v 1.2 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "main.h"
+#include "memcpy.h"
+
+#ifndef HAVE_MEMCPY
+void *egg_memcpy(void *dest, const void *src, size_t n)
+{
+  while (n--)
+    *((char *) dest)++ = *((char *) src)++;
+  return dest;
+}
+#endif /* !HAVE_MEMCPY */

+ 38 - 0
compat/memcpy.h

@@ -0,0 +1,38 @@
+/*
+ * memcpy.h
+ *   prototypes for memcpy.c
+ *
+ * $Id: memcpy.h,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_MEMCPY_H
+#define _EGG_COMPAT_MEMCPY_H
+
+#include "src/main.h"
+#include <string.h>
+
+#ifndef HAVE_MEMCPY
+/* Use our own implementation. */
+void *egg_memcpy(void *dest, const void *src, size_t n);
+#else
+#  define egg_memcpy	memcpy
+#endif
+
+#endif	/* !__EGG_COMPAT_MEMCPY_H */

BIN
compat/memcpy.o


+ 35 - 0
compat/memset.c

@@ -0,0 +1,35 @@
+/*
+ * memset.c -- provides memset() if necessary.
+ *
+ * $Id: memset.c,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "main.h"
+#include "memset.h"
+
+#ifndef HAVE_MEMSET
+void *egg_memset(void *dest, int c, size_t n)
+{
+  while (n--)
+    *((u_8bit_t *) dest)++ = c;
+  return dest;
+}
+#endif /* !HAVE_MEMSET */

+ 42 - 0
compat/memset.h

@@ -0,0 +1,42 @@
+/*
+ * memset.h
+ *   prototypes for memset.c
+ *
+ * $Id: memset.h,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_MEMSET_H
+#define _EGG_COMPAT_MEMSET_H
+
+#include "src/main.h"
+#include <string.h>
+
+#ifndef HAVE_MEMSET
+/* Use our own implementation. */
+void *egg_memset(void *dest, int c, size_t n);
+#else
+#  define egg_memset	memset
+#endif
+
+/* Use memset instead of bzero.
+ */
+#define egg_bzero(dest, n)	egg_memset(dest, 0, n)
+
+#endif	/* !__EGG_COMPAT_MEMSET_H */

BIN
compat/memset.o


+ 720 - 0
compat/snprintf.c

@@ -0,0 +1,720 @@
+/*
+ * snprintf.c - a portable implementation of snprintf and vsnprintf
+ *
+ * $Id: snprintf.c,v 1.4 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Portions Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+
+/*
+ * Copyright Patrick Powell 1995
+ * This code is based on code written by Patrick Powell (papowell@astart.com)
+ * It may be used for any purpose as long as this notice remains intact
+ * on all source code distributions
+ */
+
+/**************************************************************
+ * Original:
+ * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
+ * A bombproof version of doprnt (dopr) included.
+ * Sigh.  This sort of thing is always nasty do deal with.  Note that
+ * the version here does not include floating point...
+ *
+ * snprintf() is used instead of sprintf() as it does limit checks
+ * for string length.  This covers a nasty loophole.
+ *
+ * The other functions are there to prevent NULL pointers from
+ * causing nast effects.
+ *
+ * More Recently:
+ *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
+ *  This was ugly.  It is still ugly.  I opted out of floating point
+ *  numbers, but the formatter understands just about everything
+ *  from the normal C string format, at least as far as I can tell from
+ *  the Solaris 2.5 printf(3S) man page.
+ *
+ *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
+ *    Ok, added some minimal floating point support, which means this
+ *    probably requires libm on most operating systems.  Don't yet
+ *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
+ *    was pretty badly broken, it just wasn't being exercised in ways
+ *    which showed it, so that's been fixed.  Also, formated the code
+ *    to mutt conventions, and removed dead code left over from the
+ *    original.  Also, there is now a builtin-test, just compile with:
+ *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
+ *    and run snprintf for results.
+ *
+ *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
+ *    The PGP code was using unsigned hexadecimal formats.
+ *    Unfortunately, unsigned formats simply didn't work.
+ *
+ *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
+ *    The original code assumed that both snprintf() and vsnprintf() were
+ *    missing.  Some systems only have snprintf() but not vsnprintf(), so
+ *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
+ *
+ *  Andrew Tridgell (tridge@samba.org) Oct 1998
+ *    fixed handling of %.0f
+ *    added test for HAVE_LONG_DOUBLE
+ *
+ *  Fabian Knittel <fknittel@gmx.de> Apr 2000 for eggdrop 1.5.3
+ *    Indented code to match eggdrop style. Adjusted to fit into eggdrops
+ *    build environment. Added `egg_' prefixes to snprintf and vsnprintf.
+ *
+ **************************************************************/
+
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#ifndef HAVE_VSNPRINTF
+
+/* varargs declarations: */
+
+#if defined(__STDC__)
+#  ifdef HAVE_STDARG_H
+#    include <stdarg.h>
+#  else
+#    ifdef HAVE_STD_ARGS_H
+#      include <std_args.h>
+#    endif
+#  endif
+#  include <stdarg.h>
+#  define HAVE_STDARGS		/* let's hope that works everywhere (mj) */
+#  define VA_LOCAL_DECL	va_list ap
+#  define VA_START(f)	va_start(ap, f)
+#  define VA_SHIFT(v,t)	;	/* no-op for ANSI */
+#  define VA_END	va_end(ap)
+#else
+#  include <varargs.h>
+#  undef HAVE_STDARGS
+#  define VA_LOCAL_DECL	va_list ap
+#  define VA_START(f)	va_start(ap)	/* f is ignored! */
+#  define VA_SHIFT(v,t)	v = va_arg(ap,t)
+#  define VA_END	va_end(ap)
+#endif
+
+#ifdef HAVE_LONG_DOUBLE
+#define LDOUBLE	long double
+#else
+#define LDOUBLE	double
+#endif
+
+static void dopr(char *buffer, size_t maxlen, const char *format,
+		 va_list args);
+static void fmtstr(char *buffer, size_t * currlen, size_t maxlen,
+		   char *value, int flags, int min, int max);
+static void fmtint(char *buffer, size_t * currlen, size_t maxlen,
+		   long value, int base, int min, int max, int flags);
+static void fmtfp(char *buffer, size_t * currlen, size_t maxlen,
+		  LDOUBLE fvalue, int min, int max, int flags);
+static void dopr_outch(char *buffer, size_t * currlen, size_t maxlen,
+		       char c);
+
+/*
+ * dopr(): poor man's version of doprintf
+ */
+
+/* format read states */
+#define DP_S_DEFAULT 0
+#define DP_S_FLAGS   1
+#define DP_S_MIN     2
+#define DP_S_DOT     3
+#define DP_S_MAX     4
+#define DP_S_MOD     5
+#define DP_S_CONV    6
+#define DP_S_DONE    7
+
+/* format flags - Bits */
+#define DP_F_MINUS 	(1 << 0)
+#define DP_F_PLUS  	(1 << 1)
+#define DP_F_SPACE 	(1 << 2)
+#define DP_F_NUM   	(1 << 3)
+#define DP_F_ZERO  	(1 << 4)
+#define DP_F_UP    	(1 << 5)
+#define DP_F_UNSIGNED 	(1 << 6)
+
+/* Conversion Flags */
+#define DP_C_SHORT   1
+#define DP_C_LONG    2
+#define DP_C_LDOUBLE 3
+
+#define char_to_int(p) (p - '0')
+#define MAX(p,q) ((p >= q) ? p : q)
+
+static void dopr(char *buffer, size_t maxlen, const char *format,
+		 va_list args)
+{
+  char ch;
+  long value;
+  LDOUBLE fvalue;
+  char *strvalue;
+  int min;
+  int max;
+  int state;
+  int flags;
+  int cflags;
+  size_t currlen;
+
+  state = DP_S_DEFAULT;
+  currlen = flags = cflags = min = 0;
+  max = -1;
+  ch = *format++;
+
+  while (state != DP_S_DONE) {
+    if ((ch == '\0') || (currlen >= maxlen))
+      state = DP_S_DONE;
+
+    switch (state) {
+    case DP_S_DEFAULT:
+      if (ch == '%')
+	state = DP_S_FLAGS;
+      else
+	dopr_outch(buffer, &currlen, maxlen, ch);
+      ch = *format++;
+      break;
+    case DP_S_FLAGS:
+      switch (ch) {
+      case '-':
+	flags |= DP_F_MINUS;
+	ch = *format++;
+	break;
+      case '+':
+	flags |= DP_F_PLUS;
+	ch = *format++;
+	break;
+      case ' ':
+	flags |= DP_F_SPACE;
+	ch = *format++;
+	break;
+      case '#':
+	flags |= DP_F_NUM;
+	ch = *format++;
+	break;
+      case '0':
+	flags |= DP_F_ZERO;
+	ch = *format++;
+	break;
+      default:
+	state = DP_S_MIN;
+	break;
+      }
+      break;
+    case DP_S_MIN:
+      if (isdigit(ch)) {
+	min = 10 * min + char_to_int(ch);
+	ch = *format++;
+      } else if (ch == '*') {
+	min = va_arg(args, int);
+	ch = *format++;
+	state = DP_S_DOT;
+      } else
+	state = DP_S_DOT;
+      break;
+    case DP_S_DOT:
+      if (ch == '.') {
+	state = DP_S_MAX;
+	ch = *format++;
+      } else
+	state = DP_S_MOD;
+      break;
+    case DP_S_MAX:
+      if (isdigit(ch)) {
+	if (max < 0)
+	  max = 0;
+	max = 10 * max + char_to_int(ch);
+	ch = *format++;
+      } else if (ch == '*') {
+	max = va_arg(args, int);
+	ch = *format++;
+	state = DP_S_MOD;
+      } else
+	state = DP_S_MOD;
+      break;
+    case DP_S_MOD:
+      /* Currently, we don't support Long Long, bummer */
+      switch (ch) {
+      case 'h':
+	cflags = DP_C_SHORT;
+	ch = *format++;
+	break;
+      case 'l':
+	cflags = DP_C_LONG;
+	ch = *format++;
+	break;
+      case 'L':
+	cflags = DP_C_LDOUBLE;
+	ch = *format++;
+	break;
+      default:
+	break;
+      }
+      state = DP_S_CONV;
+      break;
+    case DP_S_CONV:
+      switch (ch) {
+      case 'd':
+      case 'i':
+	if (cflags == DP_C_SHORT)
+	  value = va_arg(args, short int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg(args, long int);
+	else
+	  value = va_arg(args, int);
+	fmtint(buffer, &currlen, maxlen, value, 10, min, max, flags);
+	break;
+      case 'o':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg(args, unsigned short int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg(args, unsigned long int);
+	else
+	  value = va_arg(args, unsigned int);
+	fmtint(buffer, &currlen, maxlen, value, 8, min, max, flags);
+	break;
+      case 'u':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg(args, unsigned short int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg(args, unsigned long int);
+	else
+	  value = va_arg(args, unsigned int);
+	fmtint(buffer, &currlen, maxlen, value, 10, min, max, flags);
+	break;
+      case 'X':
+	flags |= DP_F_UP;
+      case 'x':
+	flags |= DP_F_UNSIGNED;
+	if (cflags == DP_C_SHORT)
+	  value = va_arg(args, unsigned short int);
+	else if (cflags == DP_C_LONG)
+	  value = va_arg(args, unsigned long int);
+	else
+	  value = va_arg(args, unsigned int);
+	fmtint(buffer, &currlen, maxlen, value, 16, min, max, flags);
+	break;
+      case 'f':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg(args, LDOUBLE);
+	else
+	  fvalue = va_arg(args, double);
+	/* um, floating point? */
+	fmtfp(buffer, &currlen, maxlen, fvalue, min, max, flags);
+	break;
+      case 'E':
+	flags |= DP_F_UP;
+      case 'e':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg(args, LDOUBLE);
+	else
+	  fvalue = va_arg(args, double);
+	break;
+      case 'G':
+	flags |= DP_F_UP;
+      case 'g':
+	if (cflags == DP_C_LDOUBLE)
+	  fvalue = va_arg(args, LDOUBLE);
+	else
+	  fvalue = va_arg(args, double);
+	break;
+      case 'c':
+	dopr_outch(buffer, &currlen, maxlen, va_arg(args, int));
+	break;
+      case 's':
+	strvalue = va_arg(args, char *);
+	if (max < 0)
+	  max = maxlen;		/* ie, no max */
+	fmtstr(buffer, &currlen, maxlen, strvalue, flags, min, max);
+	break;
+      case 'p':
+	strvalue = va_arg(args, void *);
+	fmtint(buffer, &currlen, maxlen, (long) strvalue, 16, min, max,
+	       flags);
+	break;
+      case 'n':
+	if (cflags == DP_C_SHORT) {
+	  short int *num;
+	  num = va_arg(args, short int *);
+	  *num = currlen;
+	} else if (cflags == DP_C_LONG) {
+	  long int *num;
+	  num = va_arg(args, long int *);
+	  *num = currlen;
+	} else {
+	  int *num;
+	  num = va_arg(args, int *);
+	  *num = currlen;
+	}
+	break;
+      case '%':
+	dopr_outch(buffer, &currlen, maxlen, ch);
+	break;
+      case 'w':
+	/* not supported yet, treat as next char */
+	ch = *format++;
+	break;
+      default:
+	/* Unknown, skip */
+	break;
+      }
+      ch = *format++;
+      state = DP_S_DEFAULT;
+      flags = cflags = min = 0;
+      max = -1;
+      break;
+    case DP_S_DONE:
+      break;
+    default:
+      /* hmm? */
+      break;			/* some picky compilers need this */
+    }
+  }
+  if (currlen < maxlen - 1)
+    buffer[currlen] = '\0';
+  else
+    buffer[maxlen - 1] = '\0';
+}
+
+static void fmtstr(char *buffer, size_t * currlen, size_t maxlen,
+		   char *value, int flags, int min, int max)
+{
+  int padlen,
+      strln;			/* amount to pad */
+  int cnt = 0;
+
+  if (value == 0) {
+    value = "<NULL>";
+  }
+
+  for (strln = 0; value[strln]; ++strln);	/* strlen */
+  padlen = min - strln;
+  if (padlen < 0)
+    padlen = 0;
+  if (flags & DP_F_MINUS)
+    padlen = -padlen;		/* Left Justify */
+
+  while ((padlen > 0) && (cnt < max)) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    --padlen;
+    ++cnt;
+  }
+  while (*value && (cnt < max)) {
+    dopr_outch(buffer, currlen, maxlen, *value++);
+    ++cnt;
+  }
+  while ((padlen < 0) && (cnt < max)) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    ++padlen;
+    ++cnt;
+  }
+}
+
+/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
+
+static void fmtint(char *buffer, size_t * currlen, size_t maxlen,
+		   long value, int base, int min, int max, int flags)
+{
+  int signvalue = 0;
+  unsigned long uvalue;
+  char convert[20];
+  int place = 0;
+  int spadlen = 0;		/* amount to space pad */
+  int zpadlen = 0;		/* amount to zero pad */
+  int caps = 0;
+
+  if (max < 0)
+    max = 0;
+
+  uvalue = value;
+
+  if (!(flags & DP_F_UNSIGNED)) {
+    if (value < 0) {
+      signvalue = '-';
+      uvalue = -value;
+    } else if (flags & DP_F_PLUS)	/* Do a sign (+/i) */
+      signvalue = '+';
+    else if (flags & DP_F_SPACE)
+      signvalue = ' ';
+  }
+
+  if (flags & DP_F_UP)
+    caps = 1;			/* Should characters be upper case? */
+
+  do {
+    convert[place++] = (caps ? "0123456789ABCDEF" : "0123456789abcdef")
+	[uvalue % (unsigned) base];
+    uvalue = (uvalue / (unsigned) base);
+  }
+  while (uvalue && (place < 20));
+  if (place == 20)
+    place--;
+  convert[place] = 0;
+
+  zpadlen = max - place;
+  spadlen = min - MAX(max, place) - (signvalue ? 1 : 0);
+  if (zpadlen < 0)
+    zpadlen = 0;
+  if (spadlen < 0)
+    spadlen = 0;
+  if (flags & DP_F_ZERO) {
+    zpadlen = MAX(zpadlen, spadlen);
+    spadlen = 0;
+  }
+  if (flags & DP_F_MINUS)
+    spadlen = -spadlen;		/* Left Justifty */
+
+#ifdef DEBUG_SNPRINTF
+  dprint(1,
+	 (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
+	  zpadlen, spadlen, min, max, place));
+#endif
+
+  /* Spaces */
+  while (spadlen > 0) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    --spadlen;
+  }
+
+  /* Sign */
+  if (signvalue)
+    dopr_outch(buffer, currlen, maxlen, signvalue);
+
+  /* Zeros */
+  if (zpadlen > 0) {
+    while (zpadlen > 0) {
+      dopr_outch(buffer, currlen, maxlen, '0');
+      --zpadlen;
+    }
+  }
+
+  /* Digits */
+  while (place > 0)
+    dopr_outch(buffer, currlen, maxlen, convert[--place]);
+
+  /* Left Justified spaces */
+  while (spadlen < 0) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    ++spadlen;
+  }
+}
+
+static LDOUBLE abs_val(LDOUBLE value)
+{
+  LDOUBLE result = value;
+
+  if (value < 0)
+    result = -value;
+
+  return result;
+}
+
+static LDOUBLE pow10(int exp)
+{
+  LDOUBLE result = 1;
+
+  while (exp) {
+    result *= 10;
+    exp--;
+  }
+
+  return result;
+}
+
+static long round(LDOUBLE value)
+{
+  long intpart;
+
+  intpart = value;
+  value = value - intpart;
+  if (value >= 0.5)
+    intpart++;
+
+  return intpart;
+}
+
+static void fmtfp(char *buffer, size_t * currlen, size_t maxlen,
+		  LDOUBLE fvalue, int min, int max, int flags)
+{
+  int signvalue = 0;
+  LDOUBLE ufvalue;
+  char iconvert[20];
+  char fconvert[20];
+  int iplace = 0;
+  int fplace = 0;
+  int padlen = 0;		/* amount to pad */
+  int zpadlen = 0;
+  int caps = 0;
+  long intpart;
+  long fracpart;
+
+  /*
+   * AIX manpage says the default is 0, but Solaris says the default
+   * is 6, and sprintf on AIX defaults to 6
+   */
+  if (max < 0)
+    max = 6;
+
+  ufvalue = abs_val(fvalue);
+
+  if (fvalue < 0)
+    signvalue = '-';
+  else if (flags & DP_F_PLUS)	/* Do a sign (+/i) */
+    signvalue = '+';
+  else if (flags & DP_F_SPACE)
+    signvalue = ' ';
+
+#if 0
+  if (flags & DP_F_UP)
+    caps = 1;			/* Should characters be upper case? */
+#endif
+
+  intpart = ufvalue;
+
+  /*
+   * Sorry, we only support 9 digits past the decimal because of our
+   * conversion method
+   */
+  if (max > 9)
+    max = 9;
+
+  /* We "cheat" by converting the fractional part to integer by
+   * multiplying by a factor of 10
+   */
+  fracpart = round((pow10(max)) * (ufvalue - intpart));
+
+  if (fracpart >= pow10(max)) {
+    intpart++;
+    fracpart -= pow10(max);
+  }
+
+  /* Convert integer part */
+  do {
+    iconvert[iplace++] =
+	(caps ? "0123456789ABCDEF" : "0123456789abcdef")[intpart % 10];
+    intpart = (intpart / 10);
+  }
+  while (intpart && (iplace < 20));
+  if (iplace == 20)
+    iplace--;
+  iconvert[iplace] = 0;
+
+  /* Convert fractional part */
+  do {
+    fconvert[fplace++] =
+	(caps ? "0123456789ABCDEF" : "0123456789abcdef")[fracpart % 10];
+    fracpart = (fracpart / 10);
+  }
+  while (fracpart && (fplace < 20));
+  if (fplace == 20)
+    fplace--;
+  fconvert[fplace] = 0;
+
+  /* -1 for decimal point, another -1 if we are printing a sign */
+  padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
+  zpadlen = max - fplace;
+  if (zpadlen < 0)
+    zpadlen = 0;
+  if (padlen < 0)
+    padlen = 0;
+  if (flags & DP_F_MINUS)
+    padlen = -padlen;		/* Left Justifty */
+
+  if ((flags & DP_F_ZERO) && (padlen > 0)) {
+    if (signvalue) {
+      dopr_outch(buffer, currlen, maxlen, signvalue);
+      --padlen;
+      signvalue = 0;
+    }
+    while (padlen > 0) {
+      dopr_outch(buffer, currlen, maxlen, '0');
+      --padlen;
+    }
+  }
+  while (padlen > 0) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    --padlen;
+  }
+  if (signvalue)
+    dopr_outch(buffer, currlen, maxlen, signvalue);
+
+  while (iplace > 0)
+    dopr_outch(buffer, currlen, maxlen, iconvert[--iplace]);
+
+  /*
+   * Decimal point.  This should probably use locale to find the correct
+   * char to print out.
+   */
+  if (max > 0) {
+    dopr_outch(buffer, currlen, maxlen, '.');
+
+    while (fplace > 0)
+      dopr_outch(buffer, currlen, maxlen, fconvert[--fplace]);
+  }
+
+  while (zpadlen > 0) {
+    dopr_outch(buffer, currlen, maxlen, '0');
+    --zpadlen;
+  }
+
+  while (padlen < 0) {
+    dopr_outch(buffer, currlen, maxlen, ' ');
+    ++padlen;
+  }
+}
+
+static void dopr_outch(char *buffer, size_t * currlen, size_t maxlen,
+		       char c)
+{
+  if (*currlen < maxlen)
+    buffer[(*currlen)++] = c;
+}
+
+int egg_vsnprintf(char *str, size_t count, const char *fmt, va_list args)
+{
+  str[0] = 0;
+  dopr(str, count, fmt, args);
+  return (strlen(str));
+}
+#endif				/* !HAVE_VSNPRINTF */
+
+#ifndef HAVE_SNPRINTF
+#  ifdef HAVE_STDARGS
+int egg_snprintf(char *str, size_t count, const char *fmt, ...)
+#  else
+int egg_snprintf(va_alist) va_dcl
+#  endif
+{
+#  ifndef HAVE_STDARGS
+  char *str;
+  size_t count;
+  char *fmt;
+#  endif
+  VA_LOCAL_DECL;
+
+  VA_START(fmt);
+  VA_SHIFT(str, char *);
+  VA_SHIFT(count, size_t);
+  VA_SHIFT(fmt, char *);
+  (void) egg_vsnprintf(str, count, fmt, ap);
+  VA_END;
+  return (strlen(str));
+}
+#endif				/* !HAVE_SNPRINTF */

+ 49 - 0
compat/snprintf.h

@@ -0,0 +1,49 @@
+/*
+ * snprintf.h
+ *   header file for snprintf.c
+ *
+ * $Id: snprintf.h,v 1.7 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_SNPRINTF_H_
+#define _EGG_COMPAT_SNPRINTF_H_
+
+/* Use the system libraries version of vsnprintf() if available. Otherwise
+ * use our own.
+ */
+#ifndef HAVE_VSNPRINTF
+int egg_vsnprintf(char *str, size_t count, const char *fmt, va_list ap);
+#else
+#  define egg_vsnprintf	vsnprintf
+#endif
+
+/* Use the system libraries version of snprintf() if available. Otherwise
+ * use our own.
+ */
+#ifndef HAVE_SNPRINTF
+#  ifdef __STDC__
+int egg_snprintf(char *str, size_t count, const char *fmt, ...);
+#  else
+int egg_snprintf();
+#  endif
+#define snprintf egg_snprintf
+#endif
+
+#endif	/* !_EGG_COMPAT_SNPRINTF_H_ */

BIN
compat/snprintf.o


+ 50 - 0
compat/strcasecmp.c

@@ -0,0 +1,50 @@
+/*
+ * strcasecmp.c -- provides strcasecmp() and strncasecmp if necessary.
+ *
+ * $Id: strcasecmp.c,v 1.2 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 1997 Robey Pointer
+ * Copyright (C) 1999, 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "main.h"
+#include "memcpy.h"
+
+#ifndef HAVE_STRCASECMP
+int egg_strcasecmp(const char *s1, const char *s2)
+{
+  while ((*s1) && (*s2) && (toupper(*s1) == toupper(*s2))) {
+    s1++;
+    s2++;
+  }
+  return toupper(*s1) - toupper(*s2);
+}
+#endif /* !HAVE_STRCASECMP */
+
+#ifndef HAVE_STRNCASECMP
+int egg_strncasecmp(const char *s1, const char *s2, size_t n)
+{
+  if (!n)
+    return 0;
+  while (--n && (*s1) && (*s2) && (toupper(*s1) == toupper(*s2))) {
+    s1++;
+    s2++;
+  }
+  return toupper(*s1) - toupper(*s2);
+}
+#endif /* !HAVE_STRNCASECMP */

+ 46 - 0
compat/strcasecmp.h

@@ -0,0 +1,46 @@
+/*
+ * strcasecmp.h
+ *   prototypes for strcasecmp.c
+ *
+ * $Id: strcasecmp.h,v 1.3 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_STRCASECMP_H
+#define _EGG_COMPAT_STRCASECMP_H
+
+#include "src/main.h"
+#include <ctype.h>
+
+
+#ifndef HAVE_STRCASECMP
+/* Use our own implementation. */
+int egg_strcasecmp(const char *, const char *);
+#else
+#  define egg_strcasecmp	strcasecmp
+#endif
+
+#ifndef HAVE_STRNCASECMP
+/* Use our own implementation. */
+int egg_strncasecmp(const char *, const char *, size_t);
+#else
+#  define egg_strncasecmp	strncasecmp
+#endif
+
+#endif	/* !__EGG_COMPAT_STRCASECMP_H */

BIN
compat/strcasecmp.o


+ 35 - 0
compat/strftime.c

@@ -0,0 +1,35 @@
+/*
+ * strftime.c
+ *   Portable strftime implementation. Uses GNU's strftime().
+ *
+ * $Id: strftime.c,v 1.2 2001/04/12 02:39:44 guppy Exp $
+ */
+/*
+ * Copyright (C) 2000, 2001 Eggheads Development Team
+ * Written by Fabian Knittel
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "src/main.h"
+#include "strftime.h"
+
+#ifndef HAVE_STRFTIME
+#  undef emacs
+#  undef _LIBC
+#  define strftime	egg_strftime
+
+#  include "gnu_strftime.c"
+#endif	/* !HAVE_STRFTIME */

+ 42 - 0
compat/strftime.h

@@ -0,0 +1,42 @@
+/*
+ * strftime.h
+ *   header file for strftime.c
+ *
+ * $Id: strftime.h,v 1.1 2000/09/12 15:34:01 fabian Exp $
+ */
+/* 
+ * Copyright (C) 2000  Eggheads
+ * Written by Fabian Knittel
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef _EGG_COMPAT_STRFTIME_H_
+#define _EGG_COMPAT_STRFTIME_H_
+
+#include "src/main.h"
+#include <time.h>
+
+/* Use the system libraries version of strftime() if available. Otherwise
+ * use our own.
+ */
+#ifndef HAVE_STRFTIME
+size_t egg_strftime(char *s, size_t maxsize, const char *format,
+		    const struct tm *tp);
+#else
+#  define egg_strftime	strftime
+#endif
+
+#endif	/* !_EGG_COMPAT_STRFTIME_H_ */

BIN
compat/strftime.o


+ 6 - 0
core/Makefile

@@ -0,0 +1,6 @@
+# Makefile for src/mod/stats.mod/
+
+core.o: includes.c
+	gcc -pipe -g -O2 -g3 includes.c
+
+#safety hash

+ 34 - 0
core/compat/noegg.c

@@ -0,0 +1,34 @@
+#ifndef __stdlib_h__
+#include <stdlib.h>
+#endif
+
+// #include "compat/snprintf.c"
+
+static void nputlog(char *s, ...)
+{
+
+}
+
+char *newsplit(char **rest)
+{
+  register char *o, *r;
+
+  if (!rest)
+    return *rest = "";
+  o = *rest;
+  while (*o == ' ')
+    o++;
+  r = o;
+  while (*o && (*o != ' '))
+    o++;
+  if (*o)
+    *o++ = 0;
+  *rest = o;
+  return r;
+}
+
+static void fatal(char *s, int x)
+{
+	putlog(LOG_MISC, "*", "%s", s);
+	exit(EXIT_FAILURE);
+}

+ 36 - 0
core/compat/noegg.h

@@ -0,0 +1,36 @@
+// #include <assert.h>
+// #include <stdio.h>
+
+// #include "compat/snprintf.h"
+
+static void putlog(int, char *, char *s, ...);
+char *newsplit(char **rest);
+
+#define strcasecmp(s1, s2)	strcmpi(s1, s2)
+#define strncasecmp(s1, s2, n)	strnicmp(s1, s2, n)
+
+#define rfc_casecmp(s1, s2)	strcmpi(s1, s2)
+
+#define random rand
+
+#define LOG_MISC	0
+#define LOG_DEBUG	1
+
+#define debug0(s)		putlog(LOG_DEBUG, "*", s)
+#define debug1(s1, s2)		putlog(LOG_DEBUG, "*", s1, s2)
+#define debug2(s1, s2, s3)	putlog(LOG_DEBUG, "*", s1, s2, s3)
+#define debug3(s1, s2, s3, s4)	putlog(LOG_DEBUG, "*", s1, s2, s3, s4)
+
+#define now time(NULL)
+
+#define chmod(x1, x2)	assert(1)
+
+#define botnetnick "Stats.dll"
+
+#define botname "Stats.dll"
+
+#define movefile(f1, f2) rename(f1, f2)
+
+static void fatal(char *s, int x);
+
+// #define findchan_by_dname(x) NULL

+ 114 - 0
core/core.c

@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef assert
+#include <assert.h>
+#endif
+
+#ifndef Assert
+#define Assert assert
+#endif
+
+#ifdef NO_EGG
+#include "core/compat/noegg.h"
+#include "core/compat/noegg.c"
+#endif
+
+#ifndef NO_MEM_DEBUG
+#ifndef DYNAMIC_MEM_DEBUG
+#include "core/dynamic_mem_debug.c"
+#endif
+#endif
+
+#ifndef DYNAMIC_MEM_DEBUG
+
+#	ifndef nmalloc
+#		define nmalloc(x) malloc(x)
+#	endif
+
+#	ifndef nfree
+#		define nfree(x) free(x)
+#	endif
+
+#	ifndef nreallc
+#		define nrealloc(p, i) realloc(p, i)
+#	endif
+
+#endif
+
+#ifndef Context
+#define Context
+#endif
+
+#include "core/generic_linked_list.c"
+#include "core/llists.c"
+
+#include "core/data.h"
+#include "core/schan.h"
+#include "core/schan_members.h"
+#include "core/userrec.h"
+#include "core/slang.h"
+#include "core/mini_httpd.h"
+#include "core/templates.h"
+#include "core/misc.h"
+
+
+static struct stats_global *sdata = NULL;
+static struct stats_userlist *suserlist = NULL;
+static struct slang_header *coreslangs = NULL;
+static struct llist_header schanset = {NULL, NULL, 0, schan_compare, schan_expmem, schan_free};
+
+
+#include "core/vars.c"
+#include "core/global_vars.c"
+
+#include "core/slang.c"
+
+#include "core/datahandling.c"
+#include "core/data_sorting.c"
+
+#include "core/mini_httpd.c"
+
+#include "core/slang_stats_commands.c"
+
+#include "core/schan.c"
+#include "core/schan_members.c"
+#include "core/schan_interface.c"
+
+#include "core/sensors.c"
+
+#include "core/userrec.c"
+
+#include "core/user.c"
+#include "core/templates.c"
+#include "core/templates_stats_commands.c"
+#include "core/templates_httpd_commands.c"
+#include "core/http_processing.c"
+#include "core/misc.c"
+
+static void stats_core_init()
+{
+	sdata = NULL;
+	suserlist = NULL;
+	coreslangs = NULL;
+}
+
+static void stats_core_unload()
+{
+	slang_free(coreslangs);
+}

+ 166 - 0
core/data.h

@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define SAVESTATSLENGTH 5000
+
+#define TYPES "words letters started minutes topics lines actions modes bans kicks nicks joins smileys questions"
+#define SPECIAL_TYPES "age wpl vocables idle"
+#define T_GSTARTED -1
+#define T_LSTARTED -2
+#define T_PEAK -3
+#define T_WPL -4
+#define T_WORD -5
+#define T_VOCABLES -6
+#define T_QUOTE -7
+#define T_IDLE -8
+#define T_ERROR -999
+#define T_WORDS 0
+#define T_LETTERS 1
+#define T_MINUTES 2
+#define T_TOPICS 3
+#define T_LINES 4
+#define T_ACTIONS 5
+#define T_MODES 6
+#define T_BANS 7
+#define T_KICKS 8
+#define T_NICKS 9
+#define T_JOINS 10
+#define T_SMILEYS 11
+#define T_QUESTIONS 12
+#define TOTAL_TYPES 13
+#define TOTAL_SPECIAL_TYPES 8
+
+#define S_TOTAL 0
+#define S_TODAY 1
+#define S_DAILY 1
+#define S_WEEKLY 2
+#define S_MONTHLY 3
+
+#define RANGESTR_LONG(x) x ? ((x == S_DAILY) ? SLLTODAY : ((x == S_WEEKLY) ? SLLWEEKLY : SLLMONTHLY)) : SLLTOTAL
+
+#define S_USERSUM 0
+#define S_USERCOUNTS 1
+
+#define SL_PRIVMSG 0
+#define SL_KICK 1
+#define SL_MODE 2
+#define SL_NICK 3
+#define SL_PART 4
+#define SL_QUIT 5
+#define SL_JOIN 6
+
+
+typedef struct stats_words {
+  struct stats_words *next;
+  struct stats_words *left;
+  struct stats_words *right;
+  char *word;
+  int nr;
+} wordstats;
+
+typedef struct stats_quote {
+  struct stats_quote *next;
+  char *quote;
+} quotestr;
+
+typedef struct stats_topic {
+  struct stats_topic *next;
+  char *topic;
+  char *by;
+  time_t when;
+} topicstr;
+
+struct stats_url {
+  struct stats_url *next;
+  char *url;
+  char *by;
+  int shown;
+  time_t when;
+};
+
+struct stats_kick {
+  struct stats_kick *next;
+  quotestr *log;
+  int shown;
+};
+
+typedef struct stats_hosts {
+  struct stats_hosts *next;
+  char *host;
+  int nr;
+} hoststr;
+
+typedef struct stats_local {
+  struct stats_local *next;
+  struct stats_local *snext[4][TOTAL_TYPES + TOTAL_SPECIAL_TYPES];
+  char *user;
+  struct stats_userlist *u;
+  time_t started;
+  time_t lastspoke;
+  long int values[4][TOTAL_TYPES];
+  wordstats *words;
+  wordstats *tree;
+  wordstats *word;
+  int vocables;
+  quotestr *quotes;
+  int quotefr;
+  int flag;
+} locstats;
+
+typedef struct stats_global {
+  struct stats_global *next;
+  char *chan;
+  time_t started;
+  int peak[4];
+  int users[2][24];
+  int activity[24];
+  struct stats_local *local;
+  struct stats_local *slocal[4][TOTAL_TYPES + TOTAL_SPECIAL_TYPES];
+  wordstats *words;
+  topicstr *topics;
+  hoststr *hosts;
+  struct stats_url *urls;
+  quotestr *log;
+  quotestr *lastlog;
+  int log_length;
+  struct stats_kick *kicks;
+} globstats;
+
+static void locstats_init(locstats *);
+static void globstats_init(globstats *);
+
+static char *itotype(int);
+static int typetoi(char *);
+
+static void incrwordstats(locstats *, char *, int, int);
+
+static void sortstats(struct stats_global *, int, int);
+static void sort_stats_alphabetically(globstats *);
+static void sortstats_wpl(struct stats_global *, int);
+static void sortstats_vocables(struct stats_global *, int);
+static void sortstats_word(struct stats_global *, int);
+static void sortstats_idle(struct stats_global *, int);
+static void sortwordstats(locstats *, globstats *);
+static void free_stats();
+static void free_localstats(struct stats_local *sl);
+static void free_wordstats(wordstats *l);
+static void free_quotes(quotestr *l);
+static void free_topics(topicstr *e);
+static void free_urls(struct stats_url *e);
+static void free_kicks(struct stats_kick *e);
+static void free_hosts(hoststr *e);

+ 412 - 0
core/data_sorting.c

@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void sortstats(struct stats_global *gs, int itype, int today)
+{
+  int again = 1;
+  struct stats_local *last, *p, *c, *n;
+  int a, b, pitype;
+
+  Context;
+  Assert(gs);
+  again = 1;
+  last = NULL;
+  if (itype < 0) {
+    // switch to the special sorting function
+    switch (itype) {
+      case T_WPL:
+        sortstats_wpl(gs, today);
+        break;
+      case T_VOCABLES:
+        sortstats_vocables(gs, today);
+        break;
+      case T_WORD:
+        sortstats_word(gs, today);
+        break;
+      case T_IDLE:
+        sortstats_idle(gs, today);
+        break;
+      default:
+        debug1("Missing sorting algorithm for \"%d\"!!!", itype);
+    }
+    return;
+  }
+
+  // if (itype < 0) pitype = (TOTAL_TYPES - 1) + (itype * -1);
+  // not needed here
+  pitype = itype;
+  while ((gs->slocal[today][pitype] != last) && (again)) {
+    p = NULL;
+    c = gs->slocal[today][pitype];
+    n = c->snext[today][pitype];
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        a = c->values[today][itype];
+        b = n->values[today][itype];
+      }
+      if (a < b) {
+        again = 1;
+        c->snext[today][pitype] = n->snext[today][pitype];
+        n->snext[today][pitype] = c;
+        if (p == NULL)
+          gs->slocal[today][pitype] = n;
+        else
+          p->snext[today][pitype] = n;
+      }
+      p = c;
+      c = n;
+      n = n->snext[today][pitype];
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sortstats_wpl(struct stats_global *gs, int today)
+{
+  int again = 1;
+  struct stats_local *last, *p, *c, *n;
+  int a, b, pitype;
+
+  Context;
+  again = 1;
+  last = NULL;
+  pitype = (T_WPL * (-1)) + TOTAL_TYPES - 1;
+  while ((gs->slocal[today][pitype] != last) && (again)) {
+    p = NULL;
+    c = gs->slocal[today][pitype];
+    n = c->snext[today][pitype];
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        if (c->values[today][T_LINES] >= min_lines)
+          a = (int) (((float) c->values[today][T_WORDS] / (float) c->values[today][T_LINES]) * 100.0);
+        else
+          a = 0;
+        if (n->values[today][T_LINES] >= min_lines)
+          b = (int) (((float) n->values[today][T_WORDS] / (float) n->values[today][T_LINES]) * 100.0);
+        else
+          b = 0;
+      }
+      if (a < b) {
+        again = 1;
+        c->snext[today][pitype] = n->snext[today][pitype];
+        n->snext[today][pitype] = c;
+        if (p == NULL)
+          gs->slocal[today][pitype] = n;
+        else
+          p->snext[today][pitype] = n;
+      }
+      p = c;
+      c = n;
+      n = n->snext[today][pitype];
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sortstats_vocables(struct stats_global *gs, int today)
+{
+  int again = 1;
+  struct stats_local *last, *p, *c, *n;
+  int a, b, pitype;
+
+  Context;
+  again = 1;
+  last = NULL;
+  countvocables(gs);
+  pitype = (T_VOCABLES * (-1)) + TOTAL_TYPES - 1;
+  while ((gs->slocal[today][pitype] != last) && (again)) {
+    p = NULL;
+    c = gs->slocal[today][pitype];
+    n = c->snext[today][pitype];
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        a = c->vocables;
+        b = n->vocables;
+      }
+      if (a < b) {
+        again = 1;
+        c->snext[today][pitype] = n->snext[today][pitype];
+        n->snext[today][pitype] = c;
+        if (p == NULL)
+          gs->slocal[today][pitype] = n;
+        else
+          p->snext[today][pitype] = n;
+      }
+      p = c;
+      c = n;
+      n = n->snext[today][pitype];
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sortstats_word(struct stats_global *gs, int today)
+{
+  int again = 1;
+  struct stats_local *last, *p, *c, *n;
+  int a, b, pitype;
+
+  Context;
+  debug1("sortstats_word: today == %d", today);
+  again = 1;
+  last = NULL;
+  pitype = (T_WORD * (-1)) + TOTAL_TYPES - 1;
+  debug1("pitype: %d", pitype);
+  while ((gs->slocal[today][pitype] != last) && (again)) {
+    p = NULL;
+    c = gs->slocal[today][pitype];
+    n = c->snext[today][pitype];
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        if (c->word)
+          a = c->word->nr;
+        else
+          a = 0;
+        if (n->word)
+          b = n->word->nr;
+        else
+          b = 0;
+      }
+      if (a < b) {
+        again = 1;
+        c->snext[today][pitype] = n->snext[today][pitype];
+        n->snext[today][pitype] = c;
+        if (p == NULL)
+          gs->slocal[today][pitype] = n;
+        else
+          p->snext[today][pitype] = n;
+      }
+      p = c;
+      c = n;
+      n = n->snext[today][pitype];
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+// sort stats by idle-factor (minutes/lines)
+static void sortstats_idle(struct stats_global *gs, int today)
+{
+  int again = 1;
+  struct stats_local *last, *p, *c, *n;
+  int a, b, pitype;
+
+  Context;
+  again = 1;
+  last = NULL;
+  pitype = (T_IDLE * (-1)) + TOTAL_TYPES - 1;
+  while ((gs->slocal[today][pitype] != last) && (again)) {
+    p = NULL;
+    c = gs->slocal[today][pitype];
+    n = c->snext[today][pitype];
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        if (c->values[today][T_LINES] >= min_lines)
+          a = (int) (((float) c->values[today][T_MINUTES] / (float) c->values[today][T_LINES]) * 100.0);
+        else
+          a = 0;
+        if (n->values[today][T_LINES] >= min_lines)
+          b = (int) (((float) n->values[today][T_MINUTES] / (float) n->values[today][T_LINES]) * 100.0);
+        else
+          b = 0;
+      }
+      if (a < b) {
+        again = 1;
+        c->snext[today][pitype] = n->snext[today][pitype];
+        n->snext[today][pitype] = c;
+        if (p == NULL)
+          gs->slocal[today][pitype] = n;
+        else
+          p->snext[today][pitype] = n;
+      }
+      p = c;
+      c = n;
+      n = n->snext[today][pitype];
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sortwordstats(locstats *ls, globstats *gs)
+{
+  int again = 1;
+  wordstats *last, *p, *c, *n, *tmp;
+  int a, b;
+
+  Context;
+  again = 1;
+  last = NULL;
+  if (ls)
+    tmp = ls->words;
+  else
+    tmp = gs->words;
+  while ((tmp != last) && (again)) {
+    p = NULL;
+    if (ls)
+      c = ls->words;
+    else
+      c = gs->words;
+    n = c->next;
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        a = c->nr;
+        b = n->nr;
+      }
+      if (a < b) {
+  again = 1;
+  c->next = n->next;
+  n->next = c;
+  if (p == NULL) {
+    if (ls)
+      ls->words = n;
+    else
+      gs->words = n;
+    tmp = n;
+  } else
+    p->next = n;
+      }
+      p = c;
+      c = n;
+      n = n->next;
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sorthosts(struct stats_global *gs)
+{
+  int again = 1;
+  hoststr *last, *p, *c, *n;
+  int a, b;
+
+  Context;
+  again = 1;
+  last = NULL;
+  while ((gs->hosts != last) && (again)) {
+    p = NULL;
+    c = gs->hosts;
+    n = c->next;
+    again = 0;
+    while (n != last) {
+      if (!c || !n)
+        a = b = 0;
+      else {
+        a = c->nr;
+        b = n->nr;
+      }
+      if (a < b) {
+        again = 1;
+        c->next = n->next;
+        n->next = c;
+        if (p == NULL)
+          gs->hosts = n;
+        else
+          p->next = n;
+      }
+      p = c;
+      c = n;
+      n = n->next;
+    }
+    last = c;
+  }
+  Context;
+  return;
+}
+
+static void sort_stats_alphabetically(globstats *gs)
+{
+  locstats *as, *bs, *l, *last;
+  int a, b, again = 1;
+  char *astr, *bstr, n[2];
+
+  Context;
+  n[0] = n[1] = 0;
+  last = NULL;
+  while ((gs->local != last) && again) {
+    again = 0;
+    l = NULL;
+    as = gs->local;
+    bs = gs->local->next;
+    while(bs) {
+      if (!as)
+        astr = n;
+      else
+        astr = as->user;
+      if (!bs)
+        bstr = n;
+      else
+        bstr = bs->user;
+      a = (int) tolower(astr[0]);
+      b = (int) tolower(bstr[0]);
+      while ((a == b) && a && b) {
+	astr++;
+	bstr++;
+        a = (int) tolower(astr[0]);
+        b = (int) tolower(bstr[0]);
+      }
+      if (a > b) {
+        if (!l)
+          gs->local = bs;
+        else
+          l->next = bs;
+        as->next = bs->next;
+        bs->next = as;
+        again = 1;
+        if (l == NULL)
+          gs->local = bs;
+        else
+          l = bs;
+      }
+      l = as;
+      as = bs;
+      bs = bs->next;
+    }
+    last = as;
+  }
+  Context;
+}

+ 1364 - 0
core/datahandling.c

@@ -0,0 +1,1364 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void incrstats(char *user, char *chan, int type, int value, int set)
+{
+  globstats *gs, *gs2;
+  locstats *ls, *ls2;
+  int i, ii;
+
+  if (type >= TOTAL_TYPES)
+    return;
+  if (!user) {
+    debug0("Stats.mod: incrstats(..) Ups, user is NULL!");
+    return;
+  }
+  if (!chan) {
+    debug0("Stats.mod: incrstats(..) Ups, chan is NULL!");
+    return;
+  }
+  for (gs = sdata; gs; gs = gs->next) {
+    if (!strcasecmp(chan, gs->chan))
+      break;
+  }
+  if (!gs) {
+    gs2 = sdata;
+    while (gs2 && gs2->next)
+      gs2 = gs2->next;
+    gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
+    gs->started = now;
+    gs->chan = nmalloc(strlen(chan) + 1);
+    strcpy(gs->chan, chan);
+    if (gs2)
+      gs2->next = gs;
+    else
+      sdata = gs;
+  }
+  for (ls = gs->local; ls; ls = ls->next) {
+    if (!strcasecmp(ls->user, user))
+      break;
+  }
+  if (type == T_GSTARTED) {
+    gs->started = value;
+    return;
+  }
+  if (type == T_PEAK) {
+    gs->peak[set] = value;
+    return;
+  }
+  if (!ls) {
+    ls2 = gs->local;
+    while (ls2 && ls2->next)
+      ls2 = ls2->next;
+    ls = nmalloc(sizeof(locstats));
+    locstats_init(ls);
+    ls->started = now;
+    ls->user = nmalloc(strlen(user) + 1);
+    strcpy(ls->user, user);
+    if (ls2)
+      ls2->next = ls;
+    else
+      gs->local = ls;
+    for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+      ls->snext[S_TOTAL][i] = ls->snext[S_DAILY][i] = ls->snext[S_WEEKLY][i] = ls->snext[S_MONTHLY][i] = NULL;
+    for (i = 0; i < 4; i++) {
+      for (ii = 0; ii < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); ii++) {
+        ls2 = gs->slocal[i][ii];
+        while (ls2 && ls2->snext[i][ii])
+          ls2 = ls2->snext[i][ii];
+        if (ls2)
+          ls2->snext[i][ii] = ls;
+        else
+          gs->slocal[i][ii] = ls;
+      }
+    }
+  }
+  if (type == T_LSTARTED)
+    ls->started = value;
+  else {
+    if (set > 0)
+      ls->values[set - 1][type] = value;
+    else if (set < 0)
+      ls->values[(set * (-1)) - 1][type] += value;
+    else {
+      ls->values[S_TOTAL][type] += value;
+      ls->values[S_TODAY][type] += value;
+      ls->values[S_WEEKLY][type] += value;
+      ls->values[S_MONTHLY][type] += value;
+    }
+  }
+}
+
+static globstats *globstats_create(char *chan)
+{
+  globstats *gs, *gs2;
+
+  for (gs = sdata; gs; gs = gs->next) {
+    if (!rfc_casecmp(chan, gs->chan))
+      return gs;
+  }
+  gs2 = sdata;
+  while (gs2 && gs2->next)
+    gs2 = gs2->next;
+  gs = nmalloc(sizeof(globstats));
+  globstats_init(gs);
+  gs->started = now;
+  gs->chan = nmalloc(strlen(chan) + 1);
+  strcpy(gs->chan, chan);
+  if (gs2)
+    gs2->next = gs;
+  else
+    sdata = gs;
+  return gs;
+}
+
+static void nincrstats(locstats *ls, int type, int value)
+{
+  ls->values[S_TOTAL][type] += value;
+  ls->values[S_TODAY][type] += value;
+  ls->values[S_WEEKLY][type] += value;
+  ls->values[S_MONTHLY][type] += value;
+}
+
+/* initstats()
+ * erzeugt einen Eintrag in den Statistiken eines Channels
+ * für einen User.
+ * FRAGE: _Muss_ ich den Rückgabewert irgendwo verwenden, oder
+ *		  ist er nur optional?									*/
+static locstats *initstats(char *chan, char *user)
+{
+  globstats *gs, *gs2;
+  locstats *ls, *ls2;
+  int i, ii;
+
+  gs = sdata;
+  while (gs) {
+    if (!rfc_casecmp(gs->chan, chan))
+      break;
+    gs = gs->next;
+  }
+  if (!gs) {
+    gs2 = sdata;
+    while (gs2 && gs2->next)
+      gs2 = gs2->next;
+    gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
+    gs->started = now;
+    gs->chan = nmalloc(strlen(chan) + 1);
+    strcpy(gs->chan, chan);
+    if (gs2)
+      gs2->next = gs;
+    else
+      sdata = gs;
+  }
+  for (ls = gs->local; ls; ls = ls->next) {
+    if (!rfc_casecmp(ls->user, user))
+      return ls;
+  }
+  if (!ls) {
+    ls2 = gs->local;
+    while (ls2 && ls2->next)
+      ls2 = ls2->next;
+    ls = nmalloc(sizeof(locstats));
+    locstats_init(ls);
+    ls->started = now;
+    ls->user = nmalloc(strlen(user) + 1);
+    strcpy(ls->user, user);
+    if (ls2)
+      ls2->next = ls;
+    else
+      gs->local = ls;
+    for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+      ls->snext[S_TOTAL][i] = ls->snext[S_DAILY][i] = ls->snext[S_WEEKLY][i] = ls->snext[S_MONTHLY][i] = NULL;
+    for (i = 0; i < 4; i++) {
+      for (ii = 0; ii < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); ii++) {
+        ls2 = gs->slocal[i][ii];
+        while (ls2 && ls2->snext[i][ii])
+          ls2 = ls2->snext[i][ii];
+        if (ls2)
+          ls2->snext[i][ii] = ls;
+        else
+          gs->slocal[i][ii] = ls;
+      }
+    }
+  }
+  return ls;
+}
+
+static void locstats_init(locstats *ls)
+{
+  int i;
+
+  Assert(ls);
+  ls->started = now;
+  ls->next = NULL;
+  ls->words = NULL;
+  ls->tree = NULL;
+  ls->quotes = NULL;
+  ls->quotefr = 0;
+  ls->flag = 0;
+  for (i = 0; i < TOTAL_TYPES; i++) {
+    ls->values[S_TOTAL][i] = 0;
+    ls->values[S_TODAY][i] = 0;
+    ls->values[S_WEEKLY][i] = 0;
+    ls->values[S_MONTHLY][i] = 0;
+  }
+  ls->user = NULL;
+  ls->u = NULL;
+  ls->lastspoke = 0;
+  for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+    ls->snext[S_TOTAL][i] = ls->snext[S_DAILY][i] = ls->snext[S_WEEKLY][i] = ls->snext[S_MONTHLY][i] = NULL;
+}
+
+static void globstats_init(globstats *gs)
+{
+  int i;
+
+  Assert(gs);
+  gs->next = NULL;
+  gs->chan = NULL;
+  gs->started = now;
+  gs->peak[S_TOTAL] = gs->peak[S_DAILY] = gs->peak[S_WEEKLY] = gs->peak[S_MONTHLY] = 0;
+  for (i = 0; i < 24; i++) {
+    gs->users[S_USERSUM][i] = 0;
+    gs->users[S_USERCOUNTS][i] = -1;
+  }
+  for (i = 0; i < 24; i++)
+    gs->activity[i] = 0;
+  gs->local = NULL;
+  for (i = 0; i < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES); i++)
+    gs->slocal[S_TOTAL][i] = gs->slocal[S_DAILY][i] = gs->slocal[S_WEEKLY][i] = gs->slocal[S_MONTHLY][i] = NULL;
+  gs->words = NULL;
+  gs->topics = NULL;
+  gs->hosts = NULL;
+  gs->urls = NULL;
+  gs->log = gs->lastlog = NULL;
+  gs->log_length = 0;
+  gs->kicks = NULL;
+}
+
+static int getstats(char *user, char *chan, char *type, int today)
+{
+  struct stats_global *gs = sdata;
+  struct stats_local *ls;
+  int itype;
+
+  while (gs) {
+    ls = gs->local;
+    if (!strcasecmp(gs->chan, chan)) {
+      while (ls) {
+        if (!strcasecmp(ls->user, user)) {
+          itype = typetoi(type);
+          if (itype >= 0)
+            return ls->values[today][itype];
+        }
+        ls = ls->next;
+      }
+    }
+    gs = gs->next;
+  }
+  return 0;
+}
+
+static void countvocables(globstats *gs)
+{
+  locstats *ls;
+  wordstats *ws;
+
+  for (ls = gs->local; ls; ls = ls->next) {
+    ls->vocables = 0;
+    for (ws = ls->words; ws; ws = ws->next)
+      ls->vocables++;
+  }
+}
+
+// typetoi(): returns the index of a stat-type
+static int typetoi(char *type)
+{
+  if (!strcasecmp(type, "lstarted"))
+    return T_LSTARTED;
+  else if (!strcasecmp(type, "gstarted"))
+    return T_GSTARTED;
+  else if (!strcasecmp(type, "words"))
+    return T_WORDS;
+  else if (!strcasecmp(type, "letters"))
+    return T_LETTERS;
+  else if (!strcasecmp(type, "minutes"))
+    return T_MINUTES;
+  else if (!strcasecmp(type, "topics"))
+    return T_TOPICS;
+  else if (!strcasecmp(type, "lines"))
+    return T_LINES;
+  else if (!strcasecmp(type, "actions"))
+    return T_ACTIONS;
+  else if (!strcasecmp(type, "kicks"))
+    return T_KICKS;
+  else if (!strcasecmp(type, "modes"))
+    return T_MODES;
+  else if (!strcasecmp(type, "bans"))
+    return T_BANS;
+  else if (!strcasecmp(type, "nicks"))
+    return T_NICKS;
+  else if (!strcasecmp(type, "joins"))
+    return T_JOINS;
+  else if (!strcasecmp(type, "smileys"))
+    return T_SMILEYS;
+  else if (!strcasecmp(type, "questions"))
+    return T_QUESTIONS;
+  else if (!strcasecmp(type, "wpl"))
+    return T_WPL;
+  else if (!strcasecmp(type, "w/l"))
+    return T_WPL;
+  else if (!strcasecmp(type, "word"))
+    return T_WORD;
+  else if (!strcasecmp(type, "vocables"))
+    return T_VOCABLES;
+  else if (!strcasecmp(type, "started"))
+    return T_LSTARTED;
+  else if (!strcasecmp(type, "quote"))
+    return T_QUOTE;
+  else if (!strcasecmp(type, "idle"))
+    return T_IDLE;
+  else {
+    debug1("Stats.mod: Unknown stat type: %s", type);
+    return T_ERROR;
+  }
+}
+
+/* itotype():
+ * similar to typetoi(), but returns the string that describes
+ * the given type
+ */
+static char *itotype(int type)
+{
+  switch (type) {
+    case T_WORDS: return "words";
+    case T_LETTERS: return "letters";
+    case T_MINUTES: return "minutes";
+    case T_TOPICS: return "topics";
+    case T_LINES: return "lines";
+    case T_ACTIONS: return "actions";
+    case T_MODES: return "modes";
+    case T_BANS: return "bans";
+    case T_KICKS: return "kicks";
+    case T_NICKS: return "nicks";
+    case T_JOINS: return "joins";
+    case T_SMILEYS: return "smileys";
+    case T_QUESTIONS: return "questions";
+    case T_WPL: return "w/l";
+    case T_IDLE: return "idle";
+    case T_VOCABLES: return "vocables";
+  }
+  return "!!!ERROR!!!";
+}
+
+static locstats *findlocstats(char *chan, char *user)
+{
+  globstats *gl;
+  locstats *ll;
+
+  for (gl = sdata; gl; gl = gl->next) {
+    if (!rfc_casecmp(gl->chan, chan))
+      break;
+  }
+  if (!gl)
+    return NULL;
+  for (ll = gl->local; ll; ll = ll->next) {
+    if (!rfc_casecmp(ll->user, user))
+      return ll;
+  }
+  return NULL;
+}
+
+static globstats *findglobstats(char *chan)
+{
+  globstats *gl;
+
+  for (gl = sdata; gl; gl = gl->next) {
+    if (!rfc_casecmp(gl->chan, chan))
+      break;
+  }
+  return gl;
+}
+
+static void write_stats()
+{
+  char s[125];
+  FILE *f;
+  struct stats_global *gs;
+  struct stats_local *ls;
+  struct stats_userlist *u;
+  struct stats_hostlist *h;
+  int i;
+
+  Context;
+  if (!statsfile[0])
+    return;
+  sprintf(s, "%s~new", statsfile);
+  f = fopen(s, "w");
+  chmod(s, statsfilemode);
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "ERROR writing stats file.");
+    return;
+  }
+  fprintf(f, "@ # Statistics from %s.\n", botnetnick);
+  fprintf(f, "@ filever 3\n");
+  fprintf(f, "@ month %d\n", getmonth());
+  for (gs = sdata; gs; gs = gs->next) {
+    fprintf(f, "%s ! %d\n", gs->chan, (int) gs->started);
+    fprintf(f, "%s @ %d\n", gs->chan, gs->peak[S_TOTAL]);
+    fprintf(f, "@ peaks %s %d %d %d %d\n", gs->chan, gs->peak[S_TOTAL],
+            gs->peak[S_DAILY], gs->peak[S_WEEKLY], gs->peak[S_MONTHLY]);
+    for (ls = gs->local; ls; ls = ls->next) {
+      fprintf(f, "%s %s %d", gs->chan, ls->user, (int) ls->started);
+      for (i = 0; i < TOTAL_TYPES; i++)
+        fprintf(f, " %ld", ls->values[S_TOTAL][i]);
+      fprintf(f, "\n");
+      fprintf(f, "@ lastspoke %d\n", (int) ls->lastspoke);
+      fprintf(f, "@ daily %s %s", gs->chan, ls->user);
+      for (i = 0; i < TOTAL_TYPES; i++)
+        fprintf(f, " %ld", ls->values[S_DAILY][i]);
+      fprintf(f, "\n");
+      fprintf(f, "@ weekly %s %s", gs->chan, ls->user);
+      for (i = 0; i < TOTAL_TYPES; i++)
+        fprintf(f, " %ld", ls->values[S_WEEKLY][i]);
+      fprintf(f, "\n");
+      fprintf(f, "@ monthly %s %s", gs->chan, ls->user);
+      for (i = 0; i < TOTAL_TYPES; i++)
+        fprintf(f, " %ld", ls->values[S_MONTHLY][i]);
+      fprintf(f, "\n");
+      i = 0;
+    }
+  }
+  for (u = suserlist; u; u = u->next) {
+    fprintf(f, "@ user %s %d %lu %lu", u->user, u->flags, u->created, u->laston);
+    for (h = u->hosts; h; h = h->next) {
+      fprintf(f, " %s %lu %lu", h->mask, h->lastused, h->created);
+    }
+    fprintf(f, "\n");
+    if (u->password || u->email || u->homepage || u->icqnr) {
+      fprintf(f, "@ uxtra %s", u->user);
+      if (u->email)
+        fprintf(f, " e %s", u->email);
+      if (u->homepage)
+        fprintf(f, " h %s", u->homepage);
+      if (u->icqnr)
+        fprintf(f, " i %d", u->icqnr);
+      if (u->password)
+        fprintf(f, " p %s", u->password);
+      fprintf(f, "\n");
+    }
+  }
+  fclose(f);
+  unlink(statsfile);
+  movefile(s, statsfile);
+  Context;
+  return;
+}
+
+static void read_stats()
+{
+  FILE *f;
+  char buf[SAVESTATSLENGTH + 1];
+  char *s, *chan, *user, *cmd, *host, *tmp;
+  int i, version, range, month, list, addhosts, flags;
+  struct stats_userlist *u;
+  time_t lastused;
+  locstats *ls;
+  globstats *gs;
+  time_t created, laston;
+
+  Context;
+  ls = NULL;
+  gs = NULL;
+  version = 0;
+  month = 0;
+  f = fopen(statsfile, "r");
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "ERROR reading stats file");
+    return;
+  }
+  free_stats();
+  while (!feof(f)) {
+    buf[0] = 0;
+    s = buf;
+    fgets(s, SAVESTATSLENGTH - 1, f);
+    s[SAVESTATSLENGTH - 1] = 0;
+    if (buf[0] == 0)
+      continue;
+    if (s[strlen(s) - 1] == '\n')
+      s[strlen(s) - 1] = 0;
+    chan = newsplit(&s);
+    if (!strcmp(chan, "@")) {
+      cmd = newsplit(&s);
+      if (!strcmp(cmd, "filever"))
+	version = atoi(newsplit(&s));
+      else if (!strcmp(cmd, "month"))
+        month = atoi(newsplit(&s));
+      else if (!strcmp(cmd, "peaks")) {
+        chan = newsplit(&s);
+        incrstats("*", chan, T_PEAK, atoi(newsplit(&s)), S_TOTAL);
+        incrstats("*", chan, T_PEAK, atoi(newsplit(&s)), S_DAILY);
+        incrstats("*", chan, T_PEAK, atoi(newsplit(&s)), S_WEEKLY);
+        incrstats("*", chan, T_PEAK, atoi(newsplit(&s)), S_MONTHLY);
+      } else if (!strcmp(cmd, "daily") || !strcmp(cmd, "weekly")
+                 || !strcmp(cmd, "monthly")) {
+        if (!strcmp(cmd, "daily"))
+	  range = S_DAILY;
+        else if (!strcmp(cmd, "weekly"))
+	  range = S_WEEKLY;
+        else if (!strcmp(cmd, "monthly"))
+          range = S_MONTHLY;
+        else {
+          debug2("Error while reading statsfile: range uninitialized! (%s %s)", cmd, s);
+          continue;
+        }
+        if ((range == S_MONTHLY) && (month != lastmonth))
+          continue;
+        chan = newsplit(&s);
+        user = newsplit(&s);
+        // Check if pointers still point to the correct data and
+        // update them, if not.
+        if ((gs && strcmp(gs->chan, chan)) || !gs) {
+          gs = findglobstats(chan);
+          ls = findlocstats(chan, user);
+        } else {
+          if ((ls && strcmp(ls->user, user)) || !ls)
+            ls = findlocstats(chan, user);
+        }
+        if (!ls)
+          ls = initstats(chan, user);
+        for (i = 0; i < TOTAL_TYPES; i++)
+          ls->values[range][i] = atoi(newsplit(&s));
+      } else if (!strcmp(cmd, "lastspoke")) {
+	if (ls) {
+	  ls->lastspoke = atoi(newsplit(&s));
+	} else {
+	  putlog(LOG_MISC, "*", "ERROR: Can't load lastspoke info. No locstats.");
+	}
+      } else if (!strcmp(cmd, "user")) {
+	user = newsplit(&s);
+	flags = 0;
+	if (version < 2) {
+	  list = atoi(newsplit(&s));
+	  addhosts = atoi(newsplit(&s));
+	  if (list)
+	    flags |= S_LIST;
+	  if (addhosts)
+	    flags |= S_ADDHOSTS;
+	} else
+	  flags = atoi(newsplit(&s));
+	if (version >= 3) {
+	  created = atoi(newsplit(&s));
+	  laston = atoi(newsplit(&s));
+	} else {
+	  created = get_creation_time_from_locstats(user);
+	  laston = get_laston_time_from_hosts(user);
+	}
+	u = addsuser(user, created, laston);
+	u->flags = flags;
+	while (s[0]) {
+	  host = newsplit(&s);
+	  lastused = (time_t) atoi(newsplit(&s));
+	  if (version >= 3)
+	    created = (time_t) atoi(newsplit(&s));
+	  else
+	    created = lastused;
+	  saddhost(u, host, lastused, created);
+	}
+      } else if (!strcmp(cmd, "uxtra")) {
+        user = newsplit(&s);
+        u = findsuser_by_name(user);
+        while (u && s[0]) {
+          tmp = newsplit(&s);
+          if (!strcmp(tmp, "e"))
+            setemail(u, newsplit(&s));
+          else if (!strcmp(tmp, "h"))
+            sethomepage(u, newsplit(&s));
+          else if (!strcmp(tmp, "i"))
+            u->icqnr = atoi(newsplit(&s));
+          else if (!strcmp(tmp, "p"))
+            setpassword(u, newsplit(&s));
+        }
+      }
+    } else {
+      // old style data
+      // left-over from v1.0. I should change it, but I don't want
+      // to break compatibility
+      user = newsplit(&s);
+      if (!strcmp(user, "!"))
+        incrstats(user, chan, T_GSTARTED, atoi(newsplit(&s)), 1);
+      else if (!strcmp(user, "@"))
+        incrstats(user, chan, T_PEAK, atoi(newsplit(&s)), S_TOTAL);
+      else {
+        incrstats(user, chan, T_LSTARTED, atoi(newsplit(&s)), 1);
+        // initstats also returns the current 'ls' if it also exists,
+        // so better don't even use findlocstats() before to save
+        // some CPU-time
+        ls = initstats(chan, user);
+        for (i = 0; i < TOTAL_TYPES; i++)
+          ls->values[S_TOTAL][i] = atoi(newsplit(&s));
+      }
+    }
+  }
+  fclose(f);
+  Context;
+  return;
+}
+
+static void reset_tstats()
+{
+  globstats *gs;
+  locstats *ls;
+  int i;
+
+  Context;
+  putlog(LOG_MISC, "*", "Stats.mod: Resetting today's statistics...");
+  for (gs = sdata; gs; gs = gs->next) {
+    gs->peak[S_TODAY] = 0;
+    free_wordstats(gs->words);
+    gs->words = NULL;
+    free_topics(gs->topics);
+    gs->topics = NULL;
+    free_urls(gs->urls);
+    gs->urls = NULL;
+    free_hosts(gs->hosts);
+    gs->hosts = NULL;
+    free_kicks(gs->kicks);
+    gs->kicks = NULL;
+    for (ls = gs->local; ls; ls = ls->next) {
+      free_wordstats(ls->words);
+      ls->words = NULL;
+      ls->tree = NULL;
+      free_quotes(ls->quotes);
+      ls->quotes = NULL;
+      for (i = 0; i < TOTAL_TYPES; i++)
+        ls->values[S_TODAY][i] = 0;
+    }
+  }
+  Context;
+}
+
+static void reset_mwstats(int range)
+{
+  globstats *gs;
+  locstats *ls;
+  int i;
+
+  Context;
+  putlog(LOG_MISC, "*", "Stats.mod: Resetting %s statistics...", (range == S_WEEKLY) ? "weekly" : "monthly");
+  for (gs = sdata; gs; gs = gs->next) {
+    gs->peak[range] = 0;
+    for (ls = gs->local; ls; ls = ls->next) {
+      for (i = 0; i < TOTAL_TYPES; i++)
+        ls->values[range][i] = 0;
+    }
+  }
+  Context;
+}
+
+static void resetlocstats(locstats *ls)
+{
+  int i;
+
+  if (!ls) {
+    debug0("ERROR! resetlocstats called with NULL pointer!");
+    return;
+  }
+  for (i = 0; i < TOTAL_TYPES; i++) {
+    ls->values[S_TOTAL][i] = 0;
+    ls->values[S_TODAY][i] = 0;
+    ls->values[S_WEEKLY][i] = 0;
+    ls->values[S_MONTHLY][i] = 0;
+  }
+  return;
+}
+
+static void calcwordstats(locstats *stats, char *text)
+{
+  char *word;
+  int i;
+
+  Context;
+  Assert(stats);
+  if (!log_wordstats)
+    return;
+  for (i = 0; i < strlen(text); i++)
+    if (strchr("!?.,\"<>&\\", text[i]))
+      text[i] = ' ';
+  while (text[0]) {
+    word = newsplit(&text);
+    strlower(word);
+    incrwordstats(stats, word, 1, 0);
+  }
+}
+
+// add another entry to the tree
+static void incrwordstats(locstats *ls, char *word, int value, int set)
+{
+  wordstats *ne, *te, *le;
+  wordstats *ll;
+  int cmp;
+
+  Context;
+  if ((word[0] == ' ') || !word[0])
+    return;
+  if (min_word_length && (strlen(word) < min_word_length))
+    return;     /* only log words that are longer than min_word_length chars */
+  if (!ls) {
+    return;
+  }
+  // at first, check if it already exists and only needs to be updated
+  te = ls->tree;
+  le = NULL;
+  while (te) {
+    if (!(cmp = strcasecmp(te->word, word)))
+      break;
+    le = te;
+    if (cmp < 0)
+      te = te->left;
+    else
+      te = te->right;
+  }
+  if (!te) { // nothing to update, so let's append a new node
+    ne = nmalloc(sizeof(struct stats_words));
+    ne->word = nmalloc(strlen(word) + 1);
+    strcpy(ne->word, word);
+    ne->nr = 0;
+    ne->left = ne->right = ne->next = NULL;
+    if (!le)  // no last entry -> new entry is going to be the crown
+      ls->tree = ne;
+    else {
+      if (strcasecmp(le->word, word) < 0)  // -1 -> left child
+        le->left = ne;
+      else                                  // 1 -> right child
+        le->right = ne;
+    }
+    // now let's add it also to the linked list (needed for sorting)
+    ll = ls->words;
+    while (ll && ll->next)
+      ll = ll->next;
+    if (ll)
+      ll->next = ne;
+    else
+      ls->words = ne;
+    te = ne;
+  }
+  // now let's set the value
+  if (set)
+    te->nr = value;
+  else
+    te->nr += value;
+}
+
+static void nincrwordstats(globstats *gs, char *word, int value)
+{
+  wordstats *l, *ll;
+
+  for (l = gs->words; l; l = l->next)
+    if (!strcmp(word, l->word))
+      break;
+  if (!l) {
+    l = gs->words;
+    while (l && l->next)
+      l = l->next;
+    ll = nmalloc(sizeof(wordstats));
+    ll->word = nmalloc(strlen(word) + 1);
+    strcpy(ll->word, word);
+    ll->nr = 0;
+    ll->next = NULL;
+    if (l)
+      l->next = ll;
+    else
+      gs->words = ll;
+    l = ll;
+  }
+  l->nr += value;
+}
+
+static time_t glob_lastglobwordstats;
+static void do_globwordstats(globstats *gs)
+{
+  wordstats *l;
+  locstats *ls;
+
+  if (glob_lastglobwordstats == now)
+    return; /* don't recalculate everything if we already did it in this second */
+  debug0("calculating global wordstats");
+  glob_lastglobwordstats = now;
+  for (l = gs->words; l; l = l->next)
+    l->nr = 0;
+  for (ls = gs->local; ls; ls = ls->next)
+    for (l = ls->words; l; l = l->next)
+      nincrwordstats(gs, l->word, l->nr);
+  sortwordstats(NULL, gs);
+}
+
+static void addquote(locstats *stats, char *quote)
+{
+  quotestr *l, *nl;
+
+  Assert(quote);
+  if (!quote_freq)
+    return;
+  Assert(stats);
+  stats->quotefr--;
+  if (stats->quotefr > 0)
+    return;
+  stats->quotefr = quote_freq;
+  l = stats->quotes;
+  while (l && l->next)
+    l = l->next;
+  nl = nmalloc(sizeof(quotestr));
+  nl->next = NULL;
+  nl->quote = nmalloc(strlen(quote) + 1);
+  strcpy(nl->quote, quote);
+  if (l)
+    l->next = nl;
+  else
+    stats->quotes = nl;
+}
+
+static void addtopic(char *channel, char *topic, char *by)
+{
+  topicstr *e, *ne;
+  globstats *gs;
+
+  Context;
+  gs = findglobstats(channel);
+  if (!gs)
+    return;
+  for (e = gs->topics; e; e = e->next)
+    if (!strcasecmp(topic, e->topic))
+      return;
+  e = gs->topics;
+  while (e && e->next)
+    e = e->next;
+  ne = nmalloc(sizeof(topicstr));
+  ne->topic = nmalloc(strlen(topic) + 1);
+  strcpy(ne->topic, topic);
+  ne->by = nmalloc(strlen(by) + 1);
+  strcpy(ne->by, by);
+  ne->when = now;
+  ne->next = NULL;
+  if (e)
+    e->next = ne;
+  else
+    gs->topics = ne;
+}
+
+static void addhost(char *host, globstats *gs)
+{
+  hoststr *e, *ne;
+  char *s;
+
+  if (!gs || !host)
+    return;
+  s = strchr(host, '@');
+  if (s)
+    host = s + 1;
+  if (strcmp(host, "[IP]"))
+    strlower(host);
+  for (e = gs->hosts; e; e = e->next) {
+    if (!strcmp(host, e->host)) {
+      e->nr++;
+      return;
+    }
+  }
+  e = gs->hosts;
+  while (e && e->next)
+    e = e->next;
+  ne = nmalloc(sizeof(hoststr));
+  ne->host = nmalloc(strlen(host) + 1);
+  strcpy(ne->host, host);
+  ne->nr = 1;
+  ne->next = NULL;
+  if (e)
+    e->next = ne;
+  else
+    gs->hosts = ne;
+  return;
+}
+
+static void setword(globstats *gs, char *word)
+{
+  locstats *l;
+  wordstats *w;
+
+  for (l = gs->local; l; l = l->next) {
+    l->word = NULL;
+    for (w = l->words; w; w = w->next) {
+      if (!strcmp(w->word, word)) {
+        l->word = w;
+        break;
+      }
+    }
+  }
+}
+
+static int track_stat_user(char *oldnick, char *newnick)
+{
+  globstats *gs;
+  locstats *ls;
+  struct stats_userlist *u;
+  int found = 0;
+
+  Context;
+  for (gs = sdata; gs; gs = gs->next) {
+    for (ls = gs->local; ls; ls = ls->next) {
+      if (!rfc_casecmp(oldnick, ls->user) && strcmp(newnick, ls->user)) {
+        nfree(ls->user);
+        ls->user = nmalloc(strlen(newnick) + 1);
+        strcpy(ls->user, newnick);
+        // ls->u should still be valid...
+        found = 1;
+        debug3("Transferred stats from %s to %s in %s", oldnick, newnick, gs->chan);
+      }
+    }
+  }
+  for (u = suserlist; u; u = u->next) {
+    if (!rfc_casecmp(oldnick, u->user) && strcmp(newnick, u->user)) {
+      nfree(u->user);
+      u->user = nmalloc(strlen(newnick) + 1);
+      strcpy(u->user, newnick);
+      found = 1;
+      debug2("Changed user name from %s to %s in my local database.", oldnick, newnick);
+    }
+  }
+  if (found)
+    return 1;
+  return 0;
+}
+
+static void check_for_url(char *user, char *chan, char *text)
+{
+  char *p, *url;
+  char *tmp, *tmp2, *t;
+  struct stats_url *e, *ne;
+  globstats *gs;
+  int weiter;
+
+  if (log_urls < 1)
+    return;
+  gs = findglobstats(chan);
+  if (!gs)
+    return;
+  url = p = tmp = tmp2 = t = NULL;
+  if ((p = strstr(text, "http://")))
+    url = newsplit(&p);
+  else if ((p = strstr(text, "ftp://")))
+    url = newsplit(&p);
+  else if (strstr(text, "www.") || strstr(text, ".com") || strstr(text, "ftp.")) {
+    tmp = nmalloc(strlen(text) + 1);
+    strcpy(tmp, text);
+    t = tmp;
+    while (t[0]) {
+      p = newsplit(&t);
+      if (strstr(p, "www.") || strstr(p, ".com") || strstr(text, "ftp.")) {
+        url = p;
+        break;
+      }
+    }
+  }
+  if (!url)
+    return;
+  if (strchr(url, '@')) { /* probably an email address or something similar */
+    if (tmp)
+      nfree(tmp);
+    return;
+  }
+  if (strncasecmp(url, "http://", 7) && strncasecmp(url, "ftp://", 6)) {
+    if (!strncasecmp(url, "ftp.", 4)) {
+      tmp2 = nmalloc(strlen(url) + 6 + 1);
+      strcpy(tmp2, "ftp://");
+      strcpy(tmp2 + 6, url);
+    } else {
+      tmp2 = nmalloc(strlen(url) + 7 + 1);
+      strcpy(tmp2, "http://");
+      strcpy(tmp2 + 7, url);
+    }
+    url = tmp2;
+  }
+  for (e = gs->urls; e; e = e->next) {
+    if (!strcmp(e->url, url)) {
+      nfree(e->by);
+      e->by = nmalloc(strlen(user) + 1);
+      strcpy(e->by, user);
+      e->when = now;
+      if (tmp)
+        nfree(tmp);
+      if (tmp2)
+        nfree(tmp2);
+      return;
+    }
+  }
+  weiter = 1;
+  for (p = url; (p != url) && weiter; p--) {
+    if (strchr(".!?,#", p[0]))
+      p[0] = 0;
+    else
+      weiter = 0;
+  }
+  for (e = gs->urls; e && e->next; e = e->next);
+  ne = nmalloc(sizeof(struct stats_url));
+  ne->url = nmalloc(strlen(url) + 1);
+  strcpy(ne->url, url);
+  ne->by = nmalloc(strlen(user) + 1);
+  strcpy(ne->by, user);
+  ne->when = now;
+  ne->next = NULL;
+  if (e)
+    e->next = ne;
+  else
+    gs->urls = ne;
+  if (tmp)
+    nfree(tmp);
+  if (tmp2)
+    nfree(tmp2);
+  debug2("Logged URL: \"%s\" mentioned by %s.", ne->url, ne->by);
+}
+
+static void add_chanlog(globstats *gs, char *nick, char *text, int type)
+{
+  char ts[20];
+  time_t tt, ttbuf;
+  quotestr *newlog, *l;
+
+  if (!gs || (kick_context < 1))
+    return;
+  Assert(nick);
+  Assert(text);
+  ttbuf = tt = now;
+  strftime(ts, 19, "[%H:%M:%S]", localtime(&tt));
+  newlog = nmalloc(sizeof(quotestr));
+  newlog->next = NULL;
+  if (type == SL_PRIVMSG) {
+    newlog->quote = nmalloc(strlen(nick) + strlen(ts) + strlen(text) + 5);
+    sprintf(newlog->quote, "%s <%s> %s", ts, nick, text);
+  } else if (type == SL_KICK) {
+    newlog->quote = nmalloc(strlen(text) + strlen(ts) + 2);
+    sprintf(newlog->quote, "%s %s", ts, text);
+  } else if (type == SL_MODE) {
+    newlog->quote = nmalloc(strlen(ts) + strlen(text) + 2);
+    sprintf(newlog->quote, "%s %s", ts, text);
+  } else if (type == SL_NICK) {
+    newlog->quote = nmalloc(strlen(ts) + strlen(nick) + strlen(text) + 19);
+    sprintf(newlog->quote, "%s %s changed nick to %s", ts, nick, text);
+  } else if (type == SL_PART) {
+    newlog->quote = nmalloc(strlen(ts) + strlen(nick) + strlen(gs->chan) + 12);
+    sprintf(newlog->quote, "%s %s has left %s", ts, nick, gs->chan);
+  } else if (type == SL_JOIN) {
+    newlog->quote = nmalloc(strlen(ts) + strlen(nick) + strlen(gs->chan) + 14);
+    sprintf(newlog->quote, "%s %s has joined %s", ts, nick, gs->chan);
+  } else if (type == SL_QUIT) {
+    newlog->quote = nmalloc(strlen(ts) + strlen(nick) + strlen(text) + 18);
+    sprintf(newlog->quote, "%s %s has quit IRC (%s)", ts, nick, text);
+  } else {
+    debug1("Unknown log-type: %d !!!", type);
+    newlog->quote = nmalloc(1);
+    newlog->quote[0] = 0;
+  }
+  if (gs->lastlog)
+    gs->lastlog->next = newlog;
+  else
+    gs->log = newlog;
+  gs->lastlog = newlog;
+  gs->log_length++;
+  while ((gs->log_length > kick_context) && (gs->log_length > 0)) {
+    l = gs->log->next;
+    Assert(gs->log->quote);
+    nfree(gs->log->quote);
+    if (gs->lastlog == gs->log)
+      gs->lastlog = NULL;
+    nfree(gs->log);
+    gs->log = l;
+    gs->log_length--;
+  }
+  ctime(&ttbuf); /* workaround for a bug in older eggdrops */
+}
+
+static void save_kick(globstats *gs, char *kick)
+{
+  struct stats_kick *k, *nk;
+  quotestr *log, *l, *nl;
+
+  Context;
+  if (!gs)
+    return;
+  for (k = gs->kicks; k && k->next; k = k->next);
+  nk = nmalloc(sizeof(struct stats_kick));
+  nk->next = NULL;
+  nk->log = NULL;
+  if (!gs->log || (kick_context < 1)) {
+    nl = nmalloc(sizeof(quotestr));
+    nl->quote = nmalloc(strlen(kick) + 1);
+    strcpy(nl->quote, kick);
+    nl->next = NULL;
+    nk->log = nl;
+  } else {
+    for (log = gs->log; log; log = log->next) {
+      nl = nmalloc(sizeof(quotestr));
+      nl->quote = nmalloc(strlen(log->quote) + 1);
+      strcpy(nl->quote, log->quote);
+      nl->next = NULL;
+      for (l = nk->log; l && l->next; l = l->next);
+      if (l)
+        l->next = nl;
+      else
+        nk->log = nl;
+    }
+  }
+  if (k)
+    k->next = nk;
+  else
+    gs->kicks = nk;
+  debug1("Logged kick in %s.", gs->chan);
+}
+
+static int getplace(globstats *gs, int today, int itype, char *user)
+{
+  locstats *ls;
+  int place = 0;
+
+  // if itype is < 0, get the modified itype that we need for accessing the data
+  if (itype < 0)
+    itype = (TOTAL_TYPES - 1) + (itype * -1);
+  for (ls = gs->slocal[today][itype]; ls; ls = ls->snext[today][itype]) {
+    if (!listsuser(ls, gs->chan))
+      continue;
+    place++;
+    if (!rfc_casecmp(ls->user, user))
+      return place;
+  }
+  return 0;
+}
+
+static int countstatmembers(globstats *gs)
+{
+  int members = 0;
+  locstats *ls;
+
+  Context;
+  for (ls = gs->local; ls; ls = ls->next) {
+    if (listsuser(ls, gs->chan))
+      members++;
+  }
+  return members;
+}
+
+static int countallstatmembers(globstats *gs)
+{
+  int members = 0;
+  locstats *ls;
+
+  Context;
+  for (ls = gs->local; ls; ls = ls->next)
+      members++;
+  return members;
+}
+
+/* countactivestatmembers():
+ * counts all active members in a chan (and skips dead entries)
+ */
+static int countactivestatmembers(globstats *gs, int listable, int today, int type, int min)
+{
+  int members = 0;
+  locstats *ls;
+
+  Context;
+  for (ls = gs->local; ls; ls = ls->next) {
+    if (ls->values[today][type] < min)
+      continue;
+    if (listable && !listsuser(ls, gs->chan))
+      continue;
+    members++;
+  }
+  return members;
+}
+
+static int gettotal(globstats *gs, int type, int today)
+{
+  int total = 0;
+  locstats *ls;
+
+  for (ls = gs->local;ls; ls = ls->next) {
+    if (!ls->u)
+      ls->u = findsuser_by_name(ls->user);
+    if (ls->u && !suser_list(ls->u))
+      continue;
+    total += ls->values[today][type];
+  }
+  return total;
+}
+
+static void free_stats()
+{
+  struct stats_global *sl;
+
+  Context;
+  while (sdata) {
+    sl = sdata->next;
+    free_localstats(sdata->local);
+    free_wordstats(sdata->words);
+    free_topics(sdata->topics);
+    free_urls(sdata->urls);
+    free_quotes(sdata->log);
+    free_hosts(sdata->hosts);
+    free_kicks(sdata->kicks);
+    nfree(sdata->chan);
+    nfree(sdata);
+    sdata = sl;
+  }
+  sdata = NULL;
+  llist_free(&schanset);
+//  schans = NULL;
+  free_suserlist(suserlist);
+  suserlist = NULL;
+  slang_glob_free();
+  Context;
+  return;
+}
+
+static void free_localstats(struct stats_local *sl)
+{
+  struct stats_local *sll;
+
+  Context;
+  while (sl) {
+    Context;
+    sll = sl->next;
+    free_wordstats(sl->words);
+    free_quotes(sl->quotes);
+    nfree(sl->user);
+    nfree(sl);
+    sl = sll;
+    Context;
+  }
+  Context;
+  return;
+}
+
+static void free_wordstats(wordstats *l)
+{
+  wordstats *ll;
+
+  Context;
+  while (l) {
+    ll = l->next;
+    nfree(l->word);
+    nfree(l);
+    l = ll;
+  }
+  return;
+}
+
+static void free_quotes(quotestr *l)
+{
+  quotestr *ll;
+
+  Context;
+  while (l) {
+    ll = l->next;
+    nfree(l->quote);
+    nfree(l);
+    l = ll;
+  }
+  return;
+}
+
+static void free_topics(topicstr *e)
+{
+  topicstr *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    nfree(e->topic);
+    nfree(e->by);
+    nfree(e);
+    e = ee;
+  }
+  return;
+}
+
+static void free_urls(struct stats_url *e)
+{
+  struct stats_url *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    nfree(e->url);
+    nfree(e->by);
+    nfree(e);
+    e = ee;
+  }
+  return;
+}
+
+static void free_kicks(struct stats_kick *e)
+{
+  struct stats_kick *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    free_quotes(e->log);
+    nfree(e);
+    e = ee;
+  }
+}
+
+static void free_hosts(hoststr *e)
+{
+  hoststr *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    nfree(e->host);
+    nfree(e);
+    e = ee;
+  }
+  return;
+}
+
+static int userdata_merge(char *sTo, char *sFrom)
+{
+	globstats *gs;
+	locstats *ls;
+	int i, found = 0;
+
+	for (gs = sdata; gs; gs = gs->next) {
+		for (ls = gs->local; ls; ls = ls->next) {
+			if (!strcasecmp(ls->user, sFrom)) {
+				found = 1;
+				for (i = 0; i < TOTAL_TYPES; i++) {
+					incrstats(sTo, gs->chan, i, ls->values[S_TOTAL][i], (S_TOTAL + 1) * (-1));
+					incrstats(sTo, gs->chan, i, ls->values[S_DAILY][i], (S_DAILY + 1) * (-1));
+					incrstats(sTo, gs->chan, i, ls->values[S_WEEKLY][i], (S_WEEKLY + 1) * (-1));
+					incrstats(sTo, gs->chan, i, ls->values[S_MONTHLY][i], (S_MONTHLY + 1) * (-1));
+				}
+				if (getstats(sTo, "", "gstarted", 0) < getstats(sFrom, "", "gstarted", 0))
+					incrstats(sTo, "", T_LSTARTED, getstats(sFrom, "", "gstarted", 0), 1);
+			}
+		}
+	}
+	return found;
+}

+ 227 - 0
core/dynamic_mem_debug.c

@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define DYNAMIC_MEM_DEBUG 1
+
+#ifndef __stdio_h__
+#include <stdio.h>
+#endif
+
+#ifndef __string_h__
+#include <string.h>
+#endif
+
+#include <errno.h>
+
+#ifdef malloc
+#undef malloc
+#endif
+
+#ifdef free
+#undef free
+#endif
+
+#ifdef nmalloc
+#undef nmalloc
+#endif
+
+#define nmalloc(x)	dmd_malloc((x),__FILE__,__LINE__)
+
+#ifdef nrealloc
+#undef nrealloc
+#endif
+
+#define nrealloc(x,y)	dmd_realloc((x),(y),__FILE__,__LINE__)
+
+#ifdef nfree
+#undef nfree
+#endif
+
+#define nfree(x)	dmd_free((x),__FILE__,__LINE__)
+
+#ifndef GENERIC_BINARY_TREE
+#include "generic_binary_tree.c"
+#endif
+
+#define DMD_FILE_SIZE 20
+
+struct dmd_memblock {
+	void *ptr;
+	int size;
+	int line;
+	char file[DMD_FILE_SIZE + 1];
+	int expmem;
+};
+
+static int dmd_compare(void *data1, void *data2);
+static void dmd_freeblock(void *data);
+static void dmd_checkblock(void *data);
+
+static struct generic_binary_tree dmd_tree = {NULL, dmd_compare, NULL, dmd_freeblock};
+static void (*dmd_expmem_func) () = NULL;
+
+static struct dmd_memblock *dmd_create(void *ptr, int size, int line, const char *file)
+{
+	struct dmd_memblock *nb;
+	char *p;
+
+	nb = malloc(sizeof(struct dmd_memblock));
+	nb->ptr = ptr;
+	nb->size = size;
+	nb->line = line;
+	p = strrchr(file, '/');
+	strncpy(nb->file, p ? p + 1 : file, DMD_FILE_SIZE);
+	nb->file[DMD_FILE_SIZE] = 0;
+	return nb;
+}
+
+static void dmd_freeblock(void *data)
+{
+	free((struct dmd_memblock *)data);
+}
+
+static int dmd_compare(void *data1, void *data2)
+{
+	struct dmd_memblock *b1 = (struct dmd_memblock *) data1, *b2 = (struct dmd_memblock *) data2;
+	unsigned long v1, v2;
+
+	v1 = (unsigned long) b1->ptr;
+	v2 = (unsigned long) b2->ptr;
+	if (v1 > v2)
+		return 1;
+	else if (v1 < v2)
+		return -1;
+	else
+		return 0;
+}
+
+static void *dmd_malloc(int size, const char *file, int line)
+{
+	struct dmd_memblock *mb;
+	void *ptr;
+
+//	debug2("malloc %s:%d", file, line);
+	ptr = malloc(size);
+//	debug1("ptr: %u", (unsigned long) ptr);
+	if (!ptr) {
+		putlog(LOG_MISC, "*", "*** DMD: FAILED MALLOC %s (%d) (%d): %s", file, line, size, strerror(errno));
+		fatal("Memory allocation failed", 0);
+	}
+	mb = dmd_create(ptr, size, line, file);
+	if (btree_get(&dmd_tree, mb))
+		putlog(LOG_MISC, "*", "*** DMD: DOUBLED POINTER?!?!?");
+	btree_add(&dmd_tree, (void *) mb);
+	return mb->ptr;
+}
+
+static void *dmd_realloc(void *old, int size, const char *file, int line)
+{
+	struct dmd_memblock *mb, sb;
+	void *p;
+
+	// debug4("realloc %s:%d (%ul -> %i)", file, line,  (unsigned long) old, size);
+	sb.ptr = old;
+	mb = btree_get(&dmd_tree, &sb);
+	if (!mb) {
+		putlog(LOG_MISC, "*", "*** DMD: FAILED REALLOC %s:%d (%d). Old pointer not found. This is probably fatal!", file, line, size);
+		return NULL;
+	}
+	p = mb->ptr;
+	p = realloc(p, size);
+	if (!p) {
+		putlog(LOG_MISC, "*", "*** DMD: FAILED REALLOC %s:%d (%d). realloc() returned NULL", file, line, size);
+//		fatal("Memory re-allocation failed", 0);
+	}
+	if (((unsigned long) p) != ((unsigned long) old)) {
+//		debug0("newpointer");
+		btree_remove(&dmd_tree, mb);
+		mb = dmd_create(p, size, line, file);
+		btree_add(&dmd_tree, (void *) mb);
+	} else {
+//		debug0("oldpointer");
+		mb->size = size;
+/*		p = strrchr(file, '/');
+		strncpy(mb->file, p ? p + 1 : file, DMD_FILE_SIZE);
+		mb->file[DMD_FILE_SIZE] = 0;
+		mb->line = line;*/
+	}
+	return p;
+}
+
+static void dmd_free(void *p, const char *file, int line)
+{
+	struct dmd_memblock *mb, sb;
+
+//	debug2("free %s:%d", file, line);
+	sb.ptr = p;
+	mb = btree_get(&dmd_tree, &sb);
+	if (!mb) {
+		putlog(LOG_MISC, "*", "*** DMD: ATTEMPTING TO FREE NON-MALLOC'D PTR: %s:%d",
+				file, line);
+		return;
+	}
+	btree_remove(&dmd_tree, mb);
+	free(p);
+}
+
+static void dmd_reset(void *data)
+{
+	struct dmd_memblock *b = (struct dmd_memblock *)data;
+
+	b->expmem = -1;
+}
+
+static void dmd_init()
+{
+	btree_freetree(&dmd_tree);
+}
+
+static FILE *dmd_filepointer;
+static int dmd_founderrors;
+static void dmd_expmem()
+{
+	dmd_founderrors = 0;
+	btree_getall(&dmd_tree, dmd_reset);
+	if (dmd_expmem_func)
+		dmd_expmem_func();
+	dmd_filepointer = fopen("MEMDEBUG", "w");
+	btree_getall(&dmd_tree, dmd_checkblock);
+	fclose(dmd_filepointer);
+	if (dmd_founderrors)
+		putlog(LOG_MISC, "*", "*** DMD: %d EXPMEM ERRORS!", dmd_founderrors);
+}
+
+static void dmd_checkblock(void *data)
+{
+	struct dmd_memblock *b = (struct dmd_memblock *)data;
+
+	if (b->expmem == -1) {
+		fprintf(dmd_filepointer, "LEAK      : %d bytes in %s:%d\n", b->size, b->file, b->line);
+		dmd_founderrors++;
+	} else if (b->expmem != b->size) {
+		fprintf(dmd_filepointer, "wrong size: %d bytes allocated, %d bytes expected in %s:%d\n",
+				b->size, b->expmem, b->file, b->line);
+		dmd_founderrors++;
+	}
+}
+
+static void dmd_unload()
+{
+	dmd_expmem();
+	btree_freetree(&dmd_tree);
+}

+ 156 - 0
core/generic_linked_list.c

@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define GENERIC_LINKED_LIST
+
+struct llist_entry
+{
+	struct llist_entry *next;
+	struct llist_entry *last;
+	void *data;
+};
+
+struct llist_header
+{
+	struct llist_entry *root;
+	struct llist_entry *last;
+
+	int size;
+	int (*comparedata) (void *, void *);
+	int (*expmemdata) (void *);
+	void (*freedata) (void *);
+};
+
+static void llist_append(struct llist_header *head, void *data)
+{
+	struct llist_entry *p, *np;
+
+	np = nmalloc(sizeof(struct llist_entry));
+
+	np->data = NULL;
+	np->next = NULL;
+	np->last = NULL;
+
+	np->data = data;
+	for (p = head->root; p && p->next; p = p->next);
+	if (p) {
+		p->next = np;
+		np->last = p;
+	} else
+		head->root = np;
+	head->size++;
+}
+
+static void *llist_find(struct llist_header *head, void *key)
+{
+	struct llist_entry *p;
+
+	for (p = head->root; p; p = p->next)
+		if (head->comparedata(p->data, key))
+			return p->data;
+	return NULL;
+}
+
+static void llist_delete(struct llist_header *head, void *key)
+{
+	struct llist_entry *p, *last;
+
+	p = head->root;
+	last = NULL;
+	while (p) {
+		if (head->comparedata(p->data, key)) {
+			if (last)
+				last->next = p->next;
+			else
+				head->root = p->next;
+			head->freedata(p->data);
+			if (p->next)
+				p->next->last = last;
+			/* make sure that the getfirst/getnext-loop can
+			 * continue if we are deleting the currently active
+			 * item. If last is NULL, then getnext() will continue
+			 * with the root which should be the correct successor
+			 * of p in this case. */
+			if (head->last == p)
+				head->last = last;
+			nfree(p);
+			if (last)
+				p = last->next;
+			else
+				p = head->root;
+			head->size--;
+		} else {
+			last = p;
+			p = p->next;
+		}
+	}
+}
+
+static int llist_expmem(struct llist_header *head)
+{
+	struct llist_entry *p;
+	int size = 0;
+
+	for (p = head->root; p; p = p->next) {
+		size += sizeof(struct llist_entry);
+
+		size += head->expmemdata(p->data);
+	}
+	return size;
+}
+
+static void llist_free(struct llist_header *head)
+{
+	struct llist_entry *p, *n;
+
+	p = head->root;
+	while (p) {
+		n = p->next;
+		head->freedata(p->data);
+		nfree(p);
+		p = n;
+	}
+	head->root = NULL;
+	head->size = 0;
+}
+
+static struct llist_entry *llist_getfirst(struct llist_header *head)
+{
+	Assert(head);
+	head->last = head->root;
+	if (head->last)
+		return head->last->data;
+	else
+		return NULL;
+}
+
+static struct llist_entry *llist_getnext(struct llist_header *head)
+{
+	/* if head->last exists, then we can just proceed to the next
+	 * entry. If it does not exist, then it was the first entry in
+	 * the chain and got deleted while we were looping, so we can
+	 * simply proceed with the root which should be the next item. */
+	if (head->last)
+		head->last = head->last->next;
+	else
+		head->last = head->root;
+	if (head->last)
+		return head->last->data;
+	else
+		return NULL;
+}

+ 85 - 0
core/global_vars.c

@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static globstats *glob_globstats;
+static locstats *glob_locstats;
+static int glob_timerange, glob_sorting, glob_place, glob_cl_timerange;
+static int glob_au_percent, glob_wordplace, glob_graph_total, glob_range;
+static int glob_loginerror, glob_activity_timerange, glob_activity;
+static int glob_activity_percent, glob_top_start, glob_top_end;
+static char *glob_toptype, *glob_nick, *glob_word;
+static struct stats_url *glob_url;
+static float glob_au_users, glob_graph_percent, glob_graph_width_percent;
+static hoststr *glob_isp, *glob_tld;
+static topicstr *glob_topic;
+static struct stats_kick *glob_kick;
+static struct stats_quote *glob_kick_context;
+static wordstats *glob_wordstats;
+#ifndef NO_EGG
+//static memberlist *glob_chanmember;
+#endif
+static struct stats_member *glob_statsmember;
+static struct slang_header *glob_slang;
+static struct template_skin *glob_skin;
+static struct stats_userlist *glob_user;
+
+
+static void init_global_vars()
+{
+//  slang_text_buffer = 0;
+}
+
+static void reset_global_vars()
+{
+  glob_globstats = NULL;
+  glob_locstats = NULL;
+  glob_toptype = NULL;
+  glob_timerange = T_ERROR;
+  glob_sorting = T_ERROR;
+  glob_place = 0;
+  glob_au_users = 0.0;
+  glob_au_percent = 0;
+  glob_url = NULL;
+  glob_isp = glob_tld = NULL;
+  glob_topic = NULL;
+  glob_kick = NULL;
+  glob_kick_context = NULL;
+  glob_wordstats = NULL;
+  glob_wordplace = 0;
+#ifndef NO_EGG
+//  glob_chanmember = NULL;
+#endif
+  glob_statsmember = NULL;
+  glob_graph_percent = 0.0;
+  glob_graph_width_percent = 0.0;
+  glob_slang = NULL;
+  glob_nick = NULL;
+  glob_range = 0;
+  glob_word = NULL;
+  glob_loginerror = 0;
+  glob_user = NULL;
+  glob_activity_timerange = glob_activity = glob_activity_percent = 0;
+  glob_cl_timerange = 0;
+  glob_top_start = 1;
+  glob_top_end = webnr;
+}
+
+static void free_global_vars()
+{
+  Context;
+}

+ 335 - 0
core/http_processing.c

@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Don't know a better place for these defines... */
+#define SLE_USERNOTFOUND 1
+#define SLE_NOUSERPASS 2
+#define SLE_WRONGPASS 3
+
+/* send_webseen():
+ * take the URL string, split the parameters off,
+ * calculate seen-results if necessary, and finally
+ * send a template to the client
+ */
+static void process_get_request(int idx)
+{
+  char *url, urlbuf[512], *newurl, *s_timerange, *s_sorting;
+  char *chan, *cmd, *user, *pass, *lchan, *lang, *str_skin;
+  char *email, *homepage, *icqnr, *newpass, *addhosts, *list;
+  char *newpass_confirmation, *nostats, *s_start, *s_end;
+  struct stats_userlist *u;
+  struct llist_1string *langlist;
+
+  Context;
+  // init all global vars
+  reset_global_vars();
+  if (!http_connection(idx)->path) {
+    debug1("%s: no request. Dropping connection.", dcc[idx].host);
+    return;
+  }
+  // copy the url into a buffer, so we can work on it without messing it up
+  strncpy(urlbuf, http_connection(idx)->path, 512);
+  urlbuf[511] = 0;
+  url = urlbuf;
+  // make sure there is a '/' at the end of the URL, or most links will
+  // be broken.
+  if (url[strlen(url) - 1] != '/') {
+    newurl = nmalloc(strlen(url) + 1 + 1);
+    strcpy(newurl, url);
+    strcat(newurl, "/");
+    dprintf(idx, "HTTP/1.1 301 Moved Permanently\nServer: EggdropMiniHTTPd/%s\n", HTTPD_VERSION);
+    dprintf(idx, "Location: %s\nConnection: close\nContent-Type: text/html\n\n", newurl);
+    dprintf(idx, "<HTML><body>The concluding \"/\" is important!<br><center>");
+    dprintf(idx, "<a href=\"%s\">%s</a></center><br>", newurl, newurl);
+    http_connection(idx)->code = 301;
+    nfree(newurl);
+    return;
+  }
+
+  // try to get skin and lang settings from the parameter list
+  // If the parameter is specified, write it into a cookie. If it
+  // is not specified, try to get it from a cookie first, and use the default
+  // if it isn't even defined in a cookie
+  if ((str_skin = get_param_value(idx, "skin")))
+    set_cookie(idx, "skin", str_skin);
+  else if (!(str_skin = get_cookie_value(idx, "skin")))
+    str_skin = default_skin;
+  if (!(glob_skin = templates_skin_find(skins, str_skin))) {
+    if (!(glob_skin = templates_skin_find(skins, default_skin))) {
+      send_http_header(idx, 500);
+      dprintf(idx, "<HTML><BODY><H1>Internal Server Error: No skin found!</H1></BODY></HTML>");
+      return;
+    }
+  }
+
+  if ((lang = get_param_value(idx, "lang")))
+    set_cookie(idx, "lang", lang);
+  else if (!(lang = get_cookie_value(idx, "lang"))) {
+    langlist = http_connection(idx)->langs;
+    while (langlist) {
+      if (slang_valid(glob_skin->slang, langlist->s1)) {
+	lang = langlist->s1;
+	break;
+      }
+      langlist = langlist->next;
+    }
+    if (!lang)
+      lang = default_slang;
+  }
+  if (!(glob_slang = slang_find(glob_skin->slang, lang))) {
+    if (!(glob_slang = slang_find(glob_skin->slang, default_slang))) {
+      send_http_header(idx, 500);
+      dprintf(idx, "<HTML><BODY><H1>Internal Server Error: No language found!</H1></BODY></HTML>");
+      return;
+    }
+  }
+
+  // now it's time to choose what to do
+  if (!strcmp(url, "/")) {
+    // user accessed the server root? ok, send the root template...
+    send_http_header(idx, 200);
+    template_send(glob_skin, "root", idx);
+    return;
+  } else if (!strcasecmp(url, "/cgi-bin/usersettings/")) {
+    user = get_param_value(idx, "username");
+    if (!user) {
+      send_http_header(idx, 200);
+      template_send(glob_skin, "userlogin", idx);
+      return;
+    }
+    u = findsuser_by_name(user);
+    if (!u) {
+      glob_loginerror = SLE_USERNOTFOUND;
+      send_http_header(idx, 200);
+      template_send(glob_skin, "login_error", idx);
+      return;
+    }
+    glob_user = u;
+    if (get_param_value(idx, "sendpass")) {
+      user_email_password(u);
+      send_http_header(idx, 200);
+      template_send(glob_skin, "password_emailed", idx);
+      return;
+	}
+    pass = get_param_value(idx, "password");
+    if (!pass) {
+      send_http_header(idx, 200);
+      template_send(glob_skin, "userlogin", idx);
+      return;
+    }
+    if (!u->password) {
+      glob_loginerror = SLE_NOUSERPASS;
+      send_http_header(idx, 200);
+      template_send(glob_skin, "login_error", idx);
+      return;
+    }
+    if (!(!strcmp(u->password, pass))) {
+      glob_loginerror = SLE_WRONGPASS;
+      send_http_header(idx, 200);
+      template_send(glob_skin, "login_error", idx);
+      return;
+    }
+    icqnr = get_param_value(idx, "icqnr");
+    if (icqnr)
+      u->icqnr = atoi(icqnr);
+    email = get_param_value(idx, "email");
+    if (email) {
+      setemail(u, email);
+    }
+    homepage = get_param_value(idx, "homepage");
+    if (homepage) {
+      sethomepage(u, homepage);
+    }
+    newpass = get_param_value(idx, "newpassword");
+    newpass_confirmation = get_param_value(idx, "newpass_confirmation");
+    if (newpass && newpass[0] && (newpass[0] != ' ')
+        && newpass_confirmation && !strcmp(newpass_confirmation, newpass)) {
+      u->password = nrealloc(u->password, strlen(newpass) + 1);
+      strcpy(u->password, newpass);
+    }
+    list = get_param_value(idx, "list");
+    if (list) {
+      if (atoi(list))
+        suser_setflag(u, S_LIST);
+      else
+        suser_delflag(u, S_LIST);
+    }
+    addhosts = get_param_value(idx, "addhosts");
+    if (addhosts) {
+      if (atoi(addhosts))
+        suser_setflag(u, S_ADDHOSTS);
+      else
+        suser_delflag(u, S_ADDHOSTS);
+    }
+    nostats = get_param_value(idx, "nostats");
+    if (nostats) {
+      if (atoi(nostats)) {
+	suser_setflag(u, S_NOSTATS);
+	suser_delflag(u, S_LIST);
+      } else
+        suser_delflag(u, S_NOSTATS);
+    }
+    send_http_header(idx, 200);
+    template_send(glob_skin, "usersettings", idx);
+    return;
+  } else {
+    // strip the leading '/'
+    url++;
+    // and split the channel from the URL
+    chan = decode_url(csplit(&url, '/'));
+    glob_globstats = findglobstats(chan);
+    if (!glob_globstats) {
+      lchan = nmalloc(strlen(chan) + 1 + 1);
+      lchan[0] = '#';
+      strcpy(lchan + 1, chan);
+      glob_globstats = findglobstats(lchan);
+      nfree(lchan);
+      if (!glob_globstats) {
+        send_http_header(idx, 404);
+        template_send(glob_skin, "404", idx);
+        return;
+      }
+    }
+    cmd = csplit(&url, '/');
+    if (!strcasecmp(cmd, "")) {
+      send_http_header(idx, 200);
+      template_send(glob_skin, "chan", idx);
+      return;
+    } else if (!strcasecmp(cmd, "misc")) {
+      send_http_header(idx, 200);
+      template_send(glob_skin, "misc", idx);
+      return;
+    } else if (!strcasecmp(cmd, "top")) {
+      s_timerange = csplit(&url, '/');
+      s_sorting = csplit(&url, '/');
+      if (!s_sorting[0] && !strcasecmp(s_timerange, "custom")) {
+	// custom top talker list
+	s_timerange = get_param_value(idx, "timerange");
+	s_sorting = get_param_value(idx, "sorting");
+	s_start = get_param_value(idx, "start");
+	s_end = get_param_value(idx, "end");
+	if (s_timerange)
+		glob_timerange = get_timerange(s_timerange);
+	else
+		glob_timerange = S_TOTAL;
+	if (s_sorting)
+		glob_sorting = typetoi(s_sorting);
+	else
+		glob_sorting = T_WORDS;
+	if (s_start)
+		glob_top_start = atoi(s_start);
+	if (s_end)
+		glob_top_end = atoi(s_end);
+	if (!glob_top_start)
+		glob_top_start = 1;
+	if (glob_top_end <= glob_top_start)
+		glob_top_end = glob_top_start + webnr;
+	if (glob_sorting == T_ERROR) {
+	  	debug1("Invalid sorting '%s'. Defaulting to 'words'.", s_sorting);
+		glob_sorting = T_WORDS;
+	      }
+	if (glob_timerange == T_ERROR)
+		glob_sorting = S_TOTAL;
+	glob_toptype = itotype(glob_sorting);
+	sortstats(glob_globstats, glob_sorting, glob_timerange);
+	debug2("sorting: %s (%d)", s_sorting, glob_sorting);
+	send_http_header(idx, 200);
+	template_send(glob_skin, "custom_top", idx);
+	return;
+      }
+      if (!s_sorting[0] || !s_timerange[0]) {
+        // redirect client to full URL if it skipped anything
+        chan = encode_url(glob_globstats->chan);
+        newurl = nmalloc(strlen(chan) + 18 + 1);
+        sprintf(newurl, "/%s/top/total/words/", chan);
+        dprintf(idx, "HTTP/1.1 301 Moved Permanently\nServer: EggdropMiniHTTPd/%s\n", HTTPD_VERSION);
+        dprintf(idx, "Location: %s\nConnection: close\nContent-Type: text/html\n\n", newurl);
+        dprintf(idx, "<HTML><body>The concluding \"/\" is important!<br><center>");
+        dprintf(idx, "<a href=\"%s\">%s</a></center><br>", newurl, newurl);
+        http_connection(idx)->code = 301;
+        nfree(newurl);
+        return;
+      }
+      if (!strcasecmp(s_timerange, "total"))
+        glob_timerange = S_TOTAL;
+      else if (!strcasecmp(s_timerange, "today"))
+        glob_timerange = S_TODAY;
+      else if (!strcasecmp(s_timerange, "weekly"))
+        glob_timerange = S_WEEKLY;
+      else if (!strcasecmp(s_timerange, "monthly"))
+        glob_timerange = S_MONTHLY;
+      else if (!strcasecmp(s_timerange, "daily"))
+        glob_timerange = S_DAILY;
+      else {
+        send_http_header(idx, 404);
+        template_send(glob_skin, "404", idx);
+        return;
+      }
+      Assert(glob_globstats);
+      if (!strcasecmp(s_sorting, "graphs")) {
+        send_http_header(idx, 200);
+        template_send(glob_skin, "graphs", idx);
+        return;
+      }
+      glob_sorting = slangtypetoi(s_sorting);
+      if ((glob_timerange == T_ERROR) || (glob_sorting == T_ERROR)) {
+        debug2("invalid top-parameter \"%s\" or \"%s\"", s_sorting, s_timerange);
+        send_http_header(idx, 404);
+        template_send(glob_skin, "404", idx);
+        return;
+      }
+      glob_top_start = 1;
+      glob_top_end = webnr;
+      sortstats(glob_globstats, glob_sorting, glob_timerange);
+      send_http_header(idx, 200);
+      template_send(glob_skin, "top", idx);
+      return;
+    } else if (!strcasecmp(cmd, "users")) {
+      user = decode_url(csplit(&url, '/'));
+      if (!user[0]) {
+        send_http_header(idx, 200);
+        template_send(glob_skin, "userlist", idx);
+        return;
+      }
+      glob_locstats = findlocstats(glob_globstats->chan, user);
+      if (!glob_locstats) {
+        send_http_header(idx, 404);
+        template_send(glob_skin, "404", idx);
+        return;
+      }
+      if (!glob_locstats->u)
+        glob_locstats->u = findsuser_by_name(glob_locstats->user);
+      glob_user = glob_locstats->u;
+      if (glob_user && suser_nostats(glob_user)) {
+	// don't let anyone access "private" stats
+	send_http_header(idx, 404);
+	template_send(glob_skin, "404", idx);
+	return;
+      }
+      send_http_header(idx, 200);
+      template_send(glob_skin, "user", idx);
+      return;
+    } else if (!strcasecmp(cmd, "onchan")) {
+      send_http_header(idx, 200);
+      template_send(glob_skin, "onchan", idx);
+      return;
+    }
+  }
+  send_http_header(idx, 404);
+  template_send(glob_skin, "404", idx);
+}

+ 120 - 0
core/llists.c

@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct llist_2string {
+  struct llist_2string *next;
+  char *s1;
+  char *s2;
+};
+
+static struct llist_2string *llist_2string_add(struct llist_2string *where, char *s1, char *s2)
+{
+  struct llist_2string *newitem;
+
+  newitem = (struct llist_2string *) nmalloc(sizeof(struct llist_2string));
+  newitem->next = NULL;
+  newitem->s1 = (char *) nmalloc(strlen(s1) + 1);
+  strcpy(newitem->s1, s1);
+  newitem->s2 = (char *) nmalloc(strlen(s2) + 1);
+  strcpy(newitem->s2, s2);
+  newitem->next = where;
+  return newitem;
+}
+
+static int llist_2string_expmem(struct llist_2string *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct llist_2string);
+    size += strlen(what->s1) + 1;
+    size += strlen(what->s2) + 1;
+    what = what->next;
+  }
+  return size;
+}
+
+static void llist_2string_free(struct llist_2string *what)
+{
+  struct llist_2string *next;
+
+  while (what) {
+    next = what->next;
+    nfree(what->s1);
+    nfree(what->s2);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *llist_2string_get_s2(struct llist_2string *where, char *s1)
+{
+  for (; where; where = where->next)
+    if (!strcasecmp(where->s1, s1))
+      return where->s2;
+  return NULL;
+}
+
+/******************************/
+
+struct llist_1string {
+  struct llist_1string *next;
+  char *s1;
+};
+
+static struct llist_1string *llist_1string_add(struct llist_1string *where, char *s1)
+{
+  struct llist_1string *newitem, *target;
+
+  newitem = nmalloc(sizeof(struct llist_1string));
+  newitem->s1 = nmalloc(strlen(s1) + 1);
+  strcpy(newitem->s1, s1);
+  newitem->next = NULL;
+  target = where;
+  while (target && target->next)
+    target = target->next;
+  if (target)
+    target->next = newitem;
+  else
+    return newitem;
+  return where;
+}
+
+static int llist_1string_expmem(struct llist_1string *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct llist_1string);
+    size += strlen(what->s1) + 1;
+    what = what->next;
+  }
+  return size;
+}
+
+static void llist_1string_free(struct llist_1string *what)
+{
+  struct llist_1string *next;
+
+  while (what) {
+    next = what->next;
+    nfree(what->s1);
+    nfree(what);
+    what = next;
+  }
+}

+ 1010 - 0
core/mini_httpd.c

@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* mini_httpd.c
+ *
+ * minimalistic http server for use in eggdrop modules
+ *
+ * Usage:
+ *
+ * add init_httpd() to module_start()
+ * add unload_httpd() to module_close()
+ * add expmem_httpd() to module_expmem()
+ *
+ * call start_httpd(port) to start listening for incoming connections
+ * on the specified port.
+ *
+ * create a function "static void process_get_request(idx);". This function
+ * gets called when someone connects to your server and sends an GET request.
+ * you can access the requested path with http_connection(idx)->path.
+ *
+ * Don't forget to send the http header with send_http_header(int idx, code)
+ * before you start sending the output.
+ *
+ * cookies are stored in http_connection(idx)->cookies, parameters in ->params.
+ * You can also access them via get_cookie_value(idx, cookiename) or
+ * get_param_value(idx, paramname).
+ *
+ * Variables (which you might want to tcl-trace and add to your config file)
+ *
+ * char httpd_ip[21] = "";
+ *      Defines on which vhost httpd will listen for connections.
+ *      If this is set to "", it'll listen on all vhosts.
+ *
+ * static char httpd_log[121] = "";
+ *      Logfile to which http access will be loged (CLF format)
+ *
+ * static char httpd_loglevel[20] = "1";
+ *      Defines to which loglevel access will be logged.
+ *
+ * static char httpd_ignore_msg[256] = "<H1>You are on ignore.</H1>";
+ *      reply which the recipient will see, if he/she is on ignore
+ *
+ * static int max_http_thr = 0;
+ * static int max_http_time = 0;
+ *      simple flood protection. Allows only thr connections in time seconds.
+ *
+ */
+
+static char httpd_ip[21] = "";
+static char httpd_loglevel[21] = "1";
+static char httpd_ignore_msg[256] = "<H1>You are on ignore.</H1>";
+static char httpd_log[121] = "";
+static int max_http_thr = 0;
+static int max_http_time = 0;
+static int http_timeout = 5;
+static int httpd_dcc_index = -1;
+
+static char *httpd_text_buf = NULL;
+
+static struct dcc_table MHTTPD_CON_HTTPD =
+{
+  "HTTPD",
+  DCT_VALIDIDX,
+  eof_http,
+  httpd_accept,
+  0,
+  timeout_listen_httpd,
+  display_httpd_accept,
+  0,
+  NULL,
+  0
+};
+
+static struct dcc_table MHTTPD_CON_HTTP =
+{
+  "HTTP",
+  DCT_VALIDIDX,
+  eof_http,
+  http_activity,
+  &http_timeout,
+  timeout_http,
+  display_http,
+  expmem_http,
+  kill_http,
+#ifdef OLDBOT
+  out_http,
+#else
+  out_http,
+  outdone_http
+#endif
+};
+
+#define http_connection(i) ((struct http_connection_data *) dcc[(i)].u.other)
+
+/* init_httpd()
+ * initializes a few variables
+ */
+static void init_httpd()
+{
+  httpd_text_buf = NULL;
+}
+
+/* expmem_httpd()
+ * expmem function
+ */
+/*
+static int expmem_httpd()
+{
+  int size = 0;
+
+  if (httpd_text_buf)
+    size += strlen(httpd_text_buf) + 1;
+  return size;
+}
+*/
+
+/* unload_httpd():
+ * frees all allocated memory, stops listening and kills all
+ * existing connections.
+ */
+static void unload_httpd()
+{
+  stop_httpd();
+  if (httpd_text_buf)
+    nfree(httpd_text_buf);
+}
+
+/* start_httpd():
+ * starts listening for http connections on the defined port.
+ */
+static void start_httpd(int port)
+{
+  int i, zz;
+  char tmp[50];
+
+  Context;
+  // a little hack to make httpd listen on the defined vhost
+  // (or on all vhosts, if none is defined)
+  // Just set my-ip to the wanted vhost, since open_listen()
+  // uses this var
+  sprintf(tmp, "set my-ip \"%s\";", httpd_ip);
+  do_tcl("httpd-hack-start",
+      "set my-ip-httpd-backup ${my-ip};"
+      "set my-hostname-httpd-backup ${my-hostname};"
+      "set my-hostname \"\"");
+  do_tcl("httpd-hack-setip", tmp);
+  // now get a listening socket
+  zz = open_listen(&port);
+  // don't forget to restore my-ip when we're done ^_^
+  do_tcl("httpd-hack-end",
+      "set my-ip ${my-ip-httpd-backup};"
+      "set my-hostname ${my-hostname-httpd-backup}");
+  // ohoh, we didn't get a socket :(
+  if (zz == (-1)) {
+    putlog(LOG_MISC, "*", "ERROR! Cannot open listening socket for httpd!");
+    return;
+  }
+  // now add this new socket to our dcc table and display a warning,
+  // if the table is full
+  if ((i = new_dcc(&MHTTPD_CON_HTTPD, 0)) == -1) {
+    putlog(LOG_MISC, "*", "ERROR! Cannot open listening socket for httpd! DCC table is full!");
+    // better kill the socket, before we get a "phantom-socket" ^_^
+    killsock(zz);
+    return;
+  }
+  // store the index in a global var, so we can access it easily later...
+  httpd_dcc_index = i;
+  // now fill the dcc-struct with information
+  dcc[i].sock = zz;
+  dcc[i].addr = (IP) (-559026163);
+  dcc[i].port = port;
+  strcpy(dcc[i].nick, "httpd");
+  strcpy(dcc[i].host, "*");
+  dcc[i].timeval = now;
+  putlog(LOG_MISC, "*", "Now listening for http connections on port %d", port);
+}
+
+/* stop_httpd()
+ * kills all httpd connections and listening sockets
+ */
+static void stop_httpd()
+{
+  int i;
+
+  for (i = 0; i < dcc_total; i++) {
+    if (dcc[i].type == &MHTTPD_CON_HTTPD) {
+      putlog(LOG_MISC, "*",
+      	     "no longer listening for http connections on port %d",
+             dcc[i].port);
+      killsock(dcc[i].sock);
+      lostdcc(i);
+    } else if (dcc[i].type == &MHTTPD_CON_HTTP) {
+      putlog(LOG_MISC, "*", "killing http connection from %s", dcc[i].host);
+      killsock(dcc[i].sock);
+      lostdcc(i);
+    }
+  }
+}
+
+/* init_http_connection_data():
+ * inits all variables in our http_connection_data struct
+ */
+static void init_http_connection_data(int idx)
+{
+  http_connection(idx)->traffic = 0;
+  http_connection(idx)->code = -1;
+  http_connection(idx)->browser = NULL;
+  http_connection(idx)->referer = NULL;
+  http_connection(idx)->path = NULL;
+  http_connection(idx)->cmd = NULL;
+  http_connection(idx)->postparams = NULL;
+  http_connection(idx)->cookies = NULL;
+  http_connection(idx)->params = NULL;
+  http_connection(idx)->headers = NULL;
+  http_connection(idx)->langs = NULL;
+  http_connection(idx)->getpostparams = 0;
+  http_connection(idx)->content_length = 0;
+}
+
+/* expmem_http()
+ * expmem's all data allocated to store browser info, referer, cookies, etc...
+ */
+static int expmem_http(void *x)
+{
+  register struct http_connection_data *p = (struct http_connection_data *) x;
+  int tot = 0;
+
+  Context;
+  if (!p) {
+    putlog(LOG_MISC, "*", "Can't expmem clientinfo, no pointer. This should not happen!");
+    return 0;
+  }
+  tot += sizeof(struct http_connection_data);
+  if (p->browser)
+    tot += strlen(p->browser) + 1;
+  if (p->referer)
+    tot += strlen(p->referer) + 1;
+  if (p->path)
+    tot += strlen(p->path) + 1;
+  if (p->cmd)
+    tot += strlen(p->cmd) + 1;
+  if (p->postparams)
+    tot += strlen(p->postparams) + 1;
+  if (p->cookies)
+    tot += llist_2string_expmem(p->cookies);
+  if (p->params)
+    tot += llist_2string_expmem(p->params);
+  if (p->headers)
+    tot += llist_1string_expmem(p->headers);
+  if (p->langs)
+    tot += llist_1string_expmem(p->langs);
+  return tot;
+}
+
+/* free_http_connection_data():
+ * frees all data of our http_connection_data struct
+ */
+static void free_http_connection_data(int idx)
+{
+  if (http_connection(idx)->browser)
+    nfree(http_connection(idx)->browser);
+  if (http_connection(idx)->referer)
+    nfree(http_connection(idx)->referer);
+  if (http_connection(idx)->path)
+    nfree(http_connection(idx)->path);
+  if (http_connection(idx)->cmd)
+    nfree(http_connection(idx)->cmd);
+  if (http_connection(idx)->postparams)
+    nfree(http_connection(idx)->postparams);
+  if (http_connection(idx)->cookies)
+    llist_2string_free(http_connection(idx)->cookies);
+  if (http_connection(idx)->params)
+    llist_2string_free(http_connection(idx)->params);
+  if (http_connection(idx)->headers)
+    llist_1string_free(http_connection(idx)->headers);
+  if (http_connection(idx)->langs)
+    llist_1string_free(http_connection(idx)->langs);
+  n_free(http_connection(idx), __FILE__, __LINE__);
+}
+
+/* http_activity():
+ * handles all the data that the browser sends to us.
+ */
+static void http_activity(int idx, char *buf, int len)
+{
+  char *cmd, *path, *imask, *params;
+#ifdef flush_inbuf
+  int i;
+#endif
+  int lev, content_length;
+  struct timeval t;
+  double pre_time;
+
+  debug2("%s: %s", dcc[idx].host, buf);
+
+  // at first, check if the user is on ignore and therefore should
+  // be ignored
+  imask = nmalloc(strlen(dcc[idx].host) + 11);
+  sprintf(imask, "http!*@%s", dcc[idx].host);
+  if (match_ignore(imask)) {
+    debug1("Ignoring http access from %s", dcc[idx].host);
+    send_http_header(idx, 401);
+    if (httpd_ignore_msg[0])
+      dprintf(idx, "%s", httpd_ignore_msg);
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+    nfree(imask);
+    return;
+  }
+  nfree(imask);
+
+  if ((http_connection(idx)->content_length > 0) && (http_connection(idx)->getpostparams)) {
+    append_postparam_string(idx, buf);
+    return;
+  }
+
+  // then check for recognized commands which we want to be logged
+  // (only GET is supported, at the moment)
+  if ((!strncasecmp(buf, "GET ", 4) || !strncasecmp(buf, "POST ", 5))
+      && !http_connection(idx)->cmd) {
+    http_connection(idx)->cmd = nmalloc(strlen(buf) + 1);
+    strcpy(http_connection(idx)->cmd, buf);
+  }
+  // now check if we know the command and store all info that we need
+  cmd = newsplit(&buf);
+  // GET: request for a document
+  if (!strcasecmp(cmd, "GET") || !strcasecmp(cmd, "POST")) {
+    // at first, check if we're being flooded and kill the connection
+    // if there were too many requests.
+    if (http_flood()) {
+      http_connection(idx)->code = 401;
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+      return;
+    }
+//    if (!strcasecmp(cmd, "POST"))
+//      http_connection(idx)->getpostparams = 1;
+    // now log the access
+    Assert(http_connection(idx)->cmd);
+    lev = logmodes(httpd_loglevel);
+    if (lev)
+      putlog(lev, "*", "%s: %s", dcc[idx].host, http_connection(idx)->cmd);
+    // and finally store the request, if there wasn't already one before.
+    if (!http_connection(idx)->path) {
+      params = newsplit(&buf);
+      path = csplit(&params, '?');
+      // cut the parameters off and store them
+      add_params(idx, params);
+      http_connection(idx)->path = nmalloc(strlen(path) + 1);
+      strcpy(http_connection(idx)->path, path);
+    }
+  // user-agent: browser-information
+  } else if (!strcasecmp(cmd, "User-Agent:")) {
+    if (http_connection(idx)->browser)
+      return;
+    http_connection(idx)->browser = nmalloc(strlen(buf) + 1);
+    strcpy(http_connection(idx)->browser, buf);
+  } else if (!strcasecmp(cmd, "Referer:")) {
+    if (http_connection(idx)->referer)
+      return;
+    http_connection(idx)->referer = nmalloc(strlen(buf) + 1);
+    strcpy(http_connection(idx)->referer, buf);
+  } else if (!strcasecmp(cmd, "Cookie:")) {
+    add_cookies(idx, buf);
+  } else if (!strcasecmp(cmd, "Content-Length:") && !http_connection(idx)->content_length) {
+    content_length = atoi(buf);
+    debug1("setting content length to %d", content_length);
+    http_connection(idx)->content_length = content_length;
+  } else if (!strcasecmp(cmd, "Accept-language:")) {
+    add_language(idx, buf);
+  } else if (!buf[0]) {
+    if (http_connection(idx)->cmd && !(!strncasecmp(http_connection(idx)->cmd, "POST ", 5))) {
+      debug0("now sending...");
+      gettimeofday(&t, NULL);
+      pre_time = (float) t.tv_sec + (((float) t.tv_usec) / 1000000);
+      process_get_request(idx);
+      gettimeofday(&t, NULL);
+      debug1("Processing time: %.3f", ((float) t.tv_sec + (((float) t.tv_usec) / 1000000)) - pre_time);
+      dcc[idx].status = 1;
+#ifndef OLDBOT
+      /* If there's no data in our socket, we immediately get rid of it.
+       */
+      if (!sock_has_data(SOCK_DATA_OUTGOING, dcc[idx].sock)) {
+        killsock(dcc[idx].sock);
+        lostdcc(idx);
+      }
+#endif
+    } else {
+      debug0("waiting for post params...");
+      http_connection(idx)->getpostparams = 1;
+#ifdef sockoptions
+      i = sockoptions(dcc[idx].sock, EGG_OPTION_UNSET, SOCK_BUFFER);
+      if (i)
+        debug1("WARNING: sockoptions returned %d while trying to deativate "
+               "buffering for POST parameters!", i);
+#endif
+#ifdef flush_inbuf
+      flush_inbuf(idx);
+#endif
+    }
+  }
+}
+
+/* add_cookies()
+ * simple function to add one or more cookies to the cookie-list
+ */
+static void add_cookies(int idx, char *buf)
+{
+  char *cookie, *name, *value;
+
+  while (buf[0]) {
+    cookie = csplit(&buf, ';');
+    while (cookie[0] == ' ')
+      cookie++;
+    name = csplit(&cookie, '=');
+    value = cookie;
+    http_connection(idx)->cookies
+               = llist_2string_add(http_connection(idx)->cookies, name, value);
+  }
+}
+
+static char *get_cookie_value(int idx, char *name)
+{
+  Assert(idx >= 0);
+  return llist_2string_get_s2(http_connection(idx)->cookies, name);
+}
+
+/* add_params():
+ * extracts all parameters from the URL and stores them
+ * in a simple linked list
+ */
+static void add_params(int idx, char *buf)
+{
+  char *param, *name, *value;
+
+  if (strchr(buf, '?')) {
+    debug1("WARNING: '?' found in paramstring '%s'. This should have been "
+           "already split!", buf);
+    return;
+  }
+
+  while (buf[0]) {
+    param = csplit(&buf, '&');
+    name = csplit(&param, '=');
+    value = decode_url(param);
+    debug2("adding parameter: '%s'='%s'", name, value);
+    http_connection(idx)->params
+               = llist_2string_add(http_connection(idx)->params, name, value);
+  }
+}
+
+static char *get_param_value(int idx, char *name)
+{
+  Assert(idx >= 0);
+  return llist_2string_get_s2(http_connection(idx)->params, name);
+}
+
+static void add_language(int idx, char *buf)
+{
+  char *lang;
+
+  if (buf)
+    buf = csplit(&buf, ';'); /* strip "; q=1.5", whatever it means... */
+  while (buf[0]) {
+    lang = csplit(&buf, ',');
+    lang = csplit(&lang, '-'); /* en-us => en */
+    while (lang[0] == ' ')
+      lang++;
+    debug1("adding accepted language: '%s'", lang);
+    http_connection(idx)->langs =
+    	llist_1string_add(http_connection(idx)->langs, lang);
+  }
+}
+
+#ifndef OLDBOT
+static void outdone_http(int idx)
+{
+  if (dcc[idx].status) {
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+  } else
+    dcc[idx].status = 1;
+}
+#endif
+
+static void display_http(int idx, char *buf)
+{
+  sprintf(buf, "http connection");
+}
+
+static void display_httpd_accept(int idx, char *buf)
+{
+  sprintf(buf, "httpd");
+}
+
+static void timeout_http(int idx)
+{
+#ifdef flush_inbuf
+  if (http_connection(idx)->getpostparams && http_connection(idx)->path) {
+    // If there's still something in the inbuffer, then we might still be receivng
+    // POST parameters or something. Just let the connection live a bit longer.
+    // (FIXME: DOSable by flooding with body)
+    if (flush_inbuf(idx) > 0) {
+      debug0("inbuf not empty on timeout. Flushed...");
+      dcc[idx].timeval = now;
+      return;
+    }
+  }
+#endif
+  send_http_header(idx, 408);
+  dprintf(idx, "<html>\n<head><title>408 Request Time-out</title></head>\n"
+               "<body>\n<H1>Request Time-out</H1><br>\n<p>Your browser didn't "
+               "send enough information to process the request within %d "
+               "seconds.</p>\n", http_timeout);
+#ifndef flush_inbuf
+  dprintf(idx, "<p>If your browser did send the information, then the problem "
+  		"is probably that this server doesn't have the netstuff patch "
+  		"installed. Please ask the admin to install it. This "
+  		"patch is needed to receive login-information with browsers "
+  		"as Netscape Navigator, Opera or some others. (That's not a "
+  		"bug in the browser, but a missing network-related function "
+  		"in eggdrop which gets added by the patch)</p>\n");
+#endif
+  dprintf(idx, "</body>\n</html>\n");
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
+/* kill_http():
+ * This function called when connection is killed. It
+ * logs the connection to the logfile, if one exists.
+ */
+static void kill_http(int idx, void *x)
+{
+  char ts[41], test[11];
+  time_t tt;
+  FILE *f;
+
+  Context;
+  tt = now;
+  ctime(&tt);
+  /* 05/Feb/2000:12:08:17 +0100 */
+  strftime(test, 19, "%z", localtime(&tt));
+  // if test contains 'z' then strftime() doesn't support
+  // %z (timezone-offset) on this system, and we have to
+  // use a config var instead
+  if (test[0] != 'z')
+    strftime(ts, 40, "%d/%b/%Y:%H:%M:%S %z", localtime(&tt));
+  else
+    strftime(ts, 40, "%d/%b/%Y:%H:%M:%S", localtime(&tt));
+  if (httpd_log[0]) {
+    f = fopen(httpd_log, "a");
+    if (f == NULL)
+      putlog(LOG_MISC, "*", "ERROR writing httpd log.");
+    else {
+      if (test[0] != 'z')
+        fprintf(f,
+         "%s - - [%s] \"%s\" %d %d \"%s\" \"%s\"\n", dcc[idx].host, ts,
+         http_connection(idx)->cmd ? http_connection(idx)->cmd : "",
+         http_connection(idx)->code, http_connection(idx)->traffic,
+         http_connection(idx)->referer ? http_connection(idx)->referer : "-",
+         http_connection(idx)->browser ? http_connection(idx)->browser : "");
+      else
+        fprintf(f,
+         "%s - - [%s %+05d] \"%s\" %d %d \"%s\" \"%s\"\n",
+         dcc[idx].host, ts, offset * (-1) * 100,
+         http_connection(idx)->cmd ? http_connection(idx)->cmd : "",
+         http_connection(idx)->code, http_connection(idx)->traffic,
+         http_connection(idx)->referer ? http_connection(idx)->referer : "-",
+         http_connection(idx)->browser ? http_connection(idx)->browser : "");
+      fclose(f);
+    }
+  }
+  // don't forget to free the data when we're done.
+  free_http_connection_data(idx);
+}
+
+/* out_http():
+ * Called when data is sent through the socket. Used to log the
+ * sent traffic.
+ */
+static void out_http(int idx, char *buf, void *x)
+{
+  register struct http_connection_data *p = (struct http_connection_data *) x;
+
+  if (!p) {
+    putlog(LOG_MISC, "*", "No http_connection pointer. This should not happen!");
+    return;
+  }
+  p->traffic += strlen(buf);
+  tputs(dcc[idx].sock, buf, strlen(buf));
+}
+
+/* http_accept():
+ * accepts an incoming http connection
+ */
+static void httpd_accept(int idx, char *buf, int len)
+{
+  unsigned long ip;
+  unsigned short port;
+  int j = 0, sock, i;
+  char s[UHOSTLEN];
+
+  Context;
+  if (dcc_total + 1 >= max_dcc) {
+    j = answer(dcc[idx].sock, s, &ip, &port, 0);
+    if (j != -1) {
+      dprintf(-j, "Sorry, too many connections already.\r\n");
+      killsock(j);
+    }
+    return;
+  }
+  sock = answer(dcc[idx].sock, s, &ip, &port, 0);
+  if (sock < 0) {
+    neterror(s);
+    putlog(LOG_MISC, "*", "HTTPd: Error accepting connection: %s", s);
+    return;
+  }
+  if ((i = new_dcc(&MHTTPD_CON_HTTP, sizeof(struct http_connection_data))) == (-1)) {
+    putlog(LOG_MISC, "*", "Error accepting http connection. DCC table is full.");
+    killsock(sock);
+    return;
+  }
+  dcc[i].sock = sock;
+  dcc[i].addr = ip;
+  dcc[i].port = port;
+  strcpy(dcc[i].nick, "http");
+#ifndef OLDBOT
+  sprintf(s, "%s", iptostr(my_htonl(ip)));
+#else
+  sprintf(s, "%lu.%lu.%lu.%lu", (ip >> 24) & 0xff, (ip >> 16) & 0xff,
+  	  (ip >> 8) & 0xff, ip & 0xff); /* dw */
+#endif
+  strcpy(dcc[i].host, s);
+  dcc[i].timeval = now;
+  dcc[i].status = 0;
+  // init http_connection_data struct
+  init_http_connection_data(i);
+}
+
+
+static void eof_http(int idx)
+{
+  debug0("eof http");
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}
+
+
+static void set_cookie(int idx, char *name, char *value)
+{
+  char tbuf[40], *buf;
+  time_t tt;
+  int len;
+
+  tt = now + 30 * 60 * 60 * 24;
+  strftime(tbuf, sizeof(tbuf), "%a, %d-%b-%Y %H:%M:%S GMT", localtime(&tt));
+  len = 34 + strlen(name) + strlen(value) + strlen(tbuf) + 1;
+  buf = nmalloc(len);
+  snprintf(buf, len, "Set-Cookie: %s=%s; expires=%s; path=/\n", name, value, tbuf);
+  http_connection(idx)->headers = llist_1string_add(http_connection(idx)->headers, buf);
+  nfree(buf);
+}
+
+/* append_postparam_string()
+ * appends this chunk to the buffer that contains the POST parameters.
+ * when the buffer is filled, processing gets started automatically.
+ */
+static void append_postparam_string(int idx, char *buf)
+{
+  if (!http_connection(idx)->getpostparams) {
+    debug2("?!? Tried to append post param string '%s' to connection #%d, "
+           "but this connection doesn't expect any params... probably a bug. :(",
+           buf, idx);
+    return;
+  }
+  if (!http_connection(idx)->postparams) {
+
+    if (!(http_connection(idx)->content_length > 0)) {
+      send_http_header(idx, 400);
+      dprintf(idx, "<html><head><title>400 Bad Request</title></head>"
+                   "<body><H1>Bad Request:</H1> invalid "
+                   "content-length '%d'.</body></html>\n",
+                   http_connection(idx)->content_length);
+      killsock(dcc[idx].sock);
+      lostdcc(idx);
+      return;
+    }
+
+    http_connection(idx)->postparams
+       = nmalloc(http_connection(idx)->content_length + 1);
+    http_connection(idx)->postparams[0] = 0;
+    debug1("allocated %d bytes for params", http_connection(idx)->content_length + 1);
+  }
+
+  debug1("appending content: '%s'", buf);
+  debug1("old: '%s'", http_connection(idx)->postparams);
+
+  strncat(http_connection(idx)->postparams,
+          buf,
+          http_connection(idx)->content_length);
+  http_connection(idx)->postparams[http_connection(idx)->content_length] = 0;
+  debug1("new: '%s'", http_connection(idx)->postparams);
+
+  if ((http_connection(idx)->content_length > 0) &&
+       http_connection(idx)->getpostparams &&
+       http_connection(idx)->postparams)
+  {
+    if (strlen(http_connection(idx)->postparams) >= http_connection(idx)->content_length) {
+      debug0("parsing params...");
+      add_params(idx, http_connection(idx)->postparams);
+      process_request(idx);
+    }
+  }
+}
+
+/* send_http_header()
+ * sends the http header
+ */
+static void send_http_header(int idx, int code)
+{
+  struct llist_1string *h;
+
+  if (code == 200)
+    dprintf(idx, "HTTP/1.0 200 OK\n");
+  else if (code == 401)
+    dprintf(idx, "HTTP/1.0 401 Access Forbidden\n");
+  else if (code == 404)
+    dprintf(idx, "HTTP/1.1 404 Not Found\n");
+  else if (code == 500)
+    dprintf(idx, "HTTP/1.1 500 Internal Server Error\n");
+  else
+    dprintf(idx, "HTTP/1.0 %d %d\n", code, code);
+  dprintf(idx, "Server: EggdropMiniHTTPd/%s\n", HTTPD_VERSION);
+  dprintf(idx, "Content-Type: text/html\n");
+  for (h = http_connection(idx)->headers; h; h = h->next) {
+    debug1("Sending additional header: '%s'", h->s1);
+    dprintf(idx, "%s", h->s1);
+  }
+  dprintf(idx, "\n");
+  http_connection(idx)->code = code;
+}
+
+/* process_request():
+ * calls the main processing function process_get_request(), takes the
+ * processing time and tries to kill the socket if everything got already
+ * sent.
+ */
+static void process_request(int idx)
+{
+  struct timeval t;
+  double pre_time;
+
+  Context;
+  Assert(idx >= 0);
+  debug0("now sending...");
+  gettimeofday(&t, NULL);
+  pre_time = (float) t.tv_sec + (((float) t.tv_usec) / 1000000);
+  process_get_request(idx);
+  gettimeofday(&t, NULL);
+  debug1("Processing time: %.3f", ((float) t.tv_sec + (((float) t.tv_usec) / 1000000)) - pre_time);
+  dcc[idx].status = 1;
+#ifndef OLDBOT
+  /* If there's no data in our socket, we immediately get rid of it.
+   */
+  if (!sock_has_data(SOCK_DATA_OUTGOING, dcc[idx].sock)) {
+    killsock(dcc[idx].sock);
+    lostdcc(idx);
+  }
+#endif
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* http_flood()
+ * simple check for floods
+ */
+static int mhttp_time = 0, mhttp_thr = 0;
+static int http_flood()
+{
+  if (!max_http_thr || !max_http_time)
+    return 0;
+  if ((now - mhttp_time) > max_http_time) {
+    mhttp_time = now;
+    mhttp_thr = 0;
+  }
+  mhttp_thr++;
+  if (mhttp_thr > max_http_thr)
+    return 1;
+  return 0;
+}
+
+/* csplit()
+ * basically the same as nsplit, but allows you to define
+ * the divider.
+ */
+static char *csplit(char **rest, char divider)
+{
+  register char *o, *r;
+
+  if (!rest)
+    return *rest = "";
+  o = *rest;
+  while (*o == divider)
+    o++;
+  r = o;
+  while (*o && (*o != divider))
+    o++;
+  if (*o)
+    *o++ = 0;
+  *rest = o;
+  return r;
+}
+
+/* text2html():
+ * replaces all strange chars by html-unicode-codes and removes
+ * stupid color codes */
+static char *text2html(char *text)
+{
+  char *buf, ubuf[8];
+  unsigned char c;
+
+  if (httpd_text_buf)
+  	httpd_text_buf = nrealloc(httpd_text_buf, (strlen(text) * sizeof(char) * 7) + 1);
+  else
+    httpd_text_buf = nmalloc((strlen(text) * sizeof(char) * 7) + 1);
+  buf = httpd_text_buf;
+
+  *buf = 0;
+  while (text[0]) {
+    c = text[0];
+    if (((c >= 97) && (c <= 122)) || ((c >= 65) && (c <= 90))) {
+      *buf = c;
+      buf++;
+    } else if (c == 3) {	/* filter $§%#&-mirc colors! */
+      /* inspired by src/dcc.c */
+      if (isdigit(text[1])) {
+	text++;
+	if (isdigit(text[1]))
+	  text++;
+	if (text[1] == ',') {
+	  text++;
+	  if (isdigit(text[1]))
+	    text++;
+	  if (isdigit(text[1]))
+	    text++;
+	}
+      }
+
+      if (!1) { /* DELETEME!!! */
+      /* from src/dcc.c */
+      if (isdigit(text[1])) {	/* Is the first char a number? */
+	text++;		/* Skip over the ^C and the first digit */
+	if (isdigit(text[1]))
+	  text++;		/* Is this a double digit number? */
+	if (text[1] == ',') {	/* Do we have a background color next? */
+	  if (isdigit(text[2]))
+	    text += 2;	/* Skip over the first background digit */
+	  if (isdigit(text[1]))
+	    text++;		/* Is it a double digit? */
+	}
+      }
+      }
+
+
+    } else if ((c == 2) || (c == 7) || (c == 0x16) || (c == 0x1f)) {
+      /* do nothing, just ignore those $§%#&-codes! */
+      debug0("deleteme (mini_httpd.c, text2html())");
+    } else {
+      snprintf(ubuf, sizeof(ubuf), "&#%d;", (unsigned int) c);
+      strcpy(buf, ubuf);
+      buf += strlen(ubuf);
+    }
+    text++;
+  }
+  *buf = 0;
+  httpd_text_buf = nrealloc(httpd_text_buf, strlen(httpd_text_buf) + 1);
+  return httpd_text_buf;
+}
+
+/* encode_url():
+ * encodes all special characters in an url
+ */
+static char encoded_url_buf[128];
+static char *eu_last_url;
+static char *encode_url(char *url)
+{
+  char *buf;
+  unsigned char c;
+
+  Assert(url);
+  // if we are going to re-encode the same URL again, then
+  // save some CPU time and just return our buffer again
+  // (I guess noone would mess with that buffer, so it _should_
+  //  be save)
+  if (url == eu_last_url)
+    return encoded_url_buf;
+  else
+    eu_last_url = url;
+  buf = encoded_url_buf;
+  while (url[0] && (buf < (encoded_url_buf + 120))) {
+    c = url[0];
+    if (((c >= 97) && (c <= 122)) || ((c >= 65) && (c <= 90))) {
+      buf[0] = c;
+      buf++;
+    } else {
+      buf[0] = '%';
+      buf++;
+      snprintf(buf, 3, "%02x", c);
+      buf += 2;
+    }
+    url++;
+  }
+  buf[0] = 0;
+  return encoded_url_buf;
+}
+
+/* decode_url():
+ * Decodes all special characters(%3F == '?', %21 == '!', etc)
+ * and returns the decoded url
+ */
+static char *decode_url(char *paramurl)
+{
+  char *p, *buf, *url, c, hex[5];
+  long int i;
+
+  Context;
+  // free url-buffer (global var)
+  if (httpd_text_buf)
+    nfree(httpd_text_buf);
+  // initialize url-buffer
+  httpd_text_buf = nmalloc(1);
+  httpd_text_buf[0] = 0;
+  // copy parameter into a buffer
+  buf = nmalloc(strlen(paramurl) + 1);
+  strcpy(buf, paramurl);
+  url = buf;
+  // now loop to get every encoded char
+  while ((p = strchr(url, '%'))) {
+    // '%' marks the beginning of an encoded char, so cut the string here.
+    p[0] = 0;
+    // append the first part of the string to our buffer
+    httpd_text_buf = nrealloc(httpd_text_buf, strlen(httpd_text_buf) + strlen(url) + 1);
+    strcat(httpd_text_buf, url);
+    // set the pointer to the beginning of the next string
+    url = p + 1;
+    // first check if there are enough chars left to decode
+    if (strlen(url) >= 2) {
+      // the number behind '%' is a hex-number which is the ASCII code of
+      // the char, so dump the hex into a string and scan it
+      snprintf(hex, 5, "0x%c%c", p[1], p[2]);
+      i = strtol(hex, NULL, 0);
+      if (!i) {
+        i = '?';
+        debug0("MiniHTTPd: decode_url(): i is 0");
+      }
+      c = (char) i;
+      // now append the decoded char to the url
+      httpd_text_buf = nrealloc(httpd_text_buf, strlen(httpd_text_buf) + 1 + 1);
+      sprintf(httpd_text_buf, "%s%c", httpd_text_buf, c);
+      // increase the pointer to abandon the encoded char
+      url += 2;
+    } else {
+      // just append the original '%' if there weren't enough chars to decode
+      httpd_text_buf = nrealloc(httpd_text_buf, strlen(httpd_text_buf) + 1 + 1);
+      strcat(httpd_text_buf, "%");
+    }
+  }
+  // finally append the rest of the param to our buffer. There are no encoded
+  // chars left.
+  httpd_text_buf = nrealloc(httpd_text_buf, strlen(httpd_text_buf) + strlen(url) + 1);
+  strcat(httpd_text_buf, url);
+  // free the buffer
+  nfree(buf);
+  Context;
+  return httpd_text_buf;
+}
+
+static void timeout_listen_httpd(int idx)
+{
+  debug0("timeout httpd listen");
+  killsock(dcc[idx].sock);
+  lostdcc(idx);
+}

+ 71 - 0
core/mini_httpd.h

@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define HTTPD_VERSION "1.1.0"
+
+static void process_get_request(int);
+
+struct http_connection_data {
+  int traffic;
+  int code;
+  char *browser;
+  char *referer;
+  char *path;
+  char *cmd;
+  char *postparams;
+  int getpostparams;
+  int content_length;
+  struct llist_2string *cookies;
+  struct llist_2string *params;
+  struct llist_1string *headers;
+  struct llist_1string *langs;
+};
+
+static void init_httpd();
+//static int expmem_httpd();
+static void unload_httpd();
+static void start_httpd(int);
+static void stop_httpd();
+static void init_http_connection_data(int);
+static void free_http_connection_data(int);
+static void http_activity(int, char *, int);
+static void send_http_header(int, int);
+static void add_cookies(int, char *);
+static char *get_cookie_value(int, char *);
+static void add_params(int, char *);
+static char *get_param_value(int, char *);
+#ifndef OLDBOT
+static void outdone_http(int);
+#endif
+static void display_http(int, char *);
+static void display_httpd_accept(int, char *);
+static void timeout_http(int);
+static void timeout_listen_httpd(int);
+static void kill_http(int, void *);
+static int expmem_http(void *);
+static void out_http(int, char *, void *);
+static void httpd_accept(int, char *, int);
+static int http_flood();
+static void eof_http(int);
+static char *decode_url(char *);
+static char *encode_url(char *);
+static char *csplit(char **, char);
+static void append_postparam_string(int, char *);
+static void process_request(int);
+static void add_language(int, char *);
+static char *text2html(char *);

+ 17 - 0
core/mini_httpd_misc.c

@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */

+ 18 - 0
core/mini_httpd_net.c

@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+

+ 309 - 0
core/misc.c

@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static char *inverted_csplit(char **rest, char divider)
+{
+  char *p;
+
+  if (!rest)
+    return *rest = "";
+  p = *rest + strlen(*rest) - 1;
+  while ((p[0] != divider) && (p != *rest))
+    p--;
+  p[0] = 0;
+  return p + 1;
+}
+
+/* stolen from tcl_duration in tclmisc.c */
+static char duration_temp[256];
+static char *stats_duration(int seconds, int details)
+{
+  char s[256];
+  time_t sec;
+  int details_shown = 0;
+
+  sec = seconds;
+  s[0] = 0;
+  if (sec < 1) {
+    snprintf(duration_temp, sizeof(duration_temp), "%s", SLSOMETIME);
+    return duration_temp;
+  }
+  if (sec >= 31536000) {
+    sprintf(s, "%d %s ", (int) (sec / 31536000),
+            ((int) (sec / 31536000) > 1) ? SLYEARS : SLYEAR);
+    sec -= (((int) (sec / 31536000)) * 31536000);
+    details_shown++;
+  }
+  if ((sec >= 604800) && (details_shown < details)) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 604800),
+            ((int) (sec / 604800) > 1) ? SLWEEKS : SLWEEK);
+    sec -= (((int) (sec / 604800)) * 604800);
+    details_shown++;
+  }
+  if ((sec >= 86400) && (details_shown < details)) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 86400),
+            ((int) (sec / 86400) > 1) ? SLDAYS : SLDAY);
+    sec -= (((int) (sec / 86400)) * 86400);
+    details_shown++;
+  }
+  if ((sec >= 3600) && (details_shown < details)) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 3600),
+            ((int) (sec / 3600) > 1) ? SLHOURS : SLHOUR);
+    sec -= (((int) (sec / 3600)) * 3600);
+    details_shown++;
+  }
+  if ((sec >= 60) && (details_shown < details)) {
+    sprintf(&s[strlen(s)], "%d %s ", (int) (sec / 60),
+            ((int) (sec / 60) > 1) ? SLMINUTES : SLMINUTE);
+    sec -= (((int) (sec / 60)) * 60);
+    details_shown++;
+  }
+  if ((sec > 0) && (details_shown < details)) {
+    sprintf(&s[strlen(s)], "%d %s", (int) (sec / 1),
+            ((int) (sec / 1) > 1) ? SLSECONDS : SLSECOND);
+  }
+  if (s[strlen(s) - 1] == ' ')
+  	s[strlen(s) - 1]='\0';
+  snprintf(duration_temp, sizeof(duration_temp), "%s", s);
+  return duration_temp;
+}
+
+static int countsmileys(char *text)
+{
+  char buf[512], *pbuf, *smiley, *p;
+  int ismileys = 0;
+
+  sprintf(buf, "%s", smileys);
+  pbuf = buf;
+  while (strlen(pbuf) > 0) {
+    smiley = newsplit(&pbuf);
+    p = strstr(text, smiley);
+    while (p) {
+      ismileys++;
+      p += strlen(smiley);
+      p = strstr(p, smiley);
+    }
+  }
+  return ismileys;
+}
+
+static int countwords(char *buf)
+{
+  int i, words = 1;
+
+  for (i = 0; i < strlen(buf); i++) {
+    if ((buf[i] == ' ') && (buf[i+1] != ' '))
+      words++;
+  }
+  return words;
+}
+
+static int countquestions(char *buf)
+{
+  int i, questions = 0;
+
+  for (i = 0; i < strlen(buf); i++) {
+    if ((buf[i] == '?') && (buf[i+1] != '?'))
+      questions++;
+  }
+  return questions;
+}
+
+// void lower(char*p){for(;*p=tolower(*p);p++);}
+
+static void strlower(char *text)
+{
+  int i;
+
+  for (i = 0; i < strlen(text); i++)
+    text[i] = tolower(text[i]);
+}
+
+static int gethour()
+{
+  char ts[10];
+  time_t tt;
+
+  tt = now;
+  strftime(ts, 9, "%H", localtime(&tt));
+  ts[9] = 0;
+  return atoi(ts);
+}
+
+static int getmonth()
+{
+  char ts[10];
+  time_t tt;
+
+  tt = now;
+  strftime(ts, 9, "%m", localtime(&tt));
+  ts[9] = 0;
+  return atoi(ts);
+}
+
+static int ismonday()
+{
+  char ts[10];
+  time_t tt;
+
+  tt = now;
+  strftime(ts, 9, "%a", localtime(&tt));
+  ts[9] = 0;
+  return (!strcasecmp(ts, "mon"));
+}
+
+/* maskstricthost():
+ * basically the same as maskhost() from src/misc.c, but _never_ stripts
+ * "~+-^=" off the host
+ * maskhost() version: * $Id: misc.c,v 1.30 2000/10/27 19:27:32 fabian Exp $
+ */
+static void maskstricthost(const char *s, char *nw)
+{
+  register const char *p, *q, *e, *f;
+  int i;
+
+  *nw++ = '*';
+  *nw++ = '!';
+  p = (q = strchr(s, '!')) ? q + 1 : s;
+  /* Strip of any nick, if a username is found, use last 8 chars */
+  if ((q = strchr(p, '@'))) {
+    int fl = 0;
+
+    if ((q - p) > 9) {
+      nw[0] = '*';
+      p = q - 7;
+      i = 1;
+    } else
+      i = 0;
+    while (*p != '@') {
+      if (!fl && strchr("~+-^=", *p)) {
+//        if (strict_host)
+      nw[i] = '?';
+//    else
+//      i--;
+      } else
+    nw[i] = *p;
+      fl++;
+      p++;
+      i++;
+    }
+    nw[i++] = '@';
+    q++;
+  } else {
+    nw[0] = '*';
+    nw[1] = '@';
+    i = 2;
+    q = s;
+  }
+  nw += i;
+  e = NULL;
+  /* Now q points to the hostname, i point to where to put the mask */
+  if ((!(p = strchr(q, '.')) || !(e = strchr(p + 1, '.'))) && !strchr(q, ':'))
+    /* TLD or 2 part host */
+    strcpy(nw, q);
+  else {
+    if (e == NULL) {        /* IPv6 address?        */
+      const char *mask_str;
+
+      f = strrchr(q, ':');
+      if (strchr(f, '.')) { /* IPv4 wrapped in an IPv6? */
+    f = strrchr(f, '.');
+    mask_str = ".*";
+      } else            /* ... no, true IPv6.       */
+    mask_str = ":*";
+      strncpy(nw, q, f - q);
+      /* No need to nw[f-q] = 0 here, as the strcpy below will
+       * terminate the string for us.
+       */
+      nw += (f - q);
+      strcpy(nw, mask_str);
+    } else {
+      for (f = e; *f; f++);
+      f--;
+      if (*f >= '0' && *f <= '9') { /* Numeric IP address */
+    while (*f != '.')
+      f--;
+    strncpy(nw, q, f - q);
+    /* No need to nw[f-q] = 0 here, as the strcpy below will
+     * terminate the string for us.
+     */
+    nw += (f - q);
+    strcpy(nw, ".*");
+      } else {              /* Normal host >= 3 parts */
+    /*    a.b.c  -> *.b.c
+     *    a.b.c.d ->  *.b.c.d if tld is a country (2 chars)
+     *             OR   *.c.d if tld is com/edu/etc (3 chars)
+     *    a.b.c.d.e -> *.c.d.e   etc
+     */
+    const char *x = strchr(e + 1, '.');
+
+    if (!x)
+      x = p;
+    else if (strchr(x + 1, '.'))
+      x = e;
+    else if (strlen(x) == 3)
+      x = p;
+    else
+      x = e;
+    sprintf(nw, "*%s", x);
+      }
+    }
+  }
+}
+
+/* get_timerange():
+ *
+ */
+static int get_timerange(char *text)
+{
+  if (!strcasecmp(text, "today"))
+    return S_TODAY;
+  else if (!strcasecmp(text, "daily"))
+    return S_TODAY;
+  else if (!strcasecmp(text, "weekly"))
+    return S_WEEKLY;
+  else if (!strcasecmp(text, "monthly"))
+    return S_MONTHLY;
+  else if (!strcasecmp(text, "total"))
+    return S_TOTAL;
+  else
+    return T_ERROR;
+  // FIXME: Check for slanged timeranges!
+}
+
+#define SENDMAIL_ERROR 1
+
+static int email_send(char *to, char *subject, char *body)
+{
+	FILE *f;
+
+	f = popen("/usr/sbin/sendmail -t", "w");
+	if (!f)
+		return SENDMAIL_ERROR;
+	fprintf(f, "To: %s\n", to);
+	fprintf(f, "From: %s\n", botnetnick);
+	fprintf(f, "Subject: %s\n", subject);
+	fprintf(f, "\n");
+	fprintf(f, "%s", body);
+	fprintf(f, "\n.\n");
+	debug0("rückmeldung von pclose testen");
+	if (pclose(f) == -1)
+		return SENDMAIL_ERROR;
+	else
+		return 0;
+}

+ 31 - 0
core/misc.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static char *inverted_csplit(char **rest, char divider);
+static char *stats_duration(int seconds, int details);
+static int countsmileys(char *text);
+static int countsmileys(char *text);
+static int countwords(char *buf);
+static int countquestions(char *buf);
+static void strlower(char *text);
+static int gethour();
+static int getmonth();
+static int ismonday();
+static void maskstricthost(const char *s, char *nw);
+static int get_timerange(char *text);
+static int email_send(char *to, char *subject, char *body);

+ 42 - 0
core/net.c

@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef GENERIC_LINKED_LIST
+// #include "core/generic_linked_list.c"
+#endif
+
+static int stats_start_listen(int port)
+{
+	int sock;
+	struct sockaddr_in name;
+
+	sock = socket(AF_INET, SOCK_STREAM, 0);
+	if (sock < 0)
+		return 0;
+	name.sin_family = AF_INET;
+	name.sin_addr.s_addr = INADDR_ANY;
+	name.sin_port = port;
+	if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) {
+		closesocket(sock);
+		return 0;
+	}
+
+	listen(sock, 5);
+
+
+}

+ 112 - 0
core/schan.c

@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+//static struct stats_chanset *schans = NULL;
+
+/*schanset.root = NULL;
+schanset.compare = schan_compare;
+schanset.expmem = schan_expmem;
+schanset.free = schan_free;*/
+
+static int schan_compare(void *data, void *key)
+{
+	if (!rfc_casecmp(((struct stats_chan *)data)->chan, (char *)key))
+		return 1;
+	else
+		return 0;
+}
+
+static int schan_expmem(void *data)
+{
+	int size = 0;
+
+	size += sizeof(struct stats_chan);
+	size += strlen(((struct stats_chan *) data)->chan) + 1;
+	size += llist_expmem(&(((struct stats_chan *)data)->members));
+	return 0;
+}
+
+static void schan_free(void *data)
+{
+	struct stats_chan *p = (struct stats_chan *) data;
+
+	llist_free(&(p->members));
+	nfree(p->chan);
+	nfree(p);
+}
+
+static struct stats_chan *schan_find(char *name)
+{
+	return (struct stats_chan *) llist_find(&schanset, (void *) name);
+}
+
+
+static struct stats_chan *schan_create(char *name)
+{
+	struct stats_chan *ch;
+
+	ch = nmalloc(sizeof(struct stats_chan));
+	ch->chan = nmalloc(strlen(name) + 1);
+	strcpy(ch->chan, name);
+	schan_members_llist_init(&ch->members);
+	ch->stats = findglobstats(name);
+	if (!ch->stats)
+		ch->stats = globstats_create(name);
+	return ch;
+}
+
+static struct stats_chan *schan_add(char *name)
+{
+	struct stats_chan *ch;
+
+	ch = schan_find(name);
+	if (ch)
+		return ch;
+	ch = schan_create(name);
+	llist_append(&schanset, (void *) ch);
+	return ch;
+}
+
+static void schan_remove(char *name)
+{
+	llist_delete(&schanset, (void *) name);
+}
+
+static struct stats_chan *schan_getfirst()
+{
+	return (struct stats_chan *)llist_getfirst(&schanset);
+}
+
+static struct stats_chan *schan_getnext()
+{
+	return (struct stats_chan *)llist_getnext(&schanset);
+}
+
+static void schan_join(struct stats_chan *chan, char *nick, char *uhost,
+					   char *user)
+{
+	Assert(chan);
+	schan_members_join(&chan->members, nick, uhost, user, chan->chan);
+}
+
+static void schan_leave(struct stats_chan *chan, char *nick)
+{
+	Assert(chan);
+	schan_members_leave(&chan->members, nick);
+}
+

+ 28 - 0
core/schan.h

@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct stats_chan
+{
+	char *chan;
+	struct llist_header members;
+	globstats *stats;
+};
+
+static int schan_compare(void *data, void *key);
+static int schan_expmem(void *data);
+static void schan_free(void *data);

+ 74 - 0
core/schan_interface.c

@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/*
+static void ci_join(char *nick, char *uhost, char *user, char *channel)
+{
+  struct stats_chan *chan;
+
+  chan = schan_find(channel);
+  if (!chan) {
+    chan = schan_add(channel);
+  }
+  schan_join(chan, nick, uhost, user);
+}
+*/
+
+/*
+static void ci_leave(char *nick, char *channel)
+{
+	struct stats_chan *chan;
+
+	chan = schan_find(channel);
+	if (chan)
+	  schan_leave(chan, nick);
+}
+*/
+
+static void ci_clearchan(struct stats_chan *chan)
+{
+	struct stats_member *m;
+
+	Assert(chan);
+	for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members)) {
+		schan_members_leave(&chan->members, m->nick);
+	}
+}
+
+static struct stats_member *getschanmember(char *nick, char *channel)
+{
+	struct stats_chan *chan;
+
+	chan = schan_find(channel);
+	if (chan)
+		return schan_members_find(&chan->members, nick);
+	else
+		return NULL;
+}
+
+/* used when shosts/susers changed */
+static void update_schannel_members()
+{
+	struct stats_chan *chan;
+	struct stats_member *m;
+
+	Context;
+	for (chan = schan_getfirst(); chan; chan = schan_getnext())
+		for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members))
+			schan_members_update(m, chan->chan);
+}

+ 177 - 0
core/schan_members.c

@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int schan_members_compare(void *data, void *key)
+{
+	if (!rfc_casecmp(((struct stats_member *)data)->nick, (char *)key))
+		return 1;
+	return 0;
+}
+
+static int schan_members_expmem(void *data)
+{
+	struct stats_member *p = (struct stats_member *)data;
+	int size = 0;
+
+	size += sizeof(struct stats_member);
+	size += strlen(p->nick) + 1;
+	size += strlen(p->uhost) + 1;
+	return size;
+}
+
+static void schan_members_free(void *data)
+{
+	struct stats_member *p = (struct stats_member *)data;
+
+	nfree(p->nick);
+	nfree(p->uhost);
+	nfree(p);
+}
+
+static void schan_members_llist_init(struct llist_header *head)
+{
+	head->root = NULL;
+	head->size = 0;
+	head->comparedata = schan_members_compare;
+	head->expmemdata = schan_members_expmem;
+	head->freedata = schan_members_free;
+}
+
+static struct stats_member *schan_members_create()
+{
+	struct stats_member *nm;
+
+	nm = nmalloc(sizeof(struct stats_member));
+	nm->nick = NULL;
+	nm->uhost = NULL;
+	nm->joined = 0;
+	nm->last = now;
+	nm->user = NULL;
+	nm->stats = NULL;
+	nm->spoken_lines = 0;
+	return nm;
+}
+
+static void schan_members_join(struct llist_header *head, char *nick, char *uhost, char *user, char *chan)
+{
+	struct stats_member *m;
+	char *host;
+#ifndef NO_EGG
+	struct chanset_t *eggchan;
+#endif
+
+	m = schan_members_create();
+	m->nick = nmalloc(strlen(nick) + 1);
+	strcpy(m->nick, nick);
+	m->uhost = nmalloc(strlen(uhost) + 1);
+	strcpy(m->uhost, uhost);
+	m->joined = now;
+	if (user) {
+		m->user = findsuser_by_name(user);
+		if (!m->user) {
+			m->user = addsuser(user, now, now);
+			debug1("Stats.Mod: Created suserrec for %s.", user);
+		}
+	} else {
+		host = nmalloc(strlen(nick) + 1 + strlen(uhost) + 1);
+		sprintf(host, "%s!%s", nick, uhost);
+		m->user = findsuser(host);
+		nfree(host);
+	}
+	if (m->user) {
+		m->user->laston = now;
+		m->stats = findlocstats(chan, m->user->user);
+		if (!m->stats)
+			m->stats = initstats(chan, m->user->user);
+	}
+#ifndef NO_EGG
+	eggchan = findchan_by_dname(chan);
+	if (chan)
+		m->eggmember = ismember(eggchan, nick);
+	if (!m->eggmember)
+		debug2("Warning[stats.mod]: Couldn't find eggmember for %s in %s.", nick, chan);
+#endif
+	llist_append(head, (void *) m);
+}
+
+static void schan_members_update(struct stats_member *m, char *chan)
+{
+	char *host;
+#ifndef NO_EGG
+	struct userrec *u;
+#endif
+
+	m->user = NULL;
+	host = nmalloc(strlen(m->nick) + 1 + strlen(m->uhost) + 1);
+	sprintf(host, "%s!%s", m->nick, m->uhost);
+#ifndef NO_EGG
+	u = get_user_by_host(host);
+	if (u) {
+		m->user = findsuser_by_name(u->handle);
+		if (!m->user) {
+	  		m->user = addsuser(u->handle, now, now);
+	  		debug1("Stats.Mod: Created suserrec for %s.", u->handle);
+		}
+	} else
+#endif
+		m->user = findsuser(host);
+	nfree(host);
+	if (m->user) {
+		m->stats = findlocstats(chan, m->user->user);
+		if (!m->stats)
+			m->stats = initstats(chan, m->user->user);
+	}
+}
+
+static void schan_members_leave(struct llist_header *head, char *nick)
+{
+	Assert(head);
+	llist_delete(head, (void *)nick);
+}
+
+static void schan_members_rename(struct llist_header *head, char *oldnick, char *newnick)
+{
+	struct stats_member *m;
+
+	m = llist_find(head, (void *)oldnick);
+	if (!m)
+		return;
+	Assert(newnick);
+	m->nick = nrealloc(m->nick, strlen(newnick) + 1);
+	strcpy(m->nick, newnick);
+}
+
+static struct stats_member *schan_members_find(struct llist_header *head, char *nick)
+{
+	return llist_find(head, (void *)nick);
+}
+
+static struct stats_member *schan_members_getfirst(struct llist_header *head)
+{
+	return (struct stats_member *)llist_getfirst(head);
+}
+
+static struct stats_member *schan_members_getnext(struct llist_header *head)
+{
+	return (struct stats_member *)llist_getnext(head);
+}
+
+static int schan_members_count(struct llist_header *head)
+{
+	return head->size;
+}

+ 32 - 0
core/schan_members.h

@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct stats_member {
+  char *nick;
+  char *uhost;
+  time_t joined;
+  time_t last;
+  struct stats_userlist *user;
+  locstats *stats;
+  int spoken_lines;
+  memberlist *eggmember;
+};
+
+static void schan_members_llist_init(struct llist_header *head);
+static void schan_members_join(struct llist_header *head, char *nick, char *uhost, char *user, char *chan);
+static void schan_members_leave(struct llist_header *head, char *nick);

+ 283 - 0
core/sensors.c

@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "../settings.h"
+
+static void sensor_peak(struct stats_chan *chan);
+
+static void sensor_text(struct stats_chan *chan, char *nick, char *text)
+{
+	int i, hour;
+	char buf[511];
+	struct stats_member *m;
+
+	Assert(chan);
+	Assert(chan->stats);
+	if (nostats(chan->chan))
+		return;
+	strncpy(buf, text, 510);
+	buf[510] = 0;
+	text = buf;
+
+	hour = gethour();
+	chan->stats->activity[hour]++;
+	add_chanlog(chan->stats, nick, text, SL_PRIVMSG);
+	m = schan_members_find(&chan->members, nick);
+	if (!m) {
+		check_for_url(nick, chan->chan, text);
+		return;
+	}
+	m->last = now;
+	// increase spoken lines (needed for autoadd)
+	// if there's no link to the stats, call initstats() which either
+	// returns an existing stats struct, or initializes a new one
+	m->spoken_lines++;
+	if (!m->stats && m->user) {
+		m->stats = initstats(chan->chan, m->user->user);
+	}
+	if (!m->stats || !m->user) {
+		check_for_url(nick, chan->chan, text);
+		return;
+	}
+	check_for_url(m->user->user, chan->chan, text);
+	m->stats->lastspoke = now;
+	nincrstats(m->stats, T_WORDS, countwords(text));
+	nincrstats(m->stats, T_LETTERS, strlen(text));
+	nincrstats(m->stats, T_LINES, 1);
+	i = countsmileys(text);
+	if (i)
+		nincrstats(m->stats, T_SMILEYS, i);
+	i = countquestions(text);
+	if (i)
+		nincrstats(m->stats, T_QUESTIONS, i);
+	addquote(m->stats, text);
+	/* always use calcwordstats() at the end, since
+	 * it splits the string */
+	calcwordstats(m->stats, text);
+	return;
+}
+
+static void sensor_topic(char *nick, struct stats_chan *chan, char *topic)
+{
+	struct stats_member *m;
+
+	Context;
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	m = schan_members_find(&chan->members, nick);
+	if (m && m->stats)
+		nincrstats(m->stats, T_TOPICS, 1);
+	addtopic(chan->chan, topic, nick);
+	return;
+}
+
+static void sensor_action(char *nick, struct stats_chan *chan, char *text)
+{
+	char *pbuf;
+	struct stats_member *m;
+
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	m = schan_members_find(&chan->members, nick);
+	if (!m)
+		return;
+	if (!m->stats)
+		return;
+	nincrstats(m->stats, T_ACTIONS, 1);
+	pbuf = nmalloc(strlen(nick) + strlen(text) + 2);
+	sprintf(pbuf, "%s %s", nick, text);
+	sensor_text(chan, nick, pbuf);
+	nfree(pbuf);
+	return;
+}
+
+static void sensor_kick(char *nick, struct stats_chan *chan, char *victim, char *reason)
+{
+	struct stats_member *m;
+	char *buf;
+
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	Assert(chan->stats);
+	buf = nmalloc(strlen(victim) + strlen(nick) + strlen(reason) + 23);
+	sprintf(buf, "*** %s was kicked by %s (%s)", victim, nick, reason);
+	add_chanlog(chan->stats, nick, buf, SL_KICK);
+	save_kick(chan->stats, buf);
+	nfree(buf);
+	m = schan_members_find(&chan->members, nick);
+	if (!m)
+		return;
+	if (!m->stats)
+		return;
+	nincrstats(m->stats, T_KICKS, 1);
+}
+
+static void sensor_mode(char *nick, struct stats_chan *chan, char *mode, char *victim)
+{
+	struct stats_member *m;
+	char *buf;
+
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	Assert(mode);
+	Assert(chan->stats);
+	if (mode[1] != 'k') {
+		// log everything except key changes (you don't want your channel
+		// key displayed on the webpages, do you?
+		buf = nmalloc(strlen(nick) + strlen(mode) + strlen(victim) + 13);
+		sprintf(buf, "%s sets mode %s %s", nick, mode, victim);
+		add_chanlog(chan->stats, nick, buf, SL_MODE);
+		nfree(buf);
+	}
+	m = schan_members_find(&chan->members, nick);
+	if (!m)
+		return;
+	if (!m->stats)
+		return;
+	nincrstats(m->stats, T_MODES, 1);
+	if ((mode[1] == 'b') && (mode[0] == '+'))
+		nincrstats(m->stats, T_BANS, 1);
+}
+
+static void sensor_nick(char *nick, struct stats_chan *chan, char *newnick)
+{
+	struct stats_member *m;
+
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	Assert(chan->stats);
+	add_chanlog(chan->stats, nick, newnick, SL_NICK);
+	m = schan_members_find(&chan->members, nick);
+	if (!m)
+		return;
+	if (!m->stats)
+		return;
+	nincrstats(m->stats, T_NICKS, 1);
+}
+
+static void sensor_join(char *nick, char *uhost, struct stats_chan *chan)
+{
+	struct stats_member *m;
+
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	Assert(chan->stats);
+	add_chanlog(chan->stats, nick, "", SL_JOIN);
+	sensor_peak(chan);
+	m = schan_members_find(&chan->members, nick);
+	if (!m)
+		return;
+	if (!m->stats)
+		return;
+	nincrstats(m->stats, T_JOINS, 1);
+	addhost(uhost, chan->stats);
+}
+
+static void sensor_left(char *nick, struct stats_chan *chan, int type)
+{
+	Assert(chan);
+	if (nostats(chan->chan))
+		return;
+	Assert(chan->stats);
+	add_chanlog(chan->stats, nick, "", type);
+}
+
+/* sensor_minutely():
+ * - increases the spent time for each registered user
+ * - if the time already got increased (if the user has a clone
+ *   in the channel, for example) then it won't be increased again
+ *   (thanks to Zev for this idea)
+ * - if the user is not registered, stats_autosadd() gets called to
+ *   check if we might want to add him/her
+ * - count how many users there are in the chan
+ */
+static void sensor_minutely()
+{
+	struct stats_chan *chan;
+	struct stats_member *m;
+	int nr, hour;
+	globstats *gs;
+
+	Context;
+	for (chan = schan_getfirst(); chan; chan = schan_getnext()) {
+		if (nostats(chan->chan))
+			continue;
+		nr = 0;
+		gs = chan->stats;
+		for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members)) {
+			if (m->stats) {
+				if (m->stats->flag)
+					continue;
+				if (m->user && suser_list(m->user))
+					nr++;
+				nincrstats(m->stats, T_MINUTES, 1);
+				m->stats->flag = 1;
+			} else
+				stats_autosadd(m, chan);
+		}
+		for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members))
+			if (m->stats)
+				m->stats->flag = 0;
+		hour = gethour();
+		if (hour != lasthour) {
+			gs->users[S_USERSUM][hour] = nr;
+			gs->users[S_USERCOUNTS][hour] = 1;
+			lasthour = hour;
+		} else {
+			gs->users[S_USERSUM][hour] += nr;
+			if (gs->users[S_USERCOUNTS][hour] < 0)
+				gs->users[S_USERCOUNTS][hour] = 1;
+			else
+				gs->users[S_USERCOUNTS][hour]++;
+		}
+	}
+}
+
+static void sensor_peak(struct stats_chan *chan)
+{
+	struct stats_member *m;
+	int users = 0;
+	globstats *gs;
+
+	Assert(chan);
+	Assert(chan->stats);
+/*	if (nostats(chan->chan))
+		return; */
+	gs = chan->stats;
+	for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members)) {
+		if (m->user && !suser_list(m->user))
+			continue;
+		users++;
+	}
+	if (users > gs->peak[S_TOTAL]) {
+		gs->peak[S_TOTAL] = users;
+		putlog(LOG_MISC, "*", "New user peak in %s: %d.", chan->chan, users);
+	}
+	if (users > gs->peak[S_TODAY])
+		gs->peak[S_TODAY] = users;
+	if (users > gs->peak[S_WEEKLY])
+		gs->peak[S_WEEKLY] = users;
+	if (users > gs->peak[S_MONTHLY])
+		gs->peak[S_MONTHLY] = users;
+}

+ 364 - 0
core/slang.c

@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static struct slang_header *slang_find(struct slang_header *, char *);
+static int slangtypetoi(char *);
+
+#include "slang_text.c"
+#include "slang_multitext.c"
+#include "slang_ids.c"
+#ifndef SLANG_NOTYPES
+#include "slang_types.c"
+#endif
+#include "slang_duration.c"
+#ifndef SLANG_NOFACTS
+#include "slang_facts_places.c"
+#include "slang_facts.c"
+#endif
+#include "slang_chanlang.c"
+
+
+struct slang_header {
+  struct slang_header *next;
+  char *lang;
+  char *desc;
+  struct slang_id *ids;
+#ifndef SLANG_NOTYPES
+  struct slang_type *types;
+#endif
+#ifndef SLANG_NOFACTS
+  struct slang_facts *facts;
+#endif
+  struct slang_duration *durations;
+};
+
+static void slang_glob_init()
+{
+  glob_slang_cmd_list = NULL;
+}
+
+/*
+static int slang_glob_expmem()
+{
+  return slang_commands_list_expmem(glob_slang_cmd_list);
+}
+*/
+
+static void slang_glob_free()
+{
+  slang_commands_list_free(glob_slang_cmd_list);
+  glob_slang_cmd_list = NULL;
+}
+
+static struct slang_header *slang_create(struct slang_header *list, char *lang, char *desc)
+{
+  struct slang_header *nslang, *l;
+
+  Assert(lang);
+  debug2("Creating language '%s' starting by %d", lang, (int) list);
+  for (nslang = list; nslang; nslang = nslang->next)
+    if (!strcasecmp(nslang->lang, lang))
+      return list;
+  nslang = nmalloc(sizeof(struct slang_header));
+  nslang->next = NULL;
+  nslang->desc = NULL;
+  nslang->lang = nmalloc(strlen(lang) + 1);
+  strcpy(nslang->lang, lang);
+  nslang->desc = nmalloc(strlen(desc) + 1);
+  strcpy(nslang->desc, desc);
+  nslang->ids = NULL;
+#ifndef SLANG_NOTYPES
+  nslang->types = NULL;
+#endif
+#ifndef SLANG_NOFACTS
+  nslang->facts = NULL;
+#endif
+  nslang->durations = NULL;
+  for (l = list; l && l->next; l = l->next);
+  if (l)
+    l->next = nslang;
+  else {
+    Assert(!list);
+    list = nslang;
+  }
+  return list;
+}
+
+/*
+static int slang_expmem(struct slang_header *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct slang_header);
+    size += strlen(what->lang) + 1;
+    size += strlen(what->desc) + 1;
+    size += slang_id_expmem(what->ids);
+#ifndef SLANG_NOTYPES
+    size += slang_type_expmem(what->types);
+#endif
+#ifndef SLANG_NOFACTS
+    size += slang_facts_expmem(what->facts);
+#endif
+    size += slang_duration_expmem(what->durations);
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void slang_free(struct slang_header *what)
+{
+  struct slang_header *next;
+
+  while (what) {
+    next = what->next;
+    slang_id_free(what->ids);
+#ifndef SLANG_NOTYPES
+    slang_type_free(what->types);
+#endif
+#ifndef SLANG_NOFACTS
+    slang_facts_free(what->facts);
+#endif
+    slang_duration_free(what->durations);
+    nfree(what->lang);
+    nfree(what->desc);
+    nfree(what);
+    what = next;
+  }
+}
+
+static int slang_load(struct slang_header *slang, char *filename)
+{
+  FILE *f;
+  char *buffer, *s;
+  char *cmd, *sid, *strtol_ret;
+#ifndef SLANG_NOTYPES
+  char *type;
+#endif
+  int line, id, iplace, itype;
+
+  Assert(slang);
+  putlog(LOG_MISC, "*", "Loading language \"%s\" from %s...", slang->lang, filename);
+  f = fopen(filename, "r");
+  if (!f) {
+    putlog(LOG_MISC, "*", "Couldn't open slangfile \"%s\"!", filename);
+    return 0;
+  }
+  buffer = nmalloc(2000);
+  line = 0;
+  while (!feof(f)) {
+    s = buffer;
+    if (fgets(s, 2000, f)) {
+      line++;
+      // at first, kill those stupid line feeds and carriage returns...
+      if (s[strlen(s) - 1] == '\n')
+        s[strlen(s) - 1] = 0;
+      if (s[strlen(s) - 1] == '\r')
+        s[strlen(s) - 1] = 0;
+      if (!s[0])
+        continue;
+      cmd = newsplit(&s);
+
+      if (!strcasecmp(cmd, "T")) {
+#ifndef SLANG_NOTYPES
+        type = newsplit(&s);
+        slang->types = slang_type_add(slang->types, type, s);
+#endif
+      } else if (!strcasecmp(cmd, "D")) {
+        sid = newsplit(&s);
+        id = strtol(sid, &strtol_ret, 10);
+        if (strtol_ret == sid) {
+          putlog(LOG_MISC, "*", "ERROR in slangfile \"%s\", line %d: %s is not a valid "
+                 "duration index!", filename, line, sid);
+          continue;
+        }
+        slang->durations = slang_duration_add(slang->durations, id, s);
+      } else if (!strcasecmp(cmd, "F")) {
+	itype = typetoi(newsplit(&s));
+	iplace = atoi(newsplit(&s));
+	slang->facts = slang_facts_add(slang->facts, itype, iplace, s);
+      } else {
+        id = strtol(cmd, &strtol_ret, 10);
+        if (strtol_ret == cmd)
+          continue;
+        slang->ids = slang_id_add(slang->ids, id, s);
+      }
+    }
+  }
+  fclose(f);
+  nfree(buffer);
+  return 1;
+}
+
+static struct slang_header *slang_find(struct slang_header *where, char *language)
+{
+  struct slang_header *slang = NULL;
+
+  // at first, search for the specified language
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, language))
+      return slang;
+  // oops... language seems to be invalid. Let's find the default.
+  Assert(default_slang);
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, default_slang))
+      return slang;
+  // default_slang wasn't found either? *sigh*
+  // Let's return the first known language then.
+  return where;
+}
+
+#ifndef SLANG_NOVALIDATE
+/* slang_valid():
+ * check if the given language is a valid one
+ */
+static int slang_valid(struct slang_header *where, char *language)
+{
+  struct slang_header *slang = NULL;
+
+  for (slang = where; slang; slang = slang->next)
+    if (!strcasecmp(slang->lang, language))
+      return 1;
+  return 0;
+}
+#endif
+
+static char getslang_error[12];
+static char *getslang(int id)
+{
+  char *text;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslang())");
+    return "NOLANG";
+  }
+  text = slang_id_get(glob_slang->ids, id);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "SLANG%d", id);
+    return getslang_error;
+  }
+  return text;
+}
+
+static char *getdur(int idx)
+{
+  char *text;
+
+  Assert((idx >= 0) && (idx < DURATIONS));
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getdur())");
+    return "NOLANG";
+  }
+  text = slang_duration_get(glob_slang->durations, idx);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "DUR%d", idx);
+    return getslang_error;
+  }
+  return text;
+}
+
+#ifndef SLANG_NOTYPES
+static char *getslangtype(char *type)
+{
+  char *stype;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslangtype())");
+    return "NOLANG";
+  }
+  stype = slang_type_get(glob_slang->types, type);
+  if (stype)
+    return stype;
+  else
+    return type;
+}
+
+static int slangtypetoi(char *slangtype)
+{
+  char *type;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (slangtypetoi())");
+    return T_ERROR;
+  }
+  type = slang_type_slang2type(glob_slang->types, slangtype);
+  if (type) {
+    debug1("type: %s", type);
+    return typetoi(type);
+  } else
+    return typetoi(slangtype);
+}
+#endif
+
+#ifndef SLANG_NOGETALL
+static char *getslang_first(int id)
+{
+  char *text;
+
+  if (!glob_slang) {
+    putlog(LOG_MISC, "*", "WARNING! No language selected! (getslang())");
+    return "NOLANG";
+  }
+  text = slang_id_get_first(glob_slang->ids, id);
+  if (!text) {
+    snprintf(getslang_error, sizeof(getslang_error), "SLANG%d", id);
+    return getslang_error;
+  }
+  return text;
+}
+
+static char *getslang_next()
+{
+  return slang_id_get_next();
+}
+#endif
+
+#ifndef SLANG_NOFACTS
+static int selectfirstfact()
+{
+  if (!glob_slang) {
+    debug0("getfirstfact(): no language selected!");
+    return 0;
+  }
+  if (!glob_slang->facts)
+    return 0;
+  else
+    return slang_facts_selectfirst(glob_slang->facts);
+}
+
+static int selectnextfact()
+{
+  return slang_facts_selectnext();
+}
+
+/*static char *getfirstfact()
+{
+  return slang_facts_getfirst();
+}
+
+static char *getnextfact()
+{
+  return slang_facts_getnext();
+}*/
+
+static char *getfact(int place)
+{
+  return slang_facts_get(place);
+}
+#endif

+ 63 - 30
slang.h → core/slang.h

@@ -1,3 +1,64 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define SLYEAR          getdur(0)
+#define SLYEARS         getdur(1)
+#define SLWEEK          getdur(2)
+#define SLWEEKS         getdur(3)
+#define SLDAY           getdur(4)
+#define SLDAYS          getdur(5)
+#define SLHOUR          getdur(6)
+#define SLHOURS         getdur(7)
+#define SLMINUTE        getdur(8)
+#define SLMINUTES       getdur(9)
+#define SLSECOND        getdur(10)
+#define SLSECONDS       getdur(11)
+#define SLSOMETIME	getdur(12)
+
+#define SLTOTAL         getslang(110)
+#define SLDAILY         getslang(111)
+#define SLWEEKLY              getslang(112)
+#define SLMONTHLY             getslang(113)
+
+#define SLNOSTATSABOUTSOMEONE	getslang(220)
+#define SLNOSTATSABOUTYOU	getslang(221)
+
+#define SLUSERSMOSTUSEDWORDS	getslang(300)
+#define SLNOWORDSTATS	getslang(310)
+#define SLCHANSMOSTUSEDWORDS	getslang(320)
+#define SLNOCHANWORDSTATS	getslang(330)
+
+#define SLLTOTAL        getslang(750)
+#define SLLTODAY        getslang(751)
+#define SLLWEEKLY       getslang(752)
+#define SLLMONTHLY      getslang(753)
+
+#define SLDONTRECOGNIZE getslang(1020)
+
+#define SLPASSALREADYSET getslang(1100)
+#define SLPASSUSAGE      getslang(1110)
+#define SLPASSSET        getslang(1120)
+
+#define SL_EMAILPASS_SUBJECT	getslang(1200)
+#define	SL_EMAILPASS_BODY		getslang(1210)
+
+
+#ifdef __AUSKOMMENTIERT__
 #define SLCSS		getslang(1)
 #define SLCSS		getslang(1)
 #define SLBODYTAG       getslang(5)
 #define SLBODYTAG       getslang(5)
 #define SLHEADER        getslang(10)
 #define SLHEADER        getslang(10)
@@ -6,14 +67,6 @@
 #define ROOTTITLE	getslang(100)
 #define ROOTTITLE	getslang(100)
 #define SLTOP		getslang(105)
 #define SLTOP		getslang(105)
 
 
-#define SLTOTAL		getslang(110)
-#define SLDAILY		getslang(111)
-#define SLWEEKLY	      getslang(112)
-#define SLMONTHLY	      getslang(113)
-#define SLLTOTAL        getslang(114)
-#define SLLTODAY        getslang(115)
-#define SLLWEEKLY       getslang(116)
-#define SLLMONTHLY      getslang(117)
 #define SLUSERS		getslang(118)
 #define SLUSERS		getslang(118)
 #define SLONCHAN        getslang(119)
 #define SLONCHAN        getslang(119)
 #define SLMISCSTATS	getslang(120)
 #define SLMISCSTATS	getslang(120)
@@ -36,18 +89,6 @@
 #define SLGRAPHS	      getslang(220)
 #define SLGRAPHS	      getslang(220)
 #define SLOTHERCHANS	getslang(221)
 #define SLOTHERCHANS	getslang(221)
 
 
-#define SLYEAR		getdur(0)
-#define SLYEARS         getdur(1)
-#define SLWEEK          getdur(2)
-#define SLWEEKS         getdur(3)
-#define SLDAY           getdur(4)
-#define SLDAYS          getdur(5)
-#define SLHOUR          getdur(6)
-#define SLHOURS         getdur(7)
-#define SLMINUTE        getdur(8)
-#define SLMINUTES       getdur(9)
-#define SLSECOND	      getslang(260)
-#define SLSECONDS	      getslang(261)
 
 
 #define SLGRTTITLE	getslang(300)
 #define SLGRTTITLE	getslang(300)
 #define SLGRDTITLE	getslang(301)
 #define SLGRDTITLE	getslang(301)
@@ -98,13 +139,5 @@
 
 
 #define SLNOSUCHTYPE    getslang(1000)
 #define SLNOSUCHTYPE    getslang(1000)
 #define SLTOPWORD       getslang(1005)
 #define SLTOPWORD       getslang(1005)
-#define SLNOSTATSABOUTYOU     getslang(1010)
-#define SLNOSTATSABOUTSOMEONE getslang(1011)
-#define SLNOWORDSTATS         getslang(1016)
-#define SLUSERSMOSTUSEDWORDS  getslang(1017)
-#define SLCHANSMOSTUSEDWORDS  getslang(1018)
-#define SLDONTRECOGNIZE	getslang(1020)
-#define SLEMAILUSAGE	getslang(1021)
-#define SLSETEMAIL	getslang(1022)
-#define SLHPUSAGE	getslang(1023)
-#define SLSETHOMEPAGE	getslang(1024)
+
+#endif

+ 117 - 0
core/slang_chanlang.c

@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_chanlang {
+  struct slang_chanlang *next;
+  char *chan;
+  char *lang;
+};
+
+static struct slang_chanlang *chanlangs = NULL;
+
+static struct slang_chanlang *slang_chanlang_add(struct slang_chanlang *, char *, char *);
+//static int slang_chanlang_expmem(struct slang_chanlang *);
+static void slang_chanlang_free(struct slang_chanlang *);
+static char *slang_chanlang_get(struct slang_chanlang *, char *);
+
+static struct slang_chanlang *slang_chanlang_add(struct slang_chanlang *where, char *chan, char *lang)
+{
+  struct slang_chanlang *item;
+
+  for (item = where; item; item = item->next)
+    if (!rfc_casecmp(item->chan, chan))
+      break;
+  if (!item) {
+    item = nmalloc(sizeof(struct slang_chanlang));
+    item->chan = nmalloc(strlen(chan) + 1);
+    strcpy(item->chan, chan);
+    item->lang = nmalloc(strlen(lang) + 1);
+    strcpy(item->lang, lang);
+    item->next = where;
+    where = item;
+  } else {
+    Assert(item->lang);
+    item->lang = nrealloc(item->lang, strlen(lang) + 1);
+    strcpy(item->lang, lang);
+  }
+  return where;
+}
+
+/*
+static int slang_chanlang_expmem(struct slang_chanlang *what)
+{
+  int size = 0;
+
+  while (what) {
+    Assert(what);
+    Assert(what->chan);
+    Assert(what->lang);
+    size += sizeof(struct slang_chanlang);
+    size += strlen(what->chan) + 1;
+    size += strlen(what->lang) + 1;
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void slang_chanlang_free(struct slang_chanlang *what)
+{
+  struct slang_chanlang *next;
+
+  while (what) {
+    Assert(what);
+    Assert(what->chan);
+    Assert(what->lang);
+    next = what->next;
+    nfree(what->chan);
+    nfree(what->lang);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *slang_chanlang_get(struct slang_chanlang *where, char *chan)
+{
+  while (where) {
+    if (!rfc_casecmp(where->chan, chan))
+      return where->lang;
+    where = where->next;
+  }
+  return default_slang;
+}
+
+/* slang_getbynick():
+ * tries to find an appropriate language for nick by searching
+ * him on a channel and using the language of this channel.
+ */
+static struct slang_header *slang_getbynick(struct slang_header *where, char *nick)
+{
+  struct chanset_t *chan;
+
+#ifndef NO_EGG
+  for (chan = chanset; chan; chan = chan->next)
+    if (ismember(chan, nick))
+#if EGG_IS_MIN_VER(10500)
+      return slang_find(where, slang_chanlang_get(chanlangs, chan->dname));
+#else
+      return slang_find(where, slang_chanlang_get(chanlangs, chan->name));
+#endif
+#endif
+  return slang_find(where, default_slang);
+}

+ 82 - 0
core/slang_duration.c

@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define DURATIONS 13
+
+struct slang_duration {
+  char *durs[DURATIONS];
+};
+
+static struct slang_duration *slang_duration_add(struct slang_duration *where, int idx, char *text)
+{
+  int i;
+
+  if ((idx < 0) || (idx >= DURATIONS)) {
+    putlog(LOG_MISC, "*", "Warning: Invalid duration index \"%d\".", idx);
+    return where;
+  }
+  debug2("Adding duration[%d]: %s", idx, text);
+  if (!where) {
+    where = nmalloc(sizeof(struct slang_duration));
+    for (i = 0; i < DURATIONS; i++)
+      where->durs[i] = NULL;
+  }
+  if (where->durs[idx])
+    nfree(where->durs[idx]);
+  where->durs[idx] = nmalloc(strlen(text) + 1);
+  strcpy(where->durs[idx], text);
+  return where;
+}
+
+/*static int slang_duration_expmem(struct slang_duration *what)
+{
+  int i, size = 0;
+
+  if (!what)
+    return 0;
+  size += sizeof(struct slang_duration);
+  for (i = 0; i < DURATIONS; i++)
+    if (what->durs[i])
+      size += strlen(what->durs[i]) + 1;
+  return size;
+}*/
+
+static void slang_duration_free(struct slang_duration *what)
+{
+  int i;
+
+  if (what) {
+    for (i = 0; i < DURATIONS; i++)
+      if (what->durs[i])
+        nfree(what->durs[i]);
+    nfree(what);
+  }
+}
+
+static char *slang_duration_get(struct slang_duration *where, int idx)
+{
+  if (!where) {
+    debug0("no where");
+    return NULL;
+  }
+  if ((idx < 0) || (idx >= DURATIONS)) {
+    debug1("invalid duration index: %d", idx);
+    return NULL;
+  }
+  return where->durs[idx];
+}

+ 163 - 0
core/slang_facts.c

@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_facts {
+  struct slang_facts *next;
+  int sorting;
+  struct slang_facts_place *places;
+};
+
+static struct slang_facts *slang_facts_add(struct slang_facts *, int, int, char *);
+//static int slang_facts_expmem(struct slang_facts *);
+static void slang_facts_free(struct slang_facts *);
+static int slang_facts_selectfirst(struct slang_facts *);
+static int slang_facts_selectnext();
+//static char *slang_facts_getfirst();
+//static char *slang_facts_getnext();
+static char *slang_facts_get(int);
+
+static struct slang_facts *slang_facts_add(struct slang_facts *where, int sorting, int place, char *text)
+{
+  struct slang_facts *newitem, *target;
+
+  newitem = NULL;
+  if (where) {
+    for (newitem = where; newitem; newitem = newitem->next)
+      if (newitem->sorting == sorting)
+        break;
+  }
+  if (!newitem) {
+    newitem = nmalloc(sizeof(struct slang_facts));
+    newitem->next = NULL;
+    newitem->sorting = sorting;
+    newitem->places = NULL;
+    for (target = where; target && target->next; target = target->next);
+    if (target)
+      target->next = newitem;
+    else
+      where = newitem;
+  }
+  newitem->places = slang_facts_place_add(newitem->places, place, text);
+  return where;
+}
+
+/*static int slang_facts_expmem(struct slang_facts *what)
+{
+  int size = 0;
+
+  for (; what; what = what->next) {
+    size += sizeof(struct slang_facts);
+    size += slang_facts_place_expmem(what->places);
+  }
+  return size;
+}*/
+
+static void slang_facts_free(struct slang_facts *what)
+{
+  struct slang_facts *next;
+
+  while (what) {
+    next = what->next;
+    slang_facts_place_free(what->places);
+    nfree(what);
+    what = next;
+  }
+}
+
+static struct slang_facts *glob_fact;
+static int slang_facts_selectfirst(struct slang_facts *what)
+{
+  int itype, pitype;
+  locstats *ls;
+
+  if (!glob_globstats)
+    return 0;
+  if (!glob_globstats->local)
+    return 0;
+  for (glob_fact = what; glob_fact; glob_fact = glob_fact->next) {
+    sortstats(glob_globstats, glob_fact->sorting, S_DAILY);
+    itype = glob_fact->sorting;
+    glob_sorting = itype;
+    if (itype >= 0) {
+      if (!glob_globstats->slocal[S_TODAY][itype]->values[S_TODAY][itype])
+        continue;
+    } else {
+      pitype = (itype * -1) + (TOTAL_TYPES - 1);
+      ls = glob_globstats->slocal[S_TODAY][pitype];
+      if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS]
+		|| !ls->values[S_DAILY][T_LINES]))
+	continue;
+      else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES]
+      		|| !ls->values[S_DAILY][T_LINES]))
+	continue;
+      else if ((itype == T_VOCABLES) && !ls->vocables)
+	continue;
+    }
+    return 1;
+  }
+  return 0;
+}
+
+static int slang_facts_selectnext()
+{
+  int itype, pitype;
+  locstats *ls;
+
+  if (!glob_fact)
+    return 0;
+  for (glob_fact = glob_fact->next; glob_fact; glob_fact = glob_fact->next) {
+    sortstats(glob_globstats, glob_fact->sorting, S_DAILY);
+    itype = glob_fact->sorting;
+    glob_sorting = itype;
+    if (itype >= 0) {
+      if (!glob_globstats->slocal[S_TODAY][itype]->values[S_TODAY][itype])
+        continue;
+    } else {
+      pitype = (itype * -1) + (TOTAL_TYPES - 1);
+      ls = glob_globstats->slocal[S_TODAY][pitype];
+      if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS]
+		|| !ls->values[S_DAILY][T_LINES]))
+	continue;
+      else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES]
+      		|| !ls->values[S_DAILY][T_LINES]))
+	continue;
+      else if ((itype == T_VOCABLES) && !ls->vocables)
+	continue;
+    }
+    return 1;
+  }
+  return 0;
+}
+
+/*
+static char *slang_facts_getfirst()
+{
+  Assert(glob_fact);
+  return slang_facts_place_getfirst(glob_fact->places);
+}
+
+static char *slang_facts_getnext()
+{
+  return slang_facts_place_getnext();
+}
+*/
+
+static char *slang_facts_get(int place)
+{
+  return slang_facts_place_get(glob_fact->places, glob_fact->sorting, place);
+}

+ 214 - 0
core/slang_facts_places.c

@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_facts_place {
+  struct slang_facts_place *next;
+  int place;
+  struct slang_multitext *mtext;
+};
+
+static struct slang_facts_place *slang_facts_place_add(struct slang_facts_place *, int, char *);
+//static int slang_facts_place_expmem(struct slang_facts_place *);
+static void slang_facts_place_free(struct slang_facts_place *);
+//static char *slang_facts_place_getfirst(struct slang_facts_place *);
+//static char *slang_facts_place_getnext();
+static char *slang_facts_place_get(struct slang_facts_place *, int, int);
+
+static struct slang_facts_place *slang_facts_place_add(struct slang_facts_place *where, int place, char *text)
+{
+  struct slang_facts_place *newitem, *target;
+
+  newitem = NULL;
+  if (where) {
+    for (newitem = where; newitem; newitem = newitem->next)
+      if (newitem->place == place)
+        break;
+  }
+  if (!newitem) {
+    newitem = nmalloc(sizeof(struct slang_facts_place));
+    newitem->place = place;
+    newitem->mtext = NULL;
+    newitem->next = NULL;
+    for (target = where; target && target->next; target = target->next);
+    if (target)
+      target->next = newitem;
+    else
+      where = newitem;
+  }
+  newitem->mtext = slang_mtext_add(newitem->mtext, text);
+  return where;
+}
+
+/*static int slang_facts_place_expmem(struct slang_facts_place *what)
+{
+  int size = 0;
+
+  for (; what; what = what->next) {
+    size += sizeof(struct slang_facts_place);
+    size += slang_multitext_expmem(what->mtext);
+  }
+  return size;
+}*/
+
+static void slang_facts_place_free(struct slang_facts_place *what)
+{
+  struct slang_facts_place *next;
+
+  while (what) {
+    next = what->next;
+    slang_multitext_free(what->mtext);
+    nfree(what);
+    what = next;
+  }
+}
+
+/*
+static struct slang_facts_place *glob_fact_place;
+static char *slang_facts_place_getfirst(struct slang_facts_place *where)
+{
+  int itype, pitype;
+  locstats *ls;
+
+  if (!glob_globstats || !glob_fact)
+    return 0;
+  if (!glob_globstats->local)
+    return 0;
+  itype = glob_fact->sorting;
+  pitype = (itype * -1) + (TOTAL_TYPES - 1);
+  glob_sorting = itype;
+
+
+  for (glob_fact_place = what; glob_fact_place; glob_fact_place = glob_fact_place->next) {
+    glob_place = 0;
+
+    for (ls = glob_globstats->slocal[S_TODAY][pitype]; ls; ls = ls->snext[S_TODAY][pitype]) {
+
+      // skip this fact if the value seems to be 0
+      if (itype >= 0) {
+	if (!glob_globstats->slocal[S_TODAY][itype]->values[S_TODAY][itype])
+	  break;
+      } else {
+	 if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS]
+		  || !ls->values[S_DAILY][T_LINES]))
+	  break;
+	else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES]
+		  || !ls->values[S_DAILY][T_LINES]))
+	  break;
+	else if ((itype == T_VOCABLES) && !ls->vocables)
+	  break;
+      }
+
+      glob_place++;
+      if (glob_place == glob_facts_place->place) {
+	glob_locstats = ls;
+	return slang_multitext_get(glob_facts_place->mtext);
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static char *slang_facts_place_getnext()
+{
+  int itype, pitype;
+  locstats *ls;
+
+  if (!glob_globstats || !glob_fact)
+    return 0;
+  if (!glob_globstats->local)
+    return 0;
+  itype = glob_fact->sorting;
+  pitype = (itype * -1) + (TOTAL_TYPES - 1);
+  glob_sorting = itype;
+
+
+  for (; glob_fact_place; glob_fact_place = glob_fact_place->next) {
+    glob_place = 0;
+
+    for (ls = glob_globstats->slocal[S_TODAY][pitype]; ls; ls = ls->snext[S_TODAY][pitype]) {
+
+      // skip this fact if the value seems to be 0
+      if (itype >= 0) {
+	if (!glob_globstats->slocal[S_TODAY][itype]->values[S_TODAY][itype])
+	  break;
+      } else {
+	 if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS]
+		  || !ls->values[S_DAILY][T_LINES]))
+	  break;
+	else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES]
+		  || !ls->values[S_DAILY][T_LINES]))
+	  break;
+	else if ((itype == T_VOCABLES) && !ls->vocables)
+	  break;
+      }
+
+      glob_place++;
+      if (glob_place == glob_facts_place->place) {
+	glob_locstats = ls;
+	return slang_multitext_get(glob_facts_place->mtext);
+      }
+    }
+  }
+
+  return NULL;
+}
+*/
+
+static char *slang_facts_place_get(struct slang_facts_place *where, int itype, int place)
+{
+  struct slang_facts_place *fp;
+  locstats *ls;
+  int pitype;
+
+  if (!glob_globstats || !place)
+    return NULL;
+  if (itype < 0)
+    pitype = (itype * -1) + (TOTAL_TYPES - 1);
+  else
+    pitype = itype;
+  glob_sorting = itype;
+  for (fp = where; fp; fp = fp->next) {
+    if (fp->place == place) {
+      glob_place = 0;
+      for (ls = glob_globstats->slocal[S_TODAY][pitype]; ls; ls = ls->snext[S_TODAY][pitype]) {
+	if (itype >= 0) {
+	  if (!glob_globstats->slocal[S_TODAY][itype]->values[S_TODAY][itype])
+	    return NULL;
+	} else {
+	   if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS]
+		    || !ls->values[S_DAILY][T_LINES]))
+	    return NULL;
+	  else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES]
+		    || !ls->values[S_DAILY][T_LINES]))
+	    return NULL;
+	  else if ((itype == T_VOCABLES) && !ls->vocables)
+	    return NULL;
+	}
+	glob_place++;
+	if (glob_place == fp->place) {
+	  glob_locstats = ls;
+	  glob_timerange = S_TODAY;
+	  glob_toptype = itotype(itype);
+	  return slang_multitext_getrandomtext(fp->mtext);
+	}
+      }
+    }
+  }
+  return NULL;
+}

+ 106 - 0
core/slang_ids.c

@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_id {
+  struct slang_id *next;
+  int id;
+  struct slang_multitext *mtext;
+};
+
+static struct slang_id* slang_id_add(struct slang_id *, int, char *);
+//static int slang_id_expmem(struct slang_id *);
+static void slang_id_free(struct slang_id *);
+static char *slang_id_get(struct slang_id *, int);
+
+static struct slang_id* slang_id_add(struct slang_id *where, int id, char *text)
+{
+  struct slang_id *newitem;
+
+  newitem = NULL;
+  if (where) {
+    for (newitem = where; newitem; newitem = newitem->next)
+      if (newitem->id == id)
+        break;
+  }
+  if (!newitem) {
+    newitem = nmalloc(sizeof(struct slang_id));
+    newitem->next = NULL;
+    newitem->id = id;
+    newitem->mtext = NULL;
+    if (where)
+      newitem->next = where;
+    else
+      newitem->next = NULL;
+    where = newitem;
+  }
+  newitem->mtext = slang_mtext_add(newitem->mtext, text);
+  return where;
+}
+
+/*
+static int slang_id_expmem(struct slang_id *what)
+{
+  int size = 0;
+
+  for (; what; what = what->next) {
+    size += sizeof(struct slang_id);
+    size += slang_multitext_expmem(what->mtext);
+  }
+  return size;
+}
+*/
+
+static void slang_id_free(struct slang_id *what)
+{
+  struct slang_id *next;
+
+  while (what) {
+    next = what->next;
+    slang_multitext_free(what->mtext);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *slang_id_get(struct slang_id *where, int i)
+{
+  while (where) {
+    if (where->id == i)
+      return slang_multitext_getrandomtext(where->mtext);
+    where = where->next;
+  }
+  return NULL;
+}
+
+#ifndef SLANG_NOGETALL
+static char *slang_id_get_first(struct slang_id *where, int id)
+{
+  while (where) {
+    if (where->id == id) {
+      return slang_multitext_get_first(where->mtext);
+    }
+    where = where->next;
+  }
+  return NULL;
+}
+
+static char *slang_id_get_next()
+{
+  return slang_multitext_get_next();
+}
+#endif

+ 151 - 0
core/slang_multitext.c

@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_mt_content {
+  struct slang_mt_content *next;
+  struct slang_text *text;
+};
+
+struct slang_multitext {
+  int nr;
+  struct slang_mt_content *contents;
+};
+
+static struct slang_multitext *slang_mtext_add(struct slang_multitext *, char *);
+//static int slang_multitext_expmem(struct slang_multitext *);
+static void slang_multitext_free(struct slang_multitext *);
+static char *slang_multitext_getrandomtext(struct slang_multitext *);
+#ifndef SLANG_NOTYPES
+static struct slang_text *slang_multitext_find(struct slang_multitext *, char *);
+#endif
+#ifndef SLANG_NOGETALL
+static char *slang_multitext_get_first(struct slang_multitext *);
+static char *slang_multitext_get_next();
+#endif
+
+static struct slang_multitext *slang_mtext_add(struct slang_multitext *where, char *text)
+{
+  struct slang_mt_content *oc, *nc;
+
+  if (!where) {
+    where = nmalloc(sizeof(struct slang_multitext));
+    where->nr = 0;
+    where->contents = NULL;
+  }
+  nc = nmalloc(sizeof(struct slang_mt_content));
+  nc->next = NULL;
+  nc->text = slang_text_parse(text);
+  for (oc = where->contents; oc && oc->next; oc = oc->next);
+  if (oc) {
+    Assert(!oc->next);
+    oc->next = nc;
+  } else
+    where->contents = nc;
+  where->nr++;
+  return where;
+}
+
+/*static int slang_multitext_expmem(struct slang_multitext *what)
+{
+  struct slang_mt_content *content;
+  int size = 0;
+
+  if (!what) {
+    debug0("WARNING! slang_multitext_expmem() called with NULL pointer!");
+    return 0;
+  }
+  size += sizeof(struct slang_multitext);
+  for (content = what->contents; content; content = content->next) {
+    size += sizeof(struct slang_mt_content);
+    size += slang_text_expmem(content->text);
+  }
+  return size;
+}*/
+
+static void slang_multitext_free(struct slang_multitext *what)
+{
+  struct slang_mt_content *content, *next;
+
+  if (!what) {
+    debug0("WARNING! slang_multitext_free() called with NULL pointer!");
+    return;
+  }
+  content = what->contents;
+  while (content) {
+    next = content->next;
+    slang_text_free(content->text);
+    nfree(content);
+    content = next;
+  }
+  nfree(what);
+}
+
+static char *slang_multitext_getrandomtext(struct slang_multitext *where)
+{
+  struct slang_mt_content *content;
+  unsigned long x;
+
+  if (!where)
+    return NULL;
+  x = random() % where->nr;
+  for (content = where->contents; content; content = content->next)
+    if (!x)
+      return slang_text_get(content->text);
+    else
+      x--;
+  // we should never reach this part
+  debug0("warning: getrandomtext didn't find anything!");
+  return NULL;
+}
+
+#ifndef SLANG_NOTYPES
+static struct slang_text *slang_multitext_find(struct slang_multitext *where, char *what)
+{
+  struct slang_mt_content *content;
+
+  Assert(where);
+  for (content = where->contents; content; content = content->next) {
+    Assert(content->text);
+    if (!slang_text_strcasecmp(content->text, what))
+      return content->text;
+  }
+  return NULL;
+}
+#endif
+
+#ifndef SLANG_NOGETALL
+static struct slang_mt_content *glob_mtext_content;
+static char *slang_multitext_get_first(struct slang_multitext *where)
+{
+  Assert(where);
+  glob_mtext_content = where->contents;
+  if (glob_mtext_content)
+    return slang_text_get(glob_mtext_content->text);
+  else
+    return NULL;
+}
+
+static char *slang_multitext_get_next()
+{
+  glob_mtext_content = glob_mtext_content->next;
+  if (glob_mtext_content)
+    return slang_text_get(glob_mtext_content->text);
+  else
+    return NULL;
+}
+#endif

+ 339 - 0
core/slang_stats_commands.c

@@ -0,0 +1,339 @@
+
+static void slang_send_nick()
+{
+  if (glob_nick)
+    strncat(slang_text_buf, glob_nick, sizeof(slang_text_buf));
+}
+
+static void slang_send_bot()
+{
+  strncat(slang_text_buf, botnetnick, sizeof(slang_text_buf));
+}
+
+static void slang_send_topnr()
+{
+  char buf[10];
+
+  snprintf(buf, sizeof(buf), "%d", webnr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_graphnr()
+{
+  char buf[10];
+
+  snprintf(buf, sizeof(buf), "%d", graphnr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_peak()
+{
+  char buf[10];
+
+  if (glob_globstats && (glob_timerange != T_ERROR)) {
+    snprintf(buf, sizeof(buf), "%d", glob_globstats->peak[glob_timerange]);
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_totalusers()
+{
+  char buf[10];
+
+  if (glob_globstats) {
+    snprintf(buf, sizeof(buf), "%d", countstatmembers(glob_globstats));
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_chanstarted()
+{
+  time_t tt, ttbuf;
+  char sbuf[61];
+
+  if (glob_globstats) {
+    ttbuf = now;
+    tt = glob_globstats->started;
+    strftime(sbuf, 60, "%d.%m. %Y  %H:%M", localtime(&tt));
+    ctime(&ttbuf); /* workaround for eggdrop bug */
+    strncat(slang_text_buf, sbuf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_chan()
+{
+  if (glob_globstats)
+    strncat(slang_text_buf, glob_globstats->chan, sizeof(slang_text_buf));
+}
+
+static void slang_send_user()
+{
+  if (glob_locstats)
+    strncat(slang_text_buf, glob_locstats->user, sizeof(slang_text_buf));
+  else if (glob_user)
+    strncat(slang_text_buf, glob_user->user, sizeof(slang_text_buf));
+}
+
+static void slang_send_sorting()
+{
+  if (glob_sorting != T_ERROR)
+    strncat(slang_text_buf, itotype(glob_sorting), sizeof(slang_text_buf));
+}
+
+static void slang_send_range()
+{
+  char buf[10];
+
+  snprintf(buf, sizeof(buf), "%d", glob_range);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_active_users()
+{
+  char buf[10];
+
+  if (glob_globstats && (glob_timerange != T_ERROR)) {
+    snprintf(buf, sizeof(buf), "%d",
+             countactivestatmembers(glob_globstats, 1, glob_timerange,
+                 (glob_sorting >= 0) ? glob_sorting : T_LINES, 1)
+             );
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_word()
+{
+  if (glob_word)
+    strncat(slang_text_buf, glob_word, sizeof(slang_text_buf));
+}
+
+static void slang_send_place()
+{
+  char buf[10];
+
+  snprintf(buf, sizeof(buf), "%d", glob_place);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_password()
+{
+  if (glob_user && glob_user->password)
+    strncat(slang_text_buf, glob_user->password, sizeof(slang_text_buf));
+}
+
+static void slang_send_botnick()
+{
+  strncat(slang_text_buf, botname, sizeof(slang_text_buf));
+}
+
+static void slang_send_server_host()
+{
+  char s[121];
+  char *p;
+  
+#ifndef NO_EGG
+  s[0] = 0;
+  gethostname(s, 120);
+  if (!s[0]) {
+    p = strchr(botuserhost, '@');
+    if (p) {
+      strncpy(s, p, sizeof(s));
+      s[120] = 0;
+    }
+  }
+  strncat(slang_text_buf, s, sizeof(slang_text_buf));
+#endif
+}
+
+static void slang_send_server_port()
+{
+  int i;
+  char buf[10];
+
+#ifndef NO_EGG
+  for (i = 0; i < dcc_total; i++) {
+    if (dcc[i].type == &MHTTPD_CON_HTTPD) {
+      snprintf(buf, sizeof(buf), "%d", dcc[i].port);
+      strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+      return;
+    }
+  }
+#endif
+}
+
+static void slang_send_topic_by()
+{
+  if (glob_topic) {
+    Assert(glob_topic->by);
+    strncat(slang_text_buf, glob_topic->by, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_topic_when()
+{
+  char buf[20];
+  
+  if (glob_topic) {
+    strftime(buf, 19, "%H:%M", localtime(&glob_topic->when));
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_url_by()
+{
+  if (glob_url) {
+    Assert(glob_url->by);
+    strncat(slang_text_buf, glob_url->by, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_url_when()
+{
+  char buf[20];
+  
+  if (glob_url) {
+    strftime(buf, 19, "%H:%M", localtime(&glob_url->when));
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static void slang_send_random_urls()
+{
+  char buf[3];
+  static struct stats_url *url;
+  int nr = 0;
+
+  if (!glob_globstats)
+    return;
+  for (url = glob_globstats->urls; url; url = url->next)
+    nr++;
+  if (nr > log_urls)
+    nr = log_urls;
+  snprintf(buf, sizeof(buf), "%d", nr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_random_kicks()
+{
+  char buf[3];
+  struct stats_kick *kick;
+  int nr = 0;
+
+  if (!glob_globstats)
+    return;
+  for (kick = glob_globstats->kicks; kick; kick = kick->next)
+    nr++;
+  if (nr > display_kicks)
+    nr = display_kicks;
+  snprintf(buf, sizeof(buf), "%d", nr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_chanwords()
+{
+  static wordstats *ws;
+  char buf[6];
+  int nr = 0;
+
+  if (!glob_globstats)
+    return;
+  do_globwordstats(glob_globstats);
+  for (ws = glob_globstats->words; ws; ws = ws->next)
+    nr++;
+  snprintf(buf, sizeof(buf), "%d", nr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_userwords()
+{
+  wordstats *ws;
+  char buf[6];
+  int nr = 0;
+
+  if (!glob_locstats)
+    return;
+  for (ws = glob_locstats->words; ws; ws = ws->next)
+    nr++;
+  snprintf(buf, sizeof(buf), "%d", nr);
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_value()
+{
+  int i, nr;
+  char buf[30];
+  wordstats *ws;
+  
+  if (!glob_locstats || (glob_timerange == T_ERROR) || !glob_toptype)
+    return;
+  i = typetoi(glob_toptype);
+  if (i == T_MINUTES)
+    snprintf(buf, sizeof(buf), "%s", stats_duration(glob_locstats->values[glob_timerange][i] * 60, 2));
+  else if (i >= 0)
+    snprintf(buf, sizeof(buf), "%li", glob_locstats->values[glob_timerange][i]);
+  else if (i == T_WPL) {
+    if (glob_locstats->values[glob_timerange][T_LINES])
+      snprintf(buf, sizeof(buf), "%.2f",
+              ((float) glob_locstats->values[glob_timerange][T_WORDS])
+                / ((float) glob_locstats->values[glob_timerange][T_LINES]));
+  } else if (i == T_IDLE) {
+    if (glob_locstats->values[glob_timerange][T_LINES])
+      snprintf(buf, sizeof(buf), "%.2f",
+              ((float) glob_locstats->values[glob_timerange][T_MINUTES])
+                / ((float) glob_locstats->values[glob_timerange][T_LINES]));
+  } else if (i == T_VOCABLES) {
+    nr = 0;
+    for (ws = glob_locstats->words; ws; ws = ws->next)
+      nr++;
+    snprintf(buf, sizeof(buf), "%d", nr);
+  } else {
+    debug1("invalid type: %s", glob_toptype);
+    snprintf(buf, sizeof(buf), "ERROR: '%s'", glob_toptype);
+  }
+  strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+}
+
+static void slang_send_lastspoke()
+{
+  char buf[20];
+
+  if (glob_locstats) {
+    debug1("%d", glob_locstats->lastspoke);
+    snprintf(buf, sizeof(buf), "%s", stats_duration(now - glob_locstats->lastspoke, 2));
+    strncat(slang_text_buf, buf, sizeof(slang_text_buf));
+  }
+}
+
+static struct slang_text_commands slang_text_stats_command_table[] =
+{
+  {"nick", slang_send_nick},
+  {"bot", slang_send_bot},
+  {"topnr", slang_send_topnr},
+  {"graphnr", slang_send_graphnr},
+  {"peak", slang_send_peak},
+  {"totalusers", slang_send_totalusers},
+  {"chanstarted", slang_send_chanstarted},
+  {"chan", slang_send_chan},
+  {"user", slang_send_user},
+  {"nick", slang_send_nick},
+  {"sorting", slang_send_sorting},
+  {"range", slang_send_range},
+  {"active_users", slang_send_active_users},
+  {"word", slang_send_word},
+  {"place", slang_send_place},
+  {"password", slang_send_password},
+  {"botnick", slang_send_botnick},
+  {"server_host", slang_send_server_host},
+  {"server_port", slang_send_server_port},
+  {"topic_by", slang_send_topic_by},
+  {"topic_when", slang_send_topic_when},
+  {"url_by", slang_send_url_by},
+  {"url_when", slang_send_url_when},
+  {"random_urls", slang_send_random_urls},
+  {"random_kicks", slang_send_random_kicks},
+  {"chanwords", slang_send_chanwords},
+  {"userwords", slang_send_userwords},
+  {"value", slang_send_value},
+  {"lastspoke", slang_send_lastspoke},
+  {0, 0}
+};

+ 202 - 0
core/slang_text.c

@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_text {
+  struct slang_text *next;
+  char *string;
+  void (*command) ();
+};
+
+struct slang_text_commands {
+  char *command;
+  void (*targetfunc) ();
+};
+
+struct slang_command_list {
+  struct slang_command_list *next;
+  struct slang_text_commands *commands;
+};
+
+static struct slang_text *slang_text_parse(char *);
+static struct slang_text *slang_text_create(struct slang_text *);
+static void slang_text_add_string(struct slang_text *, char *);
+static void slang_text_add_command(struct slang_text *, char *);
+static void slang_text_free(struct slang_text *);
+//static int slang_text_expmem(struct slang_text *);
+static char *slang_text_get(struct slang_text *);
+#ifndef SLANG_NOTYPES
+static int slang_text_strcasecmp(struct slang_text *, char *);
+#endif
+
+static struct slang_text *slang_text_parse(char *text)
+{
+  char *cmdstart, *cmdend;
+  struct slang_text *firstitem, *item;
+
+  firstitem = slang_text_create(NULL);
+  item = firstitem;
+  while ((cmdstart = strstr(text, "<?"))) {
+    cmdstart[0] = 0;
+    slang_text_add_string(item, text);
+    item = slang_text_create(item);
+    text += 2;
+    cmdstart += 2;
+    cmdend = strstr(cmdstart, "/?>");
+    if (!cmdend) {
+      putlog(LOG_MISC, "*", "ERROR parsing slang text: unterminated command \"%s\"!", cmdstart);
+      break;
+    }
+    cmdend[0] = 0;
+    slang_text_add_command(item, cmdstart);
+    item = slang_text_create(item);
+    text = cmdend + 3;
+  }
+  slang_text_add_string(item, text);
+  return firstitem;
+}
+
+static struct slang_text *slang_text_create(struct slang_text *where)
+{
+  struct slang_text *newpart;
+
+  newpart = nmalloc(sizeof(struct slang_text));
+  newpart->next = NULL;
+  newpart->string = NULL;
+  newpart->command = NULL;
+  while (where && where->next)
+    where = where->next;
+  if (where)
+    where->next = newpart;
+  return newpart;
+}
+
+static void slang_text_add_string(struct slang_text *item, char *s)
+{
+  Assert(item);
+  Assert(!item->string);
+  item->string = nmalloc(strlen(s) + 1);
+  strcpy(item->string, s);
+}
+
+static void slang_text_free(struct slang_text *item)
+{
+  if (!item)
+    return;
+  slang_text_free(item->next);
+  if (item->string)
+    nfree(item->string);
+  nfree(item);
+}
+
+/*static int slang_text_expmem(struct slang_text *item)
+{
+  int size = 0;
+
+  while (item) {
+    size += sizeof(struct slang_text);
+    if (item->string)
+      size += strlen(item->string) + 1;
+    item = item->next;
+  }
+  return size;
+}*/
+
+#ifndef SLANG_NOTYPES
+static int slang_text_strcasecmp(struct slang_text *item, char *text)
+{
+  Assert(item);
+  debug2("s_t_sc: '%s', '%s'", text, item->string);
+  if (item->command || item->next)
+    return 1;
+  return strcasecmp(item->string, text);
+}
+#endif
+
+static char slang_text_buf[500];
+static char *slang_text_get(struct slang_text *item)
+{
+  slang_text_buf[0] = 0;
+  while (item) {
+    if (item->string)
+      strncat(slang_text_buf, item->string, sizeof(slang_text_buf));
+    else if (item->command)
+      item->command();
+    item = item->next;
+  }
+  return slang_text_buf;
+}
+
+/*****************************************************/
+
+
+static struct slang_command_list *glob_slang_cmd_list;
+
+static struct slang_command_list *slang_commands_list_add(struct slang_command_list *where, struct slang_text_commands *what)
+{
+  struct slang_command_list *newcommandlist;
+
+  newcommandlist = nmalloc(sizeof(struct slang_command_list));
+  newcommandlist->commands = what;
+  newcommandlist->next = where;
+  return newcommandlist;
+}
+
+/*
+static int slang_commands_list_expmem(struct slang_command_list *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct slang_command_list);
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void slang_commands_list_free(struct slang_command_list *what)
+{
+  struct slang_command_list *next;
+
+  while (what) {
+    next = what->next;
+    nfree(what);
+    what = next;
+  }
+}
+
+static void slang_text_add_command(struct slang_text *item, char *s)
+{
+  struct slang_command_list *cmdlist;
+  char *cmd;
+  int i;
+
+  cmd = newsplit(&s);
+  i = 0;
+  for (cmdlist = glob_slang_cmd_list; cmdlist; cmdlist = cmdlist->next) {
+    for (i = 0; 1; i++) {
+      if (!cmdlist->commands[i].command)
+        break;
+      if (!strcasecmp(cmdlist->commands[i].command, cmd)) {
+        item->command = cmdlist->commands[i].targetfunc;
+        return;
+      }
+    }
+  }
+  putlog(LOG_MISC, "*", "ERROR! Unknown slang-command: '%s'", cmd);
+}

+ 98 - 0
core/slang_types.c

@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct slang_type {
+  struct slang_type *next;
+  char *type;
+  struct slang_multitext *mtext;
+};
+
+static struct slang_type *slang_type_add(struct slang_type *, char *, char *);
+//static int slang_type_expmem(struct slang_type *);
+static void slang_type_free(struct slang_type *);
+static char *slang_type_get(struct slang_type *, char *);
+
+static struct slang_type *slang_type_add(struct slang_type *where, char *type, char *text)
+{
+  struct slang_type *newitem;
+
+  newitem = NULL;
+  if (where) {
+    for (newitem = where; newitem; newitem = newitem->next)
+      if (!strcasecmp(newitem->type, type))
+        break;
+  }
+  if (!newitem) {
+    newitem = nmalloc(sizeof(struct slang_type));
+    newitem->type = nmalloc(strlen(type) + 1);
+    strcpy(newitem->type, type);
+    newitem->mtext = NULL;
+    if (where)
+      newitem->next = where;
+    else
+      newitem->next = NULL;
+    where = newitem;
+  }
+  newitem->mtext = slang_mtext_add(newitem->mtext, text);
+  return where;
+}
+
+/*static int slang_type_expmem(struct slang_type *what)
+{
+  int size = 0;
+
+  for (; what; what = what->next) {
+    size += sizeof(struct slang_type);
+    size += strlen(what->type) + 1;
+    size += slang_multitext_expmem(what->mtext);
+  }
+  return size;
+}*/
+
+static void slang_type_free(struct slang_type *what)
+{
+  struct slang_type *next;
+
+  while (what) {
+    next = what->next;
+    slang_multitext_free(what->mtext);
+    nfree(what->type);
+    nfree(what);
+    what = next;
+  }
+}
+
+static char *slang_type_get(struct slang_type *where, char *type)
+{
+  while (where) {
+    if (!strcasecmp(where->type, type))
+      return slang_multitext_getrandomtext(where->mtext);
+    where = where->next;
+  }
+  return NULL;
+}
+
+static char *slang_type_slang2type(struct slang_type *where, char *slang)
+{
+  Assert(slang);
+  while (where) {
+    if (slang_multitext_find(where->mtext, slang))
+      return where->type;
+  }
+  return NULL;
+}

+ 171 - 0
core/templates.c

@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include "templates_commands.c"
+#include "templates_content.c"
+#include "templates_standard_commands.c"
+#include "templates_skin.c"
+#include "templates_template.c"
+
+static struct template_skin *skins;
+
+/* init_templates()
+ * initializes some global variables
+ */
+static void init_templates()
+{
+  skins = NULL;
+  glob_tpl_cmd_list = NULL;
+  glob_tpl_cmd_list = templates_commands_list_add(glob_tpl_cmd_list, templates_standard_commands);
+}
+
+/* unload_templates()
+ * removes every template-related stuff from memory
+ */
+static void unload_templates()
+{
+  Context;
+  templates_skin_free(skins);
+  templates_commands_list_free(glob_tpl_cmd_list);
+  skins = NULL;
+  Context;
+}
+
+/* expmem_templates():
+ * returns the memory usage of the template-system
+ */
+/*
+static int expmem_templates()
+{
+  int size = 0;
+
+  Context;
+  size += templates_skin_expmem(skins);
+  size += templates_commands_list_expmem(glob_tpl_cmd_list);
+  Context;
+  return size;
+}
+*/
+
+static int loadskin(char *parbuf)
+{
+  FILE *f;
+  char *buf, *s, *cmd, *str_skin, *name, *filename, *shortname, *longname;
+  char *conffile, *path, *filebuf;
+  struct template_skin *skin;
+  struct template_content *content;
+  struct slang_header *slang;
+  int len;
+
+  f = fopen(parbuf, "r");
+  if (!f) {
+    putlog(LOG_MISC, "*", "ERROR loading skin! Couldn't open config "
+           "file '%s'!", parbuf);
+    return 0;
+  }
+  debug1("parbuf: '%s'", parbuf);
+  conffile = inverted_csplit(&parbuf, '/');
+  path = parbuf;
+  debug2("path: '%s', conffile: '%s'", path, conffile);
+  if (!path[0]) {
+    path = ".";
+    debug1("empty path, new path: %s", path);
+  }
+  skin = NULL;
+  buf = nmalloc(2000);
+  while (!feof(f)) {
+    s = buf;
+    if (fgets(s, 2000, f)) {
+      // at first, kill those stupid line feeds and carriage returns...
+      if (s[strlen(s) - 1] == '\n')
+        s[strlen(s) - 1] = 0;
+      if (s[strlen(s) - 1] == '\r')
+        s[strlen(s) - 1] = 0;
+      if (!s[0])
+        continue;
+      cmd = newsplit(&s);
+      if (!strcasecmp(cmd, "skin")) {
+        str_skin = newsplit(&s);
+        debug2("adding skin '%s' (%s)", str_skin, s);
+        skins = templates_skin_add(skins, str_skin, s);
+        skin = templates_skin_find(skins, str_skin);
+        if (!skin) {
+          putlog(LOG_MISC, "*", "ERROR loading skin: unknown error creating skin structure!");
+          fclose(f);
+          nfree(buf);
+          return 0;
+        }
+      } else if (!strcasecmp(cmd, "template")) {
+        name = newsplit(&s);
+        filename = newsplit(&s);
+        if (!name[0] || !filename[0]) {
+          putlog(LOG_MISC, "*", "ERROR loading template: Too few parameters!");
+          continue;
+        }
+        len = strlen(path) + 1 + strlen(filename) + 1;
+        filebuf = nmalloc(len);
+        snprintf(filebuf, len, "%s/%s", path, filename);
+        putlog(LOG_MISC, "*", "Loading template '%s' from '%s'...", name, filebuf);
+        content = templates_content_load(filebuf);
+        nfree(filebuf);
+        if (!content) {
+          putlog(LOG_MISC, "*", "ERROR loading template from '%s'!", filename);
+          continue;
+        }
+        skin->templates = templates_template_add_parsedcontent(skin->templates,
+                                                                name, content);
+      } else if (!strcasecmp(cmd, "slang")) {
+        filename = newsplit(&s);
+        shortname = newsplit(&s);
+        longname = s;
+        if (!shortname[0] || !longname[0] || !filename[0]) {
+          putlog(LOG_MISC, "*", "ERROR loading slang for skin '%s': too few "
+                 "parameters!", skin->name);
+          continue;
+        }
+        skin->slang = slang_create(skin->slang, shortname, longname);
+        slang = slang_find(skin->slang, shortname);
+        len = strlen(path) + 1 + strlen(filename) + 1;
+        filebuf = nmalloc(len);
+        snprintf(filebuf, len, "%s/%s", path, filename);
+        if (!slang_load(slang, filebuf)) {
+          putlog(LOG_MISC, "*", "ERROR loading slang for skin '%s'",
+                 skin->name);
+          nfree(filebuf);
+          continue;
+        }
+        nfree(filebuf);
+      }
+    }
+  }
+  nfree(buf);
+  return 1;
+}
+
+static void template_send(struct template_skin *skin, char *name, int idx)
+{
+  struct templates_template *tpl;
+
+  Assert(skin);
+  tpl = templates_template_find(skin->templates, name);
+  if (!tpl) {
+    dprintf(idx, "<H1>Template not found: %s</H1>", name);
+    return;
+  }
+  templates_content_send(tpl->contents, idx);
+}

+ 76 - 0
core/templates.h

@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+// template content struct. Stores the content (html and command pointers)
+// of an template
+struct template_content {
+  struct template_content *next;
+  char *html;
+  void (*command) (int, struct template_content *);
+  int what;
+  char *charpar1;
+  float floatpar1;
+  float floatpar2;
+  int intpar1;
+  struct template_content *subcontent;
+};
+
+struct template_commands {
+  char *command;
+  void (*targetfunc) (int, struct template_content *);
+  void (*addfunc) (struct template_content *header, struct llist_2string *params, char *included_text);
+};
+
+struct template_command_list {
+  struct template_command_list *next;
+  struct template_commands *commands;
+};
+
+// template header struct. Stores the name and a pointer to the content
+// of an template.
+struct templates_template {
+  struct templates_template *next;
+  char *name;
+  struct template_content *contents;
+};
+
+// template skin struct
+// contains the name of the skin and a pointer to the language-list
+struct template_skin {
+  struct template_skin *next;
+  char *name;
+  char *desc;
+  struct slang_header *slang;
+  struct templates_template *templates;
+};
+
+static struct templates_template *templates_template_add_parsedcontent(struct templates_template *, char *, struct template_content *);
+//static int templates_template_expmem(struct templates_template *);
+static void templates_template_free(struct templates_template *);
+
+//static int templates_content_expmem(struct template_content *);
+static void templates_content_free(struct template_content *);
+static struct template_content *templates_content_load(char *);
+static struct template_content *templates_content_create();
+static struct template_content *templates_content_append(struct template_content *, struct template_content *);
+static struct template_content *templates_content_parse(char *);
+static struct template_content *templates_content_addhtml(struct template_content *, char *);
+static void templates_content_send(struct template_content *, int);
+static struct llist_2string *templates_content_parseparams(char *);
+
+static void template_send(struct template_skin *, char *, int);

+ 89 - 0
core/templates_commands.c

@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+struct template_command_list *glob_tpl_cmd_list;
+
+static struct template_command_list *templates_commands_list_add(struct template_command_list *, struct template_commands *);
+//static int templates_commands_list_expmem(struct template_command_list *);
+static void templates_commands_list_free(struct template_command_list *);
+static struct template_content *templates_commands_addtocontent(struct template_content *, char *, struct llist_2string *, char *);
+
+static struct template_command_list *templates_commands_list_add(struct template_command_list *where, struct template_commands *what)
+{
+  struct template_command_list *newcommandlist;
+
+  newcommandlist = nmalloc(sizeof(struct template_command_list));
+  newcommandlist->commands = what;
+  newcommandlist->next = where;
+  return newcommandlist;
+}
+
+/*
+static int templates_commands_list_expmem(struct template_command_list *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct template_command_list);
+    what = what->next;
+  }
+  return size;
+}*/
+
+static void templates_commands_list_free(struct template_command_list *what)
+{
+  struct template_command_list *next;
+
+  while (what) {
+    next = what->next;
+    nfree(what);
+    what = next;
+  }
+}
+
+static struct template_content *templates_commands_addtocontent(struct template_content *where,
+                                            char *command,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  struct template_command_list *clist;
+  struct template_commands *cmd;
+  struct template_content *newcontent;
+  int len, i;
+
+  newcontent = templates_content_create();
+  where = templates_content_append(where, newcontent);
+  for (clist = glob_tpl_cmd_list; clist; clist = clist->next) {
+    cmd = clist->commands;
+    for (i = 0; 1; i++) {
+      if (!cmd[i].command)
+        break;
+      if (!strcasecmp(command, cmd[i].command)) {
+        newcontent->command = cmd[i].targetfunc;
+        if (cmd[i].addfunc)
+          cmd[i].addfunc(newcontent, params, included_text);
+        return where;
+      }
+    }
+  }
+  len = strlen(command) + 34 + 1;
+  newcontent->html = nmalloc(len);
+  snprintf(newcontent->html, len, "<H1>Unknown Tag: &quot;%s&quot;</H1>", command);
+  putlog(LOG_MISC, "*", "ERROR loading template: Unknown command '%s'!", command);
+  return where;
+}

+ 258 - 0
core/templates_content.c

@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/*
+static int templates_content_expmem(struct template_content *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct template_content);
+    if (what->html)
+      size += strlen(what->html) + 1;
+    if (what->charpar1)
+      size += strlen(what->charpar1) + 1;
+    size += templates_content_expmem(what->subcontent);
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void templates_content_free(struct template_content *what)
+{
+  struct template_content *next;
+
+  while (what) {
+    next = what->next;
+    if (what->html)
+      nfree(what->html);
+    if (what->charpar1)
+      nfree(what->charpar1);
+    templates_content_free(what->subcontent);
+    nfree(what);
+    what = next;
+  }
+}
+
+#define TEMPLATE_LINE_LENGTH 1024
+static struct template_content *templates_content_load(char *filename)
+{
+  FILE *f;
+  char buf[TEMPLATE_LINE_LENGTH + 1], *contentstring;
+  struct template_content *content;
+
+  Context;
+  // at first, load the whole file into a buffer
+  f = fopen(filename, "r");
+  if (f == NULL) {
+    putlog(LOG_MISC, "*", "Couldn't open template from %s!", filename);
+    return NULL;
+  }
+  contentstring = nmalloc(1);
+  contentstring[0] = 0;
+  while (!feof(f)) {
+    if (fgets(buf, TEMPLATE_LINE_LENGTH, f)) {
+      buf[TEMPLATE_LINE_LENGTH] = 0;
+      contentstring = nrealloc(contentstring, strlen(contentstring) + strlen(buf) + 1);
+      strcat(contentstring, buf);
+    }
+  }
+  fclose(f);
+  // now process the content
+  content = templates_content_parse(contentstring);
+  nfree(contentstring);
+  return content;
+}
+
+static struct template_content *templates_content_create()
+{
+  struct template_content *newcontent;
+
+  newcontent = nmalloc(sizeof(struct template_content));
+  newcontent->next = NULL;
+  newcontent->html = NULL;
+  newcontent->command = NULL;
+  newcontent->what = 0;
+  newcontent->charpar1 = NULL;
+  newcontent->floatpar1 = 0.0;
+  newcontent->floatpar2 = 0.0;
+  newcontent->intpar1 = 0;
+  newcontent->subcontent = NULL;
+  return newcontent;
+}
+
+static struct template_content *templates_content_append(struct template_content *where, struct template_content *what)
+{
+  struct template_content *c;
+
+  if (what->next)
+    debug0("WARNING in templates_content_append(): what->next does exist!");
+  for (c = where; c && c->next; c = c->next);
+  if (c)
+    c->next = what;
+  else {
+    Assert(!where);
+    where = what;
+  }
+  return where;
+}
+
+/* template_parse_content():
+ * parse the content and return a pointer to the filled content struct
+ */
+static struct template_content *templates_content_parse(char *buf)
+{
+  char *s, *cmdstart, *cmdend, *cmd, *included_text, *end_tag;
+  char tag_buf[100];
+  struct llist_2string *params;
+  int need_end_tag;
+  struct template_content *content;
+
+  Context;
+  content = NULL;
+  while ((s = strstr(buf, "<?"))) {
+    // initialize variables
+    need_end_tag = 1;
+    included_text = cmdstart = cmd = cmdend = end_tag = NULL;
+    params = NULL;
+
+    // cut the tag from the leading text
+    s[0] = 0;
+    s += 2;
+
+    content = templates_content_addhtml(content, buf);
+
+    cmdstart = buf = s;
+
+    // and find the end of the tag
+    cmdend = strstr(cmdstart, "?>");
+    if (!cmdend) {
+      putlog(LOG_MISC, "*", "ERROR parsing template: tag not terminated! (%s)", cmdstart);
+      continue;
+    }
+    cmdend[0] = 0;
+    s = buf = cmdend + 2;
+
+    // if the command isn't really a command, but a comment, then
+    // just skip it.
+    if (!strncmp(cmdstart, "--", 2))
+      continue;
+
+    // check if we need a seperate end-tag, or if the tag is already terminated
+    // (following XML-style)
+    if (cmdstart[strlen(cmdstart) - 1] == '/') {
+      need_end_tag = 0;
+      cmdstart[strlen(cmdstart) - 1] = 0;
+    }
+
+    // now get the name of the command.
+    cmd = newsplit(&cmdstart);
+
+    // find the ending tag if needed...
+    if (need_end_tag) {
+      included_text = s;
+      snprintf(tag_buf, sizeof(tag_buf), "<?/%s?>", (cmd[0] == '!') ? cmd + 1 : cmd);
+      end_tag = strstr(s, tag_buf);
+      if (!end_tag) {
+        putlog(LOG_MISC, "*", "ERROR parsing template: end-tag (%s) not found!", tag_buf);
+        continue;
+      }
+      end_tag[0] = 0;
+      s = buf = end_tag + strlen(tag_buf);
+    }
+
+    // if this is just a comment or an disabled tag, then don't parse or store it all all.
+    if (!strcmp(cmd, "comment") || (cmd[0] == '!'))
+      continue;
+
+    // parse the parameters
+    params = templates_content_parseparams(cmdstart);
+
+    // and finally add the tag with parameters and included text to
+    // our template
+    content = templates_commands_addtocontent(content, cmd, params, included_text);
+
+    // now free the params again...
+    llist_2string_free(params);
+  }
+
+  // append all remaining html code
+  content = templates_content_addhtml(content, buf);
+
+  return content;
+}
+
+static struct template_content *templates_content_addhtml(struct template_content *where, char *html)
+{
+  struct template_content *newcontent;
+
+  Assert(html);
+  newcontent = templates_content_create();
+  newcontent->html = nmalloc(strlen(html) + 1);
+  strcpy(newcontent->html, html);
+  where = templates_content_append(where, newcontent);
+  return where;
+}
+
+static struct llist_2string *templates_content_parseparams(char *buf)
+{
+  struct llist_2string *params;
+  char *name, *value, *s;
+
+  Assert(buf);
+  params = NULL;
+
+  while (buf[0]) {
+    while (buf[0] == ' ')
+      buf++;
+    s = buf;
+    name = csplit(&buf, '=');
+    if (buf[0] != '"') {
+      putlog(LOG_MISC, "*", "ERROR parsing parameters: missing '\"'! (%s)", name);
+      continue;
+    }
+    buf++;
+    value = buf;
+    while (buf[0]) {
+      if (buf[0] == '"')
+        break;
+      buf++;
+    }
+    if (buf[0] != '"') {
+      putlog(LOG_MISC, "*", "ERROR parsing parameters: missing '\"'! (%s)", name);
+      continue;
+    }
+    buf[0] = 0;
+    buf++;
+    params = llist_2string_add(params, name, value);
+  }
+  return params;
+}
+
+static void templates_content_send(struct template_content *tpc, int idx)
+{
+  for (;tpc; tpc = tpc->next) {
+    if (tpc->html)
+      dprintf(idx, "%s", tpc->html);
+    else if (tpc->command)
+      tpc->command(idx, tpc);
+    else
+      dprintf(idx, "<H1>ERROR: No content!</H1>");
+  }
+}

+ 47 - 0
core/templates_httpd_commands.c

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void template_send_requested_url(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "%s", http_connection(idx)->path);
+}
+
+static void template_send_server_version(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "EggdropMiniHTTPd/%s", HTTPD_VERSION);
+}
+
+static void template_send_server_port(int idx, struct template_content *tpc)
+{
+  int i;
+
+  for (i = 0; i < dcc_total; i++) {
+    if (dcc[i].type == &MHTTPD_CON_HTTPD) {
+      dprintf(idx, "%d", dcc[i].port);
+      return;
+    }
+  }
+}
+
+struct template_commands template_httpd_commands[] =
+{
+  {"requested_url", template_send_requested_url, NULL},
+  {"server_version", template_send_server_version, NULL},
+  {"server_port", template_send_server_port, NULL},
+  {0, 0, 0},
+};

+ 104 - 0
core/templates_skin.c

@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static struct template_skin *templates_skin_add(struct template_skin *, char *, char *);
+//static int templates_skin_expmem(struct template_skin *);
+static void templates_skin_free(struct template_skin *);
+static struct template_skin *templates_skin_find(struct template_skin *list, char *name);
+
+static struct template_skin *templates_skin_add(struct template_skin *where, char *name, char *desc)
+{
+  struct template_skin *newskin;
+
+  for (newskin = where; newskin; newskin = newskin->next)
+    if (!strcasecmp(newskin->name, name))
+      break;
+  if (!newskin) {
+    newskin = nmalloc(sizeof(struct template_skin));
+    newskin->next = NULL;
+    newskin->name = NULL;
+    newskin->desc = NULL;
+    newskin->slang = NULL;
+    newskin->templates = NULL;
+    newskin->name = nmalloc(strlen(name) + 1);
+    strcpy(newskin->name, name);
+    if (desc) {
+      newskin->desc = nmalloc(strlen(desc) + 1);
+      strcpy(newskin->desc, desc);
+    }
+    newskin->next = where;
+    where = newskin;
+  } else {
+    // update description
+    if (newskin->desc) {
+      nfree(newskin->desc);
+      newskin->desc = NULL;
+    }
+    if (desc) {
+      newskin->desc = nmalloc(strlen(desc) + 1);
+      strcpy(newskin->desc, desc);
+    }
+  }
+  return where;
+}
+
+/*
+static int templates_skin_expmem(struct template_skin *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct template_skin);
+    Assert(what->name);
+    size += strlen(what->name) + 1;
+    if (what->desc)
+      size += strlen(what->desc) + 1;
+    size += slang_expmem(what->slang);
+    size += templates_template_expmem(what->templates);
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void templates_skin_free(struct template_skin *what)
+{
+  struct template_skin *next;
+
+  while (what) {
+    next = what->next;
+    templates_template_free(what->templates);
+    slang_free(what->slang);
+    Assert(what->name);
+    nfree(what->name);
+    if (what->desc)
+      nfree(what->desc);
+    nfree(what);
+    what = next;
+  }
+}
+
+static struct template_skin *templates_skin_find(struct template_skin *list, char *name)
+{
+  while (list) {
+    if (!strcasecmp(list->name, name))
+      return list;
+    list = list->next;
+  }
+  return NULL;
+}

+ 101 - 0
core/templates_standard_commands.c

@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static void template_add_subcontent(struct template_content *content,
+                                    struct llist_2string *params,
+                                    char *included_text)
+{
+  Assert(!content->subcontent);
+  if (included_text)
+    content->subcontent = templates_content_parse(included_text);
+}
+
+/* template_send_module_version():
+ * sends the module version (surprise!)
+ */
+static void template_send_module_version(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "%s", MODULE_VERSION);
+}
+
+/* <?slang #?>
+ * outputs a line from the slangfile
+ */
+static void template_send_slang(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "%s", getslang(tpc->intpar1));
+}
+
+static void template_add_cmd_slang(struct template_content *content,
+                                   struct llist_2string *params,
+                                   char *included_text)
+{
+  for (; params; params = params->next)
+    if (!strcasecmp(params->s1, "id"))
+      content->intpar1 = atoi(params->s2);
+}
+
+static void template_add_cmd_template(struct template_content *content,
+                                   struct llist_2string *params,
+                                   char *included_text)
+{
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "name")) {
+      content->charpar1 = nmalloc(strlen(params->s2) + 1);
+      strcpy(content->charpar1, params->s2);
+      return;
+    }
+  }
+  putlog(LOG_MISC, "*", "ERROR: missing parameter for template tag!");
+}
+
+static void template_send_template(int idx, struct template_content *tpc)
+{
+  if (tpc->charpar1)
+    template_send(glob_skin, tpc->charpar1, idx);
+}
+
+static void template_send_form_method(int idx, struct template_content *tpc)
+{
+  if (get_param_value(idx, "dontpost"))
+    dprintf(idx, "GET");
+  else
+    dprintf(idx, "POST");
+}
+
+static void template_send_if_dontpost(int idx, struct template_content *tpc)
+{
+  if (get_param_value(idx, "dontpost"))
+    templates_content_send(tpc->subcontent, idx);
+}
+
+static void template_send_botnick(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "%s", botname);
+}
+
+struct template_commands templates_standard_commands[] =
+{
+  {"module_version", template_send_module_version, NULL},
+  {"slang", template_send_slang, template_add_cmd_slang},
+  {"template", template_send_template, template_add_cmd_template},
+  {"form_method", template_send_form_method, NULL},
+  {"if_dontpost", template_send_if_dontpost, template_add_subcontent},
+  {"botnick", template_send_botnick, NULL},
+  {0, 0, 0},
+};

+ 1483 - 0
core/templates_stats_commands.c

@@ -0,0 +1,1483 @@
+/*
+ * Copyright (C) 2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static float glob_r, glob_g, glob_b, glob_rstep, glob_gstep, glob_bstep;
+
+#define CF_TOP -1
+#define CF_TOPICS -2
+#define CF_MISCFACTS -3
+#define CF_USERS -4
+#define CF_ONCHAN -5
+
+
+/* template_init_colorfade()
+ * see template_add_cmd_init_colorfade for description
+ */
+static void template_init_colorfade(int idx, struct template_content *htpc)
+{
+  int wert, steps;
+  float r2, b2, g2;
+  memberlist *m;
+  struct chanset_t *chan;
+  topicstr *topic;
+  int ret;
+
+  // find out how many steps the color fade will have
+  steps = htpc->intpar1;
+  // if it has 0 steps, then the number of steps wasn't specified as parameter,
+  // so we'll just use the number of results
+  if (steps == CF_TOP)
+    steps = webnr;
+  else if (steps == CF_MISCFACTS) {
+    steps = 0;
+    for (ret = selectfirstfact(); ret; ret = selectnextfact())
+      steps++;
+  } else if ((steps == CF_USERS) && glob_globstats)
+    steps = countallstatmembers(glob_globstats);
+  else if ((steps == CF_ONCHAN) && glob_globstats) {
+    steps = 0;
+    chan = findchan_by_dname(glob_globstats->chan);
+    if (chan)
+      for (m = chan->channel.member; m && m->nick[0]; m = m->next)
+        steps++;
+  } else if ((steps == CF_TOPICS) && glob_globstats) {
+    steps = 0;
+    for (topic = glob_globstats->topics; topic; topic = topic->next)
+      steps++;
+  } else if (steps < 1)
+    steps = 1;
+  // split our r/g/b values of the starting color (stored in intpar1)
+  wert = htpc->floatpar1;
+  glob_b = wert & 0xff; glob_g = (wert & 0xff00) >> 8; glob_r = (wert & 0xff0000) >> 16;
+  // now do the same with the target color (intpar2)
+  wert = htpc->floatpar2;
+  b2 = wert & 0xff; g2 = (wert & 0xff00) >> 8; r2 = (wert & 0xff0000) >> 16;
+  // finally, determine the "length" of a step between colors
+  glob_rstep = (r2 - glob_r) / steps;
+  glob_gstep = (g2 - glob_g) / steps;
+  glob_bstep = (b2 - glob_b) / steps;
+  // all global variables are now initialized and can be used.
+}
+
+/* template_add_cmd_init_colorfade():
+ * Parameters: <startcolor> <endcolor> [steps]
+ * initialiazes a color-fade from <startcolor> to <endcolor>
+ * in [steps] steps. (if steps isn't defined, numresults is used)
+ */
+static void template_add_cmd_init_colorfade(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  char *startcolor, *endcolor, *steps;
+  float istartcolor, iendcolor, isteps;
+
+  Context;
+  startcolor = endcolor = steps = "";
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "startcolor"))
+      startcolor = params->s2;
+    else if (!strcasecmp(params->s1, "endcolor"))
+      endcolor = params->s2;
+    else if (!strcasecmp(params->s1, "steps"))
+      steps = params->s2;
+    else
+      putlog(LOG_MISC, "*", "ERROR parsing templates: Unknown parameter '%s' "
+             "for command init_color_fade.", params->s1);
+  }
+  istartcolor = strtol(startcolor, NULL, 0);
+  iendcolor = strtol(endcolor, NULL, 0);
+  if (!istartcolor)
+    istartcolor = table_color;
+  if (!iendcolor)
+    iendcolor = fade_table_to;
+  if (!strcasecmp(steps, "toplist"))
+    isteps = CF_TOP;
+  else if (!strcasecmp(steps, "topics"))
+    isteps = CF_TOPICS;
+  else if (!strcasecmp(steps, "miscfacts"))
+    isteps = CF_MISCFACTS;
+  else if (!strcasecmp(steps, "users"))
+    isteps = CF_USERS;
+  else if (!strcasecmp(steps, "onchan"))
+    isteps = CF_ONCHAN;
+  else
+    isteps = strtol(steps, NULL, 0);
+  // now write a pointer to the executing command and all parameters into our
+  // content structure
+  h_tpc->command = template_init_colorfade;
+  h_tpc->floatpar1 = istartcolor;
+  h_tpc->floatpar2 = iendcolor;
+  h_tpc->intpar1 = isteps;
+}
+
+/* template_send_fcolor():
+ * outputs the current color-code
+ */
+static void template_send_fcolor(int idx, struct template_content *htpc)
+{
+  dprintf(idx, "#%02x%02x%02x", (int) glob_r, (int)  glob_g, (int) glob_b);
+}
+
+/* template_fade_color():
+ * fades the color one step further
+ */
+static void template_fade_color(int idx, struct template_content *htpc)
+{
+  glob_r += glob_rstep;
+  glob_g += glob_gstep;
+  glob_b += glob_bstep;
+}
+
+/* <?chanlist ...?>
+ * outputs the list of channels
+ */
+static void template_send_chanlist(int idx, struct template_content *h_tpc)
+{
+  globstats *gs;
+
+  Context;
+  for (gs = sdata; gs; gs = gs->next) {
+	if (!egg_chan_active(gs->chan))
+	  continue;
+/*    if (!list_secret_chans && secretchan(gs->chan))
+      continue; */
+/*    if (inactivechan(gs->chan))
+      continue;
+      */
+    glob_globstats = gs;
+    debug0("lang für skins nicht vergessen!");
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+/* <?chan?>
+ * outputs the current chan
+ */
+
+static void template_send_chan(int idx, struct template_content *tpc)
+{
+  char *chan;
+
+  if (glob_globstats) {
+    if (tpc->floatpar1 && glob_globstats->chan[0] == '#')
+      chan = glob_globstats->chan + 1;
+    else
+      chan = glob_globstats->chan;
+    dprintf(idx, "%s", tpc->intpar1 ? encode_url(chan) : chan);
+  }
+}
+
+/* <?encoded_chan?>
+ * outputs the current channel, but in an encoded form, so that
+ * it can be used in URLs
+ */
+/*
+static void template_send_encoded_chan(int idx, struct template_content *tpc)
+{
+  if (glob_globstats) {
+    if (glob_globstats->chan[0] == '#')
+      dprintf(idx, "%s", encode_url(glob_globstats->chan + 1));
+    else
+      dprintf(idx, "%s", encode_url(glob_globstats->chan));
+  }
+}
+*/
+
+/* <?topnr?>
+ * outputs how many users get listed in the topX
+ */
+static void template_send_topnr(int idx, struct template_content *tpc)
+{
+  dprintf(idx, "%d", webnr);
+}
+
+/* <?current_topic?>
+ * outputs the topic of the current chan (if there is any)
+ */
+static void template_send_current_topic(int idx, struct template_content *tpc)
+{
+  struct chanset_t *chan;
+
+  if (glob_globstats) {
+    chan = findchan_by_dname(glob_globstats->chan);
+    if (chan && chan->channel.topic)
+      dprintf(idx, "%s", text2html(chan->channel.topic));
+  }
+}
+
+/* <?if_total ...?>
+ * <?if_daily ...?>
+ * <?if_weekly ...?>
+ * <?if_monthly ...?>
+ * outputs its subcontent if the current timerange is
+ * TOTAL/DAILY/WEEKLY/MONTHLY
+ */
+static void template_send_if_total(int idx, struct template_content *h_tpc)
+{
+  if (glob_timerange == S_TOTAL)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_daily(int idx, struct template_content *h_tpc)
+{
+  if (glob_timerange == S_DAILY)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_weekly(int idx, struct template_content *h_tpc)
+{
+  if (glob_timerange == S_WEEKLY)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_monthly(int idx, struct template_content *h_tpc)
+{
+  if (glob_timerange == S_MONTHLY)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+/* <?topstats ...?>
+ * sends the list of stats which should be shown in the topX
+ */
+static void template_send_topstats(int idx, struct template_content *h_tpc)
+{
+  char buf[512], *pbuf;
+
+  Context;
+  strncpy(buf, webstats, sizeof(buf));
+  pbuf = buf;
+  while (pbuf[0]) {
+    glob_toptype = newsplit(&pbuf);
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+/* <?toplist ...?>
+ * sends the top X users
+ */
+static void template_send_toplist(int idx, struct template_content *h_tpc)
+{
+  locstats *ls;
+  int sort;
+
+  Context;
+  Assert((glob_timerange >= 0) && (glob_timerange <= 3));
+  if (glob_sorting == T_ERROR) {
+    dprintf(idx, "<H1>ERROR! Invalid sorting!</H1>");
+    return;
+  }
+  if (glob_sorting < 0)
+    sort = (glob_sorting * -1) + (TOTAL_TYPES - 1);
+  else
+    sort = glob_sorting;
+  debug0("überprüfen, ob toplist rekursions-fähig ist");
+  glob_place = 0;
+  for (ls = glob_globstats->slocal[glob_timerange][sort]; ls; ls = ls->snext[glob_timerange][sort]) {
+    if (glob_place > glob_top_end)
+      break;
+    // skip users who shouldn't be listed (.schattr user -list)
+    if (!listsuser(ls, glob_globstats->chan))
+      continue;
+    // break if the value is 0, because we probably reached the end
+    // of the sorted list (who want's to see the stats of 1000 users who
+    // don't have any stats in the sorted value?)
+    if ((glob_sorting >= 0) && !ls->values[glob_timerange][glob_sorting])
+      break;
+    glob_place++;
+    if (glob_place < glob_top_start)
+      continue;
+    glob_locstats = ls;
+    if (!ls->u)
+      ls->u = findsuser_by_name(ls->user);
+    glob_user = ls->u;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_type(int idx, struct template_content *h_tpc)
+{
+  if (h_tpc->intpar1)
+    dprintf(idx, "%s", getslangtype(glob_toptype));
+  else
+    dprintf(idx, "%s", glob_toptype);
+}
+
+static void template_add_cmd_type(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  Context;
+  for (; params; params = params->next)
+    if (!strcasecmp(params->s1, "slang") && !strcasecmp(params->s2, "yes"))
+      h_tpc->intpar1 = 1;
+}
+
+static void template_send_place(int idx, struct template_content *h_tpc)
+{
+  dprintf(idx, "%d", glob_place);
+}
+
+static void template_add_cmd_chan_user(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  Context;
+  for (; params; params = params->next)
+    if (!strcasecmp(params->s1, "encode") && !strcasecmp(params->s2, "yes"))
+      h_tpc->intpar1 = 1;
+    else if (!strcasecmp(params->s1, "short") && !strcasecmp(params->s2, "yes"))
+      h_tpc->floatpar1 = 1.0;
+}
+
+static void template_send_user(int idx, struct template_content *h_tpc)
+{
+  if (glob_locstats)
+    dprintf(idx, "%s", h_tpc->intpar1 ? encode_url(glob_locstats->user) : glob_locstats->user);
+  else if (glob_user)
+    dprintf(idx, "%s", h_tpc->intpar1 ? encode_url(glob_user->user) : glob_user->user);
+}
+
+static void template_send_value(int idx, struct template_content *h_tpc)
+{
+  int i;
+  if (!glob_locstats || (glob_timerange == T_ERROR) || !glob_toptype) {
+    if (!glob_locstats)
+      debug0("no locstats");
+    if (!glob_timerange == T_ERROR)
+      debug0("no timerange");
+    if (!glob_toptype)
+      debug0("no toptype");
+    return;
+  }
+  i = typetoi(glob_toptype);
+  if (i == T_MINUTES)
+    dprintf(idx, "%s", stats_duration(glob_locstats->values[glob_timerange][i] * 60, 2));
+  else if (i >= 0)
+    dprintf(idx, "%d", glob_locstats->values[glob_timerange][i]);
+  else if (i == T_WPL) {
+    if (glob_locstats->values[glob_timerange][T_LINES])
+      dprintf(idx, "%.2f",
+              ((float) glob_locstats->values[glob_timerange][T_WORDS])
+                / ((float) glob_locstats->values[glob_timerange][T_LINES]));
+  } else if (i == T_IDLE) {
+    if (glob_locstats->values[glob_timerange][T_LINES])
+      dprintf(idx, "%.2f",
+              ((float) glob_locstats->values[glob_timerange][T_MINUTES])
+                / ((float) glob_locstats->values[glob_timerange][T_LINES]));
+  } else
+    debug1("invalid type: %s", glob_toptype);
+}
+
+static void template_send_channel_load(int idx, struct template_content *h_tpc)
+{
+  float umax, uonep, aonep, f;
+  int i, amax;
+
+  Context;
+  if (!glob_globstats)
+    return;
+  umax = 0.0;
+  amax = 0;
+  for (i = 0; i < 24; i++) {
+    if (glob_globstats->users[S_USERCOUNTS][i] > 0)
+      if ((((float) glob_globstats->users[S_USERSUM][i]) / ((float) glob_globstats->users[S_USERCOUNTS][i])) > umax)
+        umax = ((float) glob_globstats->users[S_USERSUM][i]) / ((float) glob_globstats->users[S_USERCOUNTS][i]);
+    if (glob_globstats->activity[i] > amax)
+      amax = glob_globstats->activity[i];
+  }
+  uonep = umax / 100.0;
+  aonep = amax / 100.0;
+  for (i = 0; i < 24; i++) {
+    glob_cl_timerange = i;
+    glob_activity = glob_globstats->activity[i];
+    if (glob_globstats->users[S_USERCOUNTS][i] > 0) {
+      f = ((float) glob_globstats->users[S_USERSUM][i]) / ((float) glob_globstats->users[S_USERCOUNTS][i]);
+      glob_au_users = f;
+      glob_au_percent = (int) (f / uonep);
+    } else {
+      glob_au_users = -1.0;
+      glob_au_percent = -1;
+    }
+    if (glob_activity >= 0)
+      glob_activity_percent = (int) (glob_globstats->activity[i] / aonep);
+    else
+      glob_activity_percent = 0;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_au_users(int idx, struct template_content *h_tpc)
+{
+  if (glob_au_percent >= 0)
+    dprintf(idx, "%.1f", glob_au_users);
+}
+
+static void template_send_if_cl_logged(int idx, struct template_content *h_tpc)
+{
+  if (glob_au_percent != -1)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_au_value(int idx, struct template_content *h_tpc)
+{
+  if (!h_tpc->intpar1)
+    return;
+  dprintf(idx, "%d", (int) (((float) (glob_au_percent / 100.0)) * ((float) h_tpc->intpar1)));
+}
+
+static void template_add_cmd_au_value(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  int max = 100;
+
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "max"))
+      max = atoi(params->s2);
+  }
+  if (!max)
+    max = 100;
+  h_tpc->intpar1 = max;
+}
+
+static void template_send_activity_value(int idx, struct template_content *h_tpc)
+{
+  if (!h_tpc->intpar1)
+    return;
+  dprintf(idx, "%d", (int) (((float) (glob_activity_percent / 100.0)) * ((float) h_tpc->intpar1)));
+}
+
+static void template_send_activity(int idx, struct template_content *h_tpc)
+{
+  dprintf(idx, "%d", glob_activity);
+}
+
+static void template_send_if_urls(int idx, struct template_content *h_tpc)
+{
+  if (log_urls)
+    if (glob_globstats)
+      if (glob_globstats->urls)
+        templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_random_urls(int idx, struct template_content *h_tpc)
+{
+  int urls, nr, i;
+  unsigned long x;
+
+  if (!glob_globstats || !log_urls)
+    return;
+  urls = 0;
+  for (glob_url = glob_globstats->urls; glob_url; glob_url = glob_url->next) {
+    urls++;
+    glob_url->shown = 0;
+  }
+  if (!urls)
+    return;
+  Assert(glob_globstats->urls);
+  if (urls > log_urls)
+    nr = log_urls;
+  else
+    nr = urls;
+  while (nr > 0) {
+    x = random() % nr;
+    i = 0;
+    for (glob_url = glob_globstats->urls; glob_url; glob_url = glob_url->next) {
+      if (glob_url->shown)
+        continue;
+      if (i == x) {
+        glob_url->shown = 1;
+        templates_content_send(h_tpc->subcontent, idx);
+        urls--;
+      }
+      i++;
+    }
+    nr--;
+  }
+}
+
+static void template_send_url(int idx, struct template_content *h_tpc)
+{
+  if (glob_url)
+    dprintf(idx, "%s", glob_url->url);
+}
+
+static void template_send_if_hosts(int idx, struct template_content *h_tpc)
+{
+  if (glob_globstats)
+    if (glob_globstats->hosts)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_hosts(int idx, struct template_content *h_tpc)
+{
+  globstats *tlds, *isps;
+  hoststr *h;
+  char *host;
+  char *s;
+  int i;
+
+  if (!glob_globstats)
+    return;
+  if (!glob_globstats->hosts)
+    return;
+  tlds = nmalloc(sizeof(globstats));
+  tlds->hosts = NULL;
+  isps = nmalloc(sizeof(globstats));
+  isps->hosts = NULL;
+  for (h = glob_globstats->hosts; h; h = h->next) {
+	Assert(h->host);
+    // skip IPv6 hosts
+    if (strchr(h->host, ':'))
+      continue;
+    host = strrchr(h->host, '.') + 1;
+    // skip other unusable hosts
+    if (!((host - 1) && host)) {
+      debug1("[stats.mod] Skipping host-stats for '%s'.", h->host);
+      continue;
+    }
+    if (!atoi(host) && (host[0] != '0')) {
+      addhost(host, tlds);
+      host = h->host;
+      while ((s = strchr(host, '.')) && strchr(s + 1, '.'))
+        host = s + 1;
+      addhost(host, isps);
+    } else {
+      addhost("[IP]", tlds);
+      addhost("[IP]", isps);
+    }
+  }
+  sorthosts(isps);
+  sorthosts(tlds);
+  i = 0;
+  glob_isp = isps->hosts;
+  glob_tld = tlds->hosts;
+  while ((i <= 5) && (glob_isp || glob_tld)) {
+    i++;
+    templates_content_send(h_tpc->subcontent, idx);
+    if (glob_isp)
+      glob_isp = glob_isp->next;
+    if (glob_tld)
+      glob_tld = glob_tld->next;
+  }
+  free_hosts(isps->hosts);
+  free_hosts(tlds->hosts);
+  nfree(isps);
+  nfree(tlds);
+}
+
+static void template_send_isp(int idx, struct template_content *h_tpc)
+{
+  if (glob_isp)
+    dprintf(idx, "%s", glob_isp->host);
+}
+
+static void template_send_ispnr(int idx, struct template_content *h_tpc)
+{
+  if (glob_isp)
+    dprintf(idx, "%d", glob_isp->nr);
+}
+
+static void template_send_tld(int idx, struct template_content *h_tpc)
+{
+  if (glob_tld)
+    dprintf(idx, "%s", glob_tld->host);
+}
+
+static void template_send_tldnr(int idx, struct template_content *h_tpc)
+{
+  if (glob_tld)
+    dprintf(idx, "%d", glob_tld->nr);
+}
+
+static void template_send_if_kicks(int idx, struct template_content *h_tpc)
+{
+  if (glob_globstats)
+    if (glob_globstats->kicks)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_random_kicks(int idx, struct template_content *h_tpc)
+{
+  int nr, i, kicks;
+  struct stats_kick *kick;
+  unsigned long x;
+
+  if (!glob_globstats)
+    return;
+  if (!glob_globstats->kicks)
+    return;
+  nr = 0;
+  for (kick = glob_globstats->kicks; kick; kick = kick->next) {
+    nr++;
+    kick->shown = 0;
+  }
+  if (nr > display_kicks)
+    kicks = display_kicks;
+  else
+    kicks = nr;
+  while (kicks > 0) {
+    x = random() % nr;
+    i = 0;
+    for (glob_kick = glob_globstats->kicks; glob_kick; glob_kick = glob_kick->next) {
+      if (glob_kick->shown)
+        continue;
+      if (i == x) {
+        glob_kick->shown = 1;
+        templates_content_send(h_tpc->subcontent, idx);
+        break;
+      }
+      i++;
+    }
+    nr--;
+    kicks--;
+  }
+}
+
+/* <?kick_contexts ..?>
+ * sends the last few loglines before a kick occured.
+ * the log of the actual kick is _not_ sent with this command
+ */
+static void template_send_kick_contexts(int idx, struct template_content *h_tpc)
+{
+  if (!glob_kick)
+    return;
+  for (glob_kick_context = glob_kick->log; glob_kick_context && glob_kick_context->next; glob_kick_context = glob_kick_context->next)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_kick_context(int idx, struct template_content *h_tpc)
+{
+  if (glob_kick_context)
+    dprintf(idx, "%s", text2html(glob_kick_context->quote));
+}
+
+static void template_send_kick(int idx, struct template_content *h_tpc)
+{
+  struct stats_quote *k;
+
+  if (!glob_kick)
+    return;
+  for (k = glob_kick->log; k && k->next; k = k->next);
+  dprintf(idx, "%s", text2html(k->quote));
+}
+
+static void template_send_if_topics(int idx, struct template_content *h_tpc)
+{
+  if (glob_globstats)
+    if (glob_globstats->topics)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_topiclist(int idx, struct template_content *h_tpc)
+{
+  if (glob_globstats)
+    for (glob_topic = glob_globstats->topics; glob_topic; glob_topic = glob_topic->next)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_topic(int idx, struct template_content *h_tpc)
+{
+  if (glob_topic)
+    dprintf(idx, "%s", text2html(glob_topic->topic));
+}
+
+static void template_send_topic_set_by(int idx, struct template_content *h_tpc)
+{
+  if (glob_topic)
+    dprintf(idx, "%s", glob_topic->by);
+}
+
+static void template_send_topic_set_when(int idx, struct template_content *h_tpc)
+{
+  char ts[21];
+
+  if (glob_topic) {
+    strftime(ts, 20, "%H:%M", localtime(&glob_topic->when));
+    dprintf(idx, "%s", ts);
+  }
+}
+
+static void template_send_if_chan_topwords(int idx, struct template_content *h_tpc)
+{
+  locstats *ls;
+
+  if (glob_globstats) {
+    for (ls = glob_globstats->local; ls; ls = ls->next) {
+      if (ls->words) {
+        templates_content_send(h_tpc->subcontent, idx);
+        return;
+      }
+    }
+  }
+}
+
+static void template_send_chan_topwords(int idx, struct template_content *h_tpc)
+{
+  if (!glob_globstats)
+    return;
+  do_globwordstats(glob_globstats);
+  glob_wordplace = 0;
+  for (glob_wordstats = glob_globstats->words;
+       glob_wordstats && (glob_wordplace < 10);
+       glob_wordstats = glob_wordstats->next) {
+    glob_wordplace++;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_if_user_topwords(int idx, struct template_content *h_tpc)
+{
+  if (glob_locstats)
+    if (glob_locstats->words)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_user_topwords(int idx, struct template_content *h_tpc)
+{
+  if (!glob_locstats)
+    return;
+  sortwordstats(glob_locstats, NULL);
+  glob_wordplace = 0;
+  for (glob_wordstats = glob_locstats->words;
+       glob_wordstats && (glob_wordplace < 10);
+       glob_wordstats = glob_wordstats->next) {
+    glob_wordplace++;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_wordplace(int idx, struct template_content *h_tpc)
+{
+  dprintf(idx, "%d", glob_wordplace);
+}
+
+static void template_send_word(int idx, struct template_content *h_tpc)
+{
+  if (glob_wordstats)
+    dprintf(idx, "%s", text2html(glob_wordstats->word));
+}
+
+static void template_send_wordnr(int idx, struct template_content *h_tpc)
+{
+  if (glob_wordstats)
+    dprintf(idx, "%d", glob_wordstats->nr);
+}
+
+static void template_send_miscfacts(int idx, struct template_content *h_tpc)
+{
+/*  struct slang_lang *l;
+  int itype, pitype;
+  locstats *ls;
+
+  if (!glob_globstats)
+    return;
+  // at first, find the current language
+  for (l = slangs; l; l = l->next) {
+    if ((!l->lang && !slgloblang) || (l->lang && slgloblang && !strcasecmp(l->lang, slgloblang))) {
+      // now cycle through all fact-types
+      for (glob_fact = l->bignumbers; glob_fact; glob_fact = glob_fact->next) {
+        itype = typetoi(glob_fact->type);
+        if (itype < 0)
+          pitype = (itype * -1) + (TOTAL_TYPES - 1);
+        else
+          pitype = itype;
+        sortstats(glob_globstats, itype, S_DAILY);
+        ls = glob_globstats->slocal[S_DAILY][pitype];
+        if (!ls)
+          continue;
+        else if ((itype >= 0) && !ls->values[S_DAILY][itype])
+          continue;
+        else if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS] || !ls->values[S_DAILY][T_LINES]))
+          continue;
+        else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES] || !ls->values[S_DAILY][T_LINES]))
+          continue;
+        else if ((itype == T_VOCABLES) && !ls->vocables)
+          continue;
+        templates_content_send(h_tpc->subcontent, idx);
+      }
+    }
+  }*/
+  int ret;
+
+  for (ret = selectfirstfact(); ret; ret = selectnextfact())
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_fact(int idx, struct template_content *h_tpc)
+{
+/*  unsigned long x;
+  int itype, pitype, iplace;
+  locstats *ls;
+  struct slang_texts *txt;
+  struct slang_bnplaces *place;
+
+  if (!glob_fact || !glob_globstats)
+    return;
+  itype = typetoi(glob_fact->type);
+  sortstats(glob_globstats, itype, S_DAILY);
+  // now find the desired place
+  for (place = glob_fact->places; place; place = place->next)
+    if (place->place == h_tpc->intpar1)
+      break;
+  if (!place)
+    return;
+  if (itype < 0)
+    pitype = (itype * -1) + (TOTAL_TYPES - 1);
+  else
+    pitype = itype;
+  ls = glob_globstats->slocal[S_DAILY][pitype];
+  // now find the user who who is on the specified place
+  iplace = 1;
+  while (ls && (iplace < place->place)) {
+    iplace++;
+    ls = ls->snext[S_DAILY][pitype];
+  }
+  // just don't output anything if there's no useful data
+  if (!ls)
+    return;
+  else if ((itype >= 0) && !ls->values[S_DAILY][itype])
+    return;
+  else if ((itype == T_WPL) && (!ls->values[S_DAILY][T_WORDS] || !ls->values[S_DAILY][T_LINES]))
+    return;
+  else if ((itype == T_IDLE) && (!ls->values[S_DAILY][T_MINUTES] || !ls->values[S_DAILY][T_LINES]))
+    return;
+  else if ((itype == T_VOCABLES) && !ls->vocables)
+    return;
+  slgloblocstats = ls;
+  slglobtype = glob_fact->type;
+  x = random() % place->entries;
+  txt = place->texts;
+  while (txt) {
+    if (!x)
+      dprintf(idx, "%s\n", dynamicslang(txt->text));
+    x--;
+    txt = txt->next;
+  }
+*/
+  int place;
+  char *fact;
+
+  place = h_tpc->intpar1;
+  if (!place)
+    return;
+  fact = getfact(place);
+  if (fact)
+    dprintf(idx, "%s", fact);
+}
+
+
+/* template_add_cmd_fact():
+ * Parameters: <place>
+ */
+static void template_add_cmd_fact(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  int place;
+
+  Context;
+  place = 0;
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "place"))
+      place = atoi(params->s2);
+  }
+  if (!place) {
+    putlog(LOG_MISC, "*", "Error parsing template: Invalid parameter for \"<?fact <place>?>\"!");
+    place = 1;
+  }
+  h_tpc->command = template_send_fact;
+  h_tpc->intpar1 = place;
+}
+
+static void template_send_userlist(int idx, struct template_content *h_tpc)
+{
+  if (!glob_globstats)
+    return;
+  sort_stats_alphabetically(glob_globstats);
+  for (glob_locstats = glob_globstats->local; glob_locstats; glob_locstats = glob_locstats->next)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_timeranges(int idx, struct template_content *h_tpc)
+{
+  for (glob_timerange = 0; glob_timerange <= 3; glob_timerange++)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_timerange(int idx, struct template_content *h_tpc)
+{
+  dprintf(idx, "%s", RANGESTR_LONG(glob_timerange));
+}
+
+static void template_send_tplace(int idx, struct template_content *h_tpc)
+{
+  locstats *ls;
+  int place = 0;
+
+  if (!glob_globstats || !glob_locstats)
+    return;
+  sortstats(glob_globstats, T_WORDS, glob_timerange);
+  for (ls = glob_globstats->slocal[glob_timerange][T_WORDS]; ls; ls = ls->snext[glob_timerange][T_WORDS]) {
+    if (!listsuser(ls, glob_globstats->chan))
+      continue;
+    place++;
+    if (ls == glob_locstats)
+      break;
+  }
+  if (ls)
+    dprintf(idx, "%d", place);
+  else
+    dprintf(idx, "-");
+}
+
+static void template_send_if_quote(int idx, struct template_content *h_tpc)
+{
+  if (glob_locstats)
+    if (glob_locstats->quotes)
+      templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_random_quote(int idx, struct template_content *h_tpc)
+{
+  int nr, i;
+  quotestr *qs;
+  unsigned long x;
+
+  if (!glob_locstats)
+    return;
+  if (!glob_locstats->quotes)
+    return;
+  nr = 0;
+  for (qs = glob_locstats->quotes; qs; qs = qs->next)
+    nr++;
+  x = random() % nr;
+  i = 0;
+  qs = glob_locstats->quotes;
+  while (qs) {
+    if (i == x) {
+      dprintf(idx, "%s", text2html(qs->quote));
+      return;
+    }
+    qs = qs->next;
+    i++;
+  }
+}
+
+static void template_send_onchanlist(int idx, struct template_content *h_tpc)
+{
+  struct stats_member *m;
+  struct stats_chan *chan;
+
+  if (!show_usersonchan || !glob_globstats)
+    return;
+  chan = schan_find(glob_globstats->chan);
+  if (!chan)
+    return;
+  for (m = schan_members_getfirst(&chan->members); m ; m = schan_members_getnext(&chan->members)) {
+    glob_statsmember = m;
+    glob_user = m->user;
+    if (m->stats)
+      glob_locstats = m->stats;
+    else
+      glob_locstats = NULL;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_usermode(int idx, struct template_content *h_tpc)
+{
+  if (glob_statsmember && glob_statsmember->eggmember) {
+#ifndef NO_EGG
+    if (chan_hasop(glob_statsmember->eggmember))
+      dprintf(idx, "@");
+    if (chan_hasvoice(glob_statsmember->eggmember))
+      dprintf(idx, "+");
+#endif
+  }
+}
+
+static void template_send_if_user(int idx, struct template_content *h_tpc)
+{
+  if (glob_locstats)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_nouser(int idx, struct template_content *h_tpc)
+{
+  if (!glob_locstats)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_idletime(int idx, struct template_content *h_tpc)
+{
+  if (!glob_statsmember)
+    return;
+#ifndef NO_EGG
+  if (glob_statsmember->eggmember && chan_issplit(glob_statsmember->eggmember))
+    return;
+#endif
+  dprintf(idx, "%s", stats_duration(now - glob_statsmember->last, 2));
+}
+
+static void template_send_if_netsplitted(int idx, struct template_content *h_tpc)
+{
+#ifndef NO_EGG
+  if (!glob_statsmember || !glob_statsmember->eggmember)
+    return;
+  if (chan_issplit(glob_statsmember->eggmember))
+    templates_content_send(h_tpc->subcontent, idx);
+#endif
+}
+
+static void template_send_nick(int idx, struct template_content *h_tpc)
+{
+  if (glob_statsmember)
+    dprintf(idx, "%s", glob_statsmember->nick);
+}
+
+static void template_send_graphstats(int idx, struct template_content *h_tpc)
+{
+  char buf[512], *pbuf;
+  locstats ls;
+
+  Context;
+  if (!glob_globstats || (glob_timerange == T_ERROR))
+    return;
+  locstats_init(&ls);
+  strncpy(buf, graphstats, sizeof(buf));
+  pbuf = buf;
+  while (pbuf[0]) {
+    glob_toptype = newsplit(&pbuf);
+    glob_sorting = slangtypetoi(glob_toptype);
+    if (glob_sorting < 0)
+      continue;
+    sortstats(glob_globstats, glob_sorting, glob_timerange);
+    glob_graph_total = gettotal(glob_globstats, glob_sorting, glob_timerange);
+    ls.values[glob_timerange][glob_sorting] = glob_graph_total;
+    glob_locstats = &ls;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+static void template_send_graphs(int idx, struct template_content *h_tpc)
+{
+  float percent, wpercent, onep;
+  locstats *ls;
+  int max;
+
+  if (!glob_globstats || (glob_sorting == T_ERROR) ||
+      (glob_timerange == T_ERROR) || (glob_sorting < 0))
+    return;
+  max = 0;
+  for (ls = glob_globstats->slocal[glob_timerange][glob_sorting];
+       ls;
+       ls = ls->snext[glob_timerange][glob_sorting]) {
+    if (listsuser(ls, glob_globstats->chan)) {
+      max = ls->values[glob_timerange][glob_sorting];
+      break;
+    }
+  }
+  if (!glob_graph_total || !max)
+    return;
+  onep = (float) glob_graph_total / 100.0;
+  glob_place = 0;
+  for (ls = glob_globstats->slocal[glob_timerange][glob_sorting];
+       ls;
+       ls = ls->snext[glob_timerange][glob_sorting]) {
+    if (!listsuser(ls, glob_globstats->chan))
+      continue;
+    if (!ls->values[glob_timerange][glob_sorting])
+      break;
+    glob_place++;
+    if (glob_place > graphnr)
+      break;
+    percent = (float) ls->values[glob_timerange][glob_sorting] / onep;
+    wpercent = (float) ls->values[glob_timerange][glob_sorting] / ((float) max / 100.0);
+    glob_graph_percent = percent;
+    glob_graph_width_percent = wpercent;
+    glob_locstats = ls;
+    templates_content_send(h_tpc->subcontent, idx);
+  }
+}
+
+/* static void template_send_graph_filled_percent(int idx, struct template_content *h_tpc)
+ *{
+ *  dprintf(idx, "%d%%", (int) (glob_graph_width_percent * 0.8));
+ *}
+ */
+
+static void template_send_graph_percent(int idx, struct template_content *h_tpc)
+{
+  if (!h_tpc->intpar1)
+    dprintf(idx, "%.2f", glob_graph_percent);
+  else
+    dprintf(idx, "%d", (int) (((float) (glob_graph_width_percent / 100.0)) * ((float) h_tpc->intpar1)));
+}
+
+static void template_add_cmd_graph_percent(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  int max = 100, stretch = 0;
+
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "max"))
+      max = atoi(params->s2);
+    if (!strcasecmp(params->s1, "stretch") && !strcasecmp(params->s2, "true"))
+      stretch = 1;
+  }
+  if (!max)
+    max = 100;
+  if (stretch)
+    h_tpc->intpar1 = max;
+  else
+    h_tpc->intpar1 = 0;
+}
+
+/*static void template_send_graph_label_percent(int idx, struct template_content *h_tpc)
+ *{
+ *  dprintf(idx, "%d%%", 100 - ((int) (glob_graph_width_percent * 0.8)));
+ *}
+ *
+ *static void template_send_graph_exact_percent(int idx, struct template_content *h_tpc)
+ *{
+ *  dprintf(idx, "%.02f%%", glob_graph_percent);
+ *}
+ */
+
+static void template_send_password(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && glob_user->password)
+    dprintf(idx, "%s", glob_user->password);
+}
+
+static void template_send_email(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && glob_user->email)
+    dprintf(idx, "%s", glob_user->email);
+}
+
+static void template_send_homepage(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && glob_user->homepage)
+    dprintf(idx, "%s", glob_user->homepage);
+}
+
+static void template_send_icqnr(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && glob_user->icqnr)
+    dprintf(idx, "%d", glob_user->icqnr);
+}
+
+static void template_send_if_icqnr(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_icqnr)");
+    return;
+  }
+  if (glob_user->icqnr)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_email(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_email)");
+    return;
+  }
+  if (glob_user->email)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_homepage(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_homepage)");
+    return;
+  }
+  if (glob_user->homepage)
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_listuser(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_listuser)");
+    return;
+  }
+  if (suser_list(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_addhosts(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_addhosts)");
+    return;
+  }
+  if (suser_addhosts(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_not_listuser(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_listuser)");
+    return;
+  }
+  if (!suser_list(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_not_addhosts(int idx, struct template_content *h_tpc)
+{
+  if (!glob_user) {
+    debug0("WARNING: No globuser (if_addhosts)");
+    return;
+  }
+  if (!suser_addhosts(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_nostats(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && suser_nostats(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_not_nostats(int idx, struct template_content *h_tpc)
+{
+  if (glob_user && !suser_nostats(glob_user))
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_login_error(int idx, struct template_content *h_tpc)
+{
+  if (h_tpc->intpar1)
+    dprintf(idx, "%s", getslang(h_tpc->intpar1 + glob_loginerror));
+}
+
+static void template_add_cmd_login_error(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  char *s_baseid;
+
+  s_baseid = llist_2string_get_s2(params, "baseid");
+  if (s_baseid)
+    h_tpc->intpar1 = atoi(s_baseid);
+}
+
+static void template_send_binary_url(int idx, struct template_content *h_tpc)
+{
+  dprintf(idx, "%s", binary_url);
+}
+
+static void template_send_if_binary(int idx, struct template_content *h_tpc)
+{
+  if (binary_url[0])
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_if_nobinary(int idx, struct template_content *h_tpc)
+{
+  if (!binary_url[0])
+    templates_content_send(h_tpc->subcontent, idx);
+}
+
+static void template_send_memberage(int idx, struct template_content *h_tpc)
+{
+  int max, age, maxage;
+  float onep, percent;
+
+  if (glob_locstats && !h_tpc->intpar1)
+    dprintf(idx, "%s", stats_duration(now - glob_locstats->started, 3));
+  else if (glob_locstats && glob_globstats) {
+    max = h_tpc->intpar1;
+    maxage = (int) h_tpc->floatpar1 * 60 * 60 * 24;
+    if ((now - glob_globstats->started) < maxage)
+      maxage = (now - glob_globstats->started);
+    age = now - glob_locstats->started;
+
+    onep = (float) maxage / (float) max;
+    percent = (float) age / onep;
+
+    dprintf(idx, "%d", (int) percent);
+  }
+}
+
+static void template_add_cmd_memberage(struct template_content *h_tpc,
+                                            struct llist_2string *params,
+                                            char *included_text)
+{
+  int relative = 0, max = 100, maxage = 0;
+
+  for (; params; params = params->next) {
+    if (!strcasecmp(params->s1, "max"))
+      max = atoi(params->s2);
+    if (!strcasecmp(params->s1, "relative") &&
+        (!strcasecmp(params->s2, "true") || !strcasecmp(params->s2, "yes")))
+      relative = 1;
+    if (!strcasecmp(params->s1, "maxage"))
+      maxage = atoi(params->s2);
+  }
+  if (!max)
+    max = 100;
+  if (relative)
+    h_tpc->intpar1 = max;
+  else
+    h_tpc->intpar1 = 0;
+  h_tpc->floatpar1 = (float) maxage;
+}
+
+static void template_send_types(int idx, struct template_content *h_tpc)
+{
+  char *buffer, *buf, *oldtype;
+
+  oldtype = glob_toptype; /* backup glob_toptype and pray that we don't
+				the old value during the cycle */
+  buffer = nmalloc(strlen(TYPES) + 1);
+  strcpy(buffer, TYPES);
+  buf = buffer;
+  for (glob_toptype = newsplit(&buf); glob_toptype[0];
+  	glob_toptype = newsplit(&buf))
+    templates_content_send(h_tpc->subcontent, idx);
+  nfree(buffer);
+  buffer = nmalloc(strlen(SPECIAL_TYPES) + 1);
+  strcpy(buffer, SPECIAL_TYPES);
+  buf = buffer;
+  for (glob_toptype = newsplit(&buf); glob_toptype[0];
+  	glob_toptype = newsplit(&buf))
+    templates_content_send(h_tpc->subcontent, idx);
+  nfree(buffer);
+  glob_toptype = oldtype;
+}
+
+static void template_send_sorting(int idx, struct template_content *h_tpc)
+{
+  if (glob_sorting != T_ERROR)
+    dprintf(idx, "%s", itotype(glob_sorting));
+}
+
+static void template_send_userage(int idx, struct template_content *h_tpc)
+{
+  if (glob_user)
+    dprintf(idx, "%s", stats_duration((now - glob_user->created), 3));
+}
+
+static void template_send_timetolive(int idx, struct template_content *h_tpc)
+{
+  if (glob_user)
+    dprintf(idx, "%s", stats_duration(TIMETOLIVE(glob_user), 2));
+}
+
+struct template_commands stats_template_commands[] =
+{
+  {"init_colorfade", template_init_colorfade, template_add_cmd_init_colorfade},
+  {"fcolor", template_send_fcolor, NULL},
+  {"fade_color", template_fade_color, NULL},
+  {"chanlist", template_send_chanlist, template_add_subcontent},
+  {"chan", template_send_chan, template_add_cmd_chan_user},
+//  {"encoded_chan", template_send_encoded_chan, NULL},
+  {"current_topic", template_send_current_topic, NULL},
+  {"topnr", template_send_topnr, NULL},
+  {"if_total", template_send_if_total, template_add_subcontent},
+  {"if_daily", template_send_if_daily, template_add_subcontent},
+  {"if_weekly", template_send_if_weekly, template_add_subcontent},
+  {"if_monthly", template_send_if_monthly, template_add_subcontent},
+  {"topstats", template_send_topstats, template_add_subcontent},
+  {"toplist", template_send_toplist, template_add_subcontent},
+  {"type", template_send_type, template_add_cmd_type},
+  {"types", template_send_types, template_add_subcontent},
+  {"place", template_send_place, NULL},
+  {"user", template_send_user, template_add_cmd_chan_user},
+  {"value", template_send_value, NULL},
+  {"if_urls", template_send_if_urls, template_add_subcontent},
+  {"random_urls", template_send_random_urls, template_add_subcontent},
+  {"url", template_send_url, NULL},
+  {"if_hosts", template_send_if_hosts, template_add_subcontent},
+  {"hosts", template_send_hosts, template_add_subcontent},
+  {"isp", template_send_isp, NULL},
+  {"ispnr", template_send_ispnr, NULL},
+  {"tld", template_send_tld, NULL},
+  {"tldnr", template_send_tldnr, NULL},
+  {"if_kicks", template_send_if_kicks, template_add_subcontent},
+  {"random_kicks", template_send_random_kicks, template_add_subcontent},
+  {"kick_contexts", template_send_kick_contexts, template_add_subcontent},
+  {"kick_context", template_send_kick_context, NULL},
+  {"kick", template_send_kick, NULL},
+  {"if_topics", template_send_if_topics, template_add_subcontent},
+  {"topiclist", template_send_topiclist, template_add_subcontent},
+  {"topic", template_send_topic, NULL},
+  {"topic_set_by", template_send_topic_set_by, NULL},
+  {"topic_set_when", template_send_topic_set_when, NULL},
+  {"if_chan_topwords", template_send_if_chan_topwords, template_add_subcontent},
+  {"chan_topwords", template_send_chan_topwords, template_add_subcontent},
+  {"if_user_topwords", template_send_if_user_topwords, template_add_subcontent},
+  {"user_topwords", template_send_user_topwords, template_add_subcontent},
+  {"wordplace", template_send_wordplace, NULL},
+  {"word", template_send_word, NULL},
+  {"wordnr", template_send_wordnr, NULL},
+  {"userlist", template_send_userlist, template_add_subcontent},
+  {"timeranges", template_send_timeranges, template_add_subcontent},
+  {"timerange", template_send_timerange, NULL},
+  {"tplace", template_send_tplace, NULL},
+  {"if_quote", template_send_if_quote, template_add_subcontent},
+  {"random_quote", template_send_random_quote, NULL},
+  {"onchanlist", template_send_onchanlist, template_add_subcontent},
+  {"usermode", template_send_usermode, NULL},
+  {"if_user", template_send_if_user, template_add_subcontent},
+  {"if_nouser", template_send_if_nouser, template_add_subcontent},
+  {"idletime", template_send_idletime, NULL},
+  {"if_netsplitted", template_send_if_netsplitted, template_add_subcontent},
+  {"nick", template_send_nick, NULL},
+  {"graphstats", template_send_graphstats, template_add_subcontent},
+  {"graphs", template_send_graphs, template_add_subcontent},
+  {"graph_percent", template_send_graph_percent, template_add_cmd_graph_percent},
+  {"password", template_send_password, NULL},
+  {"email", template_send_email, NULL},
+  {"homepage", template_send_homepage, NULL},
+  {"icqnr", template_send_icqnr, NULL},
+  {"if_icqnr", template_send_if_icqnr, template_add_subcontent},
+  {"if_homepage", template_send_if_homepage, template_add_subcontent},
+  {"if_email", template_send_if_email, template_add_subcontent},
+  {"if_listuser", template_send_if_listuser, template_add_subcontent},
+  {"if_addhosts", template_send_if_addhosts, template_add_subcontent},
+  {"if_not_listuser", template_send_if_not_listuser, template_add_subcontent},
+  {"if_not_addhosts", template_send_if_not_addhosts, template_add_subcontent},
+  {"if_nostats", template_send_if_nostats, template_add_subcontent},
+  {"if_not_nostats", template_send_if_not_nostats, template_add_subcontent},
+  {"login_error", template_send_login_error, template_add_cmd_login_error},
+  {"if_binary", template_send_if_binary, template_add_subcontent},
+  {"if_nobinary", template_send_if_nobinary, template_add_subcontent},
+  {"binary_url", template_send_binary_url, NULL},
+  {"miscfacts", template_send_miscfacts, template_add_subcontent},
+  {"fact", template_send_fact, template_add_cmd_fact},
+  {"activity_value", template_send_activity_value, template_add_cmd_au_value},
+  {"channel_load", template_send_channel_load, template_add_subcontent},
+  {"au_users", template_send_au_users, NULL},
+  {"activity", template_send_activity, NULL},
+  {"au_value", template_send_au_value, template_add_cmd_au_value},
+  {"if_cl_logged", template_send_if_cl_logged, template_add_subcontent},
+  {"memberage", template_send_memberage, template_add_cmd_memberage},
+  {"sorting", template_send_sorting, NULL},
+  {"userage", template_send_userage, NULL},
+  {"timetolive", template_send_timetolive, NULL},
+  {0, 0, 0},
+};

+ 82 - 0
core/templates_template.c

@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static struct templates_template *templates_template_add_parsedcontent(struct templates_template *where, char *name, struct template_content *content)
+{
+  struct templates_template *newtemplate;
+
+  Assert(name);
+  Assert(content);
+  for (newtemplate = where; newtemplate; newtemplate = newtemplate->next)
+    if (!strcmp(newtemplate->name, name))
+      break;
+  if (!newtemplate) {
+    newtemplate = nmalloc(sizeof(struct templates_template));
+    newtemplate->next = NULL;
+    newtemplate->name = NULL;
+    newtemplate->contents = NULL;
+    newtemplate->name = nmalloc(strlen(name) + 1);
+    strcpy(newtemplate->name, name);
+    newtemplate->next = where;
+    where = newtemplate;
+  } else
+    templates_content_free(newtemplate->contents);
+  newtemplate->contents = content;
+  return where;
+}
+
+/*
+static int templates_template_expmem(struct templates_template *what)
+{
+  int size = 0;
+
+  while (what) {
+    size += sizeof(struct templates_template);
+    Assert(what->name);
+    size += strlen(what->name) + 1;
+    size += templates_content_expmem(what->contents);
+    what = what->next;
+  }
+  return size;
+}
+*/
+
+static void templates_template_free(struct templates_template *what)
+{
+  struct templates_template *next;
+
+  while (what) {
+    next = what->next;
+    Assert(what->name);
+    nfree(what->name);
+    templates_content_free(what->contents);
+    nfree(what);
+    what = next;
+  }
+}
+
+static struct templates_template *templates_template_find(struct templates_template *where, char *name)
+{
+  Assert(name);
+  while (where) {
+    if (!strcasecmp(where->name, name))
+      return where;
+    where = where->next;
+  }
+  return NULL;
+}

+ 217 - 0
core/user.c

@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static int stats_checkhand(char *oldnick, char *newnick)
+{
+
+	Context;
+	if (findsuser_by_name(newnick)) {
+		putlog(LOG_MISC, "*", "Stats.mod: %s changed handle to %s which already "
+				"existed in the database. The datasets have been merged.",
+				oldnick, newnick);
+		if (!user_merge(newnick, oldnick))
+			putlog(LOG_MISC, "*", ".. failed!");
+	} else {
+		if (track_stat_user(oldnick, newnick))
+			putlog(LOG_MISC, "*", "Stats.mod: Transferred stats from %s to %s",
+				   oldnick, newnick);
+		else
+			putlog(LOG_MISC, "*", "Stats.mod: Transfer from %s to %s failed!", oldnick, newnick);
+	}
+	Context;
+	return 1;
+}
+
+static cmd_t stats_nkch[] = {
+	{"*", "", (Function) stats_checkhand, "stat:nkch"},
+	{0, 0, 0, 0}
+};
+
+static void deloldstatusers()
+{
+	struct userrec *u;
+	struct stats_userlist *su, *lsu;
+	struct stats_hostlist *h, *lh;
+
+	if (expire_base < 1)
+		return;
+	su = suserlist;
+	lsu = NULL;
+	while (su) {
+		h = su->hosts;
+		lh = NULL;
+		while (h) {
+			if ((now - h->lastused) > TIMETOLIVE(h)) {
+				putlog(LOG_MISC, "*",
+					   "Stats.Mod: %s didn't use the hostmask %s during the last %d days. Removing from hostlist...",
+					   su->user, h->mask, ((now - h->lastused) / 86400));
+				nfree(h->mask);
+				if (lh)
+					lh->next = h->next;
+				else
+					su->hosts = h->next;
+				nfree(h);
+				if (lh)
+					h = lh->next;
+				else
+					h = su->hosts;
+			} else {
+				lh = h;
+				h = h->next;
+			}
+		}
+		if (!su->hosts && (TIMETOLIVE(su) < (now - su->laston))) {
+			u = get_user_by_handle(userlist, su->user);
+			if (u) {
+				lsu = su;
+				su = su->next;
+				continue;
+			}
+			putlog(LOG_MISC, "*",
+				   "Stats.Mod: %s wasn't online since %d days. "
+				   "Deleting stat user...", su->user,
+				   (now - su->laston) / 86400);
+			nfree(su->user);
+			if (lsu)
+				lsu->next = su->next;
+			else
+				suserlist = su->next;
+			weed_userlink_from_chanset(su);
+			weed_userlink_from_locstats(su);
+			nfree(su);
+			if (lsu)
+				su = lsu->next;
+			else
+				su = suserlist;
+		} else {
+			lsu = su;
+			su = su->next;
+		}
+	}
+}
+
+static void purgestats()
+{
+	globstats *gs, *gs2;
+	locstats *ls, *ls2;
+	locstats *sl, *sl2;
+	int i, ii, kill;
+	struct stats_userlist *u;
+	struct userrec *u2;
+	struct stats_chan *chan;
+
+	Context;
+	gs = sdata;
+	gs2 = NULL;
+	while (gs) {
+		chan = schan_find(gs->chan);
+		if (chan && gs->local) {
+			ls = gs->local;
+			ls2 = NULL;
+			while (ls) {
+				kill = 1;
+				u2 = get_user_by_handle(userlist, ls->user);
+				u = findsuser_by_name(ls->user);
+				if (u2 || u) {
+					for (i = 0; i < TOTAL_TYPES; i++) {
+						if (ls->values[S_TOTAL][i] != 0) {
+							kill = 0;
+							break;
+						}
+					}
+					if (!kill) {
+						if (strcmp(ls->user, u->user)) {
+							debug2
+							   ("Stats.mod: Transferred stats from %s to %s",
+								ls->user, u->user);
+							nfree(ls->user);
+							ls->user = nmalloc(strlen(u->user) + 1);
+							strcpy(ls->user, u->user);
+						}
+					}
+				}
+				if (kill) {
+					putlog(LOG_MISC, "*",
+						   "Stats.mod: Deleting stats for %s in %s(empty data or no such user)",
+						   ls->user, gs->chan);
+					for (i = 0; i < 4; i++) {
+						for (ii = 0; ii < (TOTAL_TYPES + TOTAL_SPECIAL_TYPES);
+							 ii++) {
+							sl = gs->slocal[i][ii];
+							sl2 = NULL;
+							while (sl) {
+								if (!rfc_casecmp(sl->user, ls->user))
+									break;
+								sl2 = sl;
+								sl = sl->snext[i][ii];
+							}
+							if (sl) {
+								if (sl2)
+									sl2->snext[i][ii] = sl->snext[i][ii];
+								else
+									gs->slocal[i][ii] = sl->snext[i][ii];
+							} else
+								putlog(LOG_MISC, "*",
+									   "WARNING!!! %s not found in sorted list ([%d][%d])! Corrupted data?",
+									   ls->user, i, ii);
+						}
+					}
+					if (ls2)
+						ls2->next = ls->next;
+					else
+						gs->local = ls->next;
+					nfree(ls->user);
+					free_wordstats(ls->words);
+					free_quotes(ls->quotes);
+					weed_statlink_from_chanset(ls);
+					nfree(ls);
+					if (ls2)
+						ls = ls2->next;
+					else
+						ls = gs->local;
+				} else {
+					ls2 = ls;
+					ls = ls->next;
+				}
+			}
+			gs2 = gs;
+			gs = gs->next;
+		} else {
+			putlog(LOG_MISC, "*",
+				   "Stats.mod: Deleting stats for %s. (no such channel)", gs->chan);
+			if (gs2)
+				gs2->next = gs->next;
+			else
+				sdata = gs->next;
+			free_localstats(gs->local);
+			free_wordstats(gs->words);
+			free_topics(gs->topics);
+			free_urls(gs->urls);
+			free_quotes(gs->log);
+			free_hosts(gs->hosts);
+			free_kicks(gs->kicks);
+			nfree(gs->chan);
+			nfree(gs);
+			if (gs2)
+				gs = gs2->next;
+			else
+				gs = sdata;
+		}
+	}
+	Context;
+}

+ 559 - 0
core/userrec.c

@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/* Find the stats-user that belongs to a hostmask
+*/
+static struct stats_userlist *findsuser(char *host)
+{
+  struct stats_userlist *user, *u;
+  struct stats_hostlist *h, *h2;
+  int len = 0;
+
+  Context;
+  u = NULL;
+  h2 = NULL;
+  for (user = suserlist; user; user = user->next) {
+    for (h = user->hosts; h; h = h->next) {
+      /* the longest hostmask gives the best match */
+      if (!len || (strlen(h->mask) > len)) {
+        if (wild_match(h->mask, host)) {
+          u = user;
+          h2 = h;
+          len = strlen(h->mask);
+        }
+      }
+    }
+  }
+  if (u) {
+    h2->lastused = now;
+    return u;
+  }
+  return NULL;
+}
+
+static struct stats_userlist *findsuser_by_name(char *user)
+{
+  struct stats_userlist *u;
+
+  Context;
+  for (u = suserlist; u; u = u->next)
+    if (!rfc_casecmp(u->user, user))
+      return u;
+  return NULL;
+}
+
+static struct stats_userlist *addsuser(char *user, time_t created, time_t laston)
+{
+  struct stats_userlist *u, *nu;
+
+  Context;
+  for (u = suserlist; u; u = u->next)
+    if (!rfc_casecmp(u->user, user))
+      return u;
+  u = suserlist;
+  while (u && u->next)
+    u = u->next;
+  nu = stats_userlist_create_entry(user);
+  nu->user = nmalloc(strlen(user) + 1);
+  strcpy(nu->user, user);
+  nu->created = created;
+  nu->laston = laston;
+  if (u)
+    u->next = nu;
+  else
+    suserlist = nu;
+  return nu;
+}
+
+static void delsuser(char *user)
+{
+  struct stats_userlist *u, *lu;
+
+  Context;
+  debug1("Deleting %s...", user);
+  u = suserlist;
+  lu = NULL;
+  while (u) {
+    if (!rfc_casecmp(u->user, user)) {
+      if (lu)
+        lu->next = u->next;
+      else
+        suserlist = u->next;
+      stats_userlist_free_entry(u);
+      if (lu)
+        u = lu->next;
+      else
+        u = suserlist;
+    } else {
+      lu = u;
+      u = u->next;
+    }
+  }
+}
+
+static struct stats_userlist *stats_userlist_create_entry(char *user)
+{
+  struct stats_userlist *newentry;
+
+  newentry = nmalloc(sizeof(struct stats_userlist));
+  newentry->next = NULL;
+  newentry->user = NULL;
+  newentry->password = NULL;
+  newentry->email = NULL;
+  newentry->homepage = NULL;
+  newentry->flags = 0;
+  newentry->icqnr = 0;
+  newentry->hosts = NULL;
+  newentry->created = 0;
+  newentry->laston = 0;
+  suser_setflag(newentry, S_LIST);
+  suser_setflag(newentry, S_ADDHOSTS);
+
+  return newentry;
+}
+
+/* static int stats_userlist_expmem_entry(struct stats_userlist *what)
+{
+  int size = 0;
+
+  Assert(what);
+  Assert(what->user);
+  size += sizeof(struct stats_userlist);
+  size += strlen(what->user) + 1;
+  if (what->password)
+    size += strlen(what->password) + 1;
+  if (what->email)
+    size += strlen(what->email) + 1;
+  if (what->homepage)
+    size += strlen(what->homepage) + 1;
+  size += hostlist_expmem(what->hosts);
+  return size;
+} */
+
+static void stats_userlist_free_entry(struct stats_userlist *what)
+{
+  Assert(what);
+  Assert(what->user);
+  free_hostlist(what->hosts);
+  nfree(what->user);
+  if (what->email)
+    nfree(what->email);
+  if (what->homepage)
+    nfree(what->homepage);
+  if (what->password)
+    nfree(what->password);
+  weed_userlink_from_chanset(what);
+  weed_userlink_from_locstats(what);
+  nfree(what);
+}
+
+static void saddhost(struct stats_userlist *u, char *host, time_t lastused, time_t created)
+{
+  struct stats_hostlist *h, *nh;
+
+  Context;
+  for (h = u->hosts; h; h = h->next)
+    if (!rfc_casecmp(h->mask, host))
+      return;
+  h = u->hosts;
+  while (h && h->next)
+    h = h->next;
+  nh = nmalloc(sizeof(struct stats_hostlist));
+  nh->mask = nmalloc(strlen(host) + 1);
+  strcpy(nh->mask, host);
+  nh->lastused = lastused;
+  nh->created = created;
+  nh->next = NULL;
+  if (h)
+    h->next = nh;
+  else
+    u->hosts = nh;
+}
+
+static int sdelhost(struct stats_userlist *u, char *host)
+{
+  struct stats_hostlist *h, *lh;
+
+  Context;
+  h = u->hosts;
+  lh = NULL;
+  while (h) {
+    if (!rfc_casecmp(h->mask, host)) {
+      nfree(h->mask);
+      if (lh)
+        lh->next = h->next;
+      else
+        u->hosts = h->next;
+      nfree(h);
+      return 1;
+    }
+    lh = h;
+    h = h->next;
+  }
+  return 0;
+}
+
+static void stats_autosadd(struct stats_member *m, struct stats_chan *chan)
+{
+  struct stats_userlist *u;
+  struct userrec *uu;
+  char *mhost, *host;
+
+  Context;
+  if (autoadd < 0)
+    return;
+  if (m->spoken_lines < autoadd_min_lines)
+    return;
+  if ((now - m->joined) < (autoadd * 60))
+    return;
+  if (m->user) {
+    debug3("Stats.Mod: stats_autosadd called for %s in %s, but m->user already belongs to %s",
+           m->nick, chan->chan, m->user->user);
+    return;
+  }
+  u = findsuser_by_name(m->nick);
+  host = nmalloc(strlen(m->uhost) + strlen(m->nick) + 2);
+  sprintf(host, "%s!%s", m->nick, m->uhost);
+  mhost = nmalloc(strlen(host) + 10); /* better a few bytes too much than too little */
+  // I use maskstricthost() here, because stats.mod shouldn't strip
+  // a host anywhere at all. (strict-hosts 0 sucks...)
+  maskstricthost(host, mhost);
+  // mhost = nrealloc(mhost, strlen(mhost) + strlen(nick) + 1);sprintf(mhost, "%s%s", m->nick, mhost + 1);
+  if (u) {
+    if (suser_addhosts(u)) {
+      saddhost(u, mhost, now, now);
+      m->user = u;
+      putlog(LOG_MISC, "*", "Stats.Mod: Added stats-hostmask %s to %s.", mhost, u->user);
+    }
+  } else {
+#ifndef NO_EGG
+    uu = get_user_by_host(host);
+    if (!uu && (autoadd == 0)) {
+      nfree(mhost);
+      nfree(host);
+      return;
+    }
+    if (uu)
+      u = addsuser(uu->handle, now, now);
+    else
+#endif
+      u = addsuser(m->nick, now, now);
+    saddhost(u, mhost, now, now);
+#ifndef NO_EGG
+    if (uu)
+      putlog(LOG_MISC, "*", "Stats.Mod: %s matched %s(in the \"common\" userfile), added %s to userbase.", host, uu->handle, u->user);
+    else
+#endif
+      putlog(LOG_MISC, "*", "Stats.Mod: Added %s(%s) to userbase.", u->user, mhost);
+    m->user = u;
+    // send a welcome message to our new user
+    welcome_suser(m->nick, u, chan->chan);
+  }
+  if (m->user) {
+    m->stats = findlocstats(chan->chan, m->user->user);
+    if (!m->stats)
+      m->stats = initstats(chan->chan, m->user->user);
+  } else
+    m->stats = NULL;
+  nfree(mhost);
+  nfree(host);
+}
+
+static void welcome_suser(char *nick, struct stats_userlist *u, char *chan)
+{
+  char *text;
+
+  reset_global_vars();
+  glob_user = u;
+  glob_nick = nick;
+  glob_slang = slang_find(coreslangs, slang_chanlang_get(chanlangs, chan));
+  if ((text = getslang_first(500))) {
+    dprintf(DP_HELP, "NOTICE %s :%s\n", nick, text);
+    while ((text = getslang_next()))
+      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, text);
+  }
+}
+
+static int listsuser(locstats *ls, char *chan)
+{
+  if (!ls->u)
+    ls->u = findsuser_by_name(ls->user);
+  if (ls->u && !suser_list(ls->u))
+    return 0;
+  return 1;
+}
+
+static int countsusers()
+{
+  static struct stats_userlist *u;
+  int users = 0;
+
+  Context;
+  for (u = suserlist; u; u = u->next)
+    users++;
+  return users;
+}
+
+static int counthosts()
+{
+  static struct stats_userlist *u;
+  static struct stats_hostlist *h;
+  int hosts = 0;
+
+  Context;
+  for (u = suserlist; u; u = u->next)
+    for (h = u->hosts; h; h = h->next)
+      hosts++;
+  return hosts;
+}
+
+static void weed_userlink_from_chanset(struct stats_userlist *u)
+{
+  struct stats_chan *chan;
+  struct stats_member *m;
+
+  Context;
+  for (chan = schan_getfirst(); chan; chan = schan_getnext()) {
+    for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members)) {
+      if (m->user == u) {
+        m->user = NULL;
+        m->stats = NULL;
+      }
+    }
+  }
+}
+
+static void weed_statlink_from_chanset(locstats *ls)
+{
+  struct stats_chan *chan;
+  struct stats_member *m;
+
+  Context;
+  for (chan = schan_getfirst(); chan; chan = schan_getnext()) {
+    for (m = schan_members_getfirst(&chan->members); m; m = schan_members_getnext(&chan->members)) {
+      if (m->stats == ls) {
+        m->stats = NULL;
+      }
+    }
+  }
+}
+
+/* weed_userlink_from_locstats():
+ * removes all references to a userstruct from the stat-structs
+ * (mostly used if the user got deleted)
+ */
+static void weed_userlink_from_locstats(struct stats_userlist *u)
+{
+  globstats *gs;
+  locstats *ls;
+
+  Context;
+  for (gs = sdata; gs; gs = gs->next)
+    for (ls = gs->local; ls; ls = ls->next)
+      if (ls->u == u)
+        ls->u = NULL;
+  Context;
+}
+
+
+static void setemail(struct stats_userlist *u, char *email)
+{
+  if (!u) {
+    putlog(LOG_MISC, "*", "ERROR! Tried to set email for NULL!");
+    return;
+  }
+  if (u->email) {
+    debug0("email exists... deleting");
+    nfree(u->email);
+    u->email = NULL;
+  }
+  while (email[0] == ' ')
+    email++;
+  if (email[0]) {
+    u->email = nmalloc(strlen(email) + 1);
+    strcpy(u->email, email);
+    debug1("newemail: '%s'", u->email);
+  }
+}
+
+static void sethomepage(struct stats_userlist *u, char *homepage)
+{
+  int len;
+
+  if (!u) {
+    putlog(LOG_MISC, "*", "ERROR! Tried to set homepage for NULL!");
+    return;
+  }
+  if (u->homepage) {
+    nfree(u->homepage);
+    u->homepage = NULL;
+  }
+  while (homepage[0] == ' ')
+    homepage++;
+  if (homepage[0]) {
+    if (!strncasecmp(homepage, "http://", 7)) {
+      u->homepage = nmalloc(strlen(homepage) + 1);
+      strcpy(u->homepage, homepage);
+    } else {
+      len = strlen(homepage) + 7 + 1;
+      u->homepage = nmalloc(len);
+      snprintf(u->homepage, len, "http://%s", homepage);
+    }
+  }
+}
+
+static void setpassword(struct stats_userlist *u, char *password)
+{
+  if (!u) {
+    putlog(LOG_MISC, "*", "ERROR! Tried to set password for NULL!");
+    return;
+  }
+  if (u->password) {
+    nfree(u->password);
+    u->password = NULL;
+  }
+  while (password[0] == ' ')
+    password++;
+  if (password[0]) {
+    u->password = nmalloc(strlen(password) + 1);
+    strcpy(u->password, password);
+  }
+}
+
+static time_t get_creation_time_from_locstats(char *user)
+{
+  struct stats_chan *chan;
+  locstats *ls;
+  time_t creation = now;
+
+  for (chan = schan_getfirst(); chan; chan = schan_getnext()) {
+    ls = findlocstats(chan->chan, user);
+    if (ls) {
+      if (ls->started < creation)
+        creation = ls->started;
+    } else
+      debug2("no ls: %s@%s", user, chan->chan);
+  }
+  debug2("creation of %s: %lu", user, creation);
+  if (creation == now)
+    debug0("creation == now!");
+  return creation;
+}
+
+static time_t get_laston_time_from_hosts(char *user)
+{
+  struct stats_userlist *u;
+  struct stats_hostlist *h;
+  time_t laston = now;
+
+  u = findsuser_by_name(user);
+  if (u) {
+    for (h = u->hosts; h; h = h->next)
+      if (h->lastused > laston)
+        laston = h->lastused;
+  }
+  debug2("laston of %s: %lu", user, laston);
+  return laston;
+}
+
+static int user_changeflag(struct stats_userlist *u, char *mode)
+{
+  Assert(u);
+  if (!strcasecmp(mode, "+list"))
+    suser_setflag(u, S_LIST);
+  else if (!strcasecmp(mode, "-list"))
+    suser_delflag(u, S_LIST);
+  if (!strcasecmp(mode, "+addhosts"))
+    suser_setflag(u, S_ADDHOSTS);
+  else if (!strcasecmp(mode, "-addhosts"))
+    suser_delflag(u, S_ADDHOSTS);
+  else if (!strcasecmp(mode, "+nostats")) {
+    suser_setflag(u, S_NOSTATS);
+    suser_delflag(u, S_LIST);
+  } else if (!strcasecmp(mode, "-nostats"))
+    suser_delflag(u, S_NOSTATS);
+  else
+    return 0;
+  return 1;
+}
+
+static void free_suserlist(struct stats_userlist *e)
+{
+  struct stats_userlist *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    stats_userlist_free_entry(e);
+    e = ee;
+  }
+}
+
+static void free_hostlist(struct stats_hostlist *e)
+{
+  struct stats_hostlist *ee;
+
+  Context;
+  while (e) {
+    ee = e->next;
+    nfree(e->mask);
+    nfree(e);
+    e = ee;
+  }
+}
+
+static int user_email_password(struct stats_userlist *user)
+{
+	char *p, *text = NULL;
+	int len = 0, newlen = 0, ret;
+
+	if (!user->password)
+		return U_NOPASSWORD;
+	if (!user->email)
+		return U_NOEMAIL;
+	text = nmalloc(1);
+	*text = 0;
+	for (p = getslang_first(1510); p; p = getslang_next()) {
+		newlen = strlen(p);
+		text = nrealloc(text, len + newlen + 1 + 1);
+		len += newlen + 1;
+		strcat(text, p);
+		strcat(text, "\n");
+	}
+
+    ret = email_send(user->email, getslang(1500), text);
+    nfree(text);
+    return ret;
+}
+
+static int user_merge(char *sTo, char *sFrom)
+{
+	struct stats_userlist *uTo, *uFrom;
+	struct stats_hostlist *h;
+
+	uTo = findsuser_by_name(sTo);
+	uFrom = findsuser_by_name(sFrom);
+	if (!uTo || !uFrom)
+		return 0;
+	if (!userdata_merge(sTo, sFrom))
+		return 0;
+	for (h = uFrom->hosts; h; h = h->next)
+		saddhost(uTo, h->mask, h->lastused, h->created);
+	delsuser(sFrom);
+	return 1;
+}

+ 74 - 0
core/userrec.h

@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#define S_LIST		1
+#define S_ADDHOSTS	2
+#define S_NOSTATS	4
+
+#define U_NOPASSWORD	1
+#define U_NOEMAIL		2
+
+static void stats_autosadd(struct stats_member *m, struct stats_chan *chan);
+
+struct stats_hostlist {
+  struct stats_hostlist *next;
+  char *mask;
+  time_t lastused;
+  time_t created;
+};
+
+struct stats_userlist {
+  struct stats_userlist *next;
+  char *user;
+  char *password;
+  char *email;
+  char *homepage;
+  int flags;
+  int icqnr;
+  time_t created;
+  time_t laston;
+  struct stats_hostlist *hosts;
+};
+
+#define suser_list(u)		(u->flags & S_LIST)
+#define suser_addhosts(u)	(u->flags & S_ADDHOSTS)
+#define suser_nostats(u)	(u->flags & S_NOSTATS)
+
+#define suser_setflag(u, flag)	(u->flags |= flag)
+#define suser_delflag(u, flag)	(u->flags &= ~flag)
+
+#define TIMETOLIVE(x) (((now - x->created) * (expire_factor / 100)) + (expire_base * 86400))
+
+static struct stats_userlist *addsuser(char *, time_t, time_t);
+static struct stats_userlist *findsuser(char *);
+static struct stats_userlist *findsuser_by_name(char *);
+static struct stats_userlist *stats_userlist_create_entry(char *);
+static void stats_userlist_free_entry(struct stats_userlist *);
+static void saddhost(struct stats_userlist *u, char *host, time_t lastused, time_t created);
+static void welcome_suser(char *nick, struct stats_userlist *u, char *chan);
+static int listsuser(locstats *ls, char *chan);
+static void weed_userlink_from_chanset(struct stats_userlist *u);
+static void weed_statlink_from_chanset(locstats *ls);
+static void weed_userlink_from_locstats(struct stats_userlist *u);
+static void setemail(struct stats_userlist *u, char *email);
+static void sethomepage(struct stats_userlist *u, char *homepage);
+static void setpassword(struct stats_userlist *u, char *password);
+static time_t get_creation_time_from_locstats(char *user);
+static time_t get_laston_time_from_hosts(char *user);
+static void free_suserlist(struct stats_userlist *e);
+static void free_hostlist(struct stats_hostlist *e);

+ 71 - 0
core/vars.c

@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2000,2001  Florian Sander
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+static char statsfile[121] = "statsmod.dat";
+static char webstats[510] = "words letters lines actions smileys joins kicks modes topics minutes";
+static char graphstats[510] = "words letters lines actions smileys joins kicks modes topics minutes";
+static char stat_reply[128] = "words letters smileys minutes";
+static char graphgif[128] = "";
+static char graphcolor[20] = "blue";
+static char webdir[256] = "../public_html";
+static char smileys[128] = ":-) :) ;) ;-) ^_^ :-D :-P :P =) ;D";
+static char badflags[20] = "ofvb|ofv";
+static char nostatsflags[20] = "b|-";
+static char nopeak[20] = "b|-";
+static char network[41] = "unknown-net";
+static char default_slang[21] = "en";
+static char default_skin[21] = "classic";
+static char binary_url[121] = "";
+static int statsfilemode = 0600;
+static int webnr = 15;
+static int graphnr = 15;
+static int stats_save_time = 10;
+static int autoadd = 5;
+// static int stat_expire_user = 30;
+// static int stat_expire_delay = 30;
+static int write_today = 1;
+static int maxstat_thr = 0;
+static int maxstat_time = 0;
+static int maxlivestats_thr = 0;
+static int maxlivestats_time = 0;
+static int mstat_thr = 0;
+static time_t mstat_time = 0;
+static int livestats_timeout;
+static int offset = 0;
+static int topwords_limit = 5;
+static int quote_freq = 10;
+static int log_wordstats = 0;
+static int lasthour = 0;
+static int lastmonth = 0;
+static int min_word_length = 0;
+static int table_color = 0x3850B8;
+static int fade_table_to = 0x000000;
+static int table_border = 0;
+static int log_urls = 1;
+static int kick_context = 0;
+static int display_kicks = 0;
+static int display_average_users = 1;
+static int show_userlist = 1;
+static int show_usersonchan = 1;
+static int list_secret_chans = 1;
+static int autoadd_min_lines = 0;
+static int min_lines = 0;
+static int expire_base = 7;	/* Minimum time before user gets deleted */
+static int expire_factor = 25;	/* percent value which defines how much of
+				   a user's "age" he can stay away without
+				   being expired */

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff