]>
Commit | Line | Data |
---|---|---|
212380e3 | 1 | /* |
2 | * ircd-ratbox: A slightly useful ircd. | |
3 | * m_who.c: Shows who is on a channel. | |
4 | * | |
5 | * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center | |
6 | * Copyright (C) 1996-2002 Hybrid Development Team | |
7 | * Copyright (C) 2002-2005 ircd-ratbox development team | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, write to the Free Software | |
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | |
22 | * USA | |
23 | * | |
f71e18ee | 24 | * $Id: m_who.c 3350 2007-04-02 22:03:08Z jilles $ |
212380e3 | 25 | */ |
26 | #include "stdinc.h" | |
212380e3 | 27 | #include "common.h" |
28 | #include "client.h" | |
29 | #include "channel.h" | |
30 | #include "hash.h" | |
31 | #include "ircd.h" | |
32 | #include "numeric.h" | |
33 | #include "s_serv.h" | |
34 | #include "send.h" | |
13ae2f4b | 35 | #include "match.h" |
212380e3 | 36 | #include "s_conf.h" |
d3455e2c | 37 | #include "logger.h" |
212380e3 | 38 | #include "msg.h" |
39 | #include "parse.h" | |
40 | #include "modules.h" | |
41 | #include "packet.h" | |
42 | #include "s_newconf.h" | |
43 | ||
555801db JT |
44 | #define FIELD_CHANNEL 0x0001 |
45 | #define FIELD_HOP 0x0002 | |
46 | #define FIELD_FLAGS 0x0004 | |
47 | #define FIELD_HOST 0x0008 | |
48 | #define FIELD_IP 0x0010 | |
49 | #define FIELD_IDLE 0x0020 | |
50 | #define FIELD_NICK 0x0040 | |
51 | #define FIELD_INFO 0x0080 | |
52 | #define FIELD_SERVER 0x0100 | |
53 | #define FIELD_QUERYTYPE 0x0200 /* cookie for client */ | |
54 | #define FIELD_USER 0x0400 | |
55 | #define FIELD_ACCOUNT 0x0800 | |
56 | #define FIELD_OPLEVEL 0x1000 /* meaningless and stupid, but whatever */ | |
57 | ||
58 | struct who_format | |
59 | { | |
60 | int fields; | |
61 | const char *querytype; | |
62 | }; | |
63 | ||
212380e3 | 64 | static int m_who(struct Client *, struct Client *, int, const char **); |
65 | ||
66 | struct Message who_msgtab = { | |
67 | "WHO", 0, 0, 0, MFLG_SLOW, | |
68 | {mg_unreg, {m_who, 2}, mg_ignore, mg_ignore, mg_ignore, {m_who, 2}} | |
69 | }; | |
70 | ||
71 | mapi_clist_av1 who_clist[] = { &who_msgtab, NULL }; | |
f71e18ee | 72 | DECLARE_MODULE_AV1(who, NULL, NULL, who_clist, NULL, NULL, "$Revision: 3350 $"); |
212380e3 | 73 | |
74 | static void do_who_on_channel(struct Client *source_p, struct Channel *chptr, | |
555801db JT |
75 | int server_oper, int member, |
76 | struct who_format *fmt); | |
212380e3 | 77 | |
555801db | 78 | static void who_global(struct Client *source_p, const char *mask, int server_oper, int operspy, struct who_format *fmt); |
212380e3 | 79 | |
80 | static void do_who(struct Client *source_p, | |
aff2058a JT |
81 | struct Client *target_p, struct membership *msptr, |
82 | struct who_format *fmt); | |
212380e3 | 83 | |
84 | ||
85 | /* | |
86 | ** m_who | |
87 | ** parv[0] = sender prefix | |
88 | ** parv[1] = nickname mask list | |
555801db | 89 | ** parv[2] = additional selection flag and format options |
212380e3 | 90 | */ |
91 | static int | |
92 | m_who(struct Client *client_p, struct Client *source_p, int parc, const char *parv[]) | |
93 | { | |
94 | static time_t last_used = 0; | |
95 | struct Client *target_p; | |
96 | struct membership *msptr; | |
97 | char *mask; | |
08d11e34 | 98 | rb_dlink_node *lp; |
212380e3 | 99 | struct Channel *chptr = NULL; |
100 | int server_oper = parc > 2 ? (*parv[2] == 'o') : 0; /* Show OPERS only */ | |
101 | int member; | |
102 | int operspy = 0; | |
555801db JT |
103 | struct who_format fmt; |
104 | const char *s; | |
8e0f260b | 105 | char maskcopy[512]; |
555801db JT |
106 | |
107 | fmt.fields = 0; | |
108 | fmt.querytype = NULL; | |
109 | if (parc > 2 && (s = strchr(parv[2], '%')) != NULL) | |
110 | { | |
111 | s++; | |
112 | for (; *s != '\0'; s++) | |
113 | { | |
114 | switch (*s) | |
115 | { | |
116 | case 'c': fmt.fields |= FIELD_CHANNEL; break; | |
117 | case 'd': fmt.fields |= FIELD_HOP; break; | |
118 | case 'f': fmt.fields |= FIELD_FLAGS; break; | |
119 | case 'h': fmt.fields |= FIELD_HOST; break; | |
120 | case 'i': fmt.fields |= FIELD_IP; break; | |
121 | case 'l': fmt.fields |= FIELD_IDLE; break; | |
122 | case 'n': fmt.fields |= FIELD_NICK; break; | |
123 | case 'r': fmt.fields |= FIELD_INFO; break; | |
124 | case 's': fmt.fields |= FIELD_SERVER; break; | |
125 | case 't': fmt.fields |= FIELD_QUERYTYPE; break; | |
126 | case 'u': fmt.fields |= FIELD_USER; break; | |
127 | case 'a': fmt.fields |= FIELD_ACCOUNT; break; | |
128 | case 'o': fmt.fields |= FIELD_OPLEVEL; break; | |
129 | case ',': | |
130 | s++; | |
131 | fmt.querytype = s; | |
132 | s += strlen(s); | |
133 | s--; | |
134 | break; | |
135 | } | |
136 | } | |
137 | if (EmptyString(fmt.querytype) || strlen(fmt.querytype) > 3) | |
138 | fmt.querytype = "0"; | |
139 | } | |
212380e3 | 140 | |
8e0f260b JT |
141 | strlcpy(maskcopy, parv[1], sizeof maskcopy); |
142 | mask = maskcopy; | |
212380e3 | 143 | |
144 | collapse(mask); | |
145 | ||
146 | /* '/who *' */ | |
147 | if((*(mask + 1) == '\0') && (*mask == '*')) | |
148 | { | |
149 | if(source_p->user == NULL) | |
150 | return 0; | |
151 | ||
152 | if((lp = source_p->user->channel.head) != NULL) | |
153 | { | |
154 | msptr = lp->data; | |
555801db | 155 | do_who_on_channel(source_p, msptr->chptr, server_oper, YES, &fmt); |
212380e3 | 156 | } |
157 | ||
158 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
159 | me.name, source_p->name, "*"); | |
160 | return 0; | |
161 | } | |
162 | ||
163 | if(IsOperSpy(source_p) && *mask == '!') | |
164 | { | |
165 | mask++; | |
166 | operspy = 1; | |
167 | ||
168 | if(EmptyString(mask)) | |
169 | { | |
170 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
171 | me.name, source_p->name, parv[1]); | |
172 | return 0; | |
173 | } | |
174 | } | |
175 | ||
176 | /* '/who #some_channel' */ | |
177 | if(IsChannelName(mask)) | |
178 | { | |
179 | /* List all users on a given channel */ | |
f71e18ee | 180 | chptr = find_channel(parv[1] + operspy); |
212380e3 | 181 | if(chptr != NULL) |
182 | { | |
183 | if(operspy) | |
184 | report_operspy(source_p, "WHO", chptr->chname); | |
185 | ||
186 | if(IsMember(source_p, chptr) || operspy) | |
555801db | 187 | do_who_on_channel(source_p, chptr, server_oper, YES, &fmt); |
212380e3 | 188 | else if(!SecretChannel(chptr)) |
555801db | 189 | do_who_on_channel(source_p, chptr, server_oper, NO, &fmt); |
212380e3 | 190 | } |
191 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
f71e18ee | 192 | me.name, source_p->name, parv[1] + operspy); |
212380e3 | 193 | return 0; |
194 | } | |
195 | ||
196 | /* '/who nick' */ | |
197 | ||
198 | if(((target_p = find_named_person(mask)) != NULL) && | |
199 | (!server_oper || IsOper(target_p))) | |
200 | { | |
201 | int isinvis = 0; | |
202 | ||
203 | isinvis = IsInvisible(target_p); | |
08d11e34 | 204 | RB_DLINK_FOREACH(lp, target_p->user->channel.head) |
212380e3 | 205 | { |
206 | msptr = lp->data; | |
207 | chptr = msptr->chptr; | |
208 | ||
209 | member = IsMember(source_p, chptr); | |
210 | ||
211 | if(isinvis && !member) | |
212 | continue; | |
213 | ||
214 | if(member || (!isinvis && PubChannel(chptr))) | |
215 | break; | |
216 | } | |
217 | ||
218 | /* if we stopped midlist, lp->data is the membership for | |
219 | * target_p of chptr | |
220 | */ | |
221 | if(lp != NULL) | |
aff2058a | 222 | do_who(source_p, target_p, lp->data, &fmt); |
212380e3 | 223 | else |
aff2058a | 224 | do_who(source_p, target_p, NULL, &fmt); |
212380e3 | 225 | |
226 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
227 | me.name, source_p->name, mask); | |
228 | return 0; | |
229 | } | |
230 | ||
231 | if(!IsFloodDone(source_p)) | |
232 | flood_endgrace(source_p); | |
233 | ||
234 | /* it has to be a global who at this point, limit it */ | |
235 | if(!IsOper(source_p)) | |
236 | { | |
9f6bbe3c | 237 | if((last_used + ConfigFileEntry.pace_wait) > rb_current_time()) |
212380e3 | 238 | { |
239 | sendto_one(source_p, form_str(RPL_LOAD2HI), | |
240 | me.name, source_p->name, "WHO"); | |
241 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
242 | me.name, source_p->name, "*"); | |
243 | return 0; | |
244 | } | |
245 | else | |
9f6bbe3c | 246 | last_used = rb_current_time(); |
212380e3 | 247 | } |
248 | ||
249 | /* Note: operspy_dont_care_user_info does not apply to | |
250 | * who on channels */ | |
251 | if(IsOperSpy(source_p) && ConfigFileEntry.operspy_dont_care_user_info) | |
252 | operspy = 1; | |
253 | ||
254 | /* '/who 0' for a global list. this forces clients to actually | |
255 | * request a full list. I presume its because of too many typos | |
256 | * with "/who" ;) --fl | |
257 | */ | |
258 | if((*(mask + 1) == '\0') && (*mask == '0')) | |
555801db | 259 | who_global(source_p, NULL, server_oper, 0, &fmt); |
212380e3 | 260 | else |
555801db | 261 | who_global(source_p, mask, server_oper, operspy, &fmt); |
212380e3 | 262 | |
263 | sendto_one(source_p, form_str(RPL_ENDOFWHO), | |
264 | me.name, source_p->name, mask); | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | /* who_common_channel | |
270 | * inputs - pointer to client requesting who | |
271 | * - pointer to channel member chain. | |
272 | * - char * mask to match | |
273 | * - int if oper on a server or not | |
274 | * - pointer to int maxmatches | |
555801db | 275 | * - format options |
212380e3 | 276 | * output - NONE |
277 | * side effects - lists matching invisible clients on specified channel, | |
278 | * marks matched clients. | |
279 | */ | |
280 | static void | |
281 | who_common_channel(struct Client *source_p, struct Channel *chptr, | |
555801db JT |
282 | const char *mask, int server_oper, int *maxmatches, |
283 | struct who_format *fmt) | |
212380e3 | 284 | { |
285 | struct membership *msptr; | |
286 | struct Client *target_p; | |
08d11e34 | 287 | rb_dlink_node *ptr; |
212380e3 | 288 | |
08d11e34 | 289 | RB_DLINK_FOREACH(ptr, chptr->members.head) |
212380e3 | 290 | { |
291 | msptr = ptr->data; | |
292 | target_p = msptr->client_p; | |
293 | ||
294 | if(!IsInvisible(target_p) || IsMarked(target_p)) | |
295 | continue; | |
296 | ||
297 | if(server_oper && !IsOper(target_p)) | |
298 | continue; | |
299 | ||
300 | SetMark(target_p); | |
301 | ||
302 | if(*maxmatches > 0) | |
303 | { | |
304 | if((mask == NULL) || | |
305 | match(mask, target_p->name) || match(mask, target_p->username) || | |
c88cdb00 | 306 | match(mask, target_p->host) || match(mask, target_p->servptr->name) || |
212380e3 | 307 | (IsOper(source_p) && match(mask, target_p->orighost)) || |
308 | match(mask, target_p->info)) | |
309 | { | |
aff2058a | 310 | do_who(source_p, target_p, NULL, fmt); |
212380e3 | 311 | --(*maxmatches); |
312 | } | |
313 | } | |
314 | } | |
315 | } | |
316 | ||
317 | /* | |
318 | * who_global | |
319 | * | |
320 | * inputs - pointer to client requesting who | |
321 | * - char * mask to match | |
322 | * - int if oper on a server or not | |
555801db JT |
323 | * - int if operspy or not |
324 | * - format options | |
212380e3 | 325 | * output - NONE |
326 | * side effects - do a global scan of all clients looking for match | |
327 | * this is slightly expensive on EFnet ... | |
328 | * marks assumed cleared for all clients initially | |
329 | * and will be left cleared on return | |
330 | */ | |
331 | static void | |
555801db | 332 | who_global(struct Client *source_p, const char *mask, int server_oper, int operspy, struct who_format *fmt) |
212380e3 | 333 | { |
334 | struct membership *msptr; | |
335 | struct Client *target_p; | |
08d11e34 | 336 | rb_dlink_node *lp, *ptr; |
212380e3 | 337 | int maxmatches = 500; |
338 | ||
339 | /* first, list all matching INvisible clients on common channels | |
340 | * if this is not an operspy who | |
341 | */ | |
342 | if(!operspy) | |
343 | { | |
08d11e34 | 344 | RB_DLINK_FOREACH(lp, source_p->user->channel.head) |
212380e3 | 345 | { |
346 | msptr = lp->data; | |
555801db | 347 | who_common_channel(source_p, msptr->chptr, mask, server_oper, &maxmatches, fmt); |
212380e3 | 348 | } |
349 | } | |
350 | else if (!ConfigFileEntry.operspy_dont_care_user_info) | |
351 | report_operspy(source_p, "WHO", mask); | |
352 | ||
353 | /* second, list all matching visible clients and clear all marks | |
354 | * on invisible clients | |
355 | * if this is an operspy who, list all matching clients, no need | |
356 | * to clear marks | |
357 | */ | |
08d11e34 | 358 | RB_DLINK_FOREACH(ptr, global_client_list.head) |
212380e3 | 359 | { |
360 | target_p = ptr->data; | |
361 | if(!IsPerson(target_p)) | |
362 | continue; | |
363 | ||
364 | if(IsInvisible(target_p) && !operspy) | |
365 | { | |
366 | ClearMark(target_p); | |
367 | continue; | |
368 | } | |
369 | ||
370 | if(server_oper && !IsOper(target_p)) | |
371 | continue; | |
372 | ||
373 | if(maxmatches > 0) | |
374 | { | |
375 | if(!mask || | |
376 | match(mask, target_p->name) || match(mask, target_p->username) || | |
c88cdb00 | 377 | match(mask, target_p->host) || match(mask, target_p->servptr->name) || |
212380e3 | 378 | (IsOper(source_p) && match(mask, target_p->orighost)) || |
379 | match(mask, target_p->info)) | |
380 | { | |
aff2058a | 381 | do_who(source_p, target_p, NULL, fmt); |
212380e3 | 382 | --maxmatches; |
383 | } | |
384 | } | |
385 | } | |
386 | ||
387 | if (maxmatches <= 0) | |
388 | sendto_one(source_p, | |
389 | form_str(ERR_TOOMANYMATCHES), | |
390 | me.name, source_p->name, "WHO"); | |
391 | } | |
392 | ||
393 | /* | |
394 | * do_who_on_channel | |
395 | * | |
396 | * inputs - pointer to client requesting who | |
397 | * - pointer to channel to do who on | |
398 | * - The "real name" of this channel | |
399 | * - int if source_p is a server oper or not | |
400 | * - int if client is member or not | |
555801db | 401 | * - format options |
212380e3 | 402 | * output - NONE |
403 | * side effects - do a who on given channel | |
404 | */ | |
405 | static void | |
406 | do_who_on_channel(struct Client *source_p, struct Channel *chptr, | |
555801db | 407 | int server_oper, int member, struct who_format *fmt) |
212380e3 | 408 | { |
409 | struct Client *target_p; | |
410 | struct membership *msptr; | |
08d11e34 | 411 | rb_dlink_node *ptr; |
212380e3 | 412 | |
08d11e34 | 413 | RB_DLINK_FOREACH(ptr, chptr->members.head) |
212380e3 | 414 | { |
415 | msptr = ptr->data; | |
416 | target_p = msptr->client_p; | |
417 | ||
418 | if(server_oper && !IsOper(target_p)) | |
419 | continue; | |
420 | ||
421 | if(member || !IsInvisible(target_p)) | |
aff2058a | 422 | do_who(source_p, target_p, msptr, fmt); |
212380e3 | 423 | } |
424 | } | |
425 | ||
426 | /* | |
427 | * do_who | |
428 | * | |
429 | * inputs - pointer to client requesting who | |
430 | * - pointer to client to do who on | |
aff2058a | 431 | * - channel membership or NULL |
555801db | 432 | * - format options |
212380e3 | 433 | * output - NONE |
434 | * side effects - do a who on given person | |
435 | */ | |
436 | ||
437 | static void | |
aff2058a | 438 | do_who(struct Client *source_p, struct Client *target_p, struct membership *msptr, struct who_format *fmt) |
212380e3 | 439 | { |
440 | char status[5]; | |
555801db JT |
441 | char str[512], *p, *end; |
442 | const char *q; | |
212380e3 | 443 | |
581fa5c4 | 444 | rb_sprintf(status, "%c%s%s", |
aff2058a | 445 | target_p->user->away ? 'G' : 'H', IsOper(target_p) ? "*" : "", msptr ? find_channel_status(msptr, fmt->fields || IsCapable(source_p, CLICAP_MULTI_PREFIX)) : ""); |
212380e3 | 446 | |
555801db JT |
447 | if (fmt->fields == 0) |
448 | sendto_one(source_p, form_str(RPL_WHOREPLY), me.name, | |
aff2058a | 449 | source_p->name, msptr ? msptr->chptr->chname : "*", |
555801db JT |
450 | target_p->username, target_p->host, |
451 | target_p->servptr->name, target_p->name, status, | |
452 | ConfigServerHide.flatten_links ? 0 : target_p->hopcount, | |
453 | target_p->info); | |
454 | else | |
455 | { | |
456 | str[0] = '\0'; | |
457 | p = str; | |
458 | end = str + sizeof str; | |
459 | if (fmt->fields & FIELD_QUERYTYPE) | |
460 | p += rb_snprintf(p, end - p, " %s", fmt->querytype); | |
461 | if (fmt->fields & FIELD_CHANNEL) | |
aff2058a | 462 | p += rb_snprintf(p, end - p, " %s", msptr ? msptr->chptr->chname : "*"); |
555801db JT |
463 | if (fmt->fields & FIELD_USER) |
464 | p += rb_snprintf(p, end - p, " %s", target_p->username); | |
465 | if (fmt->fields & FIELD_IP) | |
466 | { | |
467 | if (show_ip(source_p, target_p) && !EmptyString(target_p->sockhost) && strcmp(target_p->sockhost, "0")) | |
468 | p += rb_snprintf(p, end - p, " %s", target_p->sockhost); | |
469 | else | |
470 | p += rb_snprintf(p, end - p, " %s", "255.255.255.255"); | |
471 | } | |
472 | if (fmt->fields & FIELD_HOST) | |
473 | p += rb_snprintf(p, end - p, " %s", target_p->host); | |
474 | if (fmt->fields & FIELD_SERVER) | |
475 | p += rb_snprintf(p, end - p, " %s", target_p->servptr->name); | |
476 | if (fmt->fields & FIELD_NICK) | |
477 | p += rb_snprintf(p, end - p, " %s", target_p->name); | |
478 | if (fmt->fields & FIELD_FLAGS) | |
479 | p += rb_snprintf(p, end - p, " %s", status); | |
480 | if (fmt->fields & FIELD_HOP) | |
481 | p += rb_snprintf(p, end - p, " %d", ConfigServerHide.flatten_links ? 0 : target_p->hopcount); | |
482 | if (fmt->fields & FIELD_IDLE) | |
483 | p += rb_snprintf(p, end - p, " %d", MyClient(target_p) ? rb_current_time() - target_p->localClient->last : 0); | |
484 | if (fmt->fields & FIELD_ACCOUNT) | |
485 | { | |
486 | /* display as in whois */ | |
487 | q = target_p->user->suser; | |
488 | if (!EmptyString(q)) | |
489 | { | |
490 | while(IsDigit(*q)) | |
491 | q++; | |
492 | if(*q == '\0') | |
493 | q = target_p->user->suser; | |
494 | } | |
495 | else | |
496 | q = "0"; | |
497 | p += rb_snprintf(p, end - p, " %s", q); | |
498 | } | |
499 | if (fmt->fields & FIELD_OPLEVEL) | |
aff2058a | 500 | p += rb_snprintf(p, end - p, " %s", is_chanop(msptr) ? "999" : "n/a"); |
555801db JT |
501 | if (fmt->fields & FIELD_INFO) |
502 | p += rb_snprintf(p, end - p, " :%s", target_p->info); | |
503 | sendto_one_numeric(source_p, RPL_WHOSPCRPL, "%s", str + 1); | |
504 | } | |
212380e3 | 505 | } |