]> jfr.im git - irc/quakenet/newserv.git/blobdiff - versionscan/versionscan.c
CHANSERV: don't delete the last user/channel to prevent id reuse
[irc/quakenet/newserv.git] / versionscan / versionscan.c
index d8d95e706ef674c6182c538e2d02c8fbbeeafd42..2047de2b51ff57150577c1c664f6d647dc480bec 100644 (file)
-#include "versionscan.h"\r
-\r
-CommandTree* versionscan_commands;\r
-nick* versionscan_nick;\r
-int versionscannext;\r
-int versionscan_mode;\r
-vspattern* vspatterns;\r
-vsauthdata* vsauths;\r
-vsstatistic* vsstats;\r
-unsigned long hcount=0;\r
-unsigned long wcount=0;\r
-unsigned long kcount=0;\r
-unsigned long gcount=0;\r
-\r
-//sendnoticetouser(versionscan_nick, np, "");\r
-\r
-void versionscan_addstat(char* reply) {\r
-  vsstatistic* v, *pv;\r
-\r
-  pv=0;\r
-  for (v=vsstats; v; v=v->next) {\r
-    if (!ircd_strcmp(v->reply, reply)) {\r
-      v->count++;\r
-      return;\r
-    }\r
-    pv=v;\r
-  }\r
-  if (!pv) {\r
-    vsstats=(vsstatistic*)malloc(sizeof(vsstatistic));\r
-    vsstats->reply=(char*)malloc(strlen(reply)+1);\r
-    strcpy(vsstats->reply, reply);\r
-    vsstats->count=1;\r
-    vsstats->next=0;\r
-  }\r
-  else {\r
-    pv->next=(vsstatistic*)malloc(sizeof(vsstatistic));\r
-    pv->next->reply=(char*)malloc(strlen(reply)+1);\r
-    strcpy(pv->next->reply, reply);\r
-    pv->next->count=1;\r
-    pv->next->next=0;\r
-  }\r
-}\r
-\r
-unsigned char versionscan_getlevelbyauth(char* auth) {\r
-  vsauthdata* v;\r
-  \r
-  for (v=vsauths; v; v=v->next) {\r
-    if (!ircd_strcmp(v->account, auth)) {\r
-      return v->flags;\r
-    }\r
-  }\r
-  return 0;\r
-}\r
-\r
-vsauthdata* versionscan_getauthbyauth(char* auth) {\r
-  vsauthdata* v;\r
-  \r
-  for (v=vsauths; v; v=v->next) {\r
-    if (!ircd_strcmp(v->account, auth)) {\r
-      return v;\r
-    }\r
-  }\r
-  return 0;\r
-}\r
-\r
-int IsVersionscanStaff(nick* np) {\r
-  unsigned char level;\r
-  \r
-  if (!IsAccount(np)) {\r
-    return 0;\r
-  }\r
-  level=versionscan_getlevelbyauth(np->authname);\r
-  if (level & (VS_STAFF | VS_GLINE | VS_ADMIN)) {\r
-    return 1;\r
-  }\r
-  return 0;\r
-}\r
-\r
-int IsVersionscanGlineAccess(nick* np) {\r
-  unsigned char level;\r
-  \r
-  if (!IsAccount(np)) {\r
-    return 0;\r
-  }\r
-  level=versionscan_getlevelbyauth(np->authname);\r
-  if (level & (VS_GLINE | VS_ADMIN)) {\r
-    return 1;\r
-  }\r
-  return 0;\r
-}\r
-\r
-int IsVersionscanAdmin(nick* np) {\r
-  unsigned char level;\r
-  \r
-  if (!IsAccount(np)) {\r
-    return 0;\r
-  }\r
-  level=versionscan_getlevelbyauth(np->authname);\r
-  if (level & VS_ADMIN) {\r
-    return 1;\r
-  }\r
-  return 0;\r
-}\r
-\r
-const char* versionscan_flagstochar(unsigned char flags) {\r
-  static char outstring[50];\r
-  int pos=0;\r
-  \r
-  outstring[pos++]='+';\r
-  if (flags & VS_ADMIN) { outstring[pos++]='a'; }\r
-  if (flags & VS_GLINE) { outstring[pos++]='g'; }\r
-  if (flags & VS_STAFF) { outstring[pos++]='s'; }\r
-  outstring[pos]='\0';\r
-  \r
-  return outstring;\r
-}\r
-\r
-void versionscan_addpattern(char* pattern, char* data, unsigned char action) {\r
-  vspattern* v;\r
-  \r
-  if (!vspatterns) {\r
-    vspatterns=(vspattern*)malloc(sizeof(vspattern));\r
-    strncpy(vspatterns->pattern, pattern, VSPATTERNLEN);\r
-    strncpy(vspatterns->data, data, VSDATALEN);\r
-    vspatterns->action=action;\r
-    vspatterns->next=0;\r
-    vspatterns->hitcount=0;\r
-    return;\r
-  }\r
-  \r
-  for (v=vspatterns; v; v=v->next) {\r
-    if (!v->next) {\r
-      v->next=(vspattern*)malloc(sizeof(vspattern));\r
-      strncpy(v->next->pattern, pattern, VSPATTERNLEN);\r
-      strncpy(v->next->data, data, VSDATALEN);\r
-      v->next->action=action;\r
-      v->next->next=0;\r
-      v->next->hitcount=0;\r
-      return;\r
-    }\r
-  }\r
-}\r
-\r
-void versionscan_delpattern(char* pattern) {\r
-  vspattern* v, *pv;\r
-  \r
-  pv=0;\r
-  for (v=vspatterns; v; v=v->next) {\r
-    if (!ircd_strcmp(v->pattern, pattern)) {\r
-      if (pv) {\r
-        pv->next=v->next;\r
-        free(v);\r
-      }\r
-      else {\r
-        vspatterns=v->next;\r
-        free(v);\r
-      }\r
-      return;\r
-    }\r
-    pv=v;\r
-  }\r
-}\r
-\r
-vspattern* versionscan_getpattern(char* pattern) {\r
-  vspattern* v;\r
-  \r
-  for (v=vspatterns; v; v=v->next) {\r
-    if (!ircd_strcmp(v->pattern, pattern)) {\r
-      return v;\r
-    }\r
-  }\r
-  return 0;\r
-}\r
-\r
-int versionscan_whois(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  nick* target;\r
-  vsauthdata* v;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: whois <nickname>");\r
-    return CMD_ERROR;\r
-  }\r
-  if (!(target=getnickbynick(cargv[0]))) {\r
-    sendnoticetouser(versionscan_nick, np, "No such nick.");\r
-    return CMD_ERROR;\r
-  }\r
-  if (!IsAccount(target)) {\r
-    sendnoticetouser(versionscan_nick, np, "%s is not authed with the network.", target->nick);\r
-    return CMD_ERROR;\r
-  }\r
-  if (!(v=versionscan_getauthbyauth(target->authname))) {\r
-    sendnoticetouser(versionscan_nick, np, "User %s is not in my database.", target->nick);\r
-    return CMD_ERROR;\r
-  }\r
-  sendnoticetouser(versionscan_nick, np, "%s is authed as %s, with flags: %s", target->nick, target->authname, versionscan_flagstochar(v->flags));\r
-}\r
-\r
-int versionscan_showcommands(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  Command* cmdlist[150];\r
-  int i, j;\r
-  \r
-  sendnoticetouser(versionscan_nick, np, "The following commands are registered at present:");\r
-  j=getcommandlist(versionscan_commands, cmdlist, 150);\r
-  for (i=0; i<j; i++) {\r
-    if (cmdlist[i]->level & (VS_STAFF | VS_GLINE | VS_ADMIN)) {\r
-      sendnoticetouser(versionscan_nick, np, "%s (%s)", cmdlist[i]->command->content, versionscan_flagstochar(cmdlist[i]->level));\r
-    }\r
-    else {\r
-      sendnoticetouser(versionscan_nick, np, "%s", cmdlist[i]->command->content);\r
-    }\r
-  }\r
-  sendnoticetouser(versionscan_nick, np, "End of list.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_help(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: help <command>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (!strcasecmp(cargv[0], "help")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: help <command>");\r
-    sendnoticetouser(versionscan_nick, np, "Gives help on commands.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "hello")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: hello");\r
-    sendnoticetouser(versionscan_nick, np, "Creates the first account on the bot.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "scan")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: scan <target>");\r
-    sendnoticetouser(versionscan_nick, np, "Sends a version request to the specified target, which may be a nick or a channel.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "broadcast")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: broadcast [-f]");\r
-    sendnoticetouser(versionscan_nick, np, "Send a network-wide CTCP version.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "changelev")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: changelev <nick> <level>");\r
-    sendnoticetouser(versionscan_nick, np, "Changes a user's privileges.");\r
-    sendnoticetouser(versionscan_nick, np, "+a -> admin access");\r
-    sendnoticetouser(versionscan_nick, np, "+g -> g-line access");\r
-    sendnoticetouser(versionscan_nick, np, "+s -> staff access");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "mode")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: mode [<mode of operation>]");\r
-    sendnoticetouser(versionscan_nick, np, "Where <mode of operation> is one of:");\r
-    sendnoticetouser(versionscan_nick, np, "idle: do nothing");\r
-    sendnoticetouser(versionscan_nick, np, "scan: scan newly connecting users and those targeted by the 'scan' command");\r
-    sendnoticetouser(versionscan_nick, np, "stat: collect statistics after a network-wide CTCP version request");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "showcommands")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: showcommands");\r
-    sendnoticetouser(versionscan_nick, np, "Displays registered commands.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "whois")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: whois <nickname>");\r
-    sendnoticetouser(versionscan_nick, np, "Display information about the specified user.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "statistics")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: statistics [<limit>]");\r
-    sendnoticetouser(versionscan_nick, np, "Display statistics of collected CTCP version replies.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "listpatterns")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: listpatterns");\r
-    sendnoticetouser(versionscan_nick, np, "Lists CTCP version reply patterns.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "addpattern")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: addpattern <pattern> <action> <data>");\r
-    sendnoticetouser(versionscan_nick, np, "Adds a CTCP version reply pattern, where action is one of the following:");\r
-    sendnoticetouser(versionscan_nick, np, "%d - warn", VS_WARN);\r
-    sendnoticetouser(versionscan_nick, np, "%d - kill", VS_KILL);\r
-    sendnoticetouser(versionscan_nick, np, "%d - g-line user@host", VS_GLUSER);\r
-    sendnoticetouser(versionscan_nick, np, "%d - g-line *@host", VS_GLHOST);\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "delpattern")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: delpattern <pattern>");\r
-    sendnoticetouser(versionscan_nick, np, "Deletes a CTCP version reply pattern.");\r
-    return;\r
-  }\r
-  if (!strcasecmp(cargv[0], "status")) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: status");\r
-    sendnoticetouser(versionscan_nick, np, "Gives various bits of information about the bot.");\r
-    return;\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_listpatterns(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  vspattern* v;\r
-  \r
-  for (v=vspatterns; v; v=v->next) {\r
-    sendnoticetouser(versionscan_nick, np, "Pattern [%s]:", v->pattern);\r
-    sendnoticetouser(versionscan_nick, np, "Data: %s", v->data);\r
-    sendnoticetouser(versionscan_nick, np, "Action: %s", (v->action == VS_WARN)?"warn":(v->action == VS_KILL)?"kill":(v->action == VS_GLUSER)?"g-line user@host":"g-line *@host");\r
-    sendnoticetouser(versionscan_nick, np, "Hit count: %lu", v->hitcount);\r
-  }  \r
-  sendnoticetouser(versionscan_nick, np, "End of list.");\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_addpatterncmd(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  int action;\r
-  \r
-  if (cargc < 3) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: addpattern <pattern> <action> <data>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  action=atoi(cargv[1]);\r
-  if ((action < VS_WARN) || (action > VS_GLHOST)) {\r
-    sendnoticetouser(versionscan_nick, np, "Action must be a number between 1 and 4.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (versionscan_getpattern(cargv[0])) {\r
-    sendnoticetouser(versionscan_nick, np, "That pattern already exists.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if ((action > VS_KILL) && !IsVersionscanGlineAccess(np)) {\r
-    sendnoticetouser(versionscan_nick, np, "You are not allowed to add G-Lines.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  versionscan_addpattern(cargv[0], cargv[2], (unsigned char)action);\r
-  sendnoticetouser(versionscan_nick, np, "Done.");\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_delpatterncmd(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: delpattern <pattern>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (!versionscan_getpattern(cargv[0])) {\r
-    sendnoticetouser(versionscan_nick, np, "That pattern does not exist.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  versionscan_delpattern(cargv[0]);\r
-  sendnoticetouser(versionscan_nick, np, "Done.");\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_status(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  vspattern* v;\r
-  int pcount=0; unsigned long chcount=0;\r
-  \r
-  for (v=vspatterns; v; v=v->next) {\r
-    pcount++;\r
-    chcount+=v->hitcount;\r
-  }\r
-  \r
-  sendnoticetouser(versionscan_nick, np, "Patterns:       %d", pcount);\r
-  sendnoticetouser(versionscan_nick, np, "Users hit:      %lu (%lu from current patterns)", hcount, chcount);\r
-  sendnoticetouser(versionscan_nick, np, "Warnings given: %lu", wcount);\r
-  sendnoticetouser(versionscan_nick, np, "Kills sent:     %lu", kcount);\r
-  sendnoticetouser(versionscan_nick, np, "G-Lines set:    %lu", gcount);\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_hello(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  \r
-  if (vsauths) {\r
-    sendnoticetouser(versionscan_nick, np, "The hello command cannot be used after the first user account has been created.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  vsauths=(vsauthdata*)malloc(sizeof(vsauthdata));\r
-  strncpy(vsauths->account, np->authname, ACCOUNTLEN);\r
-  vsauths->flags=VS_STAFF | VS_GLINE | VS_ADMIN;\r
-  vsauths->next;\r
-  \r
-  sendnoticetouser(versionscan_nick, np, "An account has been created for you with the following flags: %s.", versionscan_flagstochar(vsauths->flags));\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_changelev(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  vsauthdata* v;\r
-  nick* target;\r
-  unsigned char flags=0;\r
-  int i; int plus=1;\r
-  \r
-  if (cargc < 2) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: changelev <nick> [+|-]<level>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (!(target=getnickbynick(cargv[0]))) {\r
-    sendnoticetouser(versionscan_nick, np, "No such nick.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (!IsAccount(target)) {\r
-    sendnoticetouser(versionscan_nick, np, "%s is not authed.", target->nick);\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if ((v=versionscan_getauthbyauth(target->authname))) {\r
-    i=0;\r
-    if ((cargv[1][0] == '+') || (cargv[1][0] =='-')) {\r
-      plus=(cargv[1][0] == '+')?1:0;\r
-      i++;\r
-      flags=v->flags;\r
-    }\r
-    for (; cargv[1][i]; i++) {\r
-      switch (cargv[1][i]) {\r
-      case 'a':\r
-        flags=(plus)?flags | VS_ADMIN:flags & (~VS_ADMIN);\r
-        break;\r
-      case 'g':\r
-        flags=(plus)?flags | VS_GLINE:flags & (~VS_GLINE);\r
-        break;\r
-      case 's':\r
-        flags=(plus)?flags | VS_STAFF:flags & (~VS_STAFF);\r
-        break;\r
-      default:\r
-        sendnoticetouser(versionscan_nick, np, "Invalid level '%c'.", cargv[1][i]);\r
-        return CMD_ERROR;\r
-        break;\r
-      }\r
-    }\r
-    if (!flags) {\r
-      vsauthdata* pv, *prevv;\r
-      \r
-      prevv=0;\r
-      for (pv=vsauths; pv; pv++) {\r
-        if (pv == v) {\r
-          if (prevv) {\r
-            prevv->next=pv->next;\r
-            free(pv);\r
-          }\r
-          else {\r
-            vsauths=pv->next;\r
-            free(pv);\r
-          }\r
-        }\r
-        prevv=pv;\r
-      }\r
-    }\r
-    else {\r
-      v->flags=flags;\r
-    }\r
-    sendnoticetouser(versionscan_nick, np, "Done.");\r
-    return CMD_OK;\r
-  }\r
-  else {\r
-    i=0;\r
-    if ((cargv[1][0] == '+') || (cargv[1][0] =='-')) {\r
-      plus=(cargv[1][0] == '+')?1:0;\r
-      i++;\r
-    }\r
-    for (; cargv[1][i]; i++) {\r
-      switch (cargv[1][i]) {\r
-      case 'a':\r
-        flags=(plus)?flags | VS_ADMIN:flags & (~VS_ADMIN);\r
-        break;\r
-      case 'g':\r
-        flags=(plus)?flags | VS_GLINE:flags & (~VS_GLINE);\r
-        break;\r
-      case 's':\r
-        flags=(plus)?flags | VS_STAFF:flags & (~VS_STAFF);\r
-        break;\r
-      default:\r
-        sendnoticetouser(versionscan_nick, np, "Invalid level '%c'.", cargv[1][i]);\r
-        return CMD_ERROR;\r
-        break;\r
-      }\r
-    }\r
-    if (flags) {\r
-      for (v=vsauths; v; v=v->next) {\r
-        if (!v->next) {\r
-          v->next=(vsauthdata*)malloc(sizeof(vsauthdata));\r
-          strncpy(v->next->account, target->authname, ACCOUNTLEN);\r
-          v->next->flags=flags;\r
-          v->next->next=0;\r
-          sendnoticetouser(versionscan_nick, np, "Done.");\r
-          return CMD_OK;\r
-        }\r
-      }\r
-      sendnoticetouser(versionscan_nick, np, "Error adding user to database.");\r
-    }\r
-    else {\r
-      sendnoticetouser(versionscan_nick, np, "No level specified.");\r
-    }\r
-  }\r
-  \r
-  return CMD_ERROR;\r
-}\r
-\r
-int versionscan_scan(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  nick* n;\r
-  channel* cp;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(versionscan_nick, np, "Syntax: scan <target>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (versionscan_mode != VS_SCAN) {\r
-    sendnoticetouser(versionscan_nick, np, "Scanning of users is currently disabled.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (cargv[0][0] == '#') {\r
-    if ((cp=findchannel(cargv[0]))) {\r
-      sendmessagetochannel(versionscan_nick, cp, "\001VERSION\001");\r
-      sendnoticetouser(versionscan_nick, np, "Done.");\r
-    }\r
-    else {\r
-      sendnoticetouser(versionscan_nick, np, "No such channel.");\r
-      return CMD_ERROR;\r
-    }\r
-  }\r
-  else {\r
-    if ((n=getnickbynick(cargv[0]))) {\r
-      if (IsOper(n)) {\r
-        sendnoticetouser(versionscan_nick, np, "Cannot scan IRC Operators.");\r
-        return CMD_ERROR;\r
-      }\r
-      sendmessagetouser(versionscan_nick, n, "\001VERSION\001");\r
-      sendnoticetouser(versionscan_nick, np, "Done.");\r
-    }\r
-    else {\r
-      sendnoticetouser(versionscan_nick, np, "No such nick.");\r
-      return CMD_ERROR;\r
-    }\r
-  }\r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_modecmd(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  int oldmode=versionscan_mode;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(versionscan_nick, np, "Currently running in %s mode.", (versionscan_mode == VS_SCAN)?"SCAN":(versionscan_mode == VS_STAT)?"STATISTICS":"IDLE");\r
-    return CMD_OK;\r
-  }\r
-  \r
-  if (!ircd_strcmp(cargv[0], "idle")) {\r
-    versionscan_mode=VS_IDLE;\r
-    sendnoticetouser(versionscan_nick, np, "Now operating in IDLE mode.");\r
-  }\r
-  else if (!ircd_strcmp(cargv[0], "scan")) {\r
-    versionscan_mode=VS_SCAN;\r
-    sendnoticetouser(versionscan_nick, np, "Now operating in SCAN mode.");\r
-  }\r
-  else if (!ircd_strcmp(cargv[0], "stat")) {\r
-    versionscan_mode=VS_STAT;\r
-    sendnoticetouser(versionscan_nick, np, "Now operating in STATISTICS mode.");\r
-  }\r
-  else {\r
-    sendnoticetouser(versionscan_nick, np, "Invalid mode of operation.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (oldmode == VS_STAT) {\r
-    vsstatistic* v, *nv;\r
-    \r
-    for (v=vsstats; v;) {\r
-      nv=v->next;\r
-      free(v->reply);\r
-      free(v);\r
-      v=nv;\r
-    }\r
-    vsstats=0;\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int versionscan_statistics(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  vsstatistic* v;\r
-  long rlimit=0, limit=100;\r
-  \r
-  if (versionscan_mode != VS_STAT) {\r
-    sendnoticetouser(versionscan_nick, np, "No statistics are available unless STATISTICS mode of operation is enabled.");\r
-    return;\r
-  }\r
-  if (cargc) {\r
-    limit=atoi(cargv[0]);\r
-  }\r
-  if ((limit < 1) || (limit > 500)) {\r
-    sendnoticetouser(versionscan_nick, np, "Invalid results limit. Valid values are 1-500.");\r
-    return;\r
-  }\r
-  sendnoticetouser(versionscan_nick, np, "Reply: [Count]:");\r
-  for (v=vsstats; (v && (rlimit < limit)); v=v->next) {\r
-    sendnoticetouser(versionscan_nick, np, "%s [%lu]", v->reply, v->count);\r
-    rlimit++;\r
-  }\r
-  sendnoticetouser(versionscan_nick, np, "End of list - %lu results returned.", rlimit);\r
-}\r
-\r
-int versionscan_broadcast(void* sender, int cargc, char** cargv) {\r
-  nick* np=(nick*)sender;\r
-  int force=0;\r
-  \r
-  if (cargc) {\r
-    if (strcmp(cargv[0], "-f")) {\r
-      sendnoticetouser(versionscan_nick, np, "Invalid flag.");\r
-      return CMD_ERROR;\r
-    }\r
-    force=1;\r
-  }\r
-  \r
-  if (versionscan_mode != VS_STAT) {\r
-    if (!force) {\r
-      sendnoticetouser(versionscan_nick, np, "Statistics collection mode is not currently enabled. Use the 'mode' command to change current mode of operation.");\r
-      sendnoticetouser(versionscan_nick, np, "If you really wish to send a network-wide CTCP version whilst running in SCAN or IDLE mode, use the -f flag.");\r
-      return CMD_ERROR;\r
-    }\r
-    sendnoticetouser(versionscan_nick, np, "Forcing network-wide CTCP version.");\r
-  }\r
-  \r
-  irc_send("%s P $* :\001VERSION\001\r\n", longtonumeric(versionscan_nick->numeric, 5));\r
-  sendnoticetouser(versionscan_nick, np, "Done.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-void versionscan_newnick(int hooknum, void* arg) {\r
-  nick* np=(nick*)arg;\r
-  \r
-  /* ignore opers or auth'd users, helps cut down on spam during a burst */\r
-  if (!(IsOper(np) || IsAccount(np)) && (versionscan_mode == VS_SCAN)) {\r
-    sendmessagetouser(versionscan_nick, np, "\001VERSION\001");\r
-  }\r
-}\r
-\r
-void versionscan_handler(nick* me, int type, void** args) {\r
-  nick* sender;\r
-  Command* cmd;\r
-  char* cargv[50];\r
-  int cargc;\r
-  vspattern* v;\r
-  char* p;\r
-\r
-  switch (type) {\r
-  case LU_PRIVMSG:\r
-  case LU_SECUREMSG:\r
-    /* nick */\r
-    sender=args[0];\r
-    \r
-    if (!strncmp("\001VERSION", args[1], 8)) {\r
-      sendnoticetouser(versionscan_nick, sender, "\001VERSION QuakeNet %s v%s.\001", VS_RNDESC, VS_VERSION);\r
-      return;\r
-    }\r
-    \r
-    cargc=splitline((char*)args[1], cargv, 50, 0);\r
-    \r
-    cmd=findcommandintree(versionscan_commands, cargv[0], 1);\r
-    if (!cmd) {\r
-      sendnoticetouser(versionscan_nick, sender, "Unknown command.");\r
-      return;\r
-    }\r
-    \r
-/*    if ((cmd->level & VS_AUTHED) && !IsAccount(sender)) {\r
-      sendnoticetouser(versionscan_nick, sender, "Sorry, you need to be authed to use this command.");\r
-      return;\r
-    }\r
-*/    \r
-    if ((cmd->level & VS_OPER) && !IsOper(sender)) {\r
-      sendnoticetouser(versionscan_nick, sender, "Sorry, you need to be opered to use this command.");\r
-      return;\r
-    }\r
-/*    \r
-    if (((cmd->level & VS_STAFF) && !IsVersionscanStaff(sender)) || \r
-        ((cmd->level & VS_GLINE) && !IsVersionscanGlineAccess(sender)) || \r
-        ((cmd->level & VS_ADMIN) && !IsVersionscanAdmin(sender))) {\r
-      sendnoticetouser(versionscan_nick, sender, "Sorry, you do not have access to this command.");\r
-      return;\r
-    }\r
-*/  /*  \r
-    if ((cmd->level & VS_GLINE) && !IsVersionscanGlineAccess(sender)) {\r
-      sendnoticetouser(versionscan_nick, sender, "Sorry, you do not have access to this command.");\r
-      return;\r
-    }\r
-    \r
-    if ((cmd->level & VS_ADMIN) && !IsVersionscanAdmin(sender)) {\r
-      sendnoticetouser(versionscan_nick, sender, "Sorry, you do not have access to this command.");\r
-      return;\r
-    }*/\r
-    \r
-    if (cmd->maxparams < (cargc-1)) {\r
-      /* We need to do some rejoining */\r
-      rejoinline(cargv[cmd->maxparams], cargc-(cmd->maxparams));\r
-      cargc=(cmd->maxparams)+1;\r
-    }\r
-    \r
-    (cmd->handler)((void*)sender, cargc-1, &(cargv[1]));\r
-    break;\r
-  case LU_PRIVNOTICE:\r
-    sender=args[0];\r
-    \r
-    if (strncmp("\001VERSION", args[1], 8)) {\r
-      break;\r
-    }\r
-    if ((p=strchr(&args[1][8], '\001'))) {\r
-      *p++='\0';\r
-    }\r
-    if (versionscan_mode == VS_SCAN) {\r
-      if (IsOper(sender)) {\r
-        break;\r
-      }\r
-      for (v=vspatterns; v; v=v->next) {\r
-        if (match2strings(v->pattern, &args[1][8])) {\r
-          v->hitcount++;\r
-          hcount++;\r
-          switch (v->action) {\r
-          case VS_WARN:\r
-            sendnoticetouser(versionscan_nick, sender, v->data);\r
-            wcount++;\r
-            break;\r
-          case VS_KILL:\r
-            killuser(versionscan_nick, sender, v->data);\r
-            kcount++;\r
-            break;\r
-          case VS_GLUSER:\r
-            irc_send("%s GL * +*!%s@%s 3600 :%s\r\n", mynumeric->content, sender->ident, sender->host->name->content, v->data);\r
-            gcount++;\r
-            break;\r
-          case VS_GLHOST:\r
-            irc_send("%s GL * +*!*@%s 3600 :%s\r\n", mynumeric->content, sender->host->name->content, v->data);\r
-            gcount++;\r
-            break;\r
-          default:\r
-            /* oh dear, something's fucked */\r
-            break;\r
-          }\r
-         break;\r
-        }\r
-      }\r
-    }\r
-    else if (versionscan_mode == VS_STAT) {\r
-      versionscan_addstat(&args[1][8]);\r
-    }\r
-    break;\r
-  case LU_KILLED:\r
-    versionscan_nick=NULL;\r
-    scheduleoneshot(time(NULL)+1, &versionscan_createfakeuser, NULL);\r
-    break;\r
-  }\r
-}\r
-\r
-void versionscan_createfakeuser(void* arg) {\r
-  channel* cp;\r
-  char buf[200];\r
-  \r
-  sprintf(buf, "%s v%s", VS_RNDESC, VS_VERSION);\r
-  versionscan_nick=registerlocaluser(VS_NICK, VS_IDENT, VS_HOST, buf, VS_AUTHNAME, UMODE_ACCOUNT | UMODE_DEAF | UMODE_OPER | UMODE_SERVICE, versionscan_handler);\r
-  if ((cp=findchannel(OPER_CHAN))) {\r
-    localjoinchannel(versionscan_nick, cp);\r
-    localgetops(versionscan_nick, cp);\r
-  } else {\r
-    localcreatechannel(versionscan_nick, OPER_CHAN);\r
-  }\r
-}\r
-\r
-void _init() {\r
-  vspatterns=0;\r
-  vsauths=0;\r
-  vsstats=0;\r
-  versionscan_mode=VS_IDLE;\r
-  \r
-  versionscan_commands=newcommandtree();\r
-  \r
-  addcommandtotree(versionscan_commands, "showcommands", 0, 0, versionscan_showcommands);\r
-  addcommandtotree(versionscan_commands, "help", VS_AUTHED | VS_STAFF, 1, versionscan_help);\r
-  addcommandtotree(versionscan_commands, "hello", VS_AUTHED | VS_OPER, 0, versionscan_hello);\r
-  addcommandtotree(versionscan_commands, "scan", VS_AUTHED | VS_STAFF, 1, versionscan_scan);\r
-  addcommandtotree(versionscan_commands, "changelev", VS_AUTHED | VS_OPER | VS_ADMIN, 2, versionscan_changelev);\r
-  addcommandtotree(versionscan_commands, "listpatterns", VS_AUTHED | VS_STAFF | VS_OPER, 0, versionscan_listpatterns);\r
-  addcommandtotree(versionscan_commands, "addpattern", VS_AUTHED | VS_STAFF | VS_OPER, 3, versionscan_addpatterncmd);\r
-  addcommandtotree(versionscan_commands, "delpattern", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_delpatterncmd);\r
-  addcommandtotree(versionscan_commands, "status", VS_AUTHED | VS_OPER | VS_ADMIN, 0, versionscan_status);\r
-  addcommandtotree(versionscan_commands, "mode", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_modecmd);\r
-  addcommandtotree(versionscan_commands, "statistics", VS_AUTHED | VS_OPER | VS_STAFF, 1, versionscan_statistics);\r
-  addcommandtotree(versionscan_commands, "broadcast", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_broadcast);\r
-  addcommandtotree(versionscan_commands, "whois", VS_AUTHED | VS_STAFF, 1, versionscan_whois);\r
-  \r
-  registerhook(HOOK_NICK_NEWNICK, &versionscan_newnick);\r
-  \r
-  scheduleoneshot(time(NULL)+1, &versionscan_createfakeuser, NULL);\r
-}\r
-\r
-void _fini() {\r
-  void* p, *np;\r
-  \r
-  deregisterhook(HOOK_NICK_NEWNICK, &versionscan_newnick);\r
-  if (versionscan_nick) {\r
-    deregisterlocaluser(versionscan_nick, "Module unloaded.");\r
-    versionscan_nick=NULL;\r
-  }\r
-  \r
-  for (p=vspatterns; p;) {\r
-    np=((vspattern*)p)->next;\r
-    free(p);\r
-    p=np;\r
-  }\r
-  for (p=vsauths; p;) {\r
-    np=((vsauthdata*)p)->next;\r
-    free(p);\r
-    p=np;\r
-  }\r
-  for (p=vsstats; p;) {\r
-    np=((vsstatistic*)p)->next;\r
-    free(((vsstatistic*)p)->reply);\r
-    free(p);\r
-    p=np;\r
-  }\r
-}\r
+#include "versionscan.h"
+#include "../lib/version.h"
+#include "../glines/glines.h"
+
+MODULE_VERSION("")
+
+CommandTree* versionscan_commands;
+nick* versionscan_nick;
+int versionscannext;
+int versionscan_mode;
+vspattern* vspatterns;
+vsauthdata* vsauths;
+vsstatistic* vsstats;
+unsigned long hcount=0;
+unsigned long wcount=0;
+unsigned long kcount=0;
+unsigned long gcount=0;
+schedule *vsconnect;
+
+void versionscan_addstat(char* reply) {
+  unsigned int replylen;
+  unsigned long replycrc;
+  vsstatistic* v, *pv;
+  
+  replylen = strlen(reply);
+  replycrc = irc_crc32i(reply);
+  
+  pv=NULL;
+  for (v=vsstats; v; v=v->next) {
+    if (v->replylen==replylen && v->replycrc==replycrc) {
+      v->count++;
+      return;
+    }
+    pv=v;
+  }
+  if (!pv) {
+    vsstats=(vsstatistic*)malloc(sizeof(vsstatistic));
+    vsstats->reply=(char*)malloc(replylen + 1);
+    strcpy(vsstats->reply, reply);
+    vsstats->replylen = replylen;
+    vsstats->replycrc = replycrc;
+    vsstats->count=1;
+    vsstats->next=NULL;
+  }
+  else {
+    pv->next=(vsstatistic*)malloc(sizeof(vsstatistic));
+    pv->next->reply=(char*)malloc(replylen + 1);
+    strcpy(pv->next->reply, reply);
+    pv->next->replylen = replylen;
+    pv->next->replycrc = replycrc;
+    pv->next->count=1;
+    pv->next->next=NULL;
+  }
+}
+
+unsigned char versionscan_getlevelbyauth(char* auth) {
+  vsauthdata* v;
+  
+  for (v=vsauths; v; v=v->next) {
+    if (!ircd_strcmp(v->account, auth)) {
+      return v->flags;
+    }
+  }
+  return 0;
+}
+
+vsauthdata* versionscan_getauthbyauth(char* auth) {
+  vsauthdata* v;
+  
+  for (v=vsauths; v; v=v->next) {
+    if (!ircd_strcmp(v->account, auth)) {
+      return v;
+    }
+  }
+  return 0;
+}
+
+int IsVersionscanStaff(nick* np) {
+  unsigned char level;
+  
+  if (!IsAccount(np)) {
+    return 0;
+  }
+  level=versionscan_getlevelbyauth(np->authname);
+  if (level & (VS_STAFF | VS_GLINE | VS_ADMIN)) {
+    return 1;
+  }
+  return 0;
+}
+
+int IsVersionscanGlineAccess(nick* np) {
+  unsigned char level;
+  
+  if (!IsAccount(np)) {
+    return 0;
+  }
+  level=versionscan_getlevelbyauth(np->authname);
+  if (level & (VS_GLINE | VS_ADMIN)) {
+    return 1;
+  }
+  return 0;
+}
+
+int IsVersionscanAdmin(nick* np) {
+  unsigned char level;
+  
+  if (!IsAccount(np)) {
+    return 0;
+  }
+  level=versionscan_getlevelbyauth(np->authname);
+  if (level & VS_ADMIN) {
+    return 1;
+  }
+  return 0;
+}
+
+const char* versionscan_flagstochar(unsigned char flags) {
+  static char outstring[50];
+  int pos=0;
+  
+  outstring[pos++]='+';
+  if (flags & VS_ADMIN) { outstring[pos++]='a'; }
+  if (flags & VS_GLINE) { outstring[pos++]='g'; }
+  if (flags & VS_STAFF) { outstring[pos++]='s'; }
+  outstring[pos]='\0';
+  
+  return outstring;
+}
+
+void versionscan_addpattern(char* pattern, char* data, unsigned char action) {
+  vspattern* v;
+  
+  if (!vspatterns) {
+    vspatterns=(vspattern*)malloc(sizeof(vspattern));
+    strncpy(vspatterns->pattern, pattern, VSPATTERNLEN);
+    strncpy(vspatterns->data, data, VSDATALEN);
+    vspatterns->action=action;
+    vspatterns->next=0;
+    vspatterns->hitcount=0;
+    return;
+  }
+  
+  for (v=vspatterns; v; v=v->next) {
+    if (!v->next) {
+      v->next=(vspattern*)malloc(sizeof(vspattern));
+      strncpy(v->next->pattern, pattern, VSPATTERNLEN);
+      strncpy(v->next->data, data, VSDATALEN);
+      v->next->action=action;
+      v->next->next=0;
+      v->next->hitcount=0;
+      return;
+    }
+  }
+}
+
+void versionscan_delpattern(char* pattern) {
+  vspattern* v, *pv;
+  
+  pv=0;
+  for (v=vspatterns; v; v=v->next) {
+    if (!ircd_strcmp(v->pattern, pattern)) {
+      if (pv) {
+        pv->next=v->next;
+        free(v);
+      }
+      else {
+        vspatterns=v->next;
+        free(v);
+      }
+      return;
+    }
+    pv=v;
+  }
+}
+
+vspattern* versionscan_getpattern(char* pattern) {
+  vspattern* v;
+  
+  for (v=vspatterns; v; v=v->next) {
+    if (!ircd_strcmp(v->pattern, pattern)) {
+      return v;
+    }
+  }
+  return 0;
+}
+
+int versionscan_whois(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  nick* target;
+  vsauthdata* v;
+  
+  if (cargc < 1) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: whois <nickname>");
+    return CMD_ERROR;
+  }
+  if (!(target=getnickbynick(cargv[0]))) {
+    sendnoticetouser(versionscan_nick, np, "No such nick.");
+    return CMD_ERROR;
+  }
+  if (!IsAccount(target)) {
+    sendnoticetouser(versionscan_nick, np, "%s is not authed with the network.", target->nick);
+    return CMD_ERROR;
+  }
+  if (!(v=versionscan_getauthbyauth(target->authname))) {
+    sendnoticetouser(versionscan_nick, np, "User %s is not in my database.", target->nick);
+    return CMD_ERROR;
+  }
+  sendnoticetouser(versionscan_nick, np, "%s is authed as %s, with flags: %s", target->nick, target->authname, versionscan_flagstochar(v->flags));
+  return CMD_OK;
+}
+
+int versionscan_showcommands(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  Command* cmdlist[150];
+  int i, j;
+  
+  sendnoticetouser(versionscan_nick, np, "The following commands are registered at present:");
+  j=getcommandlist(versionscan_commands, cmdlist, 150);
+  for (i=0; i<j; i++) {
+    if (cmdlist[i]->level & (VS_STAFF | VS_GLINE | VS_ADMIN)) {
+      sendnoticetouser(versionscan_nick, np, "%s (%s)", cmdlist[i]->command->content, versionscan_flagstochar(cmdlist[i]->level));
+    }
+    else {
+      sendnoticetouser(versionscan_nick, np, "%s", cmdlist[i]->command->content);
+    }
+  }
+  sendnoticetouser(versionscan_nick, np, "End of list.");
+  
+  return CMD_OK;
+}
+
+int versionscan_help(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  
+  if (cargc < 1) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: help <command>");
+    return CMD_ERROR;
+  }
+  
+  if (!strcasecmp(cargv[0], "help")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: help <command>");
+    sendnoticetouser(versionscan_nick, np, "Gives help on commands.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "hello")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: hello");
+    sendnoticetouser(versionscan_nick, np, "Creates the first account on the bot.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "scan")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: scan <target>");
+    sendnoticetouser(versionscan_nick, np, "Sends a version request to the specified target, which may be a nick or a channel.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "broadcast")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: broadcast [-f]");
+    sendnoticetouser(versionscan_nick, np, "Send a network-wide CTCP version.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "changelev")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: changelev <nick> <level>");
+    sendnoticetouser(versionscan_nick, np, "Changes a user's privileges.");
+    sendnoticetouser(versionscan_nick, np, "+a -> admin access");
+    sendnoticetouser(versionscan_nick, np, "+g -> g-line access");
+    sendnoticetouser(versionscan_nick, np, "+s -> staff access");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "mode")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: mode [<mode of operation>]");
+    sendnoticetouser(versionscan_nick, np, "Where <mode of operation> is one of:");
+    sendnoticetouser(versionscan_nick, np, "idle: do nothing");
+    sendnoticetouser(versionscan_nick, np, "scan: scan newly connecting users and those targeted by the 'scan' command");
+    sendnoticetouser(versionscan_nick, np, "stat: collect statistics after a network-wide CTCP version request");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "showcommands")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: showcommands");
+    sendnoticetouser(versionscan_nick, np, "Displays registered commands.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "whois")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: whois <nickname>");
+    sendnoticetouser(versionscan_nick, np, "Display information about the specified user.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "statistics")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: statistics [<limit>]");
+    sendnoticetouser(versionscan_nick, np, "Display statistics of collected CTCP version replies.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "listpatterns")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: listpatterns");
+    sendnoticetouser(versionscan_nick, np, "Lists CTCP version reply patterns.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "addpattern")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: addpattern <pattern> <action> <data>");
+    sendnoticetouser(versionscan_nick, np, "Adds a CTCP version reply pattern, where action is one of the following:");
+    sendnoticetouser(versionscan_nick, np, "%d - warn", VS_WARN);
+    sendnoticetouser(versionscan_nick, np, "%d - kill", VS_KILL);
+    sendnoticetouser(versionscan_nick, np, "%d - g-line user@host", VS_GLUSER);
+    sendnoticetouser(versionscan_nick, np, "%d - g-line *@host", VS_GLHOST);
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "delpattern")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: delpattern <pattern>");
+    sendnoticetouser(versionscan_nick, np, "Deletes a CTCP version reply pattern.");
+    return CMD_OK;
+  }
+  if (!strcasecmp(cargv[0], "status")) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: status");
+    sendnoticetouser(versionscan_nick, np, "Gives various bits of information about the bot.");
+    return CMD_OK;
+  }
+  
+  return CMD_OK;
+}
+
+int versionscan_listpatterns(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  vspattern* v;
+  
+  for (v=vspatterns; v; v=v->next) {
+    sendnoticetouser(versionscan_nick, np, "Pattern [%s]:", v->pattern);
+    sendnoticetouser(versionscan_nick, np, "Data: %s", v->data);
+    sendnoticetouser(versionscan_nick, np, "Action: %s", (v->action == VS_WARN)?"warn":(v->action == VS_KILL)?"kill":(v->action == VS_GLUSER)?"g-line user@host":"g-line *@host");
+    sendnoticetouser(versionscan_nick, np, "Hit count: %lu", v->hitcount);
+  }  
+  sendnoticetouser(versionscan_nick, np, "End of list.");
+  return CMD_OK;
+}
+
+int versionscan_addpatterncmd(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  int action;
+  
+  if (cargc < 3) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: addpattern <pattern> <action> <data>");
+    return CMD_ERROR;
+  }
+  
+  action=atoi(cargv[1]);
+  if ((action < VS_WARN) || (action > VS_GLHOST)) {
+    sendnoticetouser(versionscan_nick, np, "Action must be a number between 1 and 4.");
+    return CMD_ERROR;
+  }
+  
+  if (versionscan_getpattern(cargv[0])) {
+    sendnoticetouser(versionscan_nick, np, "That pattern already exists.");
+    return CMD_ERROR;
+  }
+  
+  if ((action > VS_KILL) && !IsVersionscanGlineAccess(np)) {
+    sendnoticetouser(versionscan_nick, np, "You are not allowed to add G-Lines.");
+    return CMD_ERROR;
+  }
+  
+  versionscan_addpattern(cargv[0], cargv[2], (unsigned char)action);
+  sendnoticetouser(versionscan_nick, np, "Done.");
+  return CMD_OK;
+}
+
+int versionscan_delpatterncmd(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  
+  if (cargc < 1) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: delpattern <pattern>");
+    return CMD_ERROR;
+  }
+  
+  if (!versionscan_getpattern(cargv[0])) {
+    sendnoticetouser(versionscan_nick, np, "That pattern does not exist.");
+    return CMD_ERROR;
+  }
+  
+  versionscan_delpattern(cargv[0]);
+  sendnoticetouser(versionscan_nick, np, "Done.");
+  return CMD_OK;
+}
+
+int versionscan_status(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  vspattern* v;
+  int pcount=0; unsigned long chcount=0;
+  
+  for (v=vspatterns; v; v=v->next) {
+    pcount++;
+    chcount+=v->hitcount;
+  }
+  
+  sendnoticetouser(versionscan_nick, np, "Patterns:       %d", pcount);
+  sendnoticetouser(versionscan_nick, np, "Users hit:      %lu (%lu from current patterns)", hcount, chcount);
+  sendnoticetouser(versionscan_nick, np, "Warnings given: %lu", wcount);
+  sendnoticetouser(versionscan_nick, np, "Kills sent:     %lu", kcount);
+  sendnoticetouser(versionscan_nick, np, "G-Lines set:    %lu", gcount);
+  return CMD_OK;
+}
+
+int versionscan_hello(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  
+  if (vsauths) {
+    sendnoticetouser(versionscan_nick, np, "The hello command cannot be used after the first user account has been created.");
+    return CMD_ERROR;
+  }
+  
+  vsauths=(vsauthdata*)malloc(sizeof(vsauthdata));
+  strncpy(vsauths->account, np->authname, ACCOUNTLEN);
+  vsauths->flags=VS_STAFF | VS_GLINE | VS_ADMIN;
+  vsauths->next=NULL;
+  
+  sendnoticetouser(versionscan_nick, np, "An account has been created for you with the following flags: %s.", versionscan_flagstochar(vsauths->flags));
+  return CMD_OK;
+}
+
+int versionscan_changelev(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  vsauthdata* v;
+  nick* target;
+  unsigned char flags=0;
+  int i; int plus=1;
+  
+  if (cargc < 2) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: changelev <nick> [+|-]<level>");
+    return CMD_ERROR;
+  }
+  
+  if (!(target=getnickbynick(cargv[0]))) {
+    sendnoticetouser(versionscan_nick, np, "No such nick.");
+    return CMD_ERROR;
+  }
+  
+  if (!IsAccount(target)) {
+    sendnoticetouser(versionscan_nick, np, "%s is not authed.", target->nick);
+    return CMD_ERROR;
+  }
+  
+  if ((v=versionscan_getauthbyauth(target->authname))) {
+    i=0;
+    if ((cargv[1][0] == '+') || (cargv[1][0] =='-')) {
+      plus=(cargv[1][0] == '+')?1:0;
+      i++;
+      flags=v->flags;
+    }
+    for (; cargv[1][i]; i++) {
+      switch (cargv[1][i]) {
+      case 'a':
+        flags=(plus)?flags | VS_ADMIN:flags & (~VS_ADMIN);
+        break;
+      case 'g':
+        flags=(plus)?flags | VS_GLINE:flags & (~VS_GLINE);
+        break;
+      case 's':
+        flags=(plus)?flags | VS_STAFF:flags & (~VS_STAFF);
+        break;
+      default:
+        sendnoticetouser(versionscan_nick, np, "Invalid level '%c'.", cargv[1][i]);
+        return CMD_ERROR;
+        break;
+      }
+    }
+    if (!flags) {
+      vsauthdata* pv, *prevv;
+      
+      prevv=0;
+      for (pv=vsauths; pv; pv++) {
+        if (pv == v) {
+          if (prevv) {
+            prevv->next=pv->next;
+            free(pv);
+          }
+          else {
+            vsauths=pv->next;
+            free(pv);
+          }
+          break;
+        }
+        prevv=pv;
+      }
+    }
+    else {
+      v->flags=flags;
+    }
+    sendnoticetouser(versionscan_nick, np, "Done.");
+    return CMD_OK;
+  }
+  else {
+    i=0;
+    if ((cargv[1][0] == '+') || (cargv[1][0] =='-')) {
+      plus=(cargv[1][0] == '+')?1:0;
+      i++;
+    }
+    for (; cargv[1][i]; i++) {
+      switch (cargv[1][i]) {
+      case 'a':
+        flags=(plus)?flags | VS_ADMIN:flags & (~VS_ADMIN);
+        break;
+      case 'g':
+        flags=(plus)?flags | VS_GLINE:flags & (~VS_GLINE);
+        break;
+      case 's':
+        flags=(plus)?flags | VS_STAFF:flags & (~VS_STAFF);
+        break;
+      default:
+        sendnoticetouser(versionscan_nick, np, "Invalid level '%c'.", cargv[1][i]);
+        return CMD_ERROR;
+        break;
+      }
+    }
+    if (flags) {
+      for (v=vsauths; v; v=v->next) {
+        if (!v->next) {
+          v->next=(vsauthdata*)malloc(sizeof(vsauthdata));
+          strncpy(v->next->account, target->authname, ACCOUNTLEN);
+          v->next->flags=flags;
+          v->next->next=0;
+          sendnoticetouser(versionscan_nick, np, "Done.");
+          return CMD_OK;
+        }
+      }
+      sendnoticetouser(versionscan_nick, np, "Error adding user to database.");
+    }
+    else {
+      sendnoticetouser(versionscan_nick, np, "No level specified.");
+    }
+  }
+  
+  return CMD_ERROR;
+}
+
+int versionscan_scan(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  nick* n;
+  channel* cp;
+  
+  if (cargc < 1) {
+    sendnoticetouser(versionscan_nick, np, "Syntax: scan <target>");
+    return CMD_ERROR;
+  }
+  
+  if (versionscan_mode != VS_SCAN) {
+    sendnoticetouser(versionscan_nick, np, "Scanning of users is currently disabled.");
+    return CMD_ERROR;
+  }
+  
+  if (cargv[0][0] == '#') {
+    if ((cp=findchannel(cargv[0]))) {
+      sendmessagetochannel(versionscan_nick, cp, "\001VERSION\001");
+      sendnoticetouser(versionscan_nick, np, "Done.");
+    }
+    else {
+      sendnoticetouser(versionscan_nick, np, "No such channel.");
+      return CMD_ERROR;
+    }
+  }
+  else {
+    if ((n=getnickbynick(cargv[0]))) {
+      if (IsOper(n)) {
+        sendnoticetouser(versionscan_nick, np, "Cannot scan IRC Operators.");
+        return CMD_ERROR;
+      }
+      sendmessagetouser(versionscan_nick, n, "\001VERSION\001");
+      sendnoticetouser(versionscan_nick, np, "Done.");
+    }
+    else {
+      sendnoticetouser(versionscan_nick, np, "No such nick.");
+      return CMD_ERROR;
+    }
+  }
+  return CMD_OK;
+}
+
+int versionscan_modecmd(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  int oldmode=versionscan_mode;
+  
+  if (cargc < 1) {
+    sendnoticetouser(versionscan_nick, np, "Currently running in %s mode.", (versionscan_mode == VS_SCAN)?"SCAN":(versionscan_mode == VS_STAT)?"STATISTICS":"IDLE");
+    return CMD_OK;
+  }
+  
+  if (!ircd_strcmp(cargv[0], "idle")) {
+    versionscan_mode=VS_IDLE;
+    sendnoticetouser(versionscan_nick, np, "Now operating in IDLE mode.");
+  }
+  else if (!ircd_strcmp(cargv[0], "scan")) {
+    versionscan_mode=VS_SCAN;
+    sendnoticetouser(versionscan_nick, np, "Now operating in SCAN mode.");
+  }
+  else if (!ircd_strcmp(cargv[0], "stat")) {
+    versionscan_mode=VS_STAT;
+    sendnoticetouser(versionscan_nick, np, "Now operating in STATISTICS mode.");
+  }
+  else {
+    sendnoticetouser(versionscan_nick, np, "Invalid mode of operation.");
+    return CMD_ERROR;
+  }
+  
+  if (oldmode == VS_STAT) {
+    vsstatistic* v, *nv;
+    
+    for (v=vsstats; v;) {
+      nv=v->next;
+      free(v->reply);
+      free(v);
+      v=nv;
+    }
+    vsstats=0;
+  }
+  
+  return CMD_OK;
+}
+
+int versionscan_statistics(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  vsstatistic* v;
+  long rlimit=0, limit=100;
+  
+  if (versionscan_mode != VS_STAT) {
+    sendnoticetouser(versionscan_nick, np, "No statistics are available unless STATISTICS mode of operation is enabled.");
+    return CMD_ERROR;
+  }
+  if (cargc) {
+    limit=atoi(cargv[0]);
+  }
+  if ((limit < 1) || (limit > 500)) {
+    sendnoticetouser(versionscan_nick, np, "Invalid results limit. Valid values are 1-500.");
+    return CMD_ERROR;
+  }
+  sendnoticetouser(versionscan_nick, np, "Reply: [Count]:");
+  for (v=vsstats; (v && (rlimit < limit)); v=v->next) {
+    sendnoticetouser(versionscan_nick, np, "%s [%lu]", v->reply, v->count);
+    rlimit++;
+  }
+  sendnoticetouser(versionscan_nick, np, "End of list - %lu results returned.", rlimit);
+  return CMD_OK;
+}
+
+int versionscan_statsdump(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  vsstatistic* v;
+  long rlimit=0;
+  FILE *fout;
+  
+  if (versionscan_mode != VS_STAT) {
+    sendnoticetouser(versionscan_nick, np, "No statistics are available unless STATISTICS mode of operation is enabled.");
+    return CMD_ERROR;
+  }
+  if (!(fout=fopen("data/versionscanstats","w"))) {
+    sendnoticetouser(versionscan_nick, np, "Unable to open save file.");
+    return CMD_ERROR;
+  }
+  for (v=vsstats; v; v=v->next) {
+    fprintf(fout, "%lu:%s\n", v->count, v->reply);
+    rlimit++;
+  }
+  fclose(fout);
+  sendnoticetouser(versionscan_nick, np, "%lu results saved.", rlimit);
+  return CMD_OK;
+}
+
+int versionscan_broadcast(void* sender, int cargc, char** cargv) {
+  nick* np=(nick*)sender;
+  int force=0;
+  
+  if (cargc) {
+    if (strcmp(cargv[0], "-f")) {
+      sendnoticetouser(versionscan_nick, np, "Invalid flag.");
+      return CMD_ERROR;
+    }
+    force=1;
+  }
+  
+  if (versionscan_mode != VS_STAT) {
+    if (!force) {
+      sendnoticetouser(versionscan_nick, np, "Statistics collection mode is not currently enabled. Use the 'mode' command to change current mode of operation.");
+      sendnoticetouser(versionscan_nick, np, "If you really wish to send a network-wide CTCP version whilst running in SCAN or IDLE mode, use the -f flag.");
+      return CMD_ERROR;
+    }
+    sendnoticetouser(versionscan_nick, np, "Forcing network-wide CTCP version.");
+  }
+  
+  irc_send("%s P $* :\001VERSION\001\r\n", longtonumeric(versionscan_nick->numeric, 5));
+  sendnoticetouser(versionscan_nick, np, "Done.");
+  
+  return CMD_OK;
+}
+
+void versionscan_newnick(int hooknum, void* arg) {
+  nick* np=(nick*)arg;
+  
+  /* ignore opers or auth'd users, helps cut down on spam during a burst */
+  if (!(IsOper(np) || IsAccount(np)) && (versionscan_mode == VS_SCAN)) {
+    sendmessagetouser(versionscan_nick, np, "\001VERSION\001");
+  }
+}
+
+void versionscan_handler(nick* me, int type, void** args) {
+  nick* sender;
+  Command* cmd;
+  char* cargv[50];
+  int cargc;
+  vspattern* v;
+  char* p;
+
+  switch (type) {
+  case LU_PRIVMSG:
+  case LU_SECUREMSG:
+    /* nick */
+    sender=args[0];
+    
+    if (!strncmp("\001VERSION", args[1], 8)) {
+      sendnoticetouser(versionscan_nick, sender, "\001VERSION QuakeNet %s v%s.\001", VS_RNDESC, VS_VERSION);
+      return;
+    }
+    
+    cargc=splitline((char*)args[1], cargv, 50, 0);
+    
+    cmd=findcommandintree(versionscan_commands, cargv[0], 1);
+    if (!cmd) {
+      sendnoticetouser(versionscan_nick, sender, "Unknown command.");
+      return;
+    }
+    
+    if ((cmd->level & VS_AUTHED) && !IsAccount(sender)) {
+      sendnoticetouser(versionscan_nick, sender, "Sorry, you need to be authed to use this command.");
+      return;
+    }
+    
+    if ((cmd->level & VS_OPER) && !IsOper(sender)) {
+      sendnoticetouser(versionscan_nick, sender, "Sorry, you need to be opered to use this command.");
+      return;
+    }
+    
+    if (((cmd->level & VS_STAFF) && !IsVersionscanStaff(sender)) || 
+        ((cmd->level & VS_GLINE) && !IsVersionscanGlineAccess(sender)) || 
+        ((cmd->level & VS_ADMIN) && !IsVersionscanAdmin(sender))) {
+      sendnoticetouser(versionscan_nick, sender, "Sorry, you do not have access to this command.");
+      return;
+    }
+    
+    if (cmd->maxparams < (cargc-1)) {
+      /* We need to do some rejoining */
+      rejoinline(cargv[cmd->maxparams], cargc-(cmd->maxparams));
+      cargc=(cmd->maxparams)+1;
+    }
+    
+    (cmd->handler)((void*)sender, cargc-1, &(cargv[1]));
+    break;
+  case LU_PRIVNOTICE:
+    sender=args[0];
+    
+    if (strncmp("\001VERSION ", args[1], 9)) {
+      break;
+    }
+    if ((p=strchr((char *)args[1] + 9, '\001'))) {
+      *p++='\0';
+    }
+    if (versionscan_mode == VS_SCAN) {
+      if (IsOper(sender)) {
+        break;
+      }
+      for (v=vspatterns; v; v=v->next) {
+        if (match2strings(v->pattern, (char *)args[1] + 9)) {
+          v->hitcount++;
+          hcount++;
+          switch (v->action) {
+          case VS_WARN:
+            sendnoticetouser(versionscan_nick, sender, "%s", v->data);
+            wcount++;
+            break;
+          case VS_KILL:
+            killuser(versionscan_nick, sender, "%s", v->data);
+            kcount++;
+            break;
+          case VS_GLUSER:
+            glinebynick(sender, 3600, v->data, GLINE_ALWAYS_USER, "versionscan");
+            gcount++;
+            break;
+          case VS_GLHOST:
+            glinebynick(sender, 3600, v->data, 0, "versionscan");
+            gcount++;
+            break;
+          default:
+            /* oh dear, something's fucked */
+            break;
+          }
+         break;
+        }
+      }
+    }
+    else if (versionscan_mode == VS_STAT) {
+      versionscan_addstat((char *)args[1] + 9);
+    }
+    break;
+  case LU_KILLED:
+    versionscan_nick=NULL;
+    scheduleoneshot(time(NULL)+1, &versionscan_createfakeuser, NULL);
+    break;
+  }
+}
+
+void versionscan_createfakeuser(void* arg) {
+  channel* cp;
+  char buf[200];
+  
+  vsconnect=NULL;
+  sprintf(buf, "%s v%s", VS_RNDESC, VS_VERSION);
+  versionscan_nick=registerlocaluser(VS_NICK, VS_IDENT, VS_HOST, buf, VS_AUTHNAME, UMODE_ACCOUNT | UMODE_DEAF | UMODE_OPER | UMODE_SERVICE, versionscan_handler);
+  if ((cp=findchannel(OPER_CHAN))) {
+    localjoinchannel(versionscan_nick, cp);
+    localgetops(versionscan_nick, cp);
+  } else {
+    localcreatechannel(versionscan_nick, OPER_CHAN);
+  }
+}
+
+void _init() {
+  vspatterns=NULL;
+  vsauths=NULL;
+  vsstats=NULL;
+  versionscan_mode=VS_IDLE;
+  
+  versionscan_commands=newcommandtree();
+  
+  addcommandtotree(versionscan_commands, "showcommands", VS_AUTHED | VS_STAFF, 0, versionscan_showcommands);
+  addcommandtotree(versionscan_commands, "help", VS_AUTHED | VS_STAFF, 1, versionscan_help);
+  addcommandtotree(versionscan_commands, "hello", VS_AUTHED | VS_OPER, 0, versionscan_hello);
+  addcommandtotree(versionscan_commands, "scan", VS_AUTHED | VS_STAFF, 1, versionscan_scan);
+  addcommandtotree(versionscan_commands, "changelev", VS_AUTHED | VS_OPER | VS_ADMIN, 2, versionscan_changelev);
+  addcommandtotree(versionscan_commands, "listpatterns", VS_AUTHED | VS_STAFF | VS_OPER, 0, versionscan_listpatterns);
+  addcommandtotree(versionscan_commands, "addpattern", VS_AUTHED | VS_STAFF | VS_OPER, 3, versionscan_addpatterncmd);
+  addcommandtotree(versionscan_commands, "delpattern", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_delpatterncmd);
+  addcommandtotree(versionscan_commands, "status", VS_AUTHED | VS_OPER | VS_ADMIN, 0, versionscan_status);
+  addcommandtotree(versionscan_commands, "mode", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_modecmd);
+  addcommandtotree(versionscan_commands, "statistics", VS_AUTHED | VS_OPER | VS_STAFF, 1, versionscan_statistics);
+  addcommandtotree(versionscan_commands, "statsdump", VS_AUTHED | VS_OPER | VS_STAFF, 1, versionscan_statsdump);
+  addcommandtotree(versionscan_commands, "broadcast", VS_AUTHED | VS_OPER | VS_ADMIN, 1, versionscan_broadcast);
+  addcommandtotree(versionscan_commands, "whois", VS_AUTHED | VS_STAFF, 1, versionscan_whois);
+  
+  registerhook(HOOK_NICK_NEWNICK, &versionscan_newnick);
+  
+  vsconnect=scheduleoneshot(time(NULL)+1, &versionscan_createfakeuser, NULL);
+}
+
+void _fini() {
+  void* p, *np;
+  
+  deregisterhook(HOOK_NICK_NEWNICK, &versionscan_newnick);
+  
+  if (vsconnect) {
+    deleteschedule(vsconnect, &versionscan_createfakeuser, NULL);
+    vsconnect=NULL;
+  }
+  
+  if (versionscan_nick) {
+    deregisterlocaluser(versionscan_nick, "Module unloaded.");
+    versionscan_nick=NULL;
+  }
+  
+  destroycommandtree(versionscan_commands);
+  
+  for (p=vspatterns; p;) {
+    np=((vspattern*)p)->next;
+    free(p);
+    p=np;
+  }
+  for (p=vsauths; p;) {
+    np=((vsauthdata*)p)->next;
+    free(p);
+    p=np;
+  }
+  for (p=vsstats; p;) {
+    np=((vsstatistic*)p)->next;
+    free(((vsstatistic*)p)->reply);
+    free(p);
+    p=np;
+  }
+}