7fcba73fea9bbd14b3f8ec596b98a12c3276a291
[ngircd-alex.git] / src / ngircd / irc-cap.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * Please read the file COPYING, README and AUTHORS for more information.
10  */
11
12 #include "portab.h"
13
14 /**
15  * @file
16  * Handler for IRC capability ("CAP") commands
17  */
18
19 #include "imp.h"
20 #include <assert.h>
21 #include <string.h>
22
23 #include "defines.h"
24 #include "conn.h"
25 #include "channel.h"
26 #include "client-cap.h"
27 #include "irc-macros.h"
28 #include "irc-write.h"
29 #include "log.h"
30 #include "login.h"
31 #include "messages.h"
32 #include "parse.h"
33
34 #include "exp.h"
35 #include "irc-cap.h"
36
37 /* Local functions */
38
39 /**
40  * Set CAP negotiation status and mark client as "supports capabilities".
41  *
42  * @param Client The client to handle.
43  */
44 static void
45 Set_CAP_Negotiation(CLIENT *Client)
46 {
47         assert(Client != NULL);
48
49         if (Client_Type(Client) != CLIENT_USER)
50                 Client_CapAdd(Client, CLIENT_CAP_PENDING);
51         Client_CapAdd(Client, CLIENT_CAP_SUPPORTED);
52 }
53
54 /**
55  * Parse capability string and return numeric flag value.
56  *
57  * @param Args The string containing space-separated capability names.
58  * @return Changed capability flags or 0 on error.
59  */
60 static int
61 Parse_CAP(int Capabilities, char *Args)
62 {
63         static char tmp[COMMAND_LEN];
64         char *ptr;
65
66         assert(Args != NULL);
67
68         strlcpy(tmp, Args, sizeof(tmp));
69
70         ptr = strtok(tmp, " ");
71         while (ptr) {
72                 if (*ptr == '-') {
73                         /* drop capabilities */
74                         ptr++;
75                         if (strcmp(ptr, "multi-prefix") == 0)
76                                 Capabilities &= ~CLIENT_CAP_MULTI_PREFIX;
77                         else
78                                 return -1;
79                 } else {
80                         /* request capabilities */
81                         if (strcmp(ptr, "multi-prefix") == 0)
82                                 Capabilities |= CLIENT_CAP_MULTI_PREFIX;
83                         else
84                                 return -1;
85                 }
86                 ptr = strtok(NULL, " ");
87         }
88
89         return Capabilities;
90 }
91
92 /**
93  * Return textual representation of capability flags.
94  *
95  * Please note: this function returns a pointer to a global buffer and
96  * therefore isn't thread safe!
97  *
98  * @param Capabilities Capability flags (bitmask).
99  * @return Pointer to textual representation.
100  */
101 static char *
102 Get_CAP_String(int Capabilities)
103 {
104         static char txt[COMMAND_LEN];
105
106         txt[0] = '\0';
107
108         if (Capabilities & CLIENT_CAP_MULTI_PREFIX)
109                 strlcat(txt, "multi-prefix ", sizeof(txt));
110
111         return txt;
112 }
113
114 /**
115  * Handler for the IRCv3 sub-command "CAP LS".
116  *
117  * @param Client The client from which this command has been received.
118  * @param Arg Command argument or NULL.
119  * @return CONNECTED or DISCONNECTED.
120  */
121 static bool
122 Handle_CAP_LS(CLIENT *Client, UNUSED char *Arg)
123 {
124         assert(Client != NULL);
125
126         Set_CAP_Negotiation(Client);
127
128         return IRC_WriteStrClient(Client,
129                                   "CAP %s LS :multi-prefix",
130                                   Client_ID(Client));
131 }
132
133 /**
134  * Handler for the IRCv3 sub-command "CAP LIST".
135  *
136  * @param Client The client from which this command has been received.
137  * @param Arg Command argument or NULL.
138  * @return CONNECTED or DISCONNECTED.
139  */
140 static bool
141 Handle_CAP_LIST(CLIENT *Client, UNUSED char *Arg)
142 {
143         assert(Client != NULL);
144
145         return IRC_WriteStrClient(Client, "CAP %s LIST :%s", Client_ID(Client),
146                                   Get_CAP_String(Client_Cap(Client)));
147 }
148
149 /**
150  * Handler for the IRCv3 sub-command "CAP REQ".
151  *
152  * @param Client The client from which this command has been received.
153  * @param Arg Command argument.
154  * @return CONNECTED or DISCONNECTED.
155  */
156 static bool
157 Handle_CAP_REQ(CLIENT *Client, char *Arg)
158 {
159         int new_cap;
160
161         assert(Client != NULL);
162         assert(Arg != NULL);
163
164         Set_CAP_Negotiation(Client);
165
166         new_cap = Parse_CAP(Client_Cap(Client), Arg);
167
168         if (new_cap < 0)
169                 return IRC_WriteStrClient(Client, "CAP %s NAK :%s",
170                                           Client_ID(Client), Arg);
171
172         Client_CapSet(Client, new_cap);
173         return IRC_WriteStrClient(Client, "CAP %s ACK :%s",
174                                   Client_ID(Client), Arg);
175 }
176
177 /**
178  * Handler for the IRCv3 sub-command "CAP ACK".
179  *
180  * @param Client The client from which this command has been received.
181  * @param Arg Command argument.
182  * @return CONNECTED or DISCONNECTED.
183  */
184 static bool
185 Handle_CAP_ACK(UNUSED CLIENT *Client, UNUSED char *Arg)
186 {
187         assert(Client != NULL);
188         assert(Arg != NULL);
189
190         return CONNECTED;
191 }
192
193 /**
194  * Handler for the IRCv3 sub-command "CAP CLEAR".
195  *
196  * @param Client The client from which this command has been received.
197  * @return CONNECTED or DISCONNECTED.
198  */
199 static bool
200 Handle_CAP_CLEAR(CLIENT *Client)
201 {
202         int cap_old;
203
204         assert(Client != NULL);
205
206         cap_old = Client_Cap(Client);
207         if (cap_old & CLIENT_CAP_MULTI_PREFIX)
208                 Client_CapDel(Client, CLIENT_CAP_MULTI_PREFIX);
209
210         return IRC_WriteStrClient(Client, "CAP %s ACK :%s", Client_ID(Client),
211                                   Get_CAP_String(cap_old));
212 }
213
214 /**
215  * Handler for the IRCv3 sub-command "CAP END".
216  *
217  * @param Client The client from which this command has been received.
218  * @return CONNECTED or DISCONNECTED.
219  */
220 static bool
221 Handle_CAP_END(CLIENT *Client)
222 {
223         assert(Client != NULL);
224
225         if (Client_Type(Client) != CLIENT_USER) {
226                 /* User is still logging in ... */
227                 Client_CapDel(Client, CLIENT_CAP_PENDING);
228
229                 if (Client_Type(Client) == CLIENT_WAITCAPEND) {
230                         /* Only "CAP END" was missing: log in! */
231                         return Login_User(Client);
232                 }
233         }
234
235         return CONNECTED;
236 }
237
238 /* Global functions */
239
240 /**
241  * Handler for the IRCv3 command "CAP".
242  *
243  * @param Client The client from which this command has been received.
244  * @param Req Request structure with prefix and all parameters.
245  * @return CONNECTED or DISCONNECTED.
246  */
247 GLOBAL bool
248 IRC_CAP(CLIENT *Client, REQUEST *Req)
249 {
250         assert(Client != NULL);
251         assert(Req != NULL);
252
253         _IRC_ARGC_BETWEEN_OR_RETURN_(Client, Req, 1, 2)
254
255         LogDebug("Got \"%s %s\" command from \"%s\" ...",
256                  Req->command, Req->argv[0], Client_ID(Client));
257
258         if (Req->argc == 1) {
259                 if (strcasecmp(Req->argv[0], "CLEAR") == 0)
260                         return Handle_CAP_CLEAR(Client);
261                 if (strcasecmp(Req->argv[0], "END") == 0)
262                         return Handle_CAP_END(Client);
263         }
264         if (Req->argc >= 1 && Req->argc <= 2) {
265                 if (strcasecmp(Req->argv[0], "LS") == 0)
266                         return Handle_CAP_LS(Client, Req->argv[1]);
267                 if (strcasecmp(Req->argv[0], "LIST") == 0)
268                         return Handle_CAP_LIST(Client, Req->argv[1]);
269         }
270         if (Req->argc == 2) {
271                 if (strcasecmp(Req->argv[0], "REQ") == 0)
272                         return Handle_CAP_REQ(Client, Req->argv[1]);
273                 if (strcasecmp(Req->argv[0], "ACK") == 0)
274                         return Handle_CAP_ACK(Client, Req->argv[1]);
275         }
276
277         return IRC_WriteErrClient(Client, ERR_INVALIDCAP_MSG,
278                                   Client_ID(Client), Req->argv[0]);
279 }
280
281 /* -eof- */