From d530dbd43df7fece2dadc40f6bb99e2798f81f7a Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Fri, 20 Jan 2012 21:25:59 +0100 Subject: [PATCH] - Add CAP support. Currently implemented are: multi-prefix (NAMESX), and userhost-in-names (UHNAMES). Patch from nenotopia (#4018, #4066) --- Changes | 2 + include/numeric.h | 2 + include/struct.h | 1 + makefile.win32 | 7 +- src/modules/Makefile.in | 11 +- src/modules/l_commands.c | 4 + src/modules/m_cap.c | 442 +++++++++++++++++++++++++++++++++++++++ src/modules/m_nick.c | 2 +- src/modules/m_user.c | 5 +- src/s_err.c | 2 +- 10 files changed, 471 insertions(+), 7 deletions(-) create mode 100644 src/modules/m_cap.c diff --git a/Changes b/Changes index d0b88e085..2bc2acd09 100644 --- a/Changes +++ b/Changes @@ -2348,3 +2348,5 @@ and also means nothing will change in a non-ESVID scenario. - Fix misuse of stdarg.h macros when calling vsyslog() (#4065 by Jimini). - Ditch vsyslog() as it's only a waste of CPU, inspired by #4065. +- Add CAP support. Currently implemented are: multi-prefix (NAMESX), and + userhost-in-names (UHNAMES). Patch from nenotopia (#4018, #4066). diff --git a/include/numeric.h b/include/numeric.h index b7c3b0827..22b89ad97 100644 --- a/include/numeric.h +++ b/include/numeric.h @@ -53,6 +53,8 @@ #define ERR_NOSUCHSERVICE 408 #define ERR_NOORIGIN 409 +#define ERR_INVALIDCAPCMD 410 + #define ERR_NORECIPIENT 411 #define ERR_NOTEXTTOSEND 412 #define ERR_NOTOPLEVEL 413 diff --git a/include/struct.h b/include/struct.h index e685bd4ca..dd640d31d 100644 --- a/include/struct.h +++ b/include/struct.h @@ -367,6 +367,7 @@ typedef unsigned int u_int32_t; /* XXX Hope this works! */ #define PROTO_NAMESX 0x4000 /* Send all rights in NAMES output */ #define PROTO_CLK 0x8000 /* Send cloaked host in the NICK command (regardless of +x/-x) */ #define PROTO_UHNAMES 0x10000 /* Send n!u@h in NAMES */ +#define PROTO_CLICAP 0x20000 /* client capability negotiation in process */ /* * flags macros. diff --git a/makefile.win32 b/makefile.win32 index 62f292439..f49cc6c6a 100644 --- a/makefile.win32 +++ b/makefile.win32 @@ -219,7 +219,7 @@ MOD_FILES=SRC/MODULES/L_COMMANDS.C SRC/MODULES/M_CHGHOST.C SRC/MODULES/M_SDESC.C SRC/MODULES/M_WATCH.C SRC/MODULES/M_PART.C SRC/MODULES/M_JOIN.C \ SRC/MODULES/M_MOTD.C SRC/MODULES/M_OPERMOTD.C SRC/MODULES/M_BOTMOTD.C \ SRC/MODULES/M_LUSERS.C SRC/MODULES/M_NAMES.C SRC/MODULES/M_SVSNOLAG.C \ - SRC/MODULES/M_STARTTLS.C SRC/MODULES/M_NOPOST.C SRC/MODULES/M_ISSECURE.C + SRC/MODULES/M_STARTTLS.C SRC/MODULES/M_NOPOST.C SRC/MODULES/M_ISSECURE.C SRC/MODULES/M_CAP.C DLL_FILES=SRC/MODULES/M_CHGHOST.DLL SRC/MODULES/M_SDESC.DLL SRC/MODULES/M_SETIDENT.DLL \ SRC/MODULES/M_SETNAME.DLL SRC/MODULES/M_SETHOST.DLL SRC/MODULES/M_CHGIDENT.DLL \ @@ -256,7 +256,7 @@ DLL_FILES=SRC/MODULES/M_CHGHOST.DLL SRC/MODULES/M_SDESC.DLL SRC/MODULES/M_SETIDE SRC/MODULES/M_MOTD.DLL SRC/MODULES/M_OPERMOTD.DLL SRC/MODULES/M_BOTMOTD.DLL \ SRC/MODULES/M_LUSERS.DLL SRC/MODULES/M_NAMES.DLL SRC/MODULES/M_SVSNOLAG.DLL \ SRC/MODULES/M_STARTTLS.DLL \ - SRC/MODULES/M_NOPOST.DLL SRC/MODULES/M_ISSECURE.DLL \ + SRC/MODULES/M_NOPOST.DLL SRC/MODULES/M_ISSECURE.DLL SRC/MODULES/M_CAP.DLL \ SRC/MODULES/CLOAK.DLL @@ -825,6 +825,9 @@ src/modules/m_nopost.dll: src/modules/m_nopost.c $(INCLUDES) src/modules/m_issecure.dll: src/modules/m_issecure.c $(INCLUDES) $(CC) $(MODCFLAGS) src/modules/m_issecure.c $(MODLFLAGS) +src/modules/m_cap.dll: src/modules/m_cap.c $(INCLUDES) + $(CC) $(MODCFLAGS) src/modules/m_cap.c $(MODLFLAGS) + dummy: diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index 5e3a78da1..a0fae71c2 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -54,7 +54,7 @@ R_MODULES= \ m_connect.so m_dccallow.so m_userip.so m_nick.so m_user.so \ m_mode.so m_watch.so m_part.so m_join.so m_motd.so m_opermotd.so \ m_botmotd.so m_lusers.so m_names.so m_svsnolag.so m_addmotd.so \ - m_svslusers.so m_starttls.so m_nopost.so m_issecure.so + m_svslusers.so m_starttls.so m_nopost.so m_issecure.so m_cap.so #note change of .c to .o COMMANDS=m_sethost.o m_chghost.o m_chgident.o m_setname.o m_setident.o \ @@ -77,7 +77,7 @@ COMMANDS=m_sethost.o m_chghost.o m_chgident.o m_setname.o m_setident.o \ m_connect.o m_dccallow.o m_userip.o m_nick.o m_user.o \ m_mode.o m_watch.o m_part.o m_join.o m_motd.o m_opermotd.o \ m_botmotd.o m_lusers.o m_names.o m_svsnolag.o m_starttls.o \ - m_nopost.o m_issecure.o + m_nopost.o m_issecure.o m_cap.o MODULES=commands.so cloak.so $(R_MODULES) @@ -419,6 +419,9 @@ m_nopost.o: m_nopost.c $(INCLUDES) m_issecure.o: m_issecure.c $(INCLUDES) $(CC) $(CFLAGS) $(MODULEFLAGS) -c m_issecure.c +m_cap.o: m_cap.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -c m_cap.c + ############################################################################# # .so's section ############################################################################# @@ -843,6 +846,10 @@ m_issecure.so: m_issecure.c $(INCLUDES) $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ -o m_issecure.so m_issecure.c +m_cap.so: m_cap.c $(INCLUDES) + $(CC) $(CFLAGS) $(MODULEFLAGS) -DDYNAMIC_LINKING \ + -o m_cap.so m_cap.c + ############################################################################# # and now the remaining modules... ############################################################################# diff --git a/src/modules/l_commands.c b/src/modules/l_commands.c index 19612a867..de938ed18 100644 --- a/src/modules/l_commands.c +++ b/src/modules/l_commands.c @@ -178,6 +178,7 @@ extern int m_svsnolag_Load(int module_load); extern int m_starttls_Load(int module_load); extern int m_nopost_Load(int module_load); extern int m_issecure_Load(int module_load); +extern int m_cap_Load(int module_load); #ifdef GUEST extern int m_guest_Load(int module_load); #endif @@ -217,6 +218,7 @@ extern int m_lusers_Unload(), m_names_Unload(), m_svsnolag_Unload(); extern int m_starttls_Unload(); extern int m_nopost_Unload(); extern int m_issecure_Unload(); +extern int m_cap_Unload(); #ifdef GUEST extern int m_guest_Unload(); #endif @@ -477,6 +479,7 @@ int l_commands_Load(int module_load) m_starttls_Load(module_load); m_nopost_Load(module_load); m_issecure_Load(module_load); + m_cap_Load(module_load); #ifdef GUEST m_guest_Load(module_load); #endif @@ -592,6 +595,7 @@ int l_commands_Unload(int module_unload) m_starttls_Unload(); m_nopost_Unload(); m_issecure_Unload(); + m_cap_Unload(); #ifdef GUEST m_guest_Unload(); #endif diff --git a/src/modules/m_cap.c b/src/modules/m_cap.c new file mode 100644 index 000000000..0b89dcce2 --- /dev/null +++ b/src/modules/m_cap.c @@ -0,0 +1,442 @@ +/* + * IRC - Internet Relay Chat, src/modules/m_cap.c + * (C) 2012 The UnrealIRCd Team + * + * See file AUTHORS in IRC package for additional names of + * the programmers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "config.h" +#include "struct.h" +#include "common.h" +#include "sys.h" +#include "numeric.h" +#include "msg.h" +#include "proto.h" +#include "channel.h" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif +#include +#include "h.h" +#ifdef STRIPBADWORDS +#include "badwords.h" +#endif +#ifdef _WIN32 +#include "version.h" +#endif + +typedef int (*bqcmp)(const void *, const void *); + +DLLFUNC int m_cap(aClient *cptr, aClient *sptr, int parc, char *parv[]); + +#define MSG_CAP "CAP" +#define TOK_CAP "CA" + +ModuleHeader MOD_HEADER(m_cap) + = { + "m_cap", /* Name of module */ + "$Id$", /* Version */ + "command /cap", /* Short description of module */ + "3.2-b8-1", + NULL + }; + +struct clicap { + const char *name; + int cap; + int flags; +}; + +#define CLICAP_FLAGS_NONE 0x0 +#define CLICAP_FLAGS_STICKY 0x1 +#define CLICAP_FLAGS_CLIACK 0x2 + +static struct clicap clicap_table[] = { + {"multi-prefix", PROTO_NAMESX, CLICAP_FLAGS_NONE}, + {"userhost-in-names", PROTO_UHNAMES, CLICAP_FLAGS_NONE}, +}; + +#define CLICAP_TABLE_SIZE (sizeof(clicap_table) / sizeof(struct clicap)) + +static int clicap_compare(const char *name, struct clicap *cap) +{ + return strcmp(name, cap->name); +} + +static struct clicap *clicap_find(const char *data, int *negate, int *finished) +{ + static char buf[BUFSIZE]; + static char *p; + struct clicap *cap; + char *s; + + *negate = 0; + + if (data) + { + strlcpy(buf, data, sizeof(buf)); + p = buf; + } + + if (*finished) + return NULL; + + /* skip any whitespace */ + while(*p && isspace(*p)) + p++; + + if (BadPtr(p)) + { + *finished = 1; + return NULL; + } + + if(*p == '-') + { + *negate = 1; + p++; + + /* someone sent a '-' without a parameter.. */ + if(*p == '\0') + return NULL; + } + + if((s = strchr(p, ' '))) + *s++ = '\0'; + + if((cap = bsearch(p, clicap_table, CLICAP_TABLE_SIZE, + sizeof(struct clicap), (bqcmp) clicap_compare))) + { + if(s) + p = s; + else + *finished = 1; + } + + return cap; +} + +static void clicap_generate(aClient *sptr, const char *subcmd, int flags, int clear) +{ + char buf[BUFSIZE]; + char capbuf[BUFSIZE]; + char *p; + int buflen = 0; + int curlen, mlen; + size_t i; + + mlen = snprintf(buf, BUFSIZE, ":%s CAP %s %s", me.name, BadPtr(sptr->name) ? "*" : sptr->name, subcmd); + + p = capbuf; + buflen = mlen; + + if (flags == -1) + { + sendto_one(sptr, "%s :", buf); + return; + } + + for (i = 0; i < CLICAP_TABLE_SIZE; i++) + { + if (flags) + { + if (!CHECKPROTO(sptr, clicap_table[i].cap)) + continue; + else if (clear && clicap_table[i].flags & CLICAP_FLAGS_STICKY) + continue; + } + + /* \r\n\0, possible "-~=", space, " *" */ + if (buflen + strlen(clicap_table[i].name) >= BUFSIZE - 10) + { + if (buflen != mlen) + *(p - 1) = '\0'; + else + *p = '\0'; + + sendto_one(sptr, "%s * :%s", buf, capbuf); + p = capbuf; + buflen = mlen; + } + + if (clear) + { + *p++ = '-'; + buflen++; + + if (clicap_table[i].flags & CLICAP_FLAGS_CLIACK && + CHECKPROTO(sptr, clicap_table[i].cap)) + { + *p++ = '~'; + buflen++; + } + } + else + { + if (clicap_table[i].flags & CLICAP_FLAGS_STICKY) + { + *p++ = '='; + buflen++; + } + + if (clicap_table[i].flags & CLICAP_FLAGS_CLIACK && + !CHECKPROTO(sptr, clicap_table[i].cap)) + { + *p++ = '~'; + buflen++; + } + } + + curlen = snprintf(p, (capbuf + BUFSIZE) - p, "%s ", clicap_table[i].name); + p += curlen; + buflen += curlen; + } + + if (buflen != mlen) + *(p - 1) = '\0'; + else + *p = '\0'; + + sendto_one(sptr, "%s :%s", buf, capbuf); +} + +static void cap_ack(aClient *sptr, const char *arg) +{ + struct clicap *cap; + int capadd = 0, capdel = 0; + int finished = 0, negate; + + if (BadPtr(arg)) + return; + + for(cap = clicap_find(arg, &negate, &finished); cap; + cap = clicap_find(NULL, &negate, &finished)) + { + /* sent an ACK for something they havent REQd */ + if(!CHECKPROTO(sptr, cap->cap)) + continue; + + if(negate) + { + /* dont let them ack something sticky off */ + if(cap->flags & CLICAP_FLAGS_STICKY) + continue; + + capdel |= cap->cap; + } + else + capadd |= cap->cap; + } + + sptr->proto |= capadd; + sptr->proto &= ~capdel; +} + +static void cap_clear(aClient *sptr, const char *arg) +{ + clicap_generate(sptr, "ACK", sptr->proto ? sptr->proto : -1, 1); + + sptr->proto = 0; +} + +static void cap_end(aClient *sptr, const char *arg) +{ + if (IsRegisteredUser(sptr)) + return; + + sptr->proto &= ~PROTO_CLICAP; + + if (sptr->name[0] && sptr->user != NULL) + register_user(sptr, sptr, sptr->name, sptr->user->username, NULL, NULL, NULL); +} + +static void cap_list(aClient *sptr, const char *arg) +{ + clicap_generate(sptr, "LIST", sptr->proto ? sptr->proto : -1, 0); +} + +static void cap_ls(aClient *sptr, const char *arg) +{ + if (!IsRegisteredUser(sptr)) + sptr->proto |= PROTO_CLICAP; + + clicap_generate(sptr, "LS", 0, 0); +} + +static void cap_req(aClient *sptr, const char *arg) +{ + char buf[BUFSIZE]; + char pbuf[2][BUFSIZE]; + struct clicap *cap; + int buflen, plen; + int i = 0; + int capadd = 0, capdel = 0; + int finished = 0, negate; + + if (!IsRegisteredUser(sptr)) + sptr->proto |= PROTO_CLICAP; + + if (BadPtr(arg)) + return; + + buflen = snprintf(buf, sizeof(buf), ":%s CAP %s ACK", + me.name, BadPtr(sptr->name) ? "*" : sptr->name); + + pbuf[0][0] = '\0'; + plen = 0; + + for(cap = clicap_find(arg, &negate, &finished); cap; + cap = clicap_find(NULL, &negate, &finished)) + { + /* filled the first array, but cant send it in case the + * request fails. one REQ should never fill more than two + * buffers --fl + */ + if (buflen + plen + strlen(cap->name) + 6 >= BUFSIZE) + { + pbuf[1][0] = '\0'; + plen = 0; + i = 1; + } + + if (negate) + { + if (cap->flags & CLICAP_FLAGS_STICKY) + { + finished = 0; + break; + } + + strcat(pbuf[i], "-"); + plen++; + + capdel |= cap->cap; + } + else + { + if (cap->flags & CLICAP_FLAGS_STICKY) + { + strcat(pbuf[i], "="); + plen++; + } + + capadd |= cap->cap; + } + + if (cap->flags & CLICAP_FLAGS_CLIACK) + { + strcat(pbuf[i], "~"); + plen++; + } + + strcat(pbuf[i], cap->name); + strcat(pbuf[i], " "); + plen += (strlen(cap->name) + 1); + } + + if (!finished) + { + sendto_one(sptr, ":%s CAP %s NAK :%s", me.name, BadPtr(sptr->name) ? "*" : sptr->name, arg); + return; + } + + if (i) + { + sendto_one(sptr, "%s * :%s", buf, pbuf[0]); + sendto_one(sptr, "%s :%s", buf, pbuf[1]); + } + else + sendto_one(sptr, "%s :%s", buf, pbuf[0]); + + sptr->proto |= capadd; + sptr->proto &= ~capdel; +} + +struct clicap_cmd { + const char *cmd; + void (*func)(struct Client *source_p, const char *arg); +}; + +static struct clicap_cmd clicap_cmdtable[] = { + { "ACK", cap_ack }, + { "CLEAR", cap_clear }, + { "END", cap_end }, + { "LIST", cap_list }, + { "LS", cap_ls }, + { "REQ", cap_req }, +}; + +static int clicap_cmd_search(const char *command, struct clicap_cmd *entry) +{ + return strcmp(command, entry->cmd); +} + +DLLFUNC int m_cap(aClient *cptr, aClient *sptr, int parc, char *parv[]) +{ + struct clicap_cmd *cmd; + + if (parc < 2) + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), + me.name, BadPtr(sptr->name) ? "*" : sptr->name, + "CAP"); + + return 0; + } + + if(!(cmd = bsearch(parv[1], clicap_cmdtable, + sizeof(clicap_cmdtable) / sizeof(struct clicap_cmd), + sizeof(struct clicap_cmd), (bqcmp) clicap_cmd_search))) + { + sendto_one(sptr, err_str(ERR_INVALIDCAPCMD), + me.name, BadPtr(sptr->name) ? "*" : sptr->name, + parv[1]); + + return 0; + } + + (cmd->func)(sptr, parv[2]); + return 0; +} + +/* This is called on module init, before Server Ready */ +DLLFUNC int MOD_INIT(m_cap)(ModuleInfo *modinfo) +{ + MARK_AS_OFFICIAL_MODULE(modinfo); + CommandAdd(modinfo->handle, MSG_CAP, TOK_CAP, m_cap, MAXPARA, M_UNREGISTERED|M_USER); + + return MOD_SUCCESS; +} + +/* Is first run when server is 100% ready */ +DLLFUNC int MOD_LOAD(m_cap)(int module_load) +{ + return MOD_SUCCESS; +} + + +/* Called when module is unloaded */ +DLLFUNC int MOD_UNLOAD(m_cap)(int module_unload) +{ + return MOD_SUCCESS; +} diff --git a/src/modules/m_nick.c b/src/modules/m_nick.c index 9667873b2..e30bf1291 100644 --- a/src/modules/m_nick.c +++ b/src/modules/m_nick.c @@ -736,7 +736,7 @@ DLLFUNC CMD_FUNC(m_nick) } /* This had to be copied here to avoid problems.. */ (void)strcpy(sptr->name, nick); - if (sptr->user && IsNotSpoof(sptr)) + if (sptr->user && IsNotSpoof(sptr) && !CHECKPROTO(sptr, PROTO_CLICAP)) { /* ** USER already received, now we have NICK. diff --git a/src/modules/m_user.c b/src/modules/m_user.c index 059cb5ca0..ba138d7e0 100644 --- a/src/modules/m_user.c +++ b/src/modules/m_user.c @@ -203,7 +203,10 @@ DLLFUNC CMD_FUNC(m_user) strlcpy(user->svid, sstamp, sizeof(user->svid)); strlcpy(sptr->info, realname, sizeof(sptr->info)); - if (sptr->name[0] && (IsServer(cptr) ? 1 : IsNotSpoof(sptr))) + if (sptr->name[0] && + (IsServer(cptr) ? 1 : IsNotSpoof(sptr)) && + (!MyConnect(sptr) || (MyConnect(sptr) && !CHECKPROTO(sptr, PROTO_CLICAP))) + ) /* NICK and no-spoof already received, now we have USER... */ { if (USE_BAN_VERSION && MyConnect(sptr)) diff --git a/src/s_err.c b/src/s_err.c index 80d30a2db..53fe4854e 100644 --- a/src/s_err.c +++ b/src/s_err.c @@ -454,7 +454,7 @@ static char *replies[] = { /* 407 ERR_TOOMANYTARGETS */ ":%s 407 %s %s :Duplicate recipients. No message delivered", /* 408 */ NULL, /* rfc2812, bahamut */ /* 409 ERR_NOORIGIN */ ":%s 409 %s :No origin specified", -/* 410 */ NULL, +/* 410 ERR_INVALIDCAPCMD */ ":%s 410 %s %s :Invalid CAP subcommand", /* 411 ERR_NORECIPIENT */ ":%s 411 %s :No recipient given (%s)", /* 412 ERR_NOTEXTTOSEND */ ":%s 412 %s :No text to send", /* 413 ERR_NOTOPLEVEL */ ":%s 413 %s %s :No toplevel domain specified",