# HG changeset patch
# User William Pitcock <nenolod@dereferenced.org>
# Date 1326086189 21600
# Node ID 3b8b15d63d19a4888a7606cb99b8dc20f91b43ec
# Parent  baf498e5e970ab972e47353fc1a14fdf2ab6917b
Add support for client-capability negotiation.

diff -r baf498e5e970 -r 3b8b15d63d19 include/numeric.h
--- a/include/numeric.h	Tue Jan 03 05:50:38 2012 +0000
+++ b/include/numeric.h	Sun Jan 08 23:16:29 2012 -0600
@@ -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 -r baf498e5e970 -r 3b8b15d63d19 include/struct.h
--- a/include/struct.h	Tue Jan 03 05:50:38 2012 +0000
+++ b/include/struct.h	Sun Jan 08 23:16:29 2012 -0600
@@ -367,6 +367,7 @@
 #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 -r baf498e5e970 -r 3b8b15d63d19 src/modules/Makefile.in
--- a/src/modules/Makefile.in	Tue Jan 03 05:50:38 2012 +0000
+++ b/src/modules/Makefile.in	Sun Jan 08 23:16:29 2012 -0600
@@ -54,7 +54,7 @@
 	 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 @@
 	 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_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 @@
 	$(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 -r baf498e5e970 -r 3b8b15d63d19 src/modules/l_commands.c
--- a/src/modules/l_commands.c	Tue Jan 03 05:50:38 2012 +0000
+++ b/src/modules/l_commands.c	Sun Jan 08 23:16:29 2012 -0600
@@ -178,6 +178,7 @@
 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_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 @@
 	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 @@
 	m_starttls_Unload();
 	m_nopost_Unload();
 	m_issecure_Unload();
+	m_cap_Unload();
 #ifdef GUEST
 	m_guest_Unload();
 #endif
diff -r baf498e5e970 -r 3b8b15d63d19 src/modules/m_cap.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/m_cap.c	Sun Jan 08 23:16:29 2012 -0600
@@ -0,0 +1,440 @@
+/*
+ *   IRC - Internet Relay Chat, src/modules/m_cap.c
+ *   (C) 2010 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 "channel.h"
+#include <time.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+#include <io.h>
+#endif
+#include <fcntl.h>
+#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.10",
+	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)
+{
+	CommandAdd(modinfo->handle, MSG_CAP, TOK_CAP, m_cap, MAXPARA, M_UNREGISTERED | M_USER | M_SHUN | M_VIRUS);
+
+	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 -r baf498e5e970 -r 3b8b15d63d19 src/modules/m_nick.c
--- a/src/modules/m_nick.c	Tue Jan 03 05:50:38 2012 +0000
+++ b/src/modules/m_nick.c	Sun Jan 08 23:16:29 2012 -0600
@@ -736,7 +736,7 @@
 		}
 		/* 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 -r baf498e5e970 -r 3b8b15d63d19 src/modules/m_user.c
--- a/src/modules/m_user.c	Tue Jan 03 05:50:38 2012 +0000
+++ b/src/modules/m_user.c	Sun Jan 08 23:16:29 2012 -0600
@@ -203,7 +203,10 @@
 		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 -r baf498e5e970 -r 3b8b15d63d19 src/s_err.c
--- a/src/s_err.c	Tue Jan 03 05:50:38 2012 +0000
+++ b/src/s_err.c	Sun Jan 08 23:16:29 2012 -0600
@@ -454,7 +454,7 @@
 /* 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",
