]> jfr.im git - solanum.git/blob - modules/m_cap.c
buildsystem: honor $DESTDIR in install-data-hook
[solanum.git] / modules / m_cap.c
1 /* modules/m_cap.c
2 *
3 * Copyright (C) 2005 Lee Hardy <lee@leeh.co.uk>
4 * Copyright (C) 2005 ircd-ratbox development team
5 * Copyright (C) 2016 William Pitcock <nenolod@dereferenced.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * 1.Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * 2.Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3.The name of the author may not be used to endorse or promote products
17 * derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
23 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
28 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "stdinc.h"
33 #include "class.h"
34 #include "client.h"
35 #include "match.h"
36 #include "ircd.h"
37 #include "numeric.h"
38 #include "msg.h"
39 #include "parse.h"
40 #include "modules.h"
41 #include "s_serv.h"
42 #include "s_user.h"
43 #include "send.h"
44 #include "s_conf.h"
45 #include "hash.h"
46
47 typedef int (*bqcmp)(const void *, const void *);
48
49 static int m_cap(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
50
51 struct Message cap_msgtab = {
52 "CAP", 0, 0, 0, 0,
53 {{m_cap, 2}, {m_cap, 2}, mg_ignore, mg_ignore, mg_ignore, {m_cap, 2}}
54 };
55
56 mapi_clist_av1 cap_clist[] = { &cap_msgtab, NULL };
57 DECLARE_MODULE_AV1(cap, NULL, NULL, cap_clist, NULL, NULL, "$Revision: 676 $");
58
59 #define IsCapableEntry(c, e) IsCapable(c, 1 << (e)->value)
60 #define HasCapabilityFlag(c, f) (c->ownerdata != NULL && (((struct ClientCapability *)c->ownerdata)->flags & (f)) == f)
61
62 /* clicap_find()
63 * Used iteratively over a buffer, extracts individual cap tokens.
64 *
65 * Inputs: buffer to start iterating over (NULL to iterate over existing buf)
66 * int pointer to whether the cap token is negated
67 * int pointer to whether we finish with success
68 * Ouputs: Cap entry if found, NULL otherwise.
69 */
70 static struct CapabilityEntry *
71 clicap_find(const char *data, int *negate, int *finished)
72 {
73 static char buf[BUFSIZE];
74 static char *p;
75 struct CapabilityEntry *cap;
76 char *s;
77
78 *negate = 0;
79
80 if(data)
81 {
82 rb_strlcpy(buf, data, sizeof(buf));
83 p = buf;
84 }
85
86 if(*finished)
87 return NULL;
88
89 /* skip any whitespace */
90 while(*p && IsSpace(*p))
91 p++;
92
93 if(EmptyString(p))
94 {
95 *finished = 1;
96 return NULL;
97 }
98
99 if(*p == '-')
100 {
101 *negate = 1;
102 p++;
103
104 /* someone sent a '-' without a parameter.. */
105 if(*p == '\0')
106 return NULL;
107 }
108
109 if((s = strchr(p, ' ')))
110 *s++ = '\0';
111
112 if((cap = capability_find(cli_capindex, p)) != NULL)
113 {
114 if(s)
115 p = s;
116 else
117 *finished = 1;
118 }
119
120 return cap;
121 }
122
123 /* clicap_generate()
124 * Generates a list of capabilities.
125 *
126 * Inputs: client to send to, subcmd to send,
127 * flags to match against: 0 to do none, -1 if client has no flags,
128 * int to whether we are doing CAP CLEAR
129 * Outputs: None
130 */
131 static void
132 clicap_generate(struct Client *source_p, const char *subcmd, int flags, int clear)
133 {
134 char buf[BUFSIZE];
135 char capbuf[BUFSIZE];
136 char *p;
137 int buflen = 0;
138 int curlen, mlen;
139 struct CapabilityEntry *entry;
140 struct DictionaryIter iter;
141
142 mlen = sprintf(buf, ":%s CAP %s %s",
143 me.name,
144 EmptyString(source_p->name) ? "*" : source_p->name,
145 subcmd);
146
147 p = capbuf;
148 buflen = mlen;
149
150 /* shortcut, nothing to do */
151 if(flags == -1)
152 {
153 sendto_one(source_p, "%s :", buf);
154 return;
155 }
156
157 DICTIONARY_FOREACH(entry, &iter, cli_capindex->cap_dict)
158 {
159 if(flags)
160 {
161 if(!IsCapableEntry(source_p, entry))
162 continue;
163 /* they are capable of this, check sticky */
164 else if(clear && HasCapabilityFlag(entry, CLICAP_FLAGS_STICKY))
165 continue;
166 }
167
168 if ((1 << entry->value) == CLICAP_SASL)
169 {
170 struct Client *agent_p = NULL;
171
172 if (!ConfigFileEntry.sasl_service)
173 continue;
174
175 agent_p = find_named_client(ConfigFileEntry.sasl_service);
176 if (agent_p == NULL || !IsService(agent_p))
177 continue;
178 }
179
180 /* \r\n\0, possible "-~=", space, " *" */
181 if(buflen + strlen(entry->cap) >= BUFSIZE - 10)
182 {
183 /* remove our trailing space -- if buflen == mlen
184 * here, we didnt even succeed in adding one.
185 */
186 if(buflen != mlen)
187 *(p - 1) = '\0';
188 else
189 *p = '\0';
190
191 sendto_one(source_p, "%s * :%s", buf, capbuf);
192 p = capbuf;
193 buflen = mlen;
194 }
195
196 if(clear)
197 {
198 *p++ = '-';
199 buflen++;
200 }
201
202 curlen = sprintf(p, "%s ", entry->cap);
203 p += curlen;
204 buflen += curlen;
205 }
206
207 /* remove trailing space */
208 if(buflen != mlen)
209 *(p - 1) = '\0';
210 else
211 *p = '\0';
212
213 sendto_one(source_p, "%s :%s", buf, capbuf);
214 }
215
216 static void
217 cap_ack(struct Client *source_p, const char *arg)
218 {
219 struct CapabilityEntry *cap;
220 int capadd = 0, capdel = 0;
221 int finished = 0, negate;
222
223 if(EmptyString(arg))
224 return;
225
226 for(cap = clicap_find(arg, &negate, &finished); cap;
227 cap = clicap_find(NULL, &negate, &finished))
228 {
229 /* sent an ACK for something they havent REQd */
230 if(!IsCapableEntry(source_p, cap))
231 continue;
232
233 if(negate)
234 {
235 /* dont let them ack something sticky off */
236 if(HasCapabilityFlag(cap, CLICAP_FLAGS_STICKY))
237 continue;
238
239 capdel |= (1 << cap->value);
240 }
241 else
242 capadd |= (1 << cap->value);
243 }
244
245 source_p->localClient->caps |= capadd;
246 source_p->localClient->caps &= ~capdel;
247 }
248
249 static void
250 cap_clear(struct Client *source_p, const char *arg)
251 {
252 clicap_generate(source_p, "ACK",
253 source_p->localClient->caps ? source_p->localClient->caps : -1, 1);
254
255 source_p->localClient->caps = 0;
256 }
257
258 static void
259 cap_end(struct Client *source_p, const char *arg)
260 {
261 if(IsRegistered(source_p))
262 return;
263
264 source_p->flags &= ~FLAGS_CLICAP;
265
266 if(source_p->name[0] && source_p->flags & FLAGS_SENTUSER)
267 {
268 register_local_user(source_p, source_p);
269 }
270 }
271
272 static void
273 cap_list(struct Client *source_p, const char *arg)
274 {
275 /* list of what theyre currently using */
276 clicap_generate(source_p, "LIST",
277 source_p->localClient->caps ? source_p->localClient->caps : -1, 0);
278 }
279
280 static void
281 cap_ls(struct Client *source_p, const char *arg)
282 {
283 if(!IsRegistered(source_p))
284 source_p->flags |= FLAGS_CLICAP;
285
286 /* list of what we support */
287 clicap_generate(source_p, "LS", 0, 0);
288 }
289
290 static void
291 cap_req(struct Client *source_p, const char *arg)
292 {
293 char buf[BUFSIZE];
294 char pbuf[2][BUFSIZE];
295 struct CapabilityEntry *cap;
296 int buflen, plen;
297 int i = 0;
298 int capadd = 0, capdel = 0;
299 int finished = 0, negate;
300
301 if(!IsRegistered(source_p))
302 source_p->flags |= FLAGS_CLICAP;
303
304 if(EmptyString(arg))
305 return;
306
307 buflen = snprintf(buf, sizeof(buf), ":%s CAP %s ACK",
308 me.name, EmptyString(source_p->name) ? "*" : source_p->name);
309
310 pbuf[0][0] = '\0';
311 plen = 0;
312
313 for(cap = clicap_find(arg, &negate, &finished); cap;
314 cap = clicap_find(NULL, &negate, &finished))
315 {
316 size_t namelen = strlen(cap->cap);
317
318 /* filled the first array, but cant send it in case the
319 * request fails. one REQ should never fill more than two
320 * buffers --fl
321 */
322 if(buflen + plen + namelen + 6 >= BUFSIZE)
323 {
324 pbuf[1][0] = '\0';
325 plen = 0;
326 i = 1;
327 }
328
329 if(negate)
330 {
331 if(HasCapabilityFlag(cap, CLICAP_FLAGS_STICKY))
332 {
333 finished = 0;
334 break;
335 }
336
337 strcat(pbuf[i], "-");
338 plen++;
339
340 capdel |= (1 << cap->value);
341 }
342 else
343 {
344 if ((1 << cap->value) == CLICAP_SASL)
345 {
346 struct Client *agent_p = NULL;
347
348 if (!ConfigFileEntry.sasl_service)
349 {
350 finished = 0;
351 break;
352 }
353
354 agent_p = find_named_client(ConfigFileEntry.sasl_service);
355 if (agent_p == NULL || !IsService(agent_p))
356 {
357 finished = 0;
358 break;
359 }
360 }
361
362 capadd |= (1 << cap->value);
363 }
364
365 /* XXX this probably should exclude REQACK'd caps from capadd/capdel, but keep old behaviour for now */
366 if(HasCapabilityFlag(cap, CLICAP_FLAGS_REQACK))
367 {
368 strcat(pbuf[i], "~");
369 plen++;
370 }
371
372 strcat(pbuf[i], cap->cap);
373 if (!finished) {
374 strcat(pbuf[i], " ");
375 plen += (namelen + 1);
376 }
377 }
378
379 if(!finished)
380 {
381 sendto_one(source_p, ":%s CAP %s NAK :%s",
382 me.name, EmptyString(source_p->name) ? "*" : source_p->name, arg);
383 return;
384 }
385
386 if(i)
387 {
388 sendto_one(source_p, "%s * :%s", buf, pbuf[0]);
389 sendto_one(source_p, "%s :%s", buf, pbuf[1]);
390 }
391 else
392 sendto_one(source_p, "%s :%s", buf, pbuf[0]);
393
394 source_p->localClient->caps |= capadd;
395 source_p->localClient->caps &= ~capdel;
396 }
397
398 static struct clicap_cmd
399 {
400 const char *cmd;
401 void (*func)(struct Client *source_p, const char *arg);
402 } clicap_cmdlist[] = {
403 /* This list *MUST* be in alphabetical order */
404 { "ACK", cap_ack },
405 { "CLEAR", cap_clear },
406 { "END", cap_end },
407 { "LIST", cap_list },
408 { "LS", cap_ls },
409 { "REQ", cap_req },
410 };
411
412 static int
413 clicap_cmd_search(const char *command, struct clicap_cmd *entry)
414 {
415 return irccmp(command, entry->cmd);
416 }
417
418 static int
419 m_cap(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
420 {
421 struct clicap_cmd *cmd;
422
423 if(!(cmd = bsearch(parv[1], clicap_cmdlist,
424 sizeof(clicap_cmdlist) / sizeof(struct clicap_cmd),
425 sizeof(struct clicap_cmd), (bqcmp) clicap_cmd_search)))
426 {
427 sendto_one(source_p, form_str(ERR_INVALIDCAPCMD),
428 me.name, EmptyString(source_p->name) ? "*" : source_p->name,
429 parv[1]);
430 return 0;
431 }
432
433 (cmd->func)(source_p, parv[2]);
434 return 0;
435 }