source: irc_cap.c @ b87e5dc

Last change on this file since b87e5dc was 58b63de6, checked in by dequis <dx@…>, at 2015-10-08T08:34:18Z

IRCv3 SASL capability + PLAIN method

Only plain, no other methods. We don't have built-in SSL to implement
EXTERNAL (certfp) and nothing else is worth implementing.

The actual authentication is pretty much like sending a server password
(when the server's authmode isn't closed), which means it happens in
cmd_identify, but this code also calls storage_check_pass() to send the
required success/failure replies.

SASL doesn't give us much benefit other than standards compliance, but
some clients might appreciate it.

And having a fifth way to do the same thing doesn't hurt! Now we have:

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