]> jfr.im git - irc/rqf/shadowircd.git/blame - modules/m_who.c
Forgot version.c.SH for libratbox/.
[irc/rqf/shadowircd.git] / modules / m_who.c
CommitLineData
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
58struct who_format
59{
60 int fields;
61 const char *querytype;
62};
63
212380e3 64static int m_who(struct Client *, struct Client *, int, const char **);
65
66struct 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
71mapi_clist_av1 who_clist[] = { &who_msgtab, NULL };
f71e18ee 72DECLARE_MODULE_AV1(who, NULL, NULL, who_clist, NULL, NULL, "$Revision: 3350 $");
212380e3 73
74static 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 78static void who_global(struct Client *source_p, const char *mask, int server_oper, int operspy, struct who_format *fmt);
212380e3 79
80static 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*/
91static int
92m_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
863efb0e 141 rb_strlcpy(maskcopy, parv[1], sizeof maskcopy);
8e0f260b 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 */
280static void
281who_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 */
331static void
555801db 332who_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 */
405static void
406do_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
437static void
aff2058a 438do_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)
79ba1629 483 p += rb_snprintf(p, end - p, " %d", (int)(MyClient(target_p) ? rb_current_time() - target_p->localClient->last : 0));
555801db
JT
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}