1 |
/* |
2 |
* ircd-hybrid: an advanced, lightweight Internet Relay Chat Daemon (ircd) |
3 |
* |
4 |
* Copyright (c) 1997-2022 ircd-hybrid development team |
5 |
* Copyright (c) 1997-1999 Andrea "Nemesi" Cocito |
6 |
* |
7 |
* This program is free software; you can redistribute it and/or modify |
8 |
* it under the terms of the GNU General Public License as published by |
9 |
* the Free Software Foundation; either version 2 of the License, or |
10 |
* (at your option) any later version. |
11 |
* |
12 |
* This program is distributed in the hope that it will be useful, |
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
* GNU General Public License for more details. |
16 |
* |
17 |
* You should have received a copy of the GNU General Public License |
18 |
* along with this program; if not, write to the Free Software |
19 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 |
20 |
* USA |
21 |
*/ |
22 |
|
23 |
/*! \file m_who.c |
24 |
* \brief Includes required functions for processing the WHO command. |
25 |
* \version $Id$ |
26 |
*/ |
27 |
|
28 |
#include "stdinc.h" |
29 |
#include "list.h" |
30 |
#include "client.h" |
31 |
#include "channel.h" |
32 |
#include "channel_mode.h" |
33 |
#include "hash.h" |
34 |
#include "ircd.h" |
35 |
#include "isupport.h" |
36 |
#include "numeric.h" |
37 |
#include "send.h" |
38 |
#include "irc_string.h" |
39 |
#include "conf.h" |
40 |
#include "parse.h" |
41 |
#include "modules.h" |
42 |
#include "hostmask.h" |
43 |
|
44 |
|
45 |
/** Maximum number of lines to send in response to a /WHO. */ |
46 |
enum { WHO_MAX_REPLIES = 500 }; |
47 |
|
48 |
enum |
49 |
{ |
50 |
WHOSELECT_OPER = 1 << 0, /**< Flag for /WHO: Show IRC operators. */ |
51 |
WHOSELECT_EXTRA = 1 << 1, /**< Flag for /WHO: Pull rank to see users. */ |
52 |
WHOSELECT_DELAY = 1 << 2, /**< Flag for /WHO: Show join-delayed users. */ |
53 |
}; |
54 |
|
55 |
enum |
56 |
{ |
57 |
WHO_FIELD_QTO = 1 << 0, /**< Display query token. */ |
58 |
WHO_FIELD_CHA = 1 << 1, /**< Show common channel name. */ |
59 |
WHO_FIELD_UID = 1 << 2, /**< Show username. */ |
60 |
WHO_FIELD_NIP = 1 << 3, /**< Show IP address. */ |
61 |
WHO_FIELD_HOS = 1 << 4, /**< Show hostname. */ |
62 |
WHO_FIELD_SER = 1 << 5, /**< Show server. */ |
63 |
WHO_FIELD_NIC = 1 << 6, /**< Show nickname. */ |
64 |
WHO_FIELD_FLA = 1 << 7, /**< Show flags (away, oper, chanop, etc). */ |
65 |
WHO_FIELD_DIS = 1 << 8, /**< Show hop count (distance). */ |
66 |
WHO_FIELD_REN = 1 << 9, /**< Show realname (info). */ |
67 |
WHO_FIELD_IDL = 1 << 10, /**< Show idle time. */ |
68 |
WHO_FIELD_ACC = 1 << 11, /**< Show account name. */ |
69 |
WHO_FIELD_OPL = 1 << 12, /**< Show oplevel. */ |
70 |
/** Default fields for /WHO */ |
71 |
WHO_FIELD_DEF = WHO_FIELD_NIC | WHO_FIELD_UID | WHO_FIELD_HOS | WHO_FIELD_SER, |
72 |
}; |
73 |
|
74 |
struct WhoQuery |
75 |
{ |
76 |
unsigned int bitsel; /**< User mode matching flags */ |
77 |
unsigned int matchsel; /**< Field matching flags */ |
78 |
unsigned int fields; /**< Fields to be shown in the output */ |
79 |
unsigned int maxmatches; /**< Maximum number of replies to be sent */ |
80 |
const char *token; /**< User-defined query token */ |
81 |
}; |
82 |
|
83 |
|
84 |
/*! \brief Send a WHO reply to a client who asked. |
85 |
* \param source_p Pointer to client requesting who. |
86 |
* \param target_p Client who is shown to \a source_p. |
87 |
* \param member ChannelMember pointer of a shared channel that provides visibility. |
88 |
* \param who Pointer to struct WhoQuery item that defines the options for this query. |
89 |
*/ |
90 |
static void |
91 |
who_send(struct Client *source_p, const struct Client *target_p, |
92 |
const struct ChannelMember *member, const struct WhoQuery *who) |
93 |
{ |
94 |
char buf[IRCD_BUFSIZE]; |
95 |
char *p = buf; |
96 |
|
97 |
/* |
98 |
* NOTE: with current fields list and sizes this _cannot_ overrun, |
99 |
* and also the message finally sent shouldn't ever be truncated. |
100 |
*/ |
101 |
buf[1] = '\0'; |
102 |
|
103 |
/* If we don't have a channel and we need one... try to find it. */ |
104 |
if (member == NULL) |
105 |
{ |
106 |
if (who->fields == 0 || (who->fields & (WHO_FIELD_CHA | WHO_FIELD_FLA))) |
107 |
{ |
108 |
dlink_node *node; |
109 |
DLINK_FOREACH(node, target_p->channel.head) |
110 |
{ |
111 |
member = node->data; |
112 |
|
113 |
if (PubChannel(member->channel)) |
114 |
break; |
115 |
member = NULL; |
116 |
} |
117 |
} |
118 |
} |
119 |
|
120 |
/* |
121 |
* Place the fields in the buffer and send it. Note that who->fields == 0 |
122 |
* means "default query". |
123 |
*/ |
124 |
|
125 |
if ((who->fields & WHO_FIELD_QTO)) /* Query token */ |
126 |
{ |
127 |
if (EmptyString(who->token)) |
128 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", "0"); |
129 |
else |
130 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", who->token); |
131 |
} |
132 |
|
133 |
if (who->fields == 0 || (who->fields & WHO_FIELD_CHA)) |
134 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", member ? member->channel->name : "*"); |
135 |
|
136 |
if (who->fields == 0 || (who->fields & WHO_FIELD_UID)) |
137 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->username); |
138 |
|
139 |
if ((who->fields & WHO_FIELD_NIP)) |
140 |
{ |
141 |
if (HasUMode(source_p, UMODE_OPER) || source_p == target_p) |
142 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->sockhost); |
143 |
else |
144 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", "255.255.255.255"); |
145 |
} |
146 |
|
147 |
if (who->fields == 0 || (who->fields & WHO_FIELD_HOS)) |
148 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->host); |
149 |
|
150 |
if (who->fields == 0 || (who->fields & WHO_FIELD_SER)) |
151 |
{ |
152 |
if (!HasUMode(source_p, UMODE_OPER) && |
153 |
(ConfigServerHide.hide_servers || IsHidden(target_p->servptr))) |
154 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", ConfigServerHide.hidden_name); |
155 |
else |
156 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->servptr->name); |
157 |
} |
158 |
|
159 |
if (who->fields == 0 || (who->fields & WHO_FIELD_NIC)) |
160 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->name); |
161 |
|
162 |
if (who->fields == 0 || (who->fields & WHO_FIELD_FLA)) |
163 |
{ |
164 |
char status[16]; |
165 |
|
166 |
if (HasUMode(source_p, UMODE_OPER)) |
167 |
snprintf(status, sizeof(status), "%c%s%s%s%s%s", target_p->away[0] ? 'G' : 'H', |
168 |
HasUMode(target_p, UMODE_BOT) ? "B" : "", |
169 |
HasUMode(target_p, UMODE_SECURE) ? "S" : "", |
170 |
HasUMode(target_p, UMODE_REGISTERED) ? "r" : "", |
171 |
HasUMode(target_p, UMODE_OPER) ? "*" : "", |
172 |
member ? member_get_prefix(member, who->fields || !!HasCap(source_p, CAP_MULTI_PREFIX)) : ""); |
173 |
|
174 |
else |
175 |
snprintf(status, sizeof(status), "%c%s%s%s%s%s", target_p->away[0] ? 'G' : 'H', |
176 |
HasUMode(target_p, UMODE_BOT) ? "B" : "", |
177 |
HasUMode(target_p, UMODE_SECURE) ? "S" : "", |
178 |
HasUMode(target_p, UMODE_REGISTERED) ? "r" : "", |
179 |
HasUMode(target_p, UMODE_OPER) && |
180 |
!HasUMode(target_p, UMODE_HIDDEN) ? "*" : "", |
181 |
member ? member_get_prefix(member, who->fields || !!HasCap(source_p, CAP_MULTI_PREFIX)) : ""); |
182 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", status); |
183 |
} |
184 |
|
185 |
if (who->fields == 0 || (who->fields & WHO_FIELD_DIS)) |
186 |
{ |
187 |
if (!HasUMode(source_p, UMODE_OPER) && |
188 |
(ConfigServerHide.hide_servers || IsHidden(target_p->servptr))) |
189 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s%u", who->fields == 0 ? ":" : "", 0); |
190 |
else |
191 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s%u", who->fields == 0 ? ":" : "", target_p->hopcount); |
192 |
} |
193 |
|
194 |
if ((who->fields & WHO_FIELD_IDL)) |
195 |
{ |
196 |
if (MyClient(target_p) && |
197 |
(HasUMode(source_p, UMODE_OPER) || target_p == source_p)) |
198 |
p += snprintf(p, sizeof(buf) - (p - buf), " %u", client_get_idle_time(source_p, target_p)); |
199 |
else |
200 |
p += snprintf(p, sizeof(buf) - (p - buf), " %u", 0); |
201 |
} |
202 |
|
203 |
if ((who->fields & WHO_FIELD_ACC)) |
204 |
{ |
205 |
if (strcmp(target_p->account, "*")) |
206 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", target_p->account); |
207 |
else |
208 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", "0"); |
209 |
} |
210 |
|
211 |
if ((who->fields & WHO_FIELD_OPL)) |
212 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s", "n/a"); |
213 |
|
214 |
if (who->fields == 0 || (who->fields & WHO_FIELD_REN)) |
215 |
p += snprintf(p, sizeof(buf) - (p - buf), " %s%s", who->fields ? ":" : "", target_p->info); |
216 |
/* Place colon here for special reply ^ */ |
217 |
|
218 |
sendto_one_numeric(source_p, &me, who->fields ? RPL_WHOSPCRPL : RPL_WHOREPLY, buf + 1); |
219 |
} |
220 |
|
221 |
/*! |
222 |
* \param source_p Pointer to client requesting who. |
223 |
* \param target_p Pointer to client to do who on. |
224 |
* \param mask Mask to match. |
225 |
* \param who Pointer to struct WhoQuery item that defines the options for this query. |
226 |
* \return true if mask matches, false otherwise. |
227 |
*/ |
228 |
static bool |
229 |
who_matches(struct Client *source_p, const struct Client *target_p, |
230 |
const char *mask, const struct WhoQuery *who) |
231 |
{ |
232 |
if ((who->bitsel & WHOSELECT_OPER)) |
233 |
if (!HasUMode(target_p, UMODE_OPER) || |
234 |
(HasUMode(target_p, UMODE_HIDDEN) && !HasUMode(source_p, UMODE_OPER))) |
235 |
return false; |
236 |
|
237 |
if (mask == NULL) |
238 |
return true; |
239 |
|
240 |
if ((who->matchsel & WHO_FIELD_NIC) && match(mask, target_p->name) == 0) |
241 |
return true; |
242 |
|
243 |
if ((who->matchsel & WHO_FIELD_UID) && match(mask, target_p->username) == 0) |
244 |
return true; |
245 |
|
246 |
if ((who->matchsel & WHO_FIELD_HOS)) |
247 |
{ |
248 |
if (match(mask, target_p->host) == 0) |
249 |
return true; |
250 |
else if (HasUMode(source_p, UMODE_OPER) && match(mask, target_p->realhost) == 0) |
251 |
return true; |
252 |
} |
253 |
|
254 |
if ((who->matchsel & WHO_FIELD_REN) && match(mask, target_p->info) == 0) |
255 |
return true; |
256 |
|
257 |
if ((who->matchsel & WHO_FIELD_ACC) && match(mask, target_p->account) == 0) |
258 |
return true; |
259 |
|
260 |
if ((who->matchsel & WHO_FIELD_NIP) && HasUMode(source_p, UMODE_OPER)) |
261 |
{ |
262 |
struct irc_ssaddr addr; |
263 |
int bits = 0; |
264 |
const int ret = parse_netmask(mask, &addr, &bits); |
265 |
|
266 |
if (ret == HM_IPV4 || ret == HM_IPV6) |
267 |
if (address_compare(&target_p->ip, &addr, false, false, bits) == true) |
268 |
return true; |
269 |
|
270 |
if (match(mask, target_p->sockhost) == 0) |
271 |
return true; |
272 |
} |
273 |
|
274 |
if ((who->matchsel & WHO_FIELD_SER)) |
275 |
if (HasUMode(source_p, UMODE_OPER) || |
276 |
(ConfigServerHide.hide_servers == 0 && !IsHidden(target_p->servptr))) |
277 |
if (match(mask, target_p->servptr->name) == 0) |
278 |
return true; |
279 |
|
280 |
return false; |
281 |
} |
282 |
|
283 |
/*! \brief Lists matching clients on specified channel. Marks matched clients. |
284 |
* \param source_p Pointer to client requesting who. |
285 |
* \param channel Pointer to channel member chain. |
286 |
* \param mask Mask to match. |
287 |
* \param who Pointer to struct WhoQuery item that defines the options for this query. |
288 |
*/ |
289 |
static void |
290 |
who_on_common_channel(struct Client *source_p, struct Channel *channel, const char *mask, |
291 |
struct WhoQuery *who) |
292 |
{ |
293 |
dlink_node *node; |
294 |
|
295 |
DLINK_FOREACH(node, channel->members.head) |
296 |
{ |
297 |
struct ChannelMember *member = node->data; |
298 |
struct Client *target_p = member->client; |
299 |
|
300 |
if (!HasUMode(target_p, UMODE_INVISIBLE) || HasFlag(target_p, FLAGS_MARK)) |
301 |
continue; |
302 |
|
303 |
AddFlag(target_p, FLAGS_MARK); |
304 |
|
305 |
if (who->maxmatches) |
306 |
{ |
307 |
if (who_matches(source_p, target_p, mask, who) == true) |
308 |
{ |
309 |
who_send(source_p, target_p, member, who); |
310 |
--who->maxmatches; |
311 |
} |
312 |
} |
313 |
} |
314 |
} |
315 |
|
316 |
/*! \brief Does a global scan of all clients looking for match. |
317 |
* \param source_p Pointer to client requesting who. |
318 |
* \param mask Mask to match. |
319 |
* \param who Pointer to struct WhoQuery item that defines the options for this query. |
320 |
*/ |
321 |
static void |
322 |
who_global(struct Client *source_p, const char *mask, struct WhoQuery *who) |
323 |
{ |
324 |
dlink_node *node; |
325 |
static uintmax_t last_used = 0; |
326 |
|
327 |
if (!HasUMode(source_p, UMODE_OPER)) |
328 |
{ |
329 |
if ((last_used + ConfigGeneral.pace_wait) > event_base->time.sec_monotonic) |
330 |
{ |
331 |
sendto_one_numeric(source_p, &me, RPL_LOAD2HI, "WHO"); |
332 |
return; |
333 |
} |
334 |
|
335 |
last_used = event_base->time.sec_monotonic; |
336 |
} |
337 |
|
338 |
/* First, list all matching invisible clients on common channels */ |
339 |
DLINK_FOREACH(node, source_p->channel.head) |
340 |
{ |
341 |
struct ChannelMember *member = node->data; |
342 |
who_on_common_channel(source_p, member->channel, mask, who); |
343 |
} |
344 |
|
345 |
/* Second, list all matching visible clients */ |
346 |
DLINK_FOREACH(node, global_client_list.head) |
347 |
{ |
348 |
struct Client *target_p = node->data; |
349 |
|
350 |
assert(IsClient(target_p)); |
351 |
|
352 |
if (HasUMode(target_p, UMODE_INVISIBLE)) |
353 |
{ |
354 |
DelFlag(target_p, FLAGS_MARK); |
355 |
continue; |
356 |
} |
357 |
|
358 |
if (who->maxmatches) |
359 |
{ |
360 |
if (who_matches(source_p, target_p, mask, who) == true) |
361 |
{ |
362 |
who_send(source_p, target_p, NULL, who); |
363 |
--who->maxmatches; |
364 |
} |
365 |
} |
366 |
} |
367 |
} |
368 |
|
369 |
/*! \brief Does a WHO on given channel. |
370 |
* \param source_p Pointer to client requesting who. |
371 |
* \param channel Pointer to channel to do who on. |
372 |
* \param who Pointer to struct WhoQuery item that defines the options for this query. |
373 |
*/ |
374 |
static void |
375 |
who_on_channel(struct Client *source_p, struct Channel *channel, const struct WhoQuery *who) |
376 |
{ |
377 |
bool is_member = false; |
378 |
|
379 |
if (HasUMode(source_p, UMODE_ADMIN) || member_find_link(source_p, channel)) |
380 |
is_member = true; |
381 |
else if (SecretChannel(channel)) |
382 |
return; |
383 |
|
384 |
dlink_node *node; |
385 |
DLINK_FOREACH(node, channel->members.head) |
386 |
{ |
387 |
struct ChannelMember *member = node->data; |
388 |
struct Client *target_p = member->client; |
389 |
|
390 |
if (is_member == true || !HasUMode(target_p, UMODE_INVISIBLE)) |
391 |
{ |
392 |
if ((who->bitsel & WHOSELECT_OPER)) |
393 |
if (!HasUMode(target_p, UMODE_OPER) || |
394 |
(HasUMode(target_p, UMODE_HIDDEN) && !HasUMode(source_p, UMODE_OPER))) |
395 |
continue; |
396 |
|
397 |
who_send(source_p, target_p, member, who); |
398 |
} |
399 |
} |
400 |
} |
401 |
|
402 |
/*! \brief WHO command handler |
403 |
* |
404 |
* \param source_p Pointer to allocated Client struct from which the message |
405 |
* originally comes from. This can be a local or remote client. |
406 |
* \param parc Integer holding the number of supplied arguments. |
407 |
* \param parv Argument vector where parv[0] .. parv[parc-1] are non-NULL |
408 |
* pointers. |
409 |
* \note Valid arguments for this command are: |
410 |
* - parv[0] = command |
411 |
* - parv[1] = mask |
412 |
* - parv[2] = additional selection flag, only 'o' for now. |
413 |
* and %flags to specify what fields to output |
414 |
* plus a ,querytype if the t flag is specified |
415 |
* so the final thing will be like o%tnchu,777 |
416 |
*/ |
417 |
static void |
418 |
m_who(struct Client *source_p, int parc, char *parv[]) |
419 |
{ |
420 |
char *mask = parv[1]; |
421 |
char *options = parv[2]; |
422 |
char *token = NULL; |
423 |
struct WhoQuery w = { .maxmatches = WHO_MAX_REPLIES }, *who = &w; |
424 |
|
425 |
if (!EmptyString(options)) |
426 |
{ |
427 |
char ch; |
428 |
char *p = options; |
429 |
|
430 |
while ((ch = *p++) && (ch != '%') && (ch != ',')) |
431 |
{ |
432 |
switch (ch) |
433 |
{ |
434 |
case 'o': |
435 |
case 'O': |
436 |
who->bitsel |= WHOSELECT_OPER; |
437 |
break; |
438 |
case 'n': |
439 |
case 'N': |
440 |
who->matchsel |= WHO_FIELD_NIC; |
441 |
break; |
442 |
case 'u': |
443 |
case 'U': |
444 |
who->matchsel |= WHO_FIELD_UID; |
445 |
break; |
446 |
case 'h': |
447 |
case 'H': |
448 |
who->matchsel |= WHO_FIELD_HOS; |
449 |
break; |
450 |
case 'i': |
451 |
case 'I': |
452 |
who->matchsel |= WHO_FIELD_NIP; |
453 |
break; |
454 |
case 's': |
455 |
case 'S': |
456 |
who->matchsel |= WHO_FIELD_SER; |
457 |
break; |
458 |
case 'r': |
459 |
case 'R': |
460 |
who->matchsel |= WHO_FIELD_REN; |
461 |
break; |
462 |
case 'a': |
463 |
case 'A': |
464 |
who->matchsel |= WHO_FIELD_ACC; |
465 |
break; |
466 |
} |
467 |
} |
468 |
|
469 |
if (ch == '%') |
470 |
{ |
471 |
while ((ch = *p++) && (ch != ',')) |
472 |
{ |
473 |
switch (ch) |
474 |
{ |
475 |
case 'c': |
476 |
case 'C': |
477 |
who->fields |= WHO_FIELD_CHA; |
478 |
break; |
479 |
case 'd': |
480 |
case 'D': |
481 |
who->fields |= WHO_FIELD_DIS; |
482 |
break; |
483 |
case 'f': |
484 |
case 'F': |
485 |
who->fields |= WHO_FIELD_FLA; |
486 |
break; |
487 |
case 'h': |
488 |
case 'H': |
489 |
who->fields |= WHO_FIELD_HOS; |
490 |
break; |
491 |
case 'i': |
492 |
case 'I': |
493 |
who->fields |= WHO_FIELD_NIP; |
494 |
break; |
495 |
case 'l': |
496 |
case 'L': |
497 |
who->fields |= WHO_FIELD_IDL; |
498 |
break; |
499 |
case 'n': |
500 |
case 'N': |
501 |
who->fields |= WHO_FIELD_NIC; |
502 |
break; |
503 |
case 'r': |
504 |
case 'R': |
505 |
who->fields |= WHO_FIELD_REN; |
506 |
break; |
507 |
case 's': |
508 |
case 'S': |
509 |
who->fields |= WHO_FIELD_SER; |
510 |
break; |
511 |
case 't': |
512 |
case 'T': |
513 |
who->fields |= WHO_FIELD_QTO; |
514 |
break; |
515 |
case 'u': |
516 |
case 'U': |
517 |
who->fields |= WHO_FIELD_UID; |
518 |
break; |
519 |
case 'a': |
520 |
case 'A': |
521 |
who->fields |= WHO_FIELD_ACC; |
522 |
break; |
523 |
case 'o': |
524 |
case 'O': |
525 |
who->fields |= WHO_FIELD_OPL; |
526 |
break; |
527 |
} |
528 |
} |
529 |
} |
530 |
|
531 |
if (ch) |
532 |
token = p; |
533 |
|
534 |
if (token && (who->fields & WHO_FIELD_QTO)) |
535 |
{ |
536 |
p = token; |
537 |
if (!((*p > '9') || (*p < '0'))) |
538 |
p++; |
539 |
if (!((*p > '9') || (*p < '0'))) |
540 |
p++; |
541 |
if (!((*p > '9') || (*p < '0'))) |
542 |
p++; |
543 |
*p = '\0'; |
544 |
} |
545 |
else |
546 |
token = NULL; |
547 |
|
548 |
who->token = token; |
549 |
} |
550 |
|
551 |
if (who->matchsel == 0) |
552 |
who->matchsel = WHO_FIELD_DEF; |
553 |
|
554 |
/* '/who #some_channel' */ |
555 |
if (IsChanPrefix(*mask)) |
556 |
{ |
557 |
/* List all users on a given channel */ |
558 |
struct Channel *channel = hash_find_channel(mask); |
559 |
if (channel) |
560 |
who_on_channel(source_p, channel, who); |
561 |
|
562 |
sendto_one_numeric(source_p, &me, RPL_ENDOFWHO, mask); |
563 |
return; |
564 |
} |
565 |
|
566 |
collapse(mask); |
567 |
if (strcmp(mask, "0") == 0 || |
568 |
strcmp(mask, "*") == 0) |
569 |
mask = NULL; |
570 |
|
571 |
who_global(source_p, mask, who); |
572 |
|
573 |
if (who->maxmatches == 0) |
574 |
sendto_one_numeric(source_p, &me, ERR_WHOLIMEXCEED, WHO_MAX_REPLIES, "WHO"); |
575 |
sendto_one_numeric(source_p, &me, RPL_ENDOFWHO, EmptyString(mask) ? "*" : mask); |
576 |
} |
577 |
|
578 |
static struct Message who_msgtab = |
579 |
{ |
580 |
.cmd = "WHO", |
581 |
.handlers[UNREGISTERED_HANDLER] = { .handler = m_unregistered }, |
582 |
.handlers[CLIENT_HANDLER] = { .handler = m_who, .args_min = 2 }, |
583 |
.handlers[SERVER_HANDLER] = { .handler = m_ignore }, |
584 |
.handlers[ENCAP_HANDLER] = { .handler = m_ignore }, |
585 |
.handlers[OPER_HANDLER] = { .handler = m_who, .args_min = 2 } |
586 |
}; |
587 |
|
588 |
static void |
589 |
module_init(void) |
590 |
{ |
591 |
mod_add_cmd(&who_msgtab); |
592 |
isupport_add("WHOX", NULL, -1); |
593 |
} |
594 |
|
595 |
static void |
596 |
module_exit(void) |
597 |
{ |
598 |
mod_del_cmd(&who_msgtab); |
599 |
isupport_delete("WHOX"); |
600 |
} |
601 |
|
602 |
struct module module_entry = |
603 |
{ |
604 |
.version = "$Revision$", |
605 |
.modinit = module_init, |
606 |
.modexit = module_exit, |
607 |
}; |