]>
Commit | Line | Data |
---|---|---|
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 | /** @file | |
23 | * @brief Capability negotiation commands | |
24 | * @version $Id: m_cap.c,v 1.2 2005/04/05 01:46:05 entrope Exp $ | |
25 | */ | |
26 | ||
27 | #include "config.h" | |
28 | ||
29 | #include "client.h" | |
30 | #include "ircd.h" | |
31 | #include "ircd_chattr.h" | |
32 | #include "ircd_log.h" | |
33 | #include "ircd_reply.h" | |
34 | #include "ircd_snprintf.h" | |
35 | #include "ircd_string.h" | |
36 | #include "msg.h" | |
37 | #include "numeric.h" | |
38 | #include "send.h" | |
39 | #include "s_user.h" | |
40 | ||
41 | #include <stdlib.h> | |
42 | #include <string.h> | |
43 | ||
44 | typedef int (*bqcmp)(const void *, const void *); | |
45 | ||
46 | static struct capabilities { | |
47 | enum Capab cap; | |
48 | char *capstr; | |
49 | unsigned long flags; | |
50 | char *name; | |
51 | int namelen; | |
52 | } capab_list[] = { | |
53 | #define _CAP(cap, flags, name) \ | |
54 | { CAP_ ## cap, #cap, (flags), (name), sizeof(name) - 1 } | |
55 | CAPLIST | |
56 | #undef _CAP | |
57 | }; | |
58 | ||
59 | #define CAPAB_LIST_LEN (sizeof(capab_list) / sizeof(struct capabilities)) | |
60 | ||
61 | static int | |
62 | capab_sort(const struct capabilities *cap1, const struct capabilities *cap2) | |
63 | { | |
64 | return ircd_strcmp(cap1->name, cap2->name); | |
65 | } | |
66 | ||
67 | static int | |
68 | capab_search(const char *key, const struct capabilities *cap) | |
69 | { | |
70 | const char *rb = cap->name; | |
71 | while (ToLower(*key) == ToLower(*rb)) /* walk equivalent part of strings */ | |
72 | if (!*key++) /* hit the end, all right... */ | |
73 | return 0; | |
74 | else /* OK, let's move on... */ | |
75 | rb++; | |
76 | ||
77 | /* If the character they differ on happens to be a space, and it happens | |
78 | * to be the same length as the capability name, then we've found a | |
79 | * match; otherwise, return the difference of the two. | |
80 | */ | |
81 | return (IsSpace(*key) && !*rb) ? 0 : (ToLower(*key) - ToLower(*rb)); | |
82 | } | |
83 | ||
84 | static struct capabilities * | |
85 | find_cap(const char **caplist_p, int *neg_p) | |
86 | { | |
87 | static int inited = 0; | |
88 | const char *caplist = *caplist_p; | |
89 | struct capabilities *cap = 0; | |
90 | ||
91 | *neg_p = 0; /* clear negative flag... */ | |
92 | ||
93 | if (!inited) { /* First, let's sort the array... */ | |
94 | qsort(capab_list, CAPAB_LIST_LEN, sizeof(struct capabilities), | |
95 | (bqcmp)capab_sort); | |
96 | inited++; /* remember that we've done this step... */ | |
97 | } | |
98 | ||
99 | /* Next, find first non-whitespace character... */ | |
100 | while (*caplist && IsSpace(*caplist)) | |
101 | caplist++; | |
102 | ||
103 | /* We are now at the beginning of an element of the list; is it negative? */ | |
104 | if (*caplist == '-') { | |
105 | caplist++; /* yes; step past the flag... */ | |
106 | *neg_p = 1; /* remember that it is negative... */ | |
107 | } | |
108 | ||
109 | /* OK, now see if we can look up the capability... */ | |
110 | if (*caplist) { | |
111 | if (!(cap = (struct capabilities *)bsearch(caplist, capab_list, | |
112 | CAPAB_LIST_LEN, | |
113 | sizeof(struct capabilities), | |
114 | (bqcmp)capab_search))) { | |
115 | /* Couldn't find the capability; advance to first whitespace character */ | |
116 | while (*caplist && !IsSpace(*caplist)) | |
117 | caplist++; | |
118 | } else | |
119 | caplist += cap->namelen; /* advance to end of capability name */ | |
120 | } | |
121 | ||
122 | assert(caplist != *caplist_p || !*caplist); /* we *must* advance */ | |
123 | ||
124 | /* move ahead in capability list string--or zero pointer if we hit end */ | |
125 | *caplist_p = *caplist ? caplist : 0; | |
126 | ||
127 | return cap; /* and return the capability (if any) */ | |
128 | } | |
129 | ||
130 | /** Send a CAP \a subcmd list of capability changes to \a sptr. | |
131 | * If more than one line is necessary, each line before the last has | |
132 | * an added "*" parameter before that line's capability list. | |
133 | * @param[in] sptr Client receiving capability list. | |
134 | * @param[in] set Capabilities to show as set (with ack and sticky modifiers). | |
135 | * @param[in] rem Capabalities to show as removed (with no other modifier). | |
136 | * @param[in] subcmd Name of capability subcommand. | |
137 | */ | |
138 | static int | |
139 | send_caplist(struct Client *sptr, const struct CapSet *set, | |
140 | const struct CapSet *rem, const char *subcmd) | |
141 | { | |
142 | char capbuf[BUFSIZE] = "", pfx[16]; | |
143 | struct MsgBuf *mb; | |
144 | int i, loc, len, flags, pfx_len; | |
145 | ||
146 | /* set up the buffer for the final LS message... */ | |
147 | mb = msgq_make(sptr, "%:#C " MSG_CAP " %s :", &me, subcmd); | |
148 | ||
149 | for (i = 0, loc = 0; i < CAPAB_LIST_LEN; i++) { | |
150 | flags = capab_list[i].flags; | |
151 | /* This is a little bit subtle, but just involves applying de | |
152 | * Morgan's laws to the obvious check: We must display the | |
153 | * capability if (and only if) it is set in \a rem or \a set, or | |
154 | * if both are null and the capability is hidden. | |
155 | */ | |
156 | if (!(rem && CapHas(rem, capab_list[i].cap)) | |
157 | && !(set && CapHas(set, capab_list[i].cap)) | |
158 | && (rem || set || (flags & CAPFL_HIDDEN))) | |
159 | continue; | |
160 | ||
161 | /* Build the prefix (space separator and any modifiers needed). */ | |
162 | pfx_len = 0; | |
163 | if (loc) | |
164 | pfx[pfx_len++] = ' '; | |
165 | if (rem && CapHas(rem, capab_list[i].cap)) | |
166 | pfx[pfx_len++] = '-'; | |
167 | else { | |
168 | if (flags & CAPFL_PROTO) | |
169 | pfx[pfx_len++] = '~'; | |
170 | if (flags & CAPFL_STICKY) | |
171 | pfx[pfx_len++] = '='; | |
172 | } | |
173 | pfx[pfx_len] = '\0'; | |
174 | ||
175 | len = capab_list[i].namelen + pfx_len; /* how much we'd add... */ | |
176 | if (msgq_bufleft(mb) < loc + len + 2) { /* would add too much; must flush */ | |
177 | sendcmdto_one(&me, CMD_CAP, sptr, "%s * :%s", subcmd, capbuf); | |
178 | capbuf[(loc = 0)] = '\0'; /* re-terminate the buffer... */ | |
179 | } | |
180 | ||
181 | loc += ircd_snprintf(0, capbuf + loc, sizeof(capbuf) - loc, "%s%s", | |
182 | pfx, capab_list[i].name); | |
183 | } | |
184 | ||
185 | msgq_append(0, mb, "%s", capbuf); /* append capabilities to the final cmd */ | |
186 | send_buffer(sptr, mb, 0); /* send them out... */ | |
187 | msgq_clean(mb); /* and release the buffer */ | |
188 | ||
189 | return 0; /* convenience return */ | |
190 | } | |
191 | ||
192 | static int | |
193 | cap_ls(struct Client *sptr, const char *caplist) | |
194 | { | |
195 | if (IsUnknown(sptr)) /* registration hasn't completed; suspend it... */ | |
196 | cli_unreg(sptr) |= CLIREG_CAP; | |
197 | ||
198 | return send_caplist(sptr, 0, 0, "LS"); /* send list of capabilities */ | |
199 | } | |
200 | ||
201 | static int | |
202 | cap_req(struct Client *sptr, const char *caplist) | |
203 | { | |
204 | const char *cl = caplist; | |
205 | struct capabilities *cap; | |
206 | struct CapSet set, rem; | |
207 | struct CapSet cs = *cli_capab(sptr); /* capability set */ | |
208 | struct CapSet as = *cli_active(sptr); /* active set */ | |
209 | int neg; | |
210 | ||
211 | if (IsUnknown(sptr)) /* registration hasn't completed; suspend it... */ | |
212 | cli_unreg(sptr) |= CLIREG_CAP; | |
213 | ||
214 | memset(&set, 0, sizeof(set)); | |
215 | memset(&rem, 0, sizeof(rem)); | |
216 | while (cl) { /* walk through the capabilities list... */ | |
217 | if (!(cap = find_cap(&cl, &neg)) /* look up capability... */ | |
218 | || (!neg && (cap->flags & CAPFL_PROHIBIT)) /* is it prohibited? */ | |
219 | || (neg && (cap->flags & CAPFL_STICKY))) { /* is it sticky? */ | |
220 | sendcmdto_one(&me, CMD_CAP, sptr, "NAK :%s", caplist); | |
221 | return 0; /* can't complete requested op... */ | |
222 | } | |
223 | ||
224 | if (neg) { /* set or clear the capability... */ | |
225 | CapSet(&rem, cap->cap); | |
226 | CapClr(&set, cap->cap); | |
227 | CapClr(&cs, cap->cap); | |
228 | if (!(cap->flags & CAPFL_PROTO)) | |
229 | CapClr(&as, cap->cap); | |
230 | } else { | |
231 | CapClr(&rem, cap->cap); | |
232 | CapSet(&set, cap->cap); | |
233 | CapSet(&cs, cap->cap); | |
234 | if (!(cap->flags & CAPFL_PROTO)) | |
235 | CapSet(&as, cap->cap); | |
236 | } | |
237 | } | |
238 | ||
239 | /* Notify client of accepted changes and copy over results. */ | |
240 | send_caplist(sptr, &set, &rem, "ACK"); | |
241 | *cli_capab(sptr) = cs; | |
242 | *cli_active(sptr) = as; | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | static int | |
248 | cap_ack(struct Client *sptr, const char *caplist) | |
249 | { | |
250 | const char *cl = caplist; | |
251 | struct capabilities *cap; | |
252 | int neg; | |
253 | ||
254 | /* Coming from the client, this generally indicates that the client | |
255 | * is using a new backwards-incompatible protocol feature. As such, | |
256 | * it does not require further response from the server. | |
257 | */ | |
258 | while (cl) { /* walk through the capabilities list... */ | |
259 | if (!(cap = find_cap(&cl, &neg)) || /* look up capability... */ | |
260 | (neg ? HasCap(sptr, cap->cap) : !HasCap(sptr, cap->cap))) /* uh... */ | |
261 | continue; | |
262 | ||
263 | if (neg) /* set or clear the active capability... */ | |
264 | CapClr(cli_active(sptr), cap->cap); | |
265 | else | |
266 | CapSet(cli_active(sptr), cap->cap); | |
267 | } | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
272 | static int | |
273 | cap_clear(struct Client *sptr, const char *caplist) | |
274 | { | |
275 | struct CapSet cleared; | |
276 | struct capabilities *cap; | |
277 | unsigned int ii; | |
278 | ||
279 | /* XXX: If we ever add a capab list sorted by capab value, it would | |
280 | * be good cache-wise to use it here. */ | |
281 | memset(&cleared, 0, sizeof(cleared)); | |
282 | for (ii = 0; ii < CAPAB_LIST_LEN; ++ii) { | |
283 | cap = &capab_list[ii]; | |
284 | /* Only clear active non-sticky capabilities. */ | |
285 | if (!HasCap(sptr, cap->cap) || (cap->flags & CAPFL_STICKY)) | |
286 | continue; | |
287 | CapSet(&cleared, cap->cap); | |
288 | CapClr(cli_capab(sptr), cap->cap); | |
289 | if (!(cap->flags & CAPFL_PROTO)) | |
290 | CapClr(cli_active(sptr), cap->cap); | |
291 | } | |
292 | send_caplist(sptr, 0, &cleared, "ACK"); | |
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), 0, "LIST"); | |
316 | } | |
317 | ||
318 | static struct subcmd { | |
319 | char *cmd; | |
320 | int (*proc)(struct Client *sptr, const char *caplist); | |
321 | } cmdlist[] = { | |
322 | { "ACK", cap_ack }, | |
323 | { "CLEAR", cap_clear }, | |
324 | { "END", cap_end }, | |
325 | { "LIST", cap_list }, | |
326 | { "LS", cap_ls }, | |
327 | { "NAK", 0 }, | |
328 | { "REQ", cap_req } | |
329 | }; | |
330 | ||
331 | static int | |
332 | subcmd_search(const char *cmd, const struct subcmd *elem) | |
333 | { | |
334 | return ircd_strcmp(cmd, elem->cmd); | |
335 | } | |
336 | ||
337 | /** Handle a capability request or response from a client. | |
338 | * @param[in] cptr Client that sent us the message. | |
339 | * @param[in] sptr Original source of message. | |
340 | * @param[in] parc Number of arguments. | |
341 | * @param[in] parv Argument vector. | |
342 | * @see \ref m_functions | |
343 | */ | |
344 | int | |
345 | m_cap(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) | |
346 | { | |
347 | char *subcmd, *caplist = 0; | |
348 | struct subcmd *cmd; | |
349 | ||
350 | if (parc < 2) /* a subcommand is required */ | |
351 | return 0; | |
352 | subcmd = parv[1]; | |
353 | if (parc > 2) /* a capability list was provided */ | |
354 | caplist = parv[2]; | |
355 | ||
356 | /* find the subcommand handler */ | |
357 | if (!(cmd = (struct subcmd *)bsearch(subcmd, cmdlist, | |
358 | sizeof(cmdlist) / sizeof(struct subcmd), | |
359 | sizeof(struct subcmd), | |
360 | (bqcmp)subcmd_search))) | |
361 | return send_reply(sptr, ERR_UNKNOWNCAPCMD, subcmd); | |
362 | ||
363 | /* then execute it... */ | |
364 | return cmd->proc ? (cmd->proc)(sptr, caplist) : 0; | |
365 | } |