Index: Makefile
===================================================================
--- Makefile	(revision d797fb45e38b005b25fa043f4d3c427ec7f7fe53)
+++ Makefile	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
@@ -10,5 +10,5 @@
 
 # Program variables
-objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o conf.o log.o
+objects = bitlbee.o dcc.o help.o ipc.o irc.o irc_im.o irc_cap.o irc_channel.o irc_commands.o irc_send.o irc_user.o irc_util.o nick.o $(OTR_BI) query.o root_commands.o set.o storage.o $(STORAGE_OBJS) unix.o conf.o log.o
 headers = $(wildcard $(_SRCDIR_)*.h $(_SRCDIR_)lib/*.h $(_SRCDIR_)protocols/*.h)
 subdirs = lib protocols
Index: irc.h
===================================================================
--- irc.h	(revision d797fb45e38b005b25fa043f4d3c427ec7f7fe53)
+++ irc.h	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
@@ -65,4 +65,9 @@
 	IRC_UTF8_NICKS = 0x10000, /* Disable ASCII restrictions on buddy nicks. */
 } irc_status_t;
+
+typedef enum {
+	CAP_FOO = (1 << 0),
+	CAP_BAR = (1 << 1),
+} irc_cap_flag_t;
 
 struct irc_user;
@@ -352,3 +357,6 @@
 void bee_irc_user_nick_reset(irc_user_t *iu);
 
+/* irc_cap.c */
+void irc_cmd_cap(irc_t *irc, char **cmd);
+
 #endif
