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>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
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.
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.
47 static const char cap_desc
[] = "Provides the commands used for client capability negotiation";
49 typedef int (*bqcmp
)(const void *, const void *);
51 static void m_cap(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
53 struct Message cap_msgtab
= {
55 {{m_cap
, 2}, {m_cap
, 2}, mg_ignore
, mg_ignore
, mg_ignore
, {m_cap
, 2}}
58 mapi_clist_av1 cap_clist
[] = { &cap_msgtab
, NULL
};
60 DECLARE_MODULE_AV2(cap
, NULL
, NULL
, cap_clist
, NULL
, NULL
, NULL
, NULL
, cap_desc
);
62 #define IsCapableEntry(c, e) IsCapable(c, 1 << (e)->value)
63 #define HasCapabilityFlag(c, f) (c->ownerdata != NULL && (((struct ClientCapability *)c->ownerdata)->flags & (f)) == f)
66 clicap_visible(struct Client
*client_p
, const struct CapabilityEntry
*cap
)
68 struct ClientCapability
*clicap
;
70 /* orphaned caps shouldn't be visible */
71 if (cap
->flags
& CAP_ORPHANED
)
74 if (cap
->ownerdata
== NULL
)
77 clicap
= cap
->ownerdata
;
78 if (clicap
->visible
== NULL
)
81 return clicap
->visible(client_p
);
85 * Used iteratively over a buffer, extracts individual cap tokens.
87 * Inputs: buffer to start iterating over (NULL to iterate over existing buf)
88 * int pointer to whether the cap token is negated
89 * int pointer to whether we finish with success
90 * Ouputs: Cap entry if found, NULL otherwise.
92 static struct CapabilityEntry
*
93 clicap_find(const char *data
, int *negate
, int *finished
)
95 static char buf
[BUFSIZE
];
97 struct CapabilityEntry
*cap
;
104 rb_strlcpy(buf
, data
, sizeof(buf
));
111 /* skip any whitespace */
112 while(*p
&& IsSpace(*p
))
126 /* someone sent a '-' without a parameter.. */
131 if((s
= strchr(p
, ' ')))
134 if((cap
= capability_find(cli_capindex
, p
)) != NULL
)
146 * Generates a list of capabilities.
148 * Inputs: client to send to, subcmd to send,
149 * flags to match against: 0 to do none, -1 if client has no flags
153 clicap_generate(struct Client
*source_p
, const char *subcmd
, int flags
)
155 static char buf_prefix
[DATALEN
+ 1];
156 static char buf_list
[DATALEN
+ 1];
157 const char *str_cont
= " * :";
158 const char *str_final
= " :";
161 struct CapabilityEntry
*entry
;
162 rb_dictionary_iter iter
;
164 buf_prefix
[0] = '\0';
165 len_prefix
= rb_snprintf_try_append(buf_prefix
, sizeof(buf_prefix
),
168 EmptyString(source_p
->name
) ? "*" : source_p
->name
,
171 /* shortcut, nothing to do */
172 if (flags
== -1 || len_prefix
< 0) {
173 sendto_one(source_p
, "%s%s", buf_prefix
, str_final
);
178 max_list
= sizeof(buf_prefix
) - len_prefix
- strlen(str_cont
);
180 for (int pass
= 0; pass
< 2; pass
++)
181 RB_DICTIONARY_FOREACH(entry
, &iter
, cli_capindex
->cap_dict
) {
182 struct ClientCapability
*clicap
= entry
->ownerdata
;
183 const char *data
= NULL
;
185 if (pass
== 0 && !HasCapabilityFlag(entry
, CLICAP_FLAGS_PRIORITY
))
187 else if (pass
== 1 && HasCapabilityFlag(entry
, CLICAP_FLAGS_PRIORITY
))
190 if (flags
&& !IsCapableEntry(source_p
, entry
))
193 if (!clicap_visible(source_p
, entry
))
196 if (!flags
&& (source_p
->flags
& FLAGS_CLICAP_DATA
) && clicap
!= NULL
&& clicap
->data
!= NULL
)
197 data
= clicap
->data(source_p
);
199 for (int attempts
= 0; attempts
< 2; attempts
++) {
200 if (rb_snprintf_try_append(buf_list
, max_list
, "%s%s%s%s",
201 buf_list
[0] == '\0' ? "" : " ", /* space between caps */
203 data
!= NULL
? "=" : "", /* '=' between cap and data */
204 data
!= NULL
? data
: "") < 0
205 && buf_list
[0] != '\0') {
207 if (!(source_p
->flags
& FLAGS_CLICAP_DATA
)) {
208 /* the client doesn't support multiple lines */
212 /* doesn't fit in the buffer, output what we have */
213 sendto_one(source_p
, "%s%s%s", buf_prefix
, str_cont
, buf_list
);
215 /* reset the buffer and go around the loop one more time */
223 sendto_one(source_p
, "%s%s%s", buf_prefix
, str_final
, buf_list
);
227 cap_ack(struct Client
*source_p
, const char *arg
)
229 struct CapabilityEntry
*cap
;
230 int capadd
= 0, capdel
= 0;
231 int finished
= 0, negate
;
236 for(cap
= clicap_find(arg
, &negate
, &finished
); cap
;
237 cap
= clicap_find(NULL
, &negate
, &finished
))
239 /* sent an ACK for something they havent REQd */
240 if(!IsCapableEntry(source_p
, cap
))
245 /* dont let them ack something sticky off */
246 if(HasCapabilityFlag(cap
, CLICAP_FLAGS_STICKY
))
249 capdel
|= (1 << cap
->value
);
252 capadd
|= (1 << cap
->value
);
255 source_p
->localClient
->caps
|= capadd
;
256 source_p
->localClient
->caps
&= ~capdel
;
260 cap_end(struct Client
*source_p
, const char *arg
)
262 if(IsRegistered(source_p
))
265 source_p
->flags
&= ~FLAGS_CLICAP
;
267 if(source_p
->name
[0] && source_p
->flags
& FLAGS_SENTUSER
)
269 register_local_user(source_p
, source_p
);
274 cap_list(struct Client
*source_p
, const char *arg
)
276 /* list of what theyre currently using */
277 clicap_generate(source_p
, "LIST",
278 source_p
->localClient
->caps
? source_p
->localClient
->caps
: -1);
282 cap_ls(struct Client
*source_p
, const char *arg
)
284 int caps_version
= 301;
286 if(!IsRegistered(source_p
))
287 source_p
->flags
|= FLAGS_CLICAP
;
289 if (!EmptyString(arg
)) {
290 caps_version
= atoi(arg
);
293 if (caps_version
>= 302) {
294 source_p
->flags
|= FLAGS_CLICAP_DATA
;
295 source_p
->localClient
->caps
|= CLICAP_CAP_NOTIFY
;
298 /* list of what we support */
299 clicap_generate(source_p
, "LS", 0);
303 cap_req(struct Client
*source_p
, const char *arg
)
305 static char buf_prefix
[DATALEN
+ 1];
306 static char buf_list
[2][DATALEN
+ 1];
307 const char *str_cont
= " * :";
308 const char *str_final
= " :";
311 struct CapabilityEntry
*cap
;
313 int capadd
= 0, capdel
= 0;
314 int finished
= 0, negate
;
315 hook_data_cap_change hdata
;
317 if(!IsRegistered(source_p
))
318 source_p
->flags
|= FLAGS_CLICAP
;
323 buf_prefix
[0] = '\0';
324 len_prefix
= rb_snprintf_try_append(buf_prefix
, sizeof(buf_prefix
),
327 EmptyString(source_p
->name
) ? "*" : source_p
->name
);
329 buf_list
[0][0] = '\0';
330 buf_list
[1][0] = '\0';
331 max_list
= sizeof(buf_prefix
) - len_prefix
- strlen(str_cont
);
333 for(cap
= clicap_find(arg
, &negate
, &finished
); cap
;
334 cap
= clicap_find(NULL
, &negate
, &finished
))
340 if(HasCapabilityFlag(cap
, CLICAP_FLAGS_STICKY
))
347 capdel
|= (1 << cap
->value
);
351 if (!clicap_visible(source_p
, cap
))
358 capadd
|= (1 << cap
->value
);
361 for (int attempts
= 0; attempts
< 2; attempts
++) {
362 if (rb_snprintf_try_append(buf_list
[i
], max_list
, "%s%s%s",
363 buf_list
[i
][0] == '\0' ? "" : " ", /* space between caps */
366 && buf_list
[i
][0] != '\0') {
368 if (!(source_p
->flags
& FLAGS_CLICAP_DATA
)) {
369 /* the client doesn't support multiple lines */
373 /* doesn't fit in the buffer, move to the next one */
381 /* reset the buffer and go around the loop one more time */
382 buf_list
[i
][0] = '\0';
391 sendto_one(source_p
, ":%s CAP %s NAK :%s",
392 me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
, arg
);
397 sendto_one(source_p
, "%s%s%s", buf_prefix
, str_cont
, buf_list
[0]);
398 sendto_one(source_p
, "%s%s%s", buf_prefix
, str_final
, buf_list
[1]);
400 sendto_one(source_p
, "%s%s%s", buf_prefix
, str_final
, buf_list
[0]);
403 hdata
.client
= source_p
;
404 hdata
.oldcaps
= source_p
->localClient
->caps
;
408 source_p
->localClient
->caps
|= capadd
;
409 source_p
->localClient
->caps
&= ~capdel
;
411 call_hook(h_cap_change
, &hdata
);
414 static struct clicap_cmd
417 void (*func
)(struct Client
*source_p
, const char *arg
);
418 } clicap_cmdlist
[] = {
419 /* This list *MUST* be in alphabetical order */
422 { "LIST", cap_list
},
428 clicap_cmd_search(const char *command
, struct clicap_cmd
*entry
)
430 return irccmp(command
, entry
->cmd
);
434 m_cap(struct MsgBuf
*msgbuf_p
, struct Client
*client_p
, struct Client
*source_p
, int parc
, const char *parv
[])
436 struct clicap_cmd
*cmd
;
438 if(!(cmd
= bsearch(parv
[1], clicap_cmdlist
,
439 sizeof(clicap_cmdlist
) / sizeof(struct clicap_cmd
),
440 sizeof(struct clicap_cmd
), (bqcmp
) clicap_cmd_search
)))
442 sendto_one(source_p
, form_str(ERR_INVALIDCAPCMD
),
443 me
.name
, EmptyString(source_p
->name
) ? "*" : source_p
->name
,
448 (cmd
->func
)(source_p
, parv
[2]);