]> jfr.im git - irc/quakenet/newserv.git/commitdiff
Commit of request module that is currently running as R.
authorPaul <redacted>
Wed, 26 Oct 2005 10:59:00 +0000 (11:59 +0100)
committerPaul <redacted>
Wed, 26 Oct 2005 10:59:00 +0000 (11:59 +0100)
Supports S/L/Q request
Supports blocks
Uses newserv's chanfix and chanstats as database

request/Makefile [new file with mode: 0644]
request/lrequest.c [new file with mode: 0644]
request/lrequest.h [new file with mode: 0644]
request/request.c [new file with mode: 0644]
request/request.h [new file with mode: 0644]
request/request_block.c [new file with mode: 0644]
request/request_block.h [new file with mode: 0644]
request/sqrequest.c [new file with mode: 0644]
request/sqrequest.h [new file with mode: 0644]

diff --git a/request/Makefile b/request/Makefile
new file mode 100644 (file)
index 0000000..aea2ee1
--- /dev/null
@@ -0,0 +1,6 @@
+
+.PHONY: all
+all: request.so
+
+request.so: lrequest.o request.o request_block.o sqrequest.o 
+       ld -shared -Bdynamic -o $@ $^
diff --git a/request/lrequest.c b/request/lrequest.c
new file mode 100644 (file)
index 0000000..eae2ad4
--- /dev/null
@@ -0,0 +1,94 @@
+/* required modules: splitlist, chanfix(3) */
+
+#include <string.h>
+#include "request.h"
+#include "lrequest.h"
+#include "request_block.h"
+#include "../localuser/localuser.h"
+
+/* stats counters */
+int lr_noregops = 0;
+int lr_scoretoolow = 0;
+int lr_top5 = 0;
+int lr_floodattempts = 0;
+
+#define min(a,b) ((a > b) ? b : a)
+
+int lr_requestl(nick *svc, nick *np, channel *cp, nick *lnick) {
+  chanfix *cf;
+  regop *rolist[LR_TOPX], *ro;
+  int i, rocount;
+
+  if (strlen(cp->index->name->content) > LR_MAXCHANLEN) {
+    sendnoticetouser(svc, np, "Channel name is too long. You will have to "
+          "create a channel with a name less than %d characters long.",
+          LR_MAXCHANLEN + 1);
+
+    return RQ_ERROR;
+  }
+
+  cf = cf_findchanfix(cp->index);
+
+  if (cf == NULL) {
+    sendnoticetouser(svc, np, "Error: Your channel is too new. Try again later.");
+
+    lr_noregops++;
+
+    return RQ_ERROR;
+  }
+
+  rocount = cf_getsortedregops(cf, LR_TOPX, rolist);
+
+  ro = NULL;
+
+  for (i = 0; i < min(LR_TOPX, rocount); i++) {
+    if (cf_cmpregopnick(rolist[i], np)) {
+      ro = rolist[i];
+      break;
+    }
+  }
+
+  if (ro == NULL) {
+    sendnoticetouser(svc, np, "Error: You must be one of the top %d ops "
+          "for that channel.", LR_TOPX);
+
+    lr_top5++;
+
+    return RQ_ERROR;
+  }
+
+  /* treat blocked users as if their score is too low */
+  if (ro->score < LR_CFSCORE || rq_findblock(np->authname)) {
+    if (rq_isspam(np)) {
+      sendnoticetouser(svc, np, "Error: Do not flood the request system. "
+            "Try again in %s.", rq_longtoduration(rq_blocktime(np)));
+
+      lr_floodattempts++;
+
+      return RQ_ERROR;
+    }
+
+    sendnoticetouser(svc, np, "Try again later. You do not meet the "
+          "requirements to request L. You may need to wait longer "
+          "(see http://www.quakenet.org/faq/faq.php?c=3&f=112 )");
+
+    lr_scoretoolow++;
+
+    return RQ_ERROR;
+  }
+
+  sendmessagetouser(svc, lnick, "addchan %s #%s %s", cp->index->name->content,
+        np->authname, np->nick);
+
+  sendnoticetouser(svc, np, "Requirements met, L should be added. Contact #help"
+        " should further assistance be required.");
+
+  return RQ_OK;
+}
+
+void lr_requeststats(nick *rqnick, nick *np) {
+  sendnoticetouser(rqnick, np, "- No registered ops (L):          %d", lr_noregops);
+  sendnoticetouser(rqnick, np, "- Score too low (L):              %d", lr_scoretoolow);
+  sendnoticetouser(rqnick, np, "- Not in top%d (L):                %d", LR_TOPX, lr_top5);
+  sendnoticetouser(rqnick, np, "- Floods (L):                     %d", lr_floodattempts);
+}
diff --git a/request/lrequest.h b/request/lrequest.h
new file mode 100644 (file)
index 0000000..b26acfb
--- /dev/null
@@ -0,0 +1,11 @@
+#include <time.h>
+#include "../lib/sstring.h"
+#include "../channel/channel.h"
+#include "../chanfix/chanfix.h"
+
+#define LR_TOPX 5
+#define LR_CFSCORE 24
+#define LR_MAXCHANLEN 29
+
+int lr_requestl(nick *svc, nick *np, channel *cp, nick *lnick);
+void lr_requeststats(nick *rqnick, nick *np);
diff --git a/request/request.c b/request/request.c
new file mode 100644 (file)
index 0000000..30d9a96
--- /dev/null
@@ -0,0 +1,501 @@
+ /* shroud's service request */
+
+#include <string.h>
+#include "../localuser/localuser.h"
+#include "../localuser/localuserchannel.h"
+#include "../core/schedule.h"
+#include "../lib/irc_string.h"
+#include "../lib/splitline.h"
+#include "../control/control.h"
+#include "request.h"
+#include "request_block.h"
+#include "lrequest.h"
+#include "sqrequest.h"
+
+nick *rqnick;
+CommandTree *rqcommands;
+
+void rq_registeruser(void);
+void rq_handler(nick *target, int type, void **args);
+
+int rqcmd_showcommands(void *user, int cargc, char **cargv);
+int rqcmd_request(void *user, int cargc, char **cargv);
+int rqcmd_requestspamscan(void *user, int cargc, char **cargv);
+int rqcmd_addblock(void *user, int cargc, char **cargv);
+int rqcmd_delblock(void *user, int cargc, char **cargv);
+int rqcmd_listblocks(void *user, int cargc, char **cargv);
+int rqcmd_stats(void *user, int cargc, char **cargv);
+int rqcmd_legacyrequest(void *user, int cargc, char **cargv);
+
+#define min(a,b) ((a > b) ? b : a)
+
+/* stats counters */
+int rq_count = 0;
+int rq_failed = 0;
+int rq_success = 0;
+int rq_blocked = 0;
+
+void _init(void) {
+  rqcommands = newcommandtree();
+
+  addcommandtotree(rqcommands, "showcommands", RQU_ANY, 1, &rqcmd_showcommands);
+  addcommandtotree(rqcommands, "requestbot", RQU_ANY, 1, &rqcmd_request);
+  addcommandtotree(rqcommands, "requestspamscan", RQU_ANY, 1, &rqcmd_requestspamscan);
+  addcommandtotree(rqcommands, "addblock", RQU_OPER, 3, &rqcmd_addblock);
+  addcommandtotree(rqcommands, "delblock", RQU_OPER, 1, &rqcmd_delblock);
+  addcommandtotree(rqcommands, "listblocks", RQU_OPER, 1, &rqcmd_listblocks);
+  addcommandtotree(rqcommands, "stats", RQU_OPER, 1, &rqcmd_stats);
+
+  /* old legacy stuff */
+  addcommandtotree(rqcommands, "requestq", RQU_ANY, 1, &rqcmd_legacyrequest);
+
+  rq_initblocks();
+  qr_initrequest();
+
+  scheduleoneshot(time(NULL) + 1, (ScheduleCallback)&rq_registeruser, NULL);
+}
+
+void _fini(void) {
+  deregisterlocaluser(rqnick, NULL);
+
+  deletecommandfromtree(rqcommands, "showcommands", &rqcmd_showcommands);
+  deletecommandfromtree(rqcommands, "requestbot", &rqcmd_request);
+  deletecommandfromtree(rqcommands, "requestspamscan", &rqcmd_requestspamscan);
+  deletecommandfromtree(rqcommands, "addblock", &rqcmd_addblock);
+  deletecommandfromtree(rqcommands, "delblock", &rqcmd_delblock);
+  deletecommandfromtree(rqcommands, "listblocks", &rqcmd_listblocks);
+  deletecommandfromtree(rqcommands, "stats", &rqcmd_stats);
+
+  /* old legacy stuff */
+  deletecommandfromtree(rqcommands, "requestq", &rqcmd_legacyrequest);
+
+  destroycommandtree(rqcommands);
+
+  rq_finiblocks();
+  qr_finirequest();
+
+  deleteallschedules((ScheduleCallback)&rq_registeruser);
+}
+
+void rq_registeruser(void) {
+  channel *cp;
+
+  rqnick = registerlocaluser(RQ_REQUEST_NICK, RQ_REQUEST_USER, RQ_REQUEST_HOST,
+                             RQ_REQUEST_REAL, RQ_REQUEST_AUTH,
+                             UMODE_ACCOUNT | UMODE_SERVICE | UMODE_OPER,
+                             rq_handler);
+
+  cp = findchannel(RQ_TLZ);
+
+  if (cp == NULL)
+    localcreatechannel(rqnick, RQ_TLZ);
+  else
+    localjoinchannel(rqnick, cp);
+}
+
+char *rq_longtoduration(unsigned long interval) {
+  static char buf[100];
+
+  strncpy(buf, longtoduration(interval, 0), sizeof(buf));
+
+  /* chop off last character if it's a space */
+  if (buf[strlen(buf)] == ' ')
+    buf[strlen(buf)] = '\0';
+
+  return buf;
+}
+
+void rq_handler(nick *target, int type, void **params) {
+  Command* cmd;
+  nick* user;
+  char* line;
+  int cargc;
+  char* cargv[30];
+
+  switch (type) {
+    case LU_PRIVMSG:
+    case LU_SECUREMSG:
+      user = params[0];
+      line = params[1];
+      cargc = splitline(line, cargv, 30, 0);
+
+      if (cargc == 0)
+        return;
+
+      cmd = findcommandintree(rqcommands, cargv[0], 1);
+
+      if (cmd == NULL) {
+        sendnoticetouser(rqnick, user, "Unknown command.");
+
+        return;
+      }
+
+      if (cmd->level & RQU_OPER && !IsOper(user)) {
+        sendnoticetouser(rqnick, user, "Sorry, this command is not "
+              "available to you.");
+
+        return;
+      }
+
+      if (cargc - 1 > cmd->maxparams)
+        rejoinline(cargv[cmd->maxparams], cargc - cmd->maxparams);
+
+      /* handle the command */
+      cmd->handler((void*)user, min(cargc - 1, cmd->maxparams), &(cargv[1]));
+
+      break;
+    case LU_KILLED:
+      scheduleoneshot(time(NULL) + 5, (ScheduleCallback)&rq_registeruser, NULL);
+
+      break;
+    case LU_PRIVNOTICE:
+      qr_handlenotice(params[0], params[1]);
+
+      break;
+  }
+}
+
+int rqcmd_showcommands(void *user, int cargc, char **cargv) {
+  int n, i;
+  Command* cmdlist[50];
+
+  n = getcommandlist(rqcommands, cmdlist, 50);
+
+  sendnoticetouser(rqnick, (nick*)user, "Available commands:");
+  sendnoticetouser(rqnick, (nick*)user, "-------------------");
+
+  for (i = 0; i < n; i++) {
+    if ((cmdlist[i]->level & RQU_OPER) == 0 || IsOper((nick*)user))
+      sendnoticetouser(rqnick, (nick*)user, "%s", cmdlist[i]->command->content);
+  }
+
+  sendnoticetouser(rqnick, (nick*)user, "End of SHOWCOMMANDS");
+
+  return 0;
+}
+
+int rq_genericrequestcheck(nick *np, char *channelname, channel **cp, nick **lnick, nick **qnick) {
+  unsigned long *userhand;
+  rq_block *block;
+
+  if (!IsAccount(np)) {
+    sendnoticetouser(rqnick, np, "Error: You must be authed.");
+
+    return RQ_ERROR;
+  }
+
+  *cp = findchannel(channelname);
+
+  if (*cp == NULL) {
+    sendnoticetouser(rqnick, np, "Error: Channel %s does not exist.",
+          channelname);
+
+    return RQ_ERROR;
+  }
+
+  *lnick = getnickbynick(RQ_LNICK);
+
+  if (*lnick == NULL || findserver(RQ_LSERVER) < 0) {
+    sendnoticetouser(rqnick, np, "Error: %s does not seem to be online. "
+          "Try again later.", RQ_LNICK);
+
+    return RQ_ERROR;
+  }
+
+  *qnick = getnickbynick(RQ_QNICK);
+
+  if (*qnick == NULL || findserver(RQ_QSERVER) < 0) {
+    sendnoticetouser(rqnick, np, "Error: %s does not seem to be online. "
+          "Try again later.", RQ_QNICK);
+
+    return RQ_ERROR;
+  }
+
+  userhand = getnumerichandlefromchanhash((*cp)->users, np->numeric);
+
+  if (userhand == NULL) {
+    sendnoticetouser(rqnick, np, "Error: You're not on that channel.");
+
+    return RQ_ERROR;
+  }
+
+  if ((*userhand & CUMODE_OP) == 0) {
+    sendnoticetouser(rqnick, np, "Error: You must be op'd on the channel to "
+          "request a service.");
+
+    return RQ_ERROR;
+  }
+
+  block = rq_findblock(channelname);
+
+  if (block != NULL) {
+    sendnoticetouser(rqnick, np, "Error: You are not allowed to request a "
+          "service to this channel.");
+    sendnoticetouser(rqnick, np, "Reason: %s", block->reason->content);
+
+    rq_blocked++;
+
+    return RQ_ERROR;
+  }
+  
+  block = rq_findblock(np->authname);
+  
+  /* only tell the user if the block is going to expire in the next 48 hours
+     so we can have our fun with longterm blocks.
+     the request subsystems should deal with longterm blocks on their own */
+  if (block != NULL && block->expires < getnettime() + 3600 * 24 * 2) {
+    sendnoticetouser(rqnick, np, "Error: You are not allowed to request a "
+          "service. Keep waiting for at least %s before you try again.", 
+          rq_longtoduration(block->expires - getnettime()));
+
+    sendnoticetouser(rqnick, np, "Reason: %s", block->reason->content);
+
+    /* give them another 5 minutes to think about it */
+    block->expires += 300;
+    rq_saveblocks();
+
+    rq_blocked++;
+    
+    return RQ_ERROR;
+  }
+  
+  return RQ_OK;
+}
+
+int rqcmd_request(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+  nick *lnick, *qnick;
+  unsigned long *lhand, *qhand;
+  channel *cp;
+  int retval;
+
+  if (cargc < 1) {
+    sendnoticetouser(rqnick, np, "Syntax: requestbot <#channel>");
+
+    return RQ_ERROR;
+  }
+
+  rq_count++;
+
+  if (rq_genericrequestcheck(np, cargv[0], &cp, &lnick, &qnick) == RQ_ERROR) {
+    rq_failed++;
+
+    return RQ_ERROR;
+  }
+
+  lhand = getnumerichandlefromchanhash(cp->users, lnick->numeric);
+
+  qhand = getnumerichandlefromchanhash(cp->users, qnick->numeric);
+
+  if (qhand != NULL) {
+    sendnoticetouser(rqnick, np, "Error: %s is already on that channel.", RQ_QNICK);
+
+    rq_failed++;
+
+    return RQ_ERROR;
+  }
+
+  retval = RQ_ERROR;
+
+  if (lhand == NULL && qhand == NULL) {
+    /* try 'instant' Q request */
+    retval = qr_instantrequestq(np, cp);
+  }
+
+  if (retval == RQ_ERROR) {
+    if (lhand == NULL) {
+      /* user 'wants' L */
+
+      retval = lr_requestl(rqnick, np, cp, lnick);
+    } else {
+      /* user 'wants' Q */
+
+      retval = qr_requestq(rqnick, np, cp, lnick, qnick);
+    }
+  }
+
+  if (retval == RQ_ERROR)
+    rq_failed++;
+  else if (retval == RQ_OK)
+    rq_success++;
+
+  return retval;
+}
+
+int rqcmd_requestspamscan(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+  channel *cp;
+  nick *lnick, *qnick, *snick;
+  unsigned long *lhand, *qhand, *shand;
+  int retval;
+
+  if (cargc < 1) {
+    sendnoticetouser(rqnick, np, "Syntax: requestspamscan <#channel>");
+
+    return RQ_ERROR;
+  }
+
+  rq_count++;
+
+  if (rq_genericrequestcheck(np, cargv[0], &cp, &lnick, &qnick) == RQ_ERROR) {
+    rq_failed++;
+
+    return RQ_ERROR;
+  }
+
+  snick = getnickbynick(RQ_SNICK);
+
+  if (snick == NULL || findserver(RQ_SSERVER) < 0) {
+    sendnoticetouser(rqnick, np, "Error: %s does not seem to be online. "
+            "Try again later.", RQ_SNICK);
+
+    rq_failed++;
+
+    return RQ_ERROR;
+  }
+
+  /* does the user already have S on that channel? */  
+  shand = getnumerichandlefromchanhash(cp->users, snick->numeric);
+
+  if (shand != NULL) {
+    sendnoticetouser(rqnick, np, "Error: %s is already on that channel.", RQ_SNICK);
+
+    rq_failed++;
+
+    return RQ_ERROR;
+  }
+
+  /* we need either L or Q */
+  lhand = getnumerichandlefromchanhash(cp->users, lnick->numeric);
+  qhand = getnumerichandlefromchanhash(cp->users, qnick->numeric);
+
+  if (lhand || qhand) {
+    /* great, now try to request */
+    retval = qr_requests(rqnick, np, cp, lnick, qnick);
+    
+    if (retval == RQ_OK)
+      rq_success++;
+    else if (retval == RQ_ERROR)
+      rq_failed++;
+      
+      return retval;
+  } else {
+    /* channel apparently doesn't have L or Q */
+    
+   sendnoticetouser(rqnick, np, "Error: You need %s or %s in order to be "
+        "able to request %s.", RQ_LNICK, RQ_QNICK, RQ_SNICK);
+
+    rq_failed++;
+
+   return RQ_ERROR; 
+  }
+}
+
+int rqcmd_addblock(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+  rq_block *block;
+  time_t expires;
+  char *account;
+
+  if (cargc < 3) {
+    sendnoticetouser(rqnick, np, "Syntax: addblock <mask> <duration> <reason>");
+
+    return RQ_ERROR;
+  }
+
+  block = rq_findblock(cargv[0]);
+
+  if (block != NULL) {
+    sendnoticetouser(rqnick, np, "That mask is already blocked by %s "
+          "(reason: %s).", block->creator->content, block->reason->content);
+
+    return RQ_ERROR;
+  }
+
+  if (IsAccount(np))
+    account = np->authname;
+  else
+    account = "unknown";
+
+  expires = getnettime() + durationtolong(cargv[1]);
+
+  rq_addblock(cargv[0], cargv[2], account, 0, expires);
+
+  sendnoticetouser(rqnick, np, "Blocked channels/accounts matching '%s' from "
+        "requesting a service.", cargv[0]);
+
+  return RQ_OK;
+}
+
+int rqcmd_delblock(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+  int result;
+
+  if (cargc < 1) {
+    controlreply(np, "Syntax: delblock <mask>");
+
+    return RQ_ERROR;
+  }
+
+  result = rq_removeblock(cargv[0]);
+
+  if (result > 0) {
+    sendnoticetouser(rqnick, np, "Block for '%s' was removed.", cargv[0]);
+
+    return RQ_OK;
+  } else {
+    sendnoticetouser(rqnick, np, "There is no such block.");
+
+    return RQ_OK;
+  }
+}
+
+int rqcmd_listblocks(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+  rq_block block;
+  int i;
+
+  sendnoticetouser(rqnick, np, "Mask        By        Expires"
+        "                   Reason");
+
+  for (i = 0; i < rqblocks.cursi; i++) {
+    block = ((rq_block*)rqblocks.content)[i];
+    
+    if (block.expires != 0 && block.expires < getnettime())
+      continue; /* ignore blocks which have already expired, 
+                   rq_findblock will deal with them later on */
+
+    if (cargc < 1 || match2strings(block.pattern->content, cargv[0]))
+      sendnoticetouser(rqnick, np, "%-11s %-9s %-25s %s",
+            block.pattern->content, block.creator->content,
+            rq_longtoduration(block.expires - getnettime()),
+            block.reason->content);
+  }
+
+  sendnoticetouser(rqnick, np, "--- End of blocklist");
+
+  return RQ_OK;
+}
+
+int rqcmd_stats(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+
+  sendnoticetouser(rqnick, np, "Total requests:                   %d", rq_count);
+  sendnoticetouser(rqnick, np, "Successful requests:              %d", rq_success);
+  sendnoticetouser(rqnick, np, "Failed requests:                  %d", rq_failed);
+  sendnoticetouser(rqnick, np, "- Blocked:                        %d", rq_blocked);
+
+  lr_requeststats(rqnick, np);
+  qr_requeststats(rqnick, np);
+
+  return RQ_OK;
+}
+
+int rqcmd_legacyrequest(void *user, int cargc, char **cargv) {
+  nick *np = (nick*)user;
+
+  sendnoticetouser(rqnick, np, "This command is no longer valid. Please use "
+  "/msg %s REQUESTBOT #channel instead.", RQ_REQUEST_NICK);
+  
+  return RQ_OK;
+}
diff --git a/request/request.h b/request/request.h
new file mode 100644 (file)
index 0000000..664700f
--- /dev/null
@@ -0,0 +1,28 @@
+#define RQ_TLZ "#minimoo"
+
+#define RQ_LSERVER "lightweight.quakenet.org"
+#define RQ_LNICK "L"
+
+#define RQ_QSERVER "CServe.quakenet.org"
+#define RQ_QNICK "Q"
+
+#define RQ_SSERVER "spamscan.quakenet.org"
+#define RQ_SNICK "S"
+
+#define RQ_REQUEST_NICK "R"
+#define RQ_REQUEST_USER "request"
+#define RQ_REQUEST_HOST "request.quakenet.org"
+#define RQ_REQUEST_REAL "Service Request v0.23"
+#define RQ_REQUEST_AUTH "R"
+
+#define RQU_ANY 0
+#define RQU_OPER 1
+
+#define RQ_OK 0
+#define RQ_ERROR 1
+#define RQ_UNKNOWN 2
+
+extern int rq_failed;
+extern int rq_success;
+
+char *rq_longtoduration(unsigned long interval);
diff --git a/request/request_block.c b/request/request_block.c
new file mode 100644 (file)
index 0000000..40d7bc8
--- /dev/null
@@ -0,0 +1,245 @@
+#include <stdio.h>
+#include <string.h>
+#include "../irc/irc.h"
+#include "../lib/irc_string.h"
+#include "request_block.h"
+
+/* array of blocks */
+array rqblocks;
+
+/* our anti-flood nick extension */
+int rqnext;
+
+/* are we currently loading blocks? */
+int rq_loading;
+
+void rqhook_lostnick(int hook, void *arg);
+
+void rq_initblocks(void) {
+  array_init(&rqblocks, sizeof(rq_block));
+  array_setlim1(&rqblocks, 5);
+  array_setlim2(&rqblocks, 20);
+
+  rq_loading = 0;
+
+  rq_loadblocks();
+
+  rq_addblock("#qnet*", "Reserved for QuakeNet use only.", "request", 0, 0);
+  rq_addblock("#help*", "Reserved for QuakeNet use only.", "request", 0, 0);
+
+  registerhook(HOOK_NICK_LOSTNICK, &rqhook_lostnick);
+
+  rqnext = registernickext("request");
+}
+
+void rq_finiblocks(void) {
+  int i;
+  rq_block block;
+  nick *nip;
+
+  for (i = 0; i < rqblocks.cursi; i++) {
+    block = ((rq_block*)rqblocks.content)[i];
+
+    freesstring(block.pattern);
+    freesstring(block.reason);
+    freesstring(block.creator);
+  }
+
+  array_free(&rqblocks);
+
+  for (i=0; i<NICKHASHSIZE; i++)
+    for (nip=nicktable[i]; nip; nip=nip->next)
+      free(nip->exts[rqnext]);
+
+  deregisterhook(HOOK_NICK_LOSTNICK, &rqhook_lostnick);
+
+  releasenickext(rqnext);
+}
+
+void rqhook_lostnick(int hook, void *arg) {
+  nick *np = (nick*)arg;
+
+  free(np->exts[rqnext]);
+}
+
+int rq_isspam(nick *np) {
+  rq_flood *lf;
+
+  if (np->exts[rqnext] == NULL) {
+    np->exts[rqnext] = lf = (rq_flood*)malloc(sizeof(rq_flood));
+
+    lf->count = 1;
+    lf->created = getnettime();
+    lf->expire = 0;
+
+    return 0;
+  } else {
+    lf = np->exts[rqnext];
+
+    lf->count -= (getnettime() - lf->created) / (RQ_SPAMBLOCK / RQ_SPAMCOUNT);
+    
+    if (lf->count < 0)
+      lf->count = 0;
+
+    if (lf->count > RQ_SPAMCOUNT && lf->expire > getnettime()) {
+      return 1;
+    } else {
+      lf->count++;
+
+      if (lf->count > RQ_SPAMCOUNT) {
+        lf->expire = getnettime() + RQ_SPAMBLOCK;
+        
+        rq_addblock(np->authname, "Flooding the request system.", "request", 0, getnettime() + 3600);
+
+        return 1;
+      }
+
+      return 0;
+    }
+  }
+}
+
+time_t rq_blocktime(nick *np) {
+  if (np->exts[rqnext] == NULL)
+    return 0;
+  else
+    return ((rq_flood*)np->exts[rqnext])->expire - getnettime();
+}
+
+rq_block *rq_findblock(const char *pattern) {
+  int i;
+  rq_block block;
+
+  for (i = rqblocks.cursi - 1; i >= 0; i--) {
+    block = ((rq_block*)rqblocks.content)[i];
+
+    if (match2strings(block.pattern->content, pattern)) {
+      if (block.expires != 0 && block.expires < getnettime())
+        rq_removeblock(block.pattern->content);
+      else
+        return &(((rq_block*)rqblocks.content)[i]);
+    }
+  }
+
+  return NULL;
+}
+
+void rq_addblock(const char *pattern, const char *reason, const char *creator, time_t created, time_t expires) {
+  int slot;
+  rq_block *block;
+
+  if (rq_findblock(pattern) != NULL)
+    return;
+
+  slot = array_getfreeslot(&rqblocks);
+
+  block = &(((rq_block*)rqblocks.content)[slot]);
+
+  block->pattern = getsstring(pattern, CHANNELLEN);
+  block->reason = getsstring(reason, RQ_BLOCKLEN);
+  block->creator = getsstring(creator, ACCOUNTLEN);
+  block->created = created == 0 ? getnettime() : created;
+  block->expires = expires;
+
+  rq_saveblocks();
+}
+
+int rq_removeblock(const char *pattern) {
+  int i;
+  rq_block block;
+
+  for (i = 0; i < rqblocks.cursi; i++) {
+    block = ((rq_block*)rqblocks.content)[i];
+
+    if (ircd_strcmp(block.pattern->content, pattern) == 0) {
+      freesstring(block.pattern);
+      freesstring(block.reason);
+      freesstring(block.creator);
+
+      array_delslot(&rqblocks, i);
+
+      rq_saveblocks();
+
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+/* pattern reason creator created expires */
+int rq_parseline(char *line) {
+  char pattern[CHANNELLEN+1];
+  char reason[RQ_BLOCKLEN+1];
+  char creator[ACCOUNTLEN+1];
+  time_t created, expires;
+
+  if (sscanf(line, "%s %s %lu %lu %[^\n]", pattern, creator, &created, &expires, reason) < 2) /* \n won't be there anyway, but %s won't return the whole string */
+    return 0; /* invalid block */
+
+  /* tell rq_addblock that it should not save the blocks to disk this time */
+  rq_loading = 1;
+  rq_addblock(pattern, reason, creator, created, expires);
+  rq_loading = 0;
+  
+  return 1;
+}
+
+int rq_loadblocks(void) {
+  char line[4096];
+  FILE *rqdata;
+  int count;
+
+  rqdata = fopen(RQ_BLOCKFILE, "r");
+
+  if (rqdata == NULL)
+    return 0;
+
+  count = 0;
+
+  while (!feof(rqdata)) {
+    if (fgets(line, sizeof(line), rqdata) == NULL)
+      break;
+
+    if (line[strlen(line) - 1] == '\n')
+      line[strlen(line) - 1] = '\0';
+
+    if (line[strlen(line) - 1] == '\r')
+      line[strlen(line) - 1] = '\0';
+
+    if (line[0] != '\0') {
+      if (rq_parseline(line))
+        count++;
+    }
+  }
+
+  fclose(rqdata);
+
+  return count;
+}
+
+int rq_saveblocks(void) {
+  FILE *rqdata;
+  int i, count = 0;
+
+  /* don't save the blocks if we're currently loading them from the disk */
+  if (rq_loading)
+    return 0;
+
+  rqdata = fopen(RQ_BLOCKFILE, "w");
+
+  if (rqdata == NULL)
+    return 0;
+
+  rq_block block;
+
+  for (i = 0; i < rqblocks.cursi; i++) {
+    block = ((rq_block*)rqblocks.content)[i];
+
+    fprintf(rqdata, "%s %s %lu %lu %s\n", block.pattern->content, block.creator->content, block.created, block.expires, block.reason->content);
+  }
+
+  fclose(rqdata);
+
+  return count;
+}
diff --git a/request/request_block.h b/request/request_block.h
new file mode 100644 (file)
index 0000000..dac6463
--- /dev/null
@@ -0,0 +1,40 @@
+#include "../nick/nick.h"
+#include "../channel/channel.h"
+
+typedef struct {
+  int count;
+  time_t created;
+  time_t expire;
+} rq_flood;
+
+typedef struct {
+  sstring *pattern;
+  sstring *reason;
+
+  sstring *creator;
+  time_t created;
+  time_t expires;
+} rq_block;
+
+extern array rqblocks;
+
+#define RQ_BLOCKFILE "rqblocks"
+#define RQ_BLOCKLEN 256
+
+#define RQ_SPAMCOUNT 5
+#define RQ_SPAMBLOCK 3600
+
+void rq_initblocks(void);
+void rq_finiblocks(void);
+
+int rq_loadblocks(void);
+int rq_saveblocks(void);
+
+/* long-term blocks */
+rq_block *rq_findblock(const char *pattern);
+void rq_addblock(const char *pattern, const char *reason, const char *creator, time_t created, time_t expires);
+int rq_removeblock(const char *pattern);
+
+/* anti-spam blocks */
+int rq_isspam(nick *np);
+time_t rq_blocktime(nick *np);
diff --git a/request/sqrequest.c b/request/sqrequest.c
new file mode 100644 (file)
index 0000000..051e565
--- /dev/null
@@ -0,0 +1,767 @@
+/*
+ * S and Q request system!
+ *
+ * Depends on "chanstats" and "chanfix"
+ */
+
+#include "request.h"
+#include "sqrequest.h"
+#include "request_block.h"
+#include "../chanfix/chanfix.h"
+#include "../chanstats/chanstats.h"
+#include "../localuser/localuser.h"
+#include "../lib/irc_string.h"
+#include "../core/schedule.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#define QRLstate_IDLE         0x0 /* No request active */
+#define QRLstate_AWAITINGCHAN 0x1 /* Awaiting "Users for channel.." */
+#define QRLstate_AWAITINGUSER 0x2 /* Looking for our user in the list */
+#define QRLstate_AWAITINGEND  0x3 /* Waiting for "End of chanlev" */
+
+#define QR_FAILED             0x0
+#define QR_OK                 0x1
+
+#define QR_CSERVE             0x0
+#define QR_SPAMSCAN           0x1
+
+#define QR_L                  0x0
+#define QR_Q                  0x1
+
+#define min(a,b) ((a > b) ? b : a)
+
+typedef struct requestrec {
+  unsigned int       reqnumeric;  /* Who made the request */
+  chanindex         *cip;         /* Which channel the request is for */
+  int                what;        /* Which service does the user want? */
+  int                who;         /* Who are we talking to about CHANLEV? */
+  struct requestrec *next;
+} requestrec;
+
+requestrec *nextreql, *lastreql;
+requestrec *nextreqq, *lastreqq;
+
+
+requestrec *nextqreq, *lastqreq;
+
+extern nick *rqnick;
+int rlstate;
+int rqstate;
+
+/* stats counters */
+int qr_suspended = 0;
+int qr_nohist = 0;
+int qr_toosmall = 0;
+int qr_nochanlev = 0;
+int qr_notowner = 0;
+
+/* Check whether the user is blocked */
+int qr_blockcheck(requestrec *req) {
+  nick *np;
+  rq_block *block;
+  
+  np = getnickbynumeric(req->reqnumeric);
+
+  /* user is not online anymore */
+  if (np == NULL)
+    return 0;
+    
+  block = rq_findblock(np->authname);
+  
+  if (block != NULL)
+    return 1; /* user is blocked */
+  else
+    return 0;
+}
+
+/*
+ * Deal with outcome of a queued request.  The request should be freed
+ * as part of the process.
+ */
+
+void qr_result(requestrec *req, int outcome, char *message, ...) {
+  sstring *user, *password;
+  requestrec **rh;
+  char msgbuf[512];
+  va_list va;
+  nick *lnp, *qnp, *np, *tnp, *snp;
+  
+  /* Delete the request from the list first.. */
+  for (rh=&nextreql;*rh;rh=&((*rh)->next)) {
+    if (*rh==req) {
+      *rh=req->next;
+      break;
+    }
+  }
+
+  for (rh=&nextreqq;*rh;rh=&((*rh)->next)) {
+    if (*rh==req) {
+      *rh=req->next;
+      break;
+    }
+  }
+
+  /* If this was the last request (unlikely),
+   * we need to fix the last pointer */
+  if (lastreql==req) {
+    if (nextreql)
+      for (lastreql=nextreql;lastreql->next;lastreql=lastreql->next)
+        ; /* empty loop */
+    else
+      lastreql=NULL;
+  }
+  
+  if (lastreqq==req) {
+    if (nextreqq)
+      for (lastreqq=nextreqq;lastreqq->next;lastreqq=lastreqq->next)
+        ; /* empty loop */
+    else
+      lastreqq=NULL;
+  }
+
+  /* Check that the nick is still here.  If not, drop the request. */
+  if (!(tnp=np=getnickbynumeric(req->reqnumeric))) {
+    free(req);
+    return;
+  }
+
+  if (outcome==QR_OK) {
+    if (req->what == QR_CSERVE) {
+      /* Delete L, add Q.  Check that they both exist first, though. */
+
+      if (!(lnp=getnickbynick(RQ_LNICK)) || !(qnp=getnickbynick(RQ_QNICK))) {
+        sendnoticetouser(rqnick, tnp,
+                         "Error: Cannot find %s and %s on the network. "
+                         "Please request again later.", RQ_LNICK, RQ_QNICK);
+        free(req);
+        return;
+      }
+  
+      /* /msg Q ADDCHAN <channel> <flags> <owners nick> <channeltype> */
+      sendmessagetouser(rqnick, qnp, "ADDCHAN %s +ap #%s upgrade",
+                        req->cip->name->content,
+                        np->authname);
+  
+      sendnoticetouser(rqnick, tnp, "Adding %s to channel, please wait...",
+                        RQ_QNICK);
+    } else if (req->what == QR_SPAMSCAN) {
+      /* Add S */
+
+      if (!(snp=getnickbynick(RQ_SNICK))) {
+        sendnoticetouser(rqnick, tnp,
+                         "Error: Cannot find %s on the network. "
+                         "Please request again later.", RQ_SNICK);
+
+        free(req);
+        return;
+      }
+
+      sendnoticetouser(rqnick, tnp, "Requirements met, %s should be added. "
+                        "Contact #help should further assistance be required.",
+                        RQ_SNICK);
+
+      /* auth */
+      user = getcopyconfigitem("request", "user", "R", 30);
+      password = getcopyconfigitem("request", "password", "bla", 30);
+      sendmessagetouser(rqnick, snp, "AUTH %s %s", user->content, password->content);
+      freesstring(user);
+      freesstring(password);
+
+      /* /msg S addchan <channel> default */
+      sendmessagetouser(rqnick, snp, "ADDCHAN %s default", req->cip->name->content);
+
+      /* we do not put the request into another queue, so free it here */
+      free(req);
+      
+      return;
+    }
+
+    if (lastqreq)
+      lastqreq->next=req;
+    else
+      lastqreq=nextqreq=req;
+
+    req->next=NULL;
+
+    rq_success++;
+
+    /* Don't free, it's in new queue now */
+  } else {
+    /* Sort out the message.. */
+    va_start(va, message);
+    vsnprintf(msgbuf,511,message,va);
+    va_end(va);
+
+    sendnoticetouser(rqnick, tnp, "%s", msgbuf);
+    /* This is a failure message.  Add disclaimer. */
+    /*sendnoticetouser(rqnick, tnp, "Do not complain about this result in #help or #feds.");*/
+    free(req);
+
+    rq_failed++;
+  }
+}
+
+/*
+ * qr_checksize: 
+ *  Checks that a channel is beeeeg enough for teh Q
+ */
+
+int qr_checksize(chanindex *cip, int what) {
+  chanstats *csp;
+  channel *cp;
+  nick *np;
+  int i , avg, tot=0, authedcount=0, count=0;
+
+  cp = cip->channel;
+  
+  if (cp == NULL)
+    return 0; /* this shouldn't ever happen */
+
+#if QR_DEBUG
+  return 1;
+#endif
+
+  /* make sure that there are enough authed users */
+  for (i=0;i<cp->users->hashsize;i++) {
+    if (cp->users->content[i] != nouser) {
+      np = getnickbynumeric(cp->users->content[i]);
+      
+      if (IsAccount(np))
+        authedcount++;
+        
+      count++;
+    }
+  }
+
+  if (authedcount * 100 / count < QR_AUTHEDPCT)
+    return 0; /* too few authed users */
+
+  if (!(csp=cip->exts[csext]))
+    return 0;
+
+  for (i=0;i<HISTORYDAYS;i++) {
+    tot += csp->lastdays[i];
+  }
+
+  avg = (what == QR_CSERVE) ? QR_REQUIREDSIZE_CSERVE : QR_REQUIREDSIZE_SPAMSCAN;
+
+  if (tot > (avg * 140))
+    return 1;
+
+  return 0;
+}
+
+/* This function deals with notices from L: basically we track the
+ * responses to the L chanlev requests we've been making until we can
+ * decide what to do with the requests.
+ *
+ * Here's the L chanlev format:
+ * 11:12 -L(TheLBot@lightweight.quakenet.org)- Users for channel #twilightzone
+ * 11:12 -L(TheLBot@lightweight.quakenet.org)- Authname         Access flags
+ * 11:12 -L(TheLBot@lightweight.quakenet.org)- -----------------------------
+ * 11:12 -L(TheLBot@lightweight.quakenet.org)- Bigfoot                  amno
+ * 11:12 -L(TheLBot@lightweight.quakenet.org)- End of chanlev for #twilightzone.
+ */
+
+void qr_handlenotice(nick *sender, char *message) {
+  char *ch, *chop;
+  chanindex *cip;
+  requestrec *rrp1, *rrp2;
+  nick *np;
+  int delrequest = 0, state, who;
+  requestrec *nextreq;
+  channel *logcp;
+
+/*  logcp = findchannel("#qnet.request");
+  
+  if (logcp)
+    sendmessagetochannel(rqnick, logcp, "%s: %s - %d %d %x %x", sender->nick, message, rlstate, rqstate, nextreql, nextreqq);
+*/
+  if (!ircd_strcmp(sender->nick, RQ_QNICK) && nextqreq) {
+    /* Message from Q */
+    if (!ircd_strcmp(message,"Done.")) {
+      /* Q added the channel: delete from L and tell the user. */
+      /* If L has conspired to vanish between the request and the outcome,
+       * we have a chan with Q and L... too bad. */
+
+      if ((np=getnickbynick(RQ_LNICK))) {
+        sendmessagetouser(rqnick, np, "SENDCHANLEV %s %s",
+                          nextqreq->cip->name->content, RQ_QNICK);
+
+        sendmessagetouser(rqnick, np, "DELCHAN %s",
+                          nextqreq->cip->name->content);
+      }
+
+      if ((np=getnickbynumeric(nextqreq->reqnumeric))) {
+        sendnoticetouser(rqnick, np, "Request completed. %s added.", RQ_QNICK);
+      }
+      
+      delrequest = 1;
+    } else if (!ircd_strcmp(message,"That channel already exists.")) {
+      if ((np=getnickbynumeric(nextqreq->reqnumeric))) {
+        sendnoticetouser(rqnick, np,
+                         "Your channel appears to have %s already "
+                         "(it may be suspended).", RQ_QNICK);
+
+        qr_suspended++;
+        
+        delrequest = 1;
+      }
+    }
+
+    /* For either of the two messages above we want to delete the request
+     * at the head of the queue. */
+    if (delrequest) {
+      rrp1=nextqreq;
+  
+      nextqreq=nextqreq->next;
+      if (!nextqreq)
+        lastqreq=NULL;
+  
+      free(rrp1);
+    }
+  }
+
+  if (!ircd_strcmp(sender->nick, RQ_LNICK) || !ircd_strcmp(sender->nick, RQ_QNICK)) {
+    who = !ircd_strcmp(sender->nick, RQ_LNICK) ? QR_L : QR_Q;
+    state = (who == QR_Q) ? rqstate : rlstate;
+    nextreq = (who == QR_Q) ? nextreqq : nextreql;
+
+    /* Message from L or Q */
+    switch (state) {
+      case QRLstate_IDLE:
+        /* We're idle, do nothing */
+        return;
+
+      case QRLstate_AWAITINGCHAN:
+        /* We're waiting for conformation of the channel name */
+        if ((!ircd_strncmp(message,"Users for",9) && who == QR_L) ||
+          (!ircd_strncmp(message,"Known users on",14) && who == QR_Q)
+        ) {
+          /* Looks like the right message.  Let's find a channel name */
+
+          for (ch=message;*ch;ch++)
+            if (*ch=='#')
+              break;
+
+          if (!*ch) {
+            Error("qrequest",ERR_WARNING,
+                  "Unable to parse channel name from L/Q message: %s",message);
+            return;
+          }
+
+          /* chop off any remaining words */
+          chop = ch;
+          while (*(chop++)) {
+            if (*chop == ' ') {
+              *chop = '\0';
+              break;
+            }
+          }
+
+          if (!(cip=findchanindex(ch))) {
+            Error("qrequest",ERR_WARNING,
+                  "Unable to find channel from L/Q message: %s",ch);
+            return;
+          }
+
+          if (cip==nextreq->cip) {
+            /* Ok, this is the correct channel, everything is proceeding
+             * exactly as I had forseen */
+            if (who == QR_L)
+              rlstate = QRLstate_AWAITINGUSER;
+            else
+              rqstate = QRLstate_AWAITINGUSER;
+
+            return;
+          } else {
+            /* Uh-oh, not the channel we wanted.  Something is fucked
+             * here.  I think the only possible way out of this mess is
+             * to skip through in case we find a match for a later channel.. 
+             */
+            for (rrp1=nextreq;rrp1;rrp1=rrp1->next)
+              if (rrp1->cip==cip)
+                break;
+
+            if (rrp1) {
+              /* OK, we found a match further down the chain.  This means
+               * that something bad has happened to every request between
+               * the head of the list and the one we just found - send 
+               * error responses.
+               *
+               * Note weird loop head: qr_result will free up requests from
+               * the list as it goes, so we can just keep picking off the first
+               * entry
+               */
+              for(rrp2=nextreq;rrp2;rrp2=nextreq) {
+                if (rrp2==rrp1)
+                  break;
+
+                Error("qrequest",ERR_WARNING,
+                      "Lost response for channel %s; skipping.",
+                      rrp2->cip->name->content);
+
+                qr_result(rrp2, QR_FAILED,
+                          "Sorry, an error occurred while processing your request.");
+              }
+
+              if (rrp2) {
+                /* We seem to be back in sync. */
+                if (who == QR_L)
+                  rlstate = QRLstate_AWAITINGUSER;
+                else
+                  rqstate = QRLstate_AWAITINGUSER;
+
+                return;
+              }
+              /* Some form of hole in the space time continuum exists
+               * if we get here.  Unclear how to proceed. */
+              return;
+            } else {
+              /* No match - let's just ignore this completely */
+              Error("qrequest",ERR_WARNING,
+                    "Ignoring L/Q response for spurious channel %s",
+                    cip->name->content);
+              return;
+            }
+          }
+        }
+        break;
+
+      case QRLstate_AWAITINGUSER:
+        if ((!ircd_strncmp(message, "End of chanlev",14) && who == QR_L) ||
+          (!ircd_strncmp(message, "End of list.",12) && who == QR_Q)) {
+          /* Oh dear, we got to the end of the chanlev in this state.
+           * This means that we didn't find the user.
+           */
+
+          qr_result(nextreq, QR_FAILED,
+                    "Error: You are not known on %s.",
+                    nextreq->cip->name->content);
+
+          /* need to reset nextreq .. just in case
+           * qr_result has cleaned up records */
+
+          nextreq = (who == QR_Q) ? nextreqq : nextreql;
+
+          if (nextreq) {
+            if (who == QR_L)
+              rlstate = QRLstate_AWAITINGUSER;
+            else
+              rqstate = QRLstate_AWAITINGUSER;
+          } else {
+            if (who == QR_L)
+              rlstate = QRLstate_IDLE;
+            else
+              rqstate = QRLstate_IDLE;
+          }
+
+          qr_nochanlev++;
+
+          return;
+        } else {
+          /* Brutalise the message :-) */
+
+          if (who == QR_Q) {
+            while (*message == ' ')
+              message++;
+          }
+
+          if (!(ch=strchr(message, ' ')))
+            return;
+
+          *ch++='\0';
+
+          if (!(np=getnickbynumeric(nextreq->reqnumeric)))
+            return;
+
+          if (ircd_strcmp(message, np->authname)) {
+            /* This is not the user you are looking for */
+            return;
+          }
+
+          /* Check for owner flag.  Both branches of this if will
+           * take the request off the list, one way or the other. */
+          if (strchr(ch, 'n')) {
+            /* They iz teh +n! */
+            
+            /* Note: We're checking for blocks kind of late, so the request
+               system gets a chance to send other error messages first (like
+              'no chanstats', 'not known on channel', etc.). This is required
+              so that the user doesn't notice that he's being blocked. */
+            if (qr_checksize(nextreq->cip, nextreq->what) && !qr_blockcheck(nextreq)) {
+              qr_result(nextreq, QR_OK, "OK");
+            } else {
+              if (nextreq->what == QR_CSERVE) {
+                qr_result(nextreq, QR_FAILED,
+                          "Error: You do not meet the requirements "
+                          "for %s. Please continue to use %s.", RQ_QNICK, RQ_LNICK);                          
+              } else {
+                qr_result(nextreq, QR_FAILED,
+                          "Error: Your channel does not require %s. "
+                          "Try again later.", RQ_SNICK);
+              }
+
+              qr_toosmall++;
+            }
+          } else {
+            qr_result(nextreq, QR_FAILED,
+                      "Error: You don't hold the +n flag on %s.",
+                      nextreq->cip->name->content);
+
+            qr_notowner++;
+          }
+
+          /* OK, we found what we wanted so make sure we skip the rest */
+          if (who == QR_L)
+            rlstate = QRLstate_AWAITINGEND;
+          else
+            rqstate = QRLstate_AWAITINGEND;
+
+          return;
+        }
+        break;
+
+      case QRLstate_AWAITINGEND:
+        if (!ircd_strncmp(message, "End of chanlev",14) ||
+          !ircd_strncmp(message, "End of list.",12)) {
+          /* Found end of list */
+
+          if (nextreq) {
+            if (who == QR_L)
+              rlstate = QRLstate_AWAITINGCHAN;
+            else
+              rqstate = QRLstate_AWAITINGCHAN;
+          } else {
+            if (who == QR_L)
+              rlstate = QRLstate_IDLE;
+            else
+              rqstate = QRLstate_IDLE;
+          }
+
+          return;
+        }
+        break;
+    }
+  }
+}
+
+/*
+ * This function deals with requests from users for Q.
+ * Some sanity checks are made and the request is
+ * added to the queue.
+ */
+
+int qr_requestq(nick *rqnick, nick *sender, channel *cp, nick *lnick, nick *qnick) {
+  chanindex *cip = cp->index;
+
+  /* Check:
+   *  - we have some form of channel stats for the channel
+   *
+   * Note that the actual channel stats will not be checked
+   * until we're sure the user has +n on the channel.
+   */
+
+  if (rq_isspam(sender)) {
+      sendnoticetouser(rqnick, sender, "Error: Do not flood the request system."
+            " Try again in %s.", rq_longtoduration(rq_blocktime(sender)));
+    
+      return RQ_ERROR;
+  }
+
+  if (!cip->exts[csext]) {
+    sendnoticetouser(rqnick, sender,
+                     "Error: No historical record exists for %s.",
+                     cip->name->content);
+
+    qr_nohist++;
+
+    return RQ_ERROR;
+  }
+
+  /* Request stats from L */
+  sendmessagetouser(rqnick, lnick, "CHANLEV %s", cip->name->content);
+
+  /* Sort out a request record */
+  if (lastreql) {
+    lastreql->next = (requestrec *)malloc(sizeof(requestrec));
+    lastreql=lastreql->next;
+  } else {
+    lastreql=nextreql=(requestrec *)malloc(sizeof(requestrec));
+  }
+
+  lastreql->next = NULL;
+  lastreql->cip = cip;
+  lastreql->what = QR_CSERVE;
+  lastreql->who = QR_L;
+  lastreql->reqnumeric = sender->numeric;
+
+  if (rlstate == QRLstate_IDLE)
+    rlstate = QRLstate_AWAITINGCHAN;
+
+  sendnoticetouser(rqnick, sender,
+                   "Checking your %s access. "
+                   "This may take a while, please be patient...", RQ_LNICK);
+                   
+  /* we don't know yet whether the request was successful */
+  return RQ_UNKNOWN;
+}
+
+int qr_instantrequestq(nick *sender, channel *cp) {
+  requestrec *fakerequest;
+  chanfix *cf;
+  regop *ro;
+  int rocount, i;
+  regop *rolist[QR_TOPX];
+
+  if (!qr_checksize(cp->index, QR_CSERVE))
+    return RQ_ERROR;
+
+  cf = cf_findchanfix(cp->index);
+
+  if (cf == NULL)
+    return RQ_ERROR;
+    
+  rocount = cf_getsortedregops(cf, QR_TOPX, rolist);
+
+  ro = NULL;
+
+  for (i = 0; i < min(QR_TOPX, rocount); i++) {
+    if (cf_cmpregopnick(rolist[i], sender)) {
+      ro = rolist[i];
+      break;
+    }
+  }
+
+  /* not in top 5 - we don't have to worry about that error here
+     as the L request code will detect it again and send the user
+     an appropriate message */
+  if (ro == NULL)
+    return RQ_ERROR;
+
+  /* allocate a fake request */
+  fakerequest = (requestrec *)malloc(sizeof(requestrec));
+
+  fakerequest->reqnumeric = sender->numeric;
+  fakerequest->cip = cp->index;
+  fakerequest->what = QR_CSERVE;
+  fakerequest->who = QR_L; /* pretend that we asked L about the chanlev */
+
+  /* add it to the queue */
+  if (nextreql == NULL) {
+    fakerequest->next = NULL;
+    nextreql = fakerequest;
+    lastreql = fakerequest;
+  } else {
+    fakerequest->next = nextreql;
+    nextreql = fakerequest;
+  }
+  
+  qr_result(fakerequest, QR_OK, "OK");
+  
+  return RQ_OK;
+}
+
+int qr_requests(nick *rqnick, nick *sender, channel *cp, nick *lnick, nick *qnick) {
+  chanindex *cip = cp->index;
+  int who;
+  requestrec *nextreq, *lastreq;
+
+  if (rq_isspam(sender)) {
+      sendnoticetouser(rqnick, sender, "Error: Do not flood the request system."
+          " Try again in %s.", rq_longtoduration(rq_blocktime(sender)));
+    
+      return RQ_ERROR;
+  }
+
+  /* check which service is on the channel */
+  if (getnumerichandlefromchanhash(cp->users, lnick->numeric) != NULL) {
+    /* we've found L */
+    who = QR_L;
+
+    /* Request stats from L */
+    sendmessagetouser(rqnick, lnick, "CHANLEV %s", cip->name->content);
+
+    if (rlstate == QRLstate_IDLE)
+      rlstate = QRLstate_AWAITINGCHAN;
+  } else if (getnumerichandlefromchanhash(cp->users, qnick->numeric) != NULL) {
+    /* we've found Q */
+    who = QR_Q;
+
+    /* Request stats from Q */
+    sendmessagetouser(rqnick, qnick, "CHANLEV %s", cip->name->content);
+
+    if (rqstate == QRLstate_IDLE)
+      rqstate = QRLstate_AWAITINGCHAN;
+  } /* 'else' cannot happen as R has already checked whether the user has L or Q */
+
+  lastreq = (who == QR_Q) ? lastreqq : lastreql;
+  nextreq = (who == QR_Q) ? nextreqq : nextreql;
+
+  /* Sort out a request record */
+  if (lastreq) {
+    lastreq->next = (requestrec *)malloc(sizeof(requestrec));
+    lastreq=lastreq->next;
+  } else {
+    lastreq=nextreq=(requestrec *)malloc(sizeof(requestrec));
+  }
+
+  lastreq->next = NULL;
+  lastreq->cip = cip;
+  lastreq->what = QR_SPAMSCAN;
+  lastreq->reqnumeric = sender->numeric;
+
+  if (who == QR_Q) {
+    nextreqq = nextreq;
+    lastreqq = lastreq;
+  } else {
+    nextreql = nextreq;
+    lastreql = lastreq;
+  }
+
+   sendnoticetouser(rqnick, sender,
+                   "Checking your %s access. "
+                   "This may take a while, please be patient...",
+                   who == QR_Q ? RQ_QNICK : RQ_LNICK);
+
+  return RQ_UNKNOWN;
+}
+
+void qr_initrequest(void) {
+  nextreql=lastreql=NULL;
+  nextreqq=lastreqq=NULL;
+  nextqreq=lastqreq=NULL;
+}
+
+void qr_finirequest(void) {
+  struct requestrec *rp;
+
+  while (nextreqq) {
+    rp=nextreqq;
+    nextreqq=nextreqq->next;
+    free(rp);
+  }
+
+  while (nextreql) {
+    rp=nextreql;
+    nextreql=nextreql->next;
+    free(rp);
+  }
+
+  while (nextqreq) {
+    rp=nextqreq;
+    nextqreq=nextqreq->next;
+    free(rp);
+  }
+}
+
+void qr_requeststats(nick *rqnick, nick *np) {
+  sendnoticetouser(rqnick, np, "- Suspended (Q):                  %d", qr_suspended);
+  sendnoticetouser(rqnick, np, "- No chanstats (Q/S):             %d", qr_nohist);
+  sendnoticetouser(rqnick, np, "- Too small (Q/S):                %d", qr_toosmall);
+  sendnoticetouser(rqnick, np, "- User was not on chanlev (Q/S):  %d", qr_nochanlev);
+  sendnoticetouser(rqnick, np, "- User was not the owner (Q/S):   %d", qr_notowner);
+}
diff --git a/request/sqrequest.h b/request/sqrequest.h
new file mode 100644 (file)
index 0000000..81a1109
--- /dev/null
@@ -0,0 +1,18 @@
+#include "../nick/nick.h"
+#include "../channel/channel.h"
+
+#define QR_REQUIREDSIZE_CSERVE   50
+#define QR_REQUIREDSIZE_SPAMSCAN 120
+#define QR_TOPX                  5
+#define QR_AUTHEDPCT             65
+
+/* should we use 'debug' requirements for Q/S? */
+#define QR_DEBUG                 0 
+
+void qr_initrequest(void);
+void qr_finirequest(void);
+int qr_requestq(nick *rqnick, nick *sender, channel *cp, nick *lnick, nick *qnick);
+int qr_instantrequestq(nick *sender, channel *cp);
+int qr_requests(nick *rqnick, nick *sender, channel *cp, nick *lnick, nick *qnick);
+void qr_requeststats(nick *rqnick, nick *np);
+void qr_handlenotice(nick *sender, char *message);