]>
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.2.1 2006/02/16 03:49:54 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_auth.h" | |
40 | #include "s_user.h" | |
41 | ||
42 | #include <stdlib.h> | |
43 | #include <string.h> | |
44 | ||
45 | typedef int (*bqcmp)(const void *, const void *); | |
46 | ||
47 | static struct capabilities { | |
48 | enum Capab cap; | |
49 | char *capstr; | |
50 | unsigned long flags; | |
51 | char *name; | |
52 | int namelen; | |
53 | } capab_list[] = { | |
54 | #define _CAP(cap, flags, name) \ | |
55 | { CAP_ ## cap, #cap, (flags), (name), sizeof(name) - 1 } | |
56 | CAPLIST | |
57 | #undef _CAP | |
58 | }; | |
59 | ||
60 | #define CAPAB_LIST_LEN (sizeof(capab_list) / sizeof(struct capabilities)) | |
61 | ||
62 | static int | |
63 | capab_sort(const struct capabilities *cap1, const struct capabilities *cap2) | |
64 | { | |
65 | return ircd_strcmp(cap1->name, cap2->name); | |
66 | } | |
67 | ||
68 | static int | |
69 | capab_search(const char *key, const struct capabilities *cap) | |
70 | { | |
71 | const char *rb = cap->name; | |
72 | while (ToLower(*key) == ToLower(*rb)) /* walk equivalent part of strings */ | |
73 | if (!*key++) /* hit the end, all right... */ | |
74 | return 0; | |
75 | else /* OK, let's move on... */ | |
76 | rb++; | |
77 | ||
78 | /* If the character they differ on happens to be a space, and it happens | |
79 | * to be the same length as the capability name, then we've found a | |
80 | * match; otherwise, return the difference of the two. | |
81 | */ | |
82 | return (IsSpace(*key) && !*rb) ? 0 : (ToLower(*key) - ToLower(*rb)); | |
83 | } | |
84 | ||
85 | static struct capabilities * | |
86 | find_cap(const char **caplist_p, int *neg_p) | |
87 | { | |
88 | static int inited = 0; | |
89 | const char *caplist = *caplist_p; | |
90 | struct capabilities *cap = 0; | |
91 | ||
92 | *neg_p = 0; /* clear negative flag... */ | |
93 | ||
94 | if (!inited) { /* First, let's sort the array... */ | |
95 | qsort(capab_list, CAPAB_LIST_LEN, sizeof(struct capabilities), | |
96 | (bqcmp)capab_sort); | |
97 | inited++; /* remember that we've done this step... */ | |
98 | } | |
99 | ||
100 | /* Next, find first non-whitespace character... */ | |
101 | while (*caplist && IsSpace(*caplist)) | |
102 | caplist++; | |
103 | ||
104 | /* We are now at the beginning of an element of the list; is it negative? */ | |
105 | if (*caplist == '-') { | |
106 | caplist++; /* yes; step past the flag... */ | |
107 | *neg_p = 1; /* remember that it is negative... */ | |
108 | } | |
109 | ||
110 | /* OK, now see if we can look up the capability... */ | |
111 | if (*caplist) { | |
112 | if (!(cap = (struct capabilities *)bsearch(caplist, capab_list, | |
113 | CAPAB_LIST_LEN, | |
114 | sizeof(struct capabilities), | |
115 | (bqcmp)capab_search))) { | |
116 | /* Couldn't find the capability; advance to first whitespace character */ | |
117 | while (*caplist && !IsSpace(*caplist)) | |
118 | caplist++; | |
119 | } else | |
120 | caplist += cap->namelen; /* advance to end of capability name */ | |
121 | } | |
122 | ||
123 | assert(caplist != *caplist_p || !*caplist); /* we *must* advance */ | |
124 | ||
125 | /* move ahead in capability list string--or zero pointer if we hit end */ | |
126 | *caplist_p = *caplist ? caplist : 0; | |
127 | ||
128 | return cap; /* and return the capability (if any) */ | |
129 | } | |
130 | ||
131 | /** Send a CAP \a subcmd list of capability changes to \a sptr. | |
132 | * If more than one line is necessary, each line before the last has | |
133 | * an added "*" parameter before that line's capability list. | |
134 | * @param[in] sptr Client receiving capability list. | |
135 | * @param[in] set Capabilities to show as set (with ack and sticky modifiers). | |
136 | * @param[in] rem Capabalities to show as removed (with no other modifier). | |
137 | * @param[in] subcmd Name of capability subcommand. | |
138 | */ | |
139 | static int | |
140 | send_caplist(struct Client *sptr, const struct CapSet *set, | |
141 | const struct CapSet *rem, const char *subcmd) | |
142 | { | |
143 | char capbuf[BUFSIZE] = "", pfx[16]; | |
144 | struct MsgBuf *mb; | |
145 | int i, loc, len, flags, pfx_len; | |
146 | ||
147 | /* set up the buffer for the final LS message... */ | |
148 | mb = msgq_make(sptr, "%:#C " MSG_CAP " %s :", &me, subcmd); | |
149 | ||
150 | for (i = 0, loc = 0; i < CAPAB_LIST_LEN; i++) { | |
151 | flags = capab_list[i].flags; | |
152 | /* This is a little bit subtle, but just involves applying de | |
153 | * Morgan's laws to the obvious check: We must display the | |
154 | * capability if (and only if) it is set in \a rem or \a set, or | |
155 | * if both are null and the capability is hidden. | |
156 | */ | |
157 | if (!(rem && CapHas(rem, capab_list[i].cap)) | |
158 | && !(set && CapHas(set, capab_list[i].cap)) | |
159 | && (rem || set || (flags & CAPFL_HIDDEN))) | |
160 | continue; | |
161 | ||
162 | /* Build the prefix (space separator and any modifiers needed). */ | |
163 | pfx_len = 0; | |
164 | if (loc) | |
165 | pfx[pfx_len++] = ' '; | |
166 | if (rem && CapHas(rem, capab_list[i].cap)) | |
167 | pfx[pfx_len++] = '-'; | |
168 | else { | |
169 | if (flags & CAPFL_PROTO) | |
170 | pfx[pfx_len++] = '~'; | |
171 | if (flags & CAPFL_STICKY) | |
172 | pfx[pfx_len++] = '='; | |
173 | } | |
174 | pfx[pfx_len] = '\0'; | |
175 | ||
176 | len = capab_list[i].namelen + pfx_len; /* how much we'd add... */ | |
177 | if (msgq_bufleft(mb) < loc + len + 2) { /* would add too much; must flush */ | |
178 | sendcmdto_one(&me, CMD_CAP, sptr, "%s * :%s", subcmd, capbuf); | |
179 | capbuf[(loc = 0)] = '\0'; /* re-terminate the buffer... */ | |
180 | } | |
181 | ||
182 | loc += ircd_snprintf(0, capbuf + loc, sizeof(capbuf) - loc, "%s%s", | |
183 | pfx, capab_list[i].name); | |
184 | } | |
185 | ||
186 | msgq_append(0, mb, "%s", capbuf); /* append capabilities to the final cmd */ | |
187 | send_buffer(sptr, mb, 0); /* send them out... */ | |
188 | msgq_clean(mb); /* and release the buffer */ | |
189 | ||
190 | return 0; /* convenience return */ | |
191 | } | |
192 | ||
193 | static int | |
194 | cap_ls(struct Client *sptr, const char *caplist) | |
195 | { | |
196 | if (IsUnknown(sptr)) /* registration hasn't completed; suspend it... */ | |
197 | auth_cap_start(cli_auth(sptr)); | |
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 | auth_cap_start(cli_auth(sptr)); | |
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 | return auth_cap_done(cli_auth(sptr)); | |
304 | } | |
305 | ||
306 | static int | |
307 | cap_list(struct Client *sptr, const char *caplist) | |
308 | { | |
309 | /* Send the list of the client's capabilities */ | |
310 | return send_caplist(sptr, cli_capab(sptr), 0, "LIST"); | |
311 | } | |
312 | ||
313 | static struct subcmd { | |
314 | char *cmd; | |
315 | int (*proc)(struct Client *sptr, const char *caplist); | |
316 | } cmdlist[] = { | |
317 | { "ACK", cap_ack }, | |
318 | { "CLEAR", cap_clear }, | |
319 | { "END", cap_end }, | |
320 | { "LIST", cap_list }, | |
321 | { "LS", cap_ls }, | |
322 | { "NAK", 0 }, | |
323 | { "REQ", cap_req } | |
324 | }; | |
325 | ||
326 | static int | |
327 | subcmd_search(const char *cmd, const struct subcmd *elem) | |
328 | { | |
329 | return ircd_strcmp(cmd, elem->cmd); | |
330 | } | |
331 | ||
332 | /** Handle a capability request or response from a client. | |
333 | * @param[in] cptr Client that sent us the message. | |
334 | * @param[in] sptr Original source of message. | |
335 | * @param[in] parc Number of arguments. | |
336 | * @param[in] parv Argument vector. | |
337 | * @see \ref m_functions | |
338 | */ | |
339 | int | |
340 | m_cap(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) | |
341 | { | |
342 | char *subcmd, *caplist = 0; | |
343 | struct subcmd *cmd; | |
344 | ||
345 | if (parc < 2) /* a subcommand is required */ | |
346 | return 0; | |
347 | subcmd = parv[1]; | |
348 | if (parc > 2) /* a capability list was provided */ | |
349 | caplist = parv[2]; | |
350 | ||
351 | /* find the subcommand handler */ | |
352 | if (!(cmd = (struct subcmd *)bsearch(subcmd, cmdlist, | |
353 | sizeof(cmdlist) / sizeof(struct subcmd), | |
354 | sizeof(struct subcmd), | |
355 | (bqcmp)subcmd_search))) | |
356 | return send_reply(sptr, ERR_UNKNOWNCAPCMD, subcmd); | |
357 | ||
358 | /* then execute it... */ | |
359 | return cmd->proc ? (cmd->proc)(sptr, caplist) : 0; | |
360 | } |