Index: irc_cap.c
===================================================================
--- irc_cap.c	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
+++ irc_cap.c	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
@@ -0,0 +1,180 @@
+/********************************************************************\
+  * BitlBee -- An IRC to other IM-networks gateway                     *
+  *                                                                    *
+  * Copyright 2002-2013 Wilmer van der Gaast and others                *
+  \********************************************************************/
+
+/* IRCv3 CAP command
+ *
+ * Specs:
+ *  - v3.1: http://ircv3.net/specs/core/capability-negotiation-3.1.html
+ *  - v3.2: http://ircv3.net/specs/core/capability-negotiation-3.2.html
+ *
+ * */
+
+/*
+  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 2 of the License, 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 with
+  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
+  Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "bitlbee.h"
+
+typedef struct {
+	char *name;
+	irc_cap_flag_t flag;
+} cap_info_t;
+
+static const cap_info_t supported_caps[] = {
+	{"foo", CAP_FOO},
+	{"bar", CAP_BAR},
+	{NULL},
+};
+
+static irc_cap_flag_t cap_flag_from_string(char *cap_name)
+{
+	int i;
+
+	if (!cap_name || !cap_name[0]) {
+		return 0;
+	}
+
+	if (cap_name[0] == '-') {
+		cap_name++;
+	}
+
+	for (i = 0; supported_caps[i].name; i++) {
+		if (strcmp(supported_caps[i].name, cap_name) == 0) {
+			return supported_caps[i].flag;
+		}
+	}
+	return 0;
+}
+
+static gboolean irc_cmd_cap_req(irc_t *irc, char *caps)
+{
+	int i;
+	char *lower = NULL;
+	char **split = NULL;
+	irc_cap_flag_t new_caps = irc->caps;
+
+	if (!caps || !caps[0]) {
+		return FALSE;
+	}
+
+	lower = g_ascii_strdown(caps, -1);
+	split = g_strsplit(lower, " ", -1);
+	g_free(lower);
+
+	for (i = 0; split[i]; i++) {
+		gboolean remove;
+		irc_cap_flag_t flag;
+
+		if (!split[i][0]) {
+			continue;   /* skip empty items (consecutive spaces) */
+		}
+
+		remove = (split[i][0] == '-');
+		flag = cap_flag_from_string(split[i]);
+		
+		if (!flag || (remove && !(irc->caps & flag))) {
+			/* unsupported cap, or removing something that isn't there */
+			g_strfreev(split);
+			return FALSE;
+		}
+
+		if (remove) {
+			new_caps &= ~flag;
+		} else {
+			new_caps |= flag;
+		}
+	}
+
+	/* if we got here, set the new caps and ack */
+	irc->caps = new_caps;
+
+	g_strfreev(split);
+	return TRUE;
+}
+
+/* version can be "302" or NULL, but we don't need cap-3.2 for anything yet */
+static void irc_cmd_cap_ls(irc_t *irc, char *version)
+{
+	int i;
+	GString *str = g_string_sized_new(256);
+
+	for (i = 0; supported_caps[i].name; i++) {
+		if (i != 0) {
+			g_string_append_c(str, ' ');
+		}
+		g_string_append(str, supported_caps[i].name);
+	}
+
+	irc_send_cap(irc, "LS", str->str);
+
+	g_string_free(str, TRUE);
+}
+
+/* this one looks suspiciously similar to cap ls,
+ * but cap-3.2 will make them very different */
+static void irc_cmd_cap_list(irc_t *irc)
+{
+	int i;
+	gboolean first = TRUE;
+	GString *str = g_string_sized_new(256);
+
+	for (i = 0; supported_caps[i].name; i++) {
+		if (irc->caps & supported_caps[i].flag) {
+			if (!first) {
+				g_string_append_c(str, ' ');
+			}
+			first = FALSE;
+
+			g_string_append(str, supported_caps[i].name);
+		}
+	}
+
+	irc_send_cap(irc, "LIST", str->str);
+
+	g_string_free(str, TRUE);
+}
+
+void irc_cmd_cap(irc_t *irc, char **cmd)
+{
+	if (!(irc->status & USTATUS_LOGGED_IN)) {
+		/* Put registration on hold until CAP END */
+		irc->status |= USTATUS_CAP_PENDING;
+	}
+
+	if (g_strcasecmp(cmd[1], "LS") == 0) {
+		irc_cmd_cap_ls(irc, cmd[2]);
+
+	} else if (g_strcasecmp(cmd[1], "LIST") == 0) {
+		irc_cmd_cap_list(irc);
+
+	} else if (g_strcasecmp(cmd[1], "REQ") == 0) {
+		gboolean ack = irc_cmd_cap_req(irc, cmd[2]);
+
+		irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : "");
+
+	} else if (g_strcasecmp(cmd[1], "END") == 0) {
+		irc->status &= ~USTATUS_CAP_PENDING;
+		irc_check_login(irc);
+
+	} else {
+		irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]);
+	}
+
+}
+
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision d797fb45e38b005b25fa043f4d3c427ec7f7fe53)
+++ irc_commands.c	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
@@ -28,153 +28,4 @@
 #include "help.h"
 #include "ipc.h"
