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