--- /dev/null
+
+.PHONY: all
+all: request.so
+
+request.so: lrequest.o request.o request_block.o sqrequest.o
+ ld -shared -Bdynamic -o $@ $^
--- /dev/null
+/* 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);
+}
--- /dev/null
+#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);
--- /dev/null
+ /* 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;
+}
--- /dev/null
+#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);
--- /dev/null
+#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;
+}
--- /dev/null
+#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);
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+#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);