-
-typedef guint32 cap_flag;     /* 32 bits ought to be enough for anybody */
-
-typedef struct _cap_info {
-	char *name;
-	cap_flag flag;
-} cap_info_t;
-
-#define CAP_FOO (1 << 0)
-#define CAP_BAR (1 << 1)
-
-static const cap_info_t supported_caps[] = {
-	{"foo", CAP_FOO},
-	{"bar", CAP_BAR},
-	{NULL},
-};
-
-static cap_flag cap_flag_from_string(char *cap_name) {
-	int i;
-
-	if (!cap_name && !cap_name[0]) {
-		return 0;
-	}
-
-	if (cap_name[0] == '-') {
-		cap_name++;
-	}
-
-	for (i = 0; supported_caps[i].name; i++) {
-		if (strcmp(supported_caps[i].name, cap_name) == 0) {
-			return supported_caps[i].flag;
-		}
-	}
-	return 0;
-}
-
-static gboolean irc_cmd_cap_req(irc_t *irc, char *caps)
-{
-	int i;
-	char *lower = NULL;
-        char **split = NULL;
-	cap_flag new_caps = irc->caps;
-
-	if (!caps || !caps[0]) {
-		return FALSE;
-	}
-
-	lower = g_ascii_strdown(caps, -1);
-	split = g_strsplit(lower, " ", -1);
-	g_free(lower);
-
-	for (i = 0; split[i]; i++) {
-		gboolean remove;
-		cap_flag flag;
-
-		if (!split[i][0]) {
-			continue;   /* skip empty items (consecutive spaces) */
-		}
-
-		remove = (split[i][0] == '-');
-		flag = cap_flag_from_string(split[i]);
-		
-		if (!flag || (remove && !(irc->caps & flag))) {
-			/* unsupported cap, or removing something that isn't there */
-			g_strfreev(split);
-			return FALSE;
-		}
-
-		if (remove) {
-			new_caps &= ~flag;
-		} else {
-			new_caps |= flag;
-		}
-	}
-
-	/* if we got here, set the new caps and ack */
-	irc->caps = new_caps;
-
-	g_strfreev(split);
-	return TRUE;
-}
-
-/* version can be "302" or NULL, but we don't need cap-3.2 for anything yet */
-static void irc_cmd_cap_ls(irc_t *irc, char *version) {
-	int i;
-	GString *str = g_string_sized_new(256);
-
-	for (i = 0; supported_caps[i].name; i++) {
-		if (i != 0) {
-			g_string_append_c(str, ' ');
-		}
-		g_string_append(str, supported_caps[i].name);
-	}
-
-	irc_send_cap(irc, "LS", str->str);
-
-	g_string_free(str, TRUE);
-}
-
-/* this one looks suspiciously similar to cap ls,
- * but cap-3.2 will make them very different */
-static void irc_cmd_cap_list(irc_t *irc) {
-	int i;
-	gboolean first = TRUE;
-	GString *str = g_string_sized_new(256);
-
-	for (i = 0; supported_caps[i].name; i++) {
-		if (irc->caps & supported_caps[i].flag) {
-			if (!first) {
-				g_string_append_c(str, ' ');
-			}
-			first = FALSE;
-
-			g_string_append(str, supported_caps[i].name);
-		}
-	}
-
-	irc_send_cap(irc, "LIST", str->str);
-
-	g_string_free(str, TRUE);
-}
-
-static void irc_cmd_cap(irc_t *irc, char **cmd)
-{
-	if (!(irc->status & USTATUS_LOGGED_IN)) {
-		/* Put registration on hold until CAP END */
-		irc->status |= USTATUS_CAP_PENDING;
-	}
-
-	if (g_strcasecmp(cmd[1], "LS") == 0) {
-		irc_cmd_cap_ls(irc, cmd[2]);
-
-	} else if (g_strcasecmp(cmd[1], "LIST") == 0) {
-		irc_cmd_cap_list(irc);
-
-	} else if (g_strcasecmp(cmd[1], "REQ") == 0) {
-		gboolean ack = irc_cmd_cap_req(irc, cmd[2]);
-
-		irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : "");
-
-	} else if (g_strcasecmp(cmd[1], "END") == 0) {
-		irc->status &= ~USTATUS_CAP_PENDING;
-		irc_check_login(irc);
-
-	} else {
-		irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]);
-	}
-
-}
 
 static void irc_cmd_pass(irc_t *irc, char **cmd)
Index: tests/Makefile
===================================================================
--- tests/Makefile	(revision d797fb45e38b005b25fa043f4d3c427ec7f7fe53)
+++ tests/Makefile	(revision 355e2ade69fe6555320d8fe5b4b9d3a56c3e6edb)
@@ -15,5 +15,5 @@
 distclean: clean
 
-main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o
+main_objs = bitlbee.o conf.o dcc.o help.o ipc.o irc.o irc_cap.o irc_channel.o irc_commands.o irc_im.o irc_send.o irc_user.o irc_util.o irc_commands.o log.o nick.o query.o root_commands.o set.o storage.o storage_xml.o
 
 test_objs = check.o check_util.o check_nick.o check_md5.o check_arc.o check_irc.o check_help.o check_user.o check_set.o check_jabber_sasl.o check_jabber_util.o
