source: irc_cap.c @ 59a7dc5

Last change on this file since 59a7dc5 was c9603a3, checked in by Marius Halden <marius.h@…>, at 2016-04-06T18:08:41Z

Ignore CAP END when received multiple times

This fixes a segfault when CAP END was received after the capabilty
negotiation was already over.

  • Property mode set to 100644
File size: 4.6 KB
Line 
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
34typedef struct {
35        char *name;
36        irc_cap_flag_t flag;
37} cap_info_t;
38
39static 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        {NULL},
46};
47
48static irc_cap_flag_t cap_flag_from_string(char *cap_name)
49{
50        int i;
51
52        if (!cap_name || !cap_name[0]) {
53                return 0;
54        }
55
56        if (cap_name[0] == '-') {
57                cap_name++;
58        }
59
60        for (i = 0; supported_caps[i].name; i++) {
61                if (strcmp(supported_caps[i].name, cap_name) == 0) {
62                        return supported_caps[i].flag;
63                }
64        }
65        return 0;
66}
67
68static gboolean irc_cmd_cap_req(irc_t *irc, char *caps)
69{
70        int i;
71        char *lower = NULL;
72        char **split = NULL;
73        irc_cap_flag_t new_caps = irc->caps;
74
75        if (!caps || !caps[0]) {
76                return FALSE;
77        }
78
79        lower = g_ascii_strdown(caps, -1);
80        split = g_strsplit(lower, " ", -1);
81        g_free(lower);
82
83        for (i = 0; split[i]; i++) {
84                gboolean remove;
85                irc_cap_flag_t flag;
86
87                if (!split[i][0]) {
88                        continue;   /* skip empty items (consecutive spaces) */
89                }
90
91                remove = (split[i][0] == '-');
92                flag = cap_flag_from_string(split[i]);
93               
94                if (!flag || (remove && !(irc->caps & flag))) {
95                        /* unsupported cap, or removing something that isn't there */
96                        g_strfreev(split);
97                        return FALSE;
98                }
99
100                if (remove) {
101                        new_caps &= ~flag;
102                } else {
103                        new_caps |= flag;
104                }
105        }
106
107        /* if we got here, set the new caps and ack */
108        irc->caps = new_caps;
109
110        g_strfreev(split);
111        return TRUE;
112}
113
114/* version can be 0, 302, 303, or garbage from user input. thanks user input */
115static void irc_cmd_cap_ls(irc_t *irc, long version)
116{
117        int i;
118        GString *str = g_string_sized_new(256);
119
120        for (i = 0; supported_caps[i].name; i++) {
121                if (i != 0) {
122                        g_string_append_c(str, ' ');
123                }
124                g_string_append(str, supported_caps[i].name);
125
126                if (version >= 302 && supported_caps[i].flag == CAP_SASL) {
127                        g_string_append(str, "=PLAIN");
128                }
129        }
130
131        irc_send_cap(irc, "LS", str->str);
132
133        g_string_free(str, TRUE);
134}
135
136/* this one looks suspiciously similar to cap ls,
137 * but cap-3.2 will make them very different */
138static void irc_cmd_cap_list(irc_t *irc)
139{
140        int i;
141        gboolean first = TRUE;
142        GString *str = g_string_sized_new(256);
143
144        for (i = 0; supported_caps[i].name; i++) {
145                if (irc->caps & supported_caps[i].flag) {
146                        if (!first) {
147                                g_string_append_c(str, ' ');
148                        }
149                        first = FALSE;
150
151                        g_string_append(str, supported_caps[i].name);
152                }
153        }
154
155        irc_send_cap(irc, "LIST", str->str);
156
157        g_string_free(str, TRUE);
158}
159
160void irc_cmd_cap(irc_t *irc, char **cmd)
161{
162        if (!(irc->status & USTATUS_LOGGED_IN)) {
163                /* Put registration on hold until CAP END */
164                irc->status |= USTATUS_CAP_PENDING;
165        }
166
167        if (g_strcasecmp(cmd[1], "LS") == 0) {
168                irc_cmd_cap_ls(irc, cmd[2] ? strtol(cmd[2], NULL, 10) : 0);
169
170        } else if (g_strcasecmp(cmd[1], "LIST") == 0) {
171                irc_cmd_cap_list(irc);
172
173        } else if (g_strcasecmp(cmd[1], "REQ") == 0) {
174                gboolean ack = irc_cmd_cap_req(irc, cmd[2]);
175
176                irc_send_cap(irc, ack ? "ACK" : "NAK", cmd[2] ? : "");
177
178        } else if (g_strcasecmp(cmd[1], "END") == 0) {
179                if (!(irc->status & USTATUS_CAP_PENDING)) {
180                        return;
181                }
182                irc->status &= ~USTATUS_CAP_PENDING;
183
184                if (irc->status & USTATUS_SASL_PLAIN_PENDING) {
185                        irc_send_num(irc, 906, ":SASL authentication aborted");
186                        irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
187                }
188
189                irc_check_login(irc);
190
191        } else {
192                irc_send_num(irc, 410, "%s :Invalid CAP command", cmd[1]);
193        }
194
195}
196
Note: See TracBrowser for help on using the repository browser.