2 * ircd-ratbox: A slightly useful ircd.
3 * m_who.c: Shows who is on a channel.
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
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.
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.
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
39 #include "s_newconf.h"
40 #include "ratelimit.h"
41 #include "supported.h"
43 #define FIELD_CHANNEL 0x0001
44 #define FIELD_HOP 0x0002
45 #define FIELD_FLAGS 0x0004
46 #define FIELD_HOST 0x0008
47 #define FIELD_IP 0x0010
48 #define FIELD_IDLE 0x0020
49 #define FIELD_NICK 0x0040
50 #define FIELD_INFO 0x0080
51 #define FIELD_SERVER 0x0100
52 #define FIELD_QUERYTYPE 0x0200 /* cookie for client */
53 #define FIELD_USER 0x0400
54 #define FIELD_ACCOUNT 0x0800
55 #define FIELD_OPLEVEL 0x1000 /* meaningless and stupid, but whatever */
57 static const char who_desc
[] =
58 "Provides the WHO command to display information for users on a channel";
63 const char *querytype
;
66 static void m_who(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
68 static void do_who_on_channel(struct Client
*source_p
, struct Channel
*chptr
,
69 int server_oper
, int member
,
70 struct who_format
*fmt
);
71 static void who_global(struct Client
*source_p
, const char *mask
, int server_oper
, int operspy
, struct who_format
*fmt
);
72 static void do_who(struct Client
*source_p
,
73 struct Client
*target_p
, struct membership
*msptr
,
74 struct who_format
*fmt
);
76 struct Message who_msgtab
= {
78 {mg_unreg
, {m_who
, 2}, mg_ignore
, mg_ignore
, mg_ignore
, {m_who
, 2}}
84 add_isupport("WHOX", isupport_string
, "");
91 delete_isupport("WHOX");
94 int doing_who_show_idle_hook
;
96 mapi_clist_av1 who_clist
[] = { &who_msgtab
, NULL
};
97 mapi_hlist_av1 who_hlist
[] = {
98 { "doing_who_show_idle", &doing_who_show_idle_hook
},
101 DECLARE_MODULE_AV2(who
, _modinit
, _moddeinit
, who_clist
, who_hlist
, NULL
, NULL
, NULL
, who_desc
);
105 ** parv[1] = nickname mask list
106 ** parv[2] = additional selection flag and format options
109 m_who(struct MsgBuf
*msgbuf_p
, struct Client
*client_p
, struct Client
*source_p
, int parc
, const char *parv
[])
111 static time_t last_used
= 0;
112 struct Client
*target_p
;
113 struct membership
*msptr
;
116 struct Channel
*chptr
= NULL
;
117 int server_oper
= parc
> 2 ? (*parv
[2] == 'o') : 0; /* Show OPERS only */
120 struct who_format fmt
;
125 fmt
.querytype
= NULL
;
126 if (parc
> 2 && (s
= strchr(parv
[2], '%')) != NULL
)
129 for (; *s
!= '\0'; s
++)
133 case 'c': fmt
.fields
|= FIELD_CHANNEL
; break;
134 case 'd': fmt
.fields
|= FIELD_HOP
; break;
135 case 'f': fmt
.fields
|= FIELD_FLAGS
; break;
136 case 'h': fmt
.fields
|= FIELD_HOST
; break;
137 case 'i': fmt
.fields
|= FIELD_IP
; break;
138 case 'l': fmt
.fields
|= FIELD_IDLE
; break;
139 case 'n': fmt
.fields
|= FIELD_NICK
; break;
140 case 'r': fmt
.fields
|= FIELD_INFO
; break;
141 case 's': fmt
.fields
|= FIELD_SERVER
; break;
142 case 't': fmt
.fields
|= FIELD_QUERYTYPE
; break;
143 case 'u': fmt
.fields
|= FIELD_USER
; break;
144 case 'a': fmt
.fields
|= FIELD_ACCOUNT
; break;
145 case 'o': fmt
.fields
|= FIELD_OPLEVEL
; break;
154 if (EmptyString(fmt
.querytype
) || strlen(fmt
.querytype
) > 3)
158 rb_strlcpy(maskcopy
, parv
[1], sizeof maskcopy
);
164 if((*(mask
+ 1) == '\0') && (*mask
== '*'))
166 if(source_p
->user
== NULL
)
169 if((lp
= source_p
->user
->channel
.head
) != NULL
)
172 do_who_on_channel(source_p
, msptr
->chptr
, server_oper
, true, &fmt
);
175 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
176 me
.name
, source_p
->name
, "*");
180 if(IsOperSpy(source_p
) && *mask
== '!')
185 if(EmptyString(mask
))
187 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
188 me
.name
, source_p
->name
, parv
[1]);
193 /* '/who #some_channel' */
194 if(IsChannelName(mask
))
196 /* List all users on a given channel */
197 chptr
= find_channel(parv
[1] + operspy
);
201 if (!IsOperGeneral(source_p
) && !ratelimit_client_who(source_p
, rb_dlink_list_length(&chptr
->members
)/50))
203 sendto_one(source_p
, form_str(RPL_LOAD2HI
),
204 me
.name
, source_p
->name
, "WHO");
205 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
206 me
.name
, source_p
->name
, "*");
211 report_operspy(source_p
, "WHO", chptr
->chname
);
213 if(IsMember(source_p
, chptr
) || operspy
)
214 do_who_on_channel(source_p
, chptr
, server_oper
, true, &fmt
);
215 else if(!SecretChannel(chptr
))
216 do_who_on_channel(source_p
, chptr
, server_oper
, false, &fmt
);
219 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
220 me
.name
, source_p
->name
, parv
[1] + operspy
);
226 if(((target_p
= find_named_person(mask
)) != NULL
) &&
227 (!server_oper
|| SeesOper(target_p
, source_p
)))
231 isinvis
= IsInvisible(target_p
);
232 RB_DLINK_FOREACH(lp
, target_p
->user
->channel
.head
)
235 chptr
= msptr
->chptr
;
237 member
= IsMember(source_p
, chptr
);
239 if(isinvis
&& !member
)
242 if(member
|| (!isinvis
&& PubChannel(chptr
)))
246 /* if we stopped midlist, lp->data is the membership for
250 do_who(source_p
, target_p
, lp
->data
, &fmt
);
252 do_who(source_p
, target_p
, NULL
, &fmt
);
254 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
255 me
.name
, source_p
->name
, mask
);
259 if(!IsFloodDone(source_p
))
260 flood_endgrace(source_p
);
262 /* it has to be a global who at this point, limit it */
263 if(!IsOperGeneral(source_p
))
265 if((last_used
+ ConfigFileEntry
.pace_wait
) > rb_current_time() || !ratelimit_client(source_p
, 1))
267 sendto_one(source_p
, form_str(RPL_LOAD2HI
),
268 me
.name
, source_p
->name
, "WHO");
269 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
270 me
.name
, source_p
->name
, "*");
274 last_used
= rb_current_time();
277 /* Note: operspy_dont_care_user_info does not apply to
279 if(IsOperSpy(source_p
) && ConfigFileEntry
.operspy_dont_care_user_info
)
282 /* '/who 0' for a global list. this forces clients to actually
283 * request a full list. I presume its because of too many typos
284 * with "/who" ;) --fl
286 if((*(mask
+ 1) == '\0') && (*mask
== '0'))
287 who_global(source_p
, NULL
, server_oper
, 0, &fmt
);
289 who_global(source_p
, mask
, server_oper
, operspy
, &fmt
);
291 sendto_one(source_p
, form_str(RPL_ENDOFWHO
),
292 me
.name
, source_p
->name
, mask
);
295 /* who_common_channel
296 * inputs - pointer to client requesting who
297 * - pointer to channel member chain.
298 * - char * mask to match
299 * - int if oper on a server or not
300 * - pointer to int maxmatches
303 * side effects - lists matching invisible clients on specified channel,
304 * marks matched clients.
307 who_common_channel(struct Client
*source_p
, struct Channel
*chptr
,
308 const char *mask
, int server_oper
, int *maxmatches
,
309 struct who_format
*fmt
)
311 struct membership
*msptr
;
312 struct Client
*target_p
;
315 RB_DLINK_FOREACH(ptr
, chptr
->members
.head
)
318 target_p
= msptr
->client_p
;
320 if(!IsInvisible(target_p
) || IsMarked(target_p
))
323 if(server_oper
&& !SeesOper(target_p
, source_p
))
331 match(mask
, target_p
->name
) || match(mask
, target_p
->username
) ||
332 match(mask
, target_p
->host
) || match(mask
, target_p
->servptr
->name
) ||
333 (IsOperGeneral(source_p
) && match(mask
, target_p
->orighost
)) ||
334 match(mask
, target_p
->info
))
336 do_who(source_p
, target_p
, NULL
, fmt
);
346 * inputs - pointer to client requesting who
347 * - char * mask to match
348 * - int if oper on a server or not
349 * - int if operspy or not
352 * side effects - do a global scan of all clients looking for match
353 * this is slightly expensive on EFnet ...
354 * marks assumed cleared for all clients initially
355 * and will be left cleared on return
358 who_global(struct Client
*source_p
, const char *mask
, int server_oper
, int operspy
, struct who_format
*fmt
)
360 struct membership
*msptr
;
361 struct Client
*target_p
;
362 rb_dlink_node
*lp
, *ptr
;
363 int maxmatches
= 500;
365 /* first, list all matching INvisible clients on common channels
366 * if this is not an operspy who
370 RB_DLINK_FOREACH(lp
, source_p
->user
->channel
.head
)
373 who_common_channel(source_p
, msptr
->chptr
, mask
, server_oper
, &maxmatches
, fmt
);
378 maxmatches
= INT_MAX
;
379 if (!ConfigFileEntry
.operspy_dont_care_user_info
)
380 report_operspy(source_p
, "WHO", mask
);
383 /* second, list all matching visible clients and clear all marks
384 * on invisible clients
385 * if this is an operspy who, list all matching clients, no need
388 RB_DLINK_FOREACH(ptr
, global_client_list
.head
)
390 target_p
= ptr
->data
;
391 if(!IsPerson(target_p
))
394 if(IsInvisible(target_p
) && !operspy
)
400 if(server_oper
&& !SeesOper(target_p
, source_p
))
406 match(mask
, target_p
->name
) || match(mask
, target_p
->username
) ||
407 match(mask
, target_p
->host
) || match(mask
, target_p
->servptr
->name
) ||
408 (IsOperGeneral(source_p
) && match(mask
, target_p
->orighost
)) ||
409 match(mask
, target_p
->info
))
411 do_who(source_p
, target_p
, NULL
, fmt
);
419 form_str(ERR_TOOMANYMATCHES
),
420 me
.name
, source_p
->name
, "WHO");
426 * inputs - pointer to client requesting who
427 * - pointer to channel to do who on
428 * - The "real name" of this channel
429 * - int if source_p is a server oper or not
430 * - int if client is member or not
433 * side effects - do a who on given channel
436 do_who_on_channel(struct Client
*source_p
, struct Channel
*chptr
,
437 int server_oper
, int member
, struct who_format
*fmt
)
439 struct Client
*target_p
;
440 struct membership
*msptr
;
443 RB_DLINK_FOREACH(ptr
, chptr
->members
.head
)
446 target_p
= msptr
->client_p
;
448 if(server_oper
&& !SeesOper(target_p
, source_p
))
451 if(member
|| !IsInvisible(target_p
))
452 do_who(source_p
, target_p
, msptr
, fmt
);
459 * inputs - pointer to buffer
461 * - pointer to position
463 * - arguments for format
465 * side effects - position incremented, possibly beyond size of buffer
466 * this allows detecting overflow
469 append_format(char *buf
, size_t bufsize
, size_t *pos
, const char *fmt
, ...)
474 max
= *pos
>= bufsize
? 0 : bufsize
- *pos
;
476 result
= vsnprintf(buf
+ *pos
, max
, fmt
, ap
);
484 * inputs - pointer to client requesting who
485 * - pointer to client to do who on
486 * - channel membership or NULL
489 * side effects - do a who on given person
493 do_who(struct Client
*source_p
, struct Client
*target_p
, struct membership
*msptr
, struct who_format
*fmt
)
496 char str
[510 + 1]; /* linebuf.c will add \r\n */
500 sprintf(status
, "%c%s%s",
501 target_p
->user
->away
? 'G' : 'H', SeesOper(target_p
, source_p
) ? "*" : "", msptr
? find_channel_status(msptr
, fmt
->fields
|| IsCapable(source_p
, CLICAP_MULTI_PREFIX
)) : "");
503 if (fmt
->fields
== 0)
504 sendto_one(source_p
, form_str(RPL_WHOREPLY
), me
.name
,
505 source_p
->name
, msptr
? msptr
->chptr
->chname
: "*",
506 target_p
->username
, target_p
->host
,
507 target_p
->servptr
->name
, target_p
->name
, status
,
508 ConfigServerHide
.flatten_links
&& !IsOperGeneral(source_p
) && !IsExemptShide(source_p
) ? 0 : target_p
->hopcount
,
514 append_format(str
, sizeof str
, &pos
, ":%s %d %s",
515 me
.name
, RPL_WHOSPCRPL
, source_p
->name
);
516 if (fmt
->fields
& FIELD_QUERYTYPE
)
517 append_format(str
, sizeof str
, &pos
, " %s", fmt
->querytype
);
518 if (fmt
->fields
& FIELD_CHANNEL
)
519 append_format(str
, sizeof str
, &pos
, " %s", msptr
? msptr
->chptr
->chname
: "*");
520 if (fmt
->fields
& FIELD_USER
)
521 append_format(str
, sizeof str
, &pos
, " %s", target_p
->username
);
522 if (fmt
->fields
& FIELD_IP
)
524 if (show_ip(source_p
, target_p
) && !EmptyString(target_p
->sockhost
) && strcmp(target_p
->sockhost
, "0"))
525 append_format(str
, sizeof str
, &pos
, " %s", target_p
->sockhost
);
527 append_format(str
, sizeof str
, &pos
, " %s", "255.255.255.255");
529 if (fmt
->fields
& FIELD_HOST
)
530 append_format(str
, sizeof str
, &pos
, " %s", target_p
->host
);
531 if (fmt
->fields
& FIELD_SERVER
)
532 append_format(str
, sizeof str
, &pos
, " %s", target_p
->servptr
->name
);
533 if (fmt
->fields
& FIELD_NICK
)
534 append_format(str
, sizeof str
, &pos
, " %s", target_p
->name
);
535 if (fmt
->fields
& FIELD_FLAGS
)
536 append_format(str
, sizeof str
, &pos
, " %s", status
);
537 if (fmt
->fields
& FIELD_HOP
)
538 append_format(str
, sizeof str
, &pos
, " %d", ConfigServerHide
.flatten_links
&& !IsOperGeneral(source_p
) && !IsExemptShide(source_p
) ? 0 : target_p
->hopcount
);
539 if (fmt
->fields
& FIELD_IDLE
)
541 /* fire the doing_who_show_idle hook to allow modules to tell us whether to show the idle time */
542 hook_data_client_approval hdata_showidle
;
544 hdata_showidle
.client
= source_p
;
545 hdata_showidle
.target
= target_p
;
546 hdata_showidle
.approved
= WHOIS_IDLE_SHOW
;
548 call_hook(doing_who_show_idle_hook
, &hdata_showidle
);
550 append_format(str
, sizeof str
, &pos
, " %d",
551 hdata_showidle
.approved
? (int)(MyClient(target_p
) ? rb_current_time() - target_p
->localClient
->last
: 0) : 0);
553 if (fmt
->fields
& FIELD_ACCOUNT
)
555 /* display as in whois */
556 q
= target_p
->user
->suser
;
562 q
= target_p
->user
->suser
;
566 append_format(str
, sizeof str
, &pos
, " %s", q
);
568 if (fmt
->fields
& FIELD_OPLEVEL
)
569 append_format(str
, sizeof str
, &pos
, " %s", is_chanop(msptr
) ? "999" : "n/a");
570 if (fmt
->fields
& FIELD_INFO
)
571 append_format(str
, sizeof str
, &pos
, " :%s", target_p
->info
);
573 if (pos
>= sizeof str
)
575 static bool warned
= false;
577 sendto_realops_snomask(SNO_DEBUG
, L_NETWIDE
,
578 "WHOX overflow while sending information about %s to %s",
579 target_p
->name
, source_p
->name
);
582 sendto_one(source_p
, "%s", str
);