Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / m_cap.c
1 /*
2  * IRC - Internet Relay Chat, ircd/m_cap.c
3  * Copyright (C) 2004 Kevin L. Mitchell <klmitch@mit.edu>
4  *
5  * See file AUTHORS in IRC package for additional names of
6  * the programmers.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 1, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  * $Id$
23  */
24
25 /*
26  * m_functions execute protocol messages on this server:
27  *
28  *    cptr    is always NON-NULL, pointing to a *LOCAL* client
29  *            structure (with an open socket connected!). This
30  *            identifies the physical socket where the message
31  *            originated (or which caused the m_function to be
32  *            executed--some m_functions may call others...).
33  *
34  *    sptr    is the source of the message, defined by the
35  *            prefix part of the message if present. If not
36  *            or prefix not found, then sptr==cptr.
37  *
38  *            (!IsServer(cptr)) => (cptr == sptr), because
39  *            prefixes are taken *only* from servers...
40  *
41  *            (IsServer(cptr))
42  *                    (sptr == cptr) => the message didn't
43  *                    have the prefix.
44  *
45  *                    (sptr != cptr && IsServer(sptr) means
46  *                    the prefix specified servername. (?)
47  *
48  *                    (sptr != cptr && !IsServer(sptr) means
49  *                    that message originated from a remote
50  *                    user (not local).
51  *
52  *            combining
53  *
54  *            (!IsServer(sptr)) means that, sptr can safely
55  *            taken as defining the target structure of the
56  *            message in this server.
57  *
58  *    *Always* true (if 'parse' and others are working correct):
59  *
60  *    1)      sptr->from == cptr  (note: cptr->from == cptr)
61  *
62  *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
63  *            *cannot* be a local connection, unless it's
64  *            actually cptr!). [MyConnect(x) should probably
65  *            be defined as (x == x->from) --msa ]
66  *
67  *    parc    number of variable parameter strings (if zero,
68  *            parv is allowed to be NULL)
69  *
70  *    parv    a NULL terminated list of parameter pointers,
71  *
72  *                    parv[0], sender (prefix string), if not present
73  *                            this points to an empty string.
74  *                    parv[1]...parv[parc-1]
75  *                            pointers to additional parameters
76  *                    parv[parc] == NULL, *always*
77  *
78  *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
79  *                    non-NULL pointers.
80  */
81 #include "config.h"
82
83 #include "client.h"
84 #include "ircd.h"
85 #include "ircd_chattr.h"
86 #include "ircd_log.h"
87 #include "ircd_reply.h"
88 #include "ircd_snprintf.h"
89 #include "ircd_string.h"
90 #include "msg.h"
91 #include "numeric.h"
92 #include "send.h"
93 #include "s_user.h"
94
95 #include <stdlib.h>
96 #include <string.h>
97
98 typedef int (*bqcmp)(const void *, const void *);
99
100 static struct capabilities {
101   enum Capab cap;
102   char *capstr;
103   unsigned long flags;
104   char *name;
105   int namelen;
106 } capab_list[] = {
107 #define _CAP(cap, flags, name)                                                \
108         { CAP_ ## cap, #cap, (flags), (name), sizeof(name) - 1 }
109   CAPLIST
110 #undef _CAP
111 };
112
113 #define CAPAB_LIST_LEN  (sizeof(capab_list) / sizeof(struct capabilities))
114
115 static struct CapSet clean_set; /* guaranteed to be all zeros (right?) */
116
117 static int
118 capab_sort(const struct capabilities *cap1, const struct capabilities *cap2)
119 {
120   return ircd_strcmp(cap1->name, cap2->name);
121 }
122
123 static int
124 capab_search(const char *key, const struct capabilities *cap)
125 {
126   const char *rb = cap->name;
127   while (ToLower(*key) == ToLower(*rb)) /* walk equivalent part of strings */
128     if (!*key++) /* hit the end, all right... */
129       return 0;
130     else /* OK, let's move on... */
131       rb++;
132
133   /* If the character they differ on happens to be a space, and it happens
134    * to be the same length as the capability name, then we've found a
135    * match; otherwise, return the difference of the two.
136    */
137   return (IsSpace(*key) && !*rb) ? 0 : (ToLower(*key) - ToLower(*rb));
138 }
139
140 static struct capabilities *
141 find_cap(const char **caplist_p, int *neg_p)
142 {
143   static int inited = 0;
144   const char *caplist = *caplist_p;
145   struct capabilities *cap = 0;
146
147   *neg_p = 0; /* clear negative flag... */
148
149   if (!inited) { /* First, let's sort the array... */
150     qsort(capab_list, CAPAB_LIST_LEN, sizeof(struct capabilities),
151           (bqcmp)capab_sort);
152     inited++; /* remember that we've done this step... */
153   }
154
155   /* Next, find first non-whitespace character... */
156   while (*caplist && IsSpace(*caplist))
157     caplist++;
158
159   /* We are now at the beginning of an element of the list; is it negative? */
160   if (*caplist == '-') {
161     caplist++; /* yes; step past the flag... */
162     *neg_p = 1; /* remember that it is negative... */
163   }
164
165   /* OK, now see if we can look up the capability... */
166   if (*caplist) {
167     if (!(cap = (struct capabilities *)bsearch(caplist, capab_list,
168                                                CAPAB_LIST_LEN,
169                                                sizeof(struct capabilities),
170                                                (bqcmp)capab_search))) {
171       /* Couldn't find the capability; advance to first whitespace character */
172       while (*caplist && !IsSpace(*caplist))
173         caplist++;
174     } else
175       caplist += cap->namelen; /* advance to end of capability name */
176   }
177
178   assert(caplist != *caplist_p || !*caplist); /* we *must* advance */
179
180   /* move ahead in capability list string--or zero pointer if we hit end */
181   *caplist_p = *caplist ? caplist : 0;
182
183   return cap; /* and return the capability (if any) */
184 }
185
186 static int
187 send_caplist(struct Client *sptr, const struct CapSet *cs)
188 {
189   char capbuf[BUFSIZE] = "";
190   struct MsgBuf *mb;
191   int i, loc, len;
192
193   /* set up the buffer for the LSL message... */
194   mb = msgq_make(sptr, "%:#C " MSG_CAP " LSL :", &me);
195
196   for (i = 0, loc = 0; i < CAPAB_LIST_LEN; i++) {
197     if (cs ? !CapHas(cs, capab_list[i].cap) :
198         (capab_list[i].flags & CAPFL_HIDDEN))
199       continue; /* not including this capability in the list */
200       
201     len = capab_list[i].namelen + (loc != 0); /* how much we'd add... */
202
203     if (msgq_bufleft(mb) < loc + len) { /* would add too much; must flush */
204       sendcmdto_one(&me, CMD_CAP, sptr, "LS :%s", capbuf);
205       capbuf[(loc = 0)] = '\0'; /* re-terminate the buffer... */
206     }
207
208     loc += ircd_snprintf(0, capbuf + loc, sizeof(capbuf) - loc, "%s%s",
209                          loc ? " " : "", capab_list[i].name);
210   }
211
212   msgq_append(0, mb, "%s", capbuf); /* append capabilities to the LSL cmd */
213   send_buffer(sptr, mb, 0); /* send them out... */
214   msgq_clean(mb); /* and release the buffer */
215
216   return 0; /* convenience return */
217 }
218
219 static int
220 cap_empty(struct Client *sptr, const char *caplist)
221 {
222   if (IsUnknown(sptr)) /* registration hasn't completed; suspend it... */
223     cli_unreg(sptr) |= CLIREG_CAP;
224
225   return send_caplist(sptr, 0); /* send list of capabilities */
226 }
227
228 static int
229 cap_req(struct Client *sptr, const char *caplist)
230 {
231   const char *cl = caplist;
232   struct capabilities *cap;
233   struct CapSet cs = *cli_capab(sptr); /* capability set */
234   struct CapSet as = *cli_active(sptr); /* active set */
235   int neg;
236
237   if (IsUnknown(sptr)) /* registration hasn't completed; suspend it... */
238     cli_unreg(sptr) |= CLIREG_CAP;
239
240   while (cl) { /* walk through the capabilities list... */
241     if (!(cap = find_cap(&cl, &neg)) || /* look up capability... */
242         (!neg && (cap->flags & CAPFL_PROHIBIT))) { /* is it prohibited? */
243       sendcmdto_one(&me, CMD_CAP, sptr, "NAK :%s", caplist);
244       return 0; /* can't complete requested op... */
245     }
246
247     if (neg) { /* set or clear the capability... */
248       CapClr(&cs, cap->cap);
249       if (!(cap->flags & CAPFL_PROTO))
250         CapClr(&as, cap->cap);
251     } else {
252       CapSet(&cs, cap->cap);
253       if (!(cap->flags & CAPFL_PROTO))
254         CapSet(&as, cap->cap);
255     }
256   }
257
258   sendcmdto_one(&me, CMD_CAP, sptr, "ACK :%s", caplist);
259
260   *cli_capab(sptr) = cs; /* copy the completed results */
261   *cli_active(sptr) = as;
262
263   return 0;
264 }
265
266 static int
267 cap_ack(struct Client *sptr, const char *caplist)
268 {
269   const char *cl = caplist;
270   struct capabilities *cap;
271   int neg;
272
273   while (cl) { /* walk through the capabilities list... */
274     if (!(cap = find_cap(&cl, &neg)) || /* look up capability... */
275         (neg ? HasCap(sptr, cap->cap) : !HasCap(sptr, cap->cap))) /* uh... */
276       continue;
277
278     if (neg) /* set or clear the active capability... */
279       CapClr(cli_active(sptr), cap->cap);
280     else
281       CapSet(cli_active(sptr), cap->cap);
282   }
283
284   return 0;
285 }
286
287 static int
288 cap_clear(struct Client *sptr, const char *caplist)
289 {
290   sendcmdto_one(&me, CMD_CAP, sptr, "CLEAR"); /* Reply... */
291
292   *cli_capab(sptr) = clean_set; /* then clear! */
293
294   return 0;
295 }
296
297 static int
298 cap_end(struct Client *sptr, const char *caplist)
299 {
300   if (!IsUnknown(sptr)) /* registration has completed... */
301     return 0; /* so just ignore the message... */
302
303   cli_unreg(sptr) &= ~CLIREG_CAP; /* capability negotiation is now done... */
304
305   if (!cli_unreg(sptr)) /* if client is now done... */
306     return register_user(sptr, sptr, cli_name(sptr), cli_user(sptr)->username);
307
308   return 0; /* Can't do registration yet... */
309 }
310
311 static int
312 cap_list(struct Client *sptr, const char *caplist)
313 {
314   /* Send the list of the client's capabilities */
315   return send_caplist(sptr, cli_capab(sptr));
316 }
317
318 static struct subcmd {
319   char *cmd;
320   int (*proc)(struct Client *sptr, const char *caplist);
321 } cmdlist[] = {
322   { "",      cap_empty },
323   { "ACK",   cap_ack   },
324   { "CLEAR", cap_clear },
325   { "END",   cap_end   },
326   { "LIST",  cap_list  },
327   { "LS",    0         },
328   { "LSL",   0         },
329   { "NAK",   0         },
330   { "REQ",   cap_req   }
331 };
332
333 static int
334 subcmd_search(const char *cmd, const struct subcmd *elem)
335 {
336   return ircd_strcmp(cmd, elem->cmd);
337 }
338
339 /*
340  * m_cap - user message handler
341  *
342  * parv[0] = Send prefix
343  *
344  * From user:
345  *
346  * parv[1] = [<subcommand>]
347  * parv[2] = [<capab list>]
348  *
349  */
350 int
351 m_cap(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
352 {
353   char *subcmd = "", *caplist = 0;
354   struct subcmd *cmd;
355
356   if (parc > 1 && parv[1]) /* a subcommand was provided */
357     subcmd = parv[1];
358   if (parc > 2) /* a capability list was provided */
359     caplist = parv[2];
360
361   /* find the subcommand handler */
362   if (!(cmd = (struct subcmd *)bsearch(subcmd, cmdlist,
363                                        sizeof(cmdlist) / sizeof(struct subcmd),
364                                        sizeof(struct subcmd),
365                                        (bqcmp)subcmd_search)))
366     return send_reply(sptr, ERR_UNKNOWNCAPCMD, subcmd);
367
368   /* then execute it... */
369   return cmd->proc ? (cmd->proc)(sptr, caplist) : 0;
370 }