Sfoglia il codice sorgente

Init: stats.mod 1.4.0 dev20

Florian Sander 13 anni fa
parent
commit
9316b6a99b
100 ha cambiato i file con 13941 aggiunte e 121 eliminazioni
  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)
       gs2 = gs2->next;
     gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
     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);
     strcpy(gs->chan, chan);
     if (gs2)
@@ -82,23 +67,10 @@ static void incrstats(char *user, char *chan, int type, int value, int set)
     while (ls2 && ls2->next)
       ls2 = ls2->next;
     ls = nmalloc(sizeof(locstats));
+    locstats_init(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 = nmalloc(strlen(user) + 1);
     strcpy(ls->user, user);
-    // we'll initialize this later, if it's needed
-    ls->u = NULL;
     if (ls2)
       ls2->next = ls;
     else
@@ -158,25 +130,10 @@ static locstats *initstats(char *chan, char *user)
     while (gs2 && gs2->next)
       gs2 = gs2->next;
     gs = nmalloc(sizeof(globstats));
+    globstats_init(gs);
     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);
     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)
       gs2->next = gs;
     else
@@ -191,23 +148,10 @@ static locstats *initstats(char *chan, char *user)
     while (ls2 && ls2->next)
       ls2 = ls2->next;
     ls = nmalloc(sizeof(locstats));
+    locstats_init(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 = nmalloc(strlen(user) + 1);
     strcpy(ls->user, user);
-    // we'll initialize this later, if it's needed
-    ls->u = NULL;
     if (ls2)
       ls2->next = ls;
     else
@@ -229,6 +173,58 @@ static locstats *initstats(char *chan, char *user)
   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;
@@ -259,6 +255,7 @@ static void sortstats(struct stats_global *gs, int itype, int today)
   int a, b, pitype;
 
   Context;
+  Assert(gs);
   again = 1;
   last = NULL;
   if (itype < 0) {
@@ -335,11 +332,11 @@ static void sortstats_wpl(struct stats_global *gs, int today)
       if (!c || !n)
         a = b = 0;
       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);
         else
           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);
         else
           b = 0;
@@ -412,9 +409,11 @@ static void sortstats_word(struct stats_global *gs, int today)
   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];
@@ -472,11 +471,11 @@ static void sortstats_idle(struct stats_global *gs, int today)
       if (!c || !n)
         a = b = 0;
       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);
         else
           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);
         else
           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)
 {
   globstats *gl;
@@ -706,7 +732,7 @@ static void write_stats()
     return;
   }
   fprintf(f, "@ # Statistics from %s.\n", botnetnick);
-  fprintf(f, "@ filever 1\n");
+  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);
@@ -718,6 +744,7 @@ static void write_stats()
       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]);
@@ -734,17 +761,21 @@ static void write_stats()
     }
   }
   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) {
-      fprintf(f, " %s %lu", h->mask, h->lastused);
+      fprintf(f, " %s %lu %lu", h->mask, h->lastused, h->created);
     }
     fprintf(f, "\n");
-    if (u->email || u->homepage) {
+    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");
     }
   }
@@ -760,11 +791,12 @@ static void read_stats()
   FILE *f;
   char buf[SAVESTATSLENGTH + 1];
   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;
   time_t lastused;
   locstats *ls;
   globstats *gs;
+  time_t created, laston;
 
   Context;
   ls = NULL;
@@ -828,15 +860,41 @@ static void read_stats()
           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);
-	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]) {
 	  host = 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")) {
         user = newsplit(&s);
@@ -845,8 +903,12 @@ static void read_stats()
           tmp = newsplit(&s);
           if (!strcmp(tmp, "e"))
             setemail(u, newsplit(&s));
-          else
+          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 {
@@ -1112,11 +1174,16 @@ static void nincrwordstats(globstats *gs, char *word, int value)
   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)
@@ -1376,8 +1443,8 @@ static void add_chanlog(globstats *gs, char *nick, char *text, int type)
   newlog = nmalloc(sizeof(quotestr));
   newlog->next = NULL;
   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) {
     newlog->quote = nmalloc(strlen(text) + strlen(ts) + 2);
     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);
   } else {
     debug1("Unknown log-type: %d !!!", type);
-    newlog->quote = NULL;
+    newlog->quote = nmalloc(1);
+    newlog->quote[0] = 0;
   }
   if (gs->lastlog)
     gs->lastlog->next = newlog;
@@ -1408,6 +1476,7 @@ static void add_chanlog(globstats *gs, char *nick, char *text, int type)
   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;
@@ -1455,3 +1524,21 @@ static void save_kick(globstats *gs, char *kick)
     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;
+}

+ 13 - 8
Makefile

@@ -7,21 +7,26 @@ doofus:
 	@cd ../../../; make
 
 clean:
-	@rm -f *.o *.so *~
+	@rm -f *.o *.$(MOD_EXT) *~
 
 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
 	rm -f ../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

+ 3 - 2
README

@@ -146,7 +146,8 @@ Livestats:
 ----------
 
 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
 channel and user list which makes it easier for your users to browse through
 the several channel statistics of your bot.
@@ -238,6 +239,6 @@ Thanks to:
 ----------
 
 - 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
 - the eggdev team for developing eggdrop

+ 14 - 4
UPDATES

@@ -1,10 +1,20 @@
 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
 - 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 SLBODYTAG       getslang(5)
 #define SLHEADER        getslang(10)
@@ -6,14 +67,6 @@
 #define ROOTTITLE	getslang(100)
 #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 SLONCHAN        getslang(119)
 #define SLMISCSTATS	getslang(120)
@@ -36,18 +89,6 @@
 #define SLGRAPHS	      getslang(220)
 #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 SLGRDTITLE	getslang(301)
@@ -98,13 +139,5 @@
 
 #define SLNOSUCHTYPE    getslang(1000)
 #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 */

Some files were not shown because too many files changed in this diff