1 /********************************************************************\
2   * BitlBee -- An IRC to other IM-networks gateway                     *
3   *                                                                    *
4   * Copyright 2002-2013 Wilmer van der Gaast and others                *
5   \********************************************************************/
6 
7 /* IRCv3 CAP command
8  *
9  * Specs:
10  *  - v3.1: http://ircv3.net/specs/core/capability-negotiation-3.1.html
11  *  - v3.2: http://ircv3.net/specs/core/capability-negotiation-3.2.html
12  *
13  * */
14 
15 /*
16   This program is free software; you can redistribute it and/or modify
17   it under the terms of the GNU General Public License as published by
18   the Free Software Foundation; either version 2 of the License, or
19   (at your option) any later version.
20 
21   This program is distributed in the hope that it will be useful,
22   but WITHOUT ANY WARRANTY; without even the implied warranty of
23   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24   GNU General Public License for more details.
25 
26   You should have received a copy of the GNU General Public License with
27   the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
28   if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
29   Fifth Floor, Boston, MA  02110-1301  USA
30 */
31 
32 #include "bitlbee.h"
33 
34 typedef struct {
35 	char *name;
36 	irc_cap_flag_t flag;
37 } cap_info_t;
38 
39 static const cap_info_t supported_caps[] = {
40 	{"sasl", CAP_SASL},
41 	{"multi-prefix", CAP_MULTI_PREFIX},
42 	{"extended-join", CAP_EXTENDED_JOIN},
43 	{"away-notify", CAP_AWAY_NOTIFY},
44 	{"userhost-in-names", CAP_USERHOST_IN_NAMES},
45 	{"server-time", CAP_SERVER_TIME},
46 	{NULL},
47 };
48 
cap_flag_from_string(char * cap_name)49 static irc_cap_flag_t cap_flag_from_string(char *cap_name)
50 {
51 	int i;
52 
53 	if (!cap_name || !cap_name[0]) {
54 		return 0;
55 	}
56 
57 	if (cap_name[0] == '-') {
58 		cap_name++;
59 	}
60 
61 	for (i = 0; supported_caps[i].name; i++) {
62 		if (strcmp(supported_caps[i].name, cap_name) == 0) {
63 			return supported_caps[i].flag;
64 		}
65 	}
66 	return 0;
67 }
68 
irc_cmd_cap_req(irc_t * irc,char * caps)69 static gboolean irc_cmd_cap_req(irc_t *irc, char *caps)
70 {
71 	int i;
72 	char *lower = NULL;
73 	char **split = NULL;
74 	irc_cap_flag_t new_caps = irc->caps;
75 
76 	if (!caps || !caps[0]) {
77 		return FALSE;
78 	}
79 
80 	lower = g_ascii_strdown(caps, -1);
81 	split = g_strsplit(lower, " ", -1);
82 	g_free(lower);
83 
84 	for (i = 0; split[i]; i++) {
85 		gboolean remove;
86 		irc_cap_flag_t flag;
87 
88 		if (!split[i][0]) {
89 			continue;   /* skip empty items (consecutive spaces) */
90 		}
91 
92 		remove = (split[i][0] == '-');
93 		flag = cap_flag_from_string(split[i]);
94 
95 		if (!flag || (remove && !(irc->caps & flag))) {
96 			/* unsupported cap, or removing something that isn't there */
97 			g_strfreev(split);
98 			return FALSE;
99 		}
100 
101 		if (remove) {
102 			new_caps &= ~flag;
103 		} else {
104 			new_caps |= flag;
105 		}
106 	}
107 
108 	/* if we got here, set the new caps and ack */
109 	irc->caps = new_caps;
110 
111 	g_strfreev(split);
112 	return TRUE;
113 }
114 
115 /* version can be 0, 302, 303, or garbage from user input. thanks user input */
irc_cmd_cap_ls(irc_t * irc,long version)116 static void irc_cmd_cap_ls(irc_t *irc, long version)
117 {
118 	int i;
119 	GString *str = g_string_sized_new(256);
120 
121 	for (i = 0; supported_caps[i].name; i++) {
122 		if (i != 0) {
123 			g_string_append_c(str, ' ');
124 		}
125 		g_string_append(str, supported_caps[i].name);
126 
127 		if (version >= 302 && supported_caps[i].flag == CAP_SASL) {
128 			g_string_append(str, "=PLAIN");
129 		}
130 	}
131 
132 	irc_send_cap(irc, "LS", str->str);
133 
134 	g_string_free(str, TRUE);
135 }
136 
137 /* this one looks suspiciously similar to cap ls,
138  * but cap-3.2 will make them very different */
irc_cmd_cap_list(irc_t * irc)139 static void irc_cmd_cap_list(irc_t *irc)
140 {
141 	int i;
142 	gboolean first = TRUE;
143 	GString *str = g_string_sized_new(256);
144 
145 	for (i = 0; supported_caps[i].name; i++) {
146 		if (irc->caps & supported_caps[i].flag) {
147 			if (!first) {
148 				g_string_append_c(str, ' ');
149 			}
150 			first = FALSE;
151 
152 			g_string_append(str, supported_caps[i].name);
153 		}
154 	}
155 
156 	irc_send_cap(irc, "LIST", str->str);
157 
158 	g_string_free(str, TRUE);
159 }
160 
irc_cmd_cap(irc_t * irc,char ** cmd)161 void irc_cmd_cap(irc_t *irc, char **cmd)
162 {
163 	if (!(irc->status & USTATUS_LOGGED_IN)) {
164 		/* Put registration on hold until CAP END */
165 		irc->status |= USTATUS_CAP_PENDING;
166 	}
167 
168 	if (g_strcasecmp(cmd[1], "LS") == 0) {
169 		irc_cmd_cap_ls(irc, cmd[2] ? strtol(cmd[2], NULL, 10) : 0);
170 
171 	} else if (g_strcasecmp(cmd[1], "LIST") == 0) {
172 		irc_cmd_cap_list(irc);
173 
174 	} else if (g_strcasecmp(cmd[1], "REQ") == 0) {
175 		gboolean ack = irc_cmd_cap_req(irc, cmd[2]);
176 
177 		irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : "");
178 
179 	} else if (g_strcasecmp(cmd[1], "END") == 0) {
180 		if (!(irc->status & USTATUS_CAP_PENDING)) {
181 			return;
182 		}
183 		irc->status &= ~USTATUS_CAP_PENDING;
184 
185 		if (irc->status & USTATUS_SASL_PLAIN_PENDING) {
186 			irc_send_num(irc, 906, ":SASL authentication aborted");
187 			irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
188 		}
189 
190 		irc_check_login(irc);
191 
192 	} else {
193 		irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]);
194 	}
195 
196 }
197 
198