3 * IRC - Internet Relay Chat, ircd/s_user.c (formerly ircd/s_msg.c)
4 * Copyright (C) 1990 Jarkko Oikarinen and
5 * University of Oulu, Computing Center
7 * See file AUTHORS in IRC package for additional names of
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 1, or (at your option)
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
63 #include "sprintf_irc.h"
64 #include "querycmds.h"
71 * m_who with support routines rewritten by Nemesi, August 1997
72 * - Alghoritm have been flattened (no more recursive)
74 * - Strong performance improvement
75 * - Added possibility to have specific fields in the output
76 * See readme.who for further details.
79 /* Macros used only in here by m_who and its support functions */
81 #define WHOSELECT_OPER 1
82 #define WHOSELECT_EXTRA 2
84 #define WHO_FIELD_QTY 1
85 #define WHO_FIELD_CHA 2
86 #define WHO_FIELD_UID 4
87 #define WHO_FIELD_NIP 8
88 #define WHO_FIELD_HOS 16
89 #define WHO_FIELD_SER 32
90 #define WHO_FIELD_NIC 64
91 #define WHO_FIELD_FLA 128
92 #define WHO_FIELD_DIS 256
93 #define WHO_FIELD_REN 512
95 #define WHO_FIELD_DEF ( WHO_FIELD_NIC | WHO_FIELD_UID | WHO_FIELD_HOS | WHO_FIELD_SER )
97 #define IS_VISIBLE_USER(s,ac) ((s==ac) || (!IsInvisible(ac)))
99 #if defined(SHOW_INVISIBLE_LUSERS) || defined(SHOW_ALL_INVISIBLE_USERS)
100 #define SEE_LUSER(s, ac, b) (IS_VISIBLE_USER(s, ac) || ((b & WHOSELECT_EXTRA) && MyConnect(ac) && IsAnOper(s)))
102 #define SEE_LUSER(s, ac, b) (IS_VISIBLE_USER(s, ac))
105 #ifdef SHOW_ALL_INVISIBLE_USERS
106 #define SEE_USER(s, ac, b) (SEE_LUSER(s, ac, b) || ((b & WHOSELECT_EXTRA) && IsOper(s)))
108 #define SEE_USER(s, ac, b) (SEE_LUSER(s, ac, b))
111 #ifdef UNLIMIT_OPER_QUERY
112 #define SHOW_MORE(sptr, counter) (IsAnOper(sptr) || (!(counter-- < 0)) )
114 #define SHOW_MORE(sptr, counter) (!(counter-- < 0))
117 #ifdef OPERS_SEE_IN_SECRET_CHANNELS
118 #ifdef LOCOP_SEE_IN_SECRET_CHANNELS
119 #define SEE_CHANNEL(s, chptr, b) (!SecretChannel(chptr) || ((b & WHOSELECT_EXTRA) && IsAnOper(s)))
121 #define SEE_CHANNEL(s, chptr, b) (!SecretChannel(chptr) || ((b & WHOSELECT_EXTRA) && IsOper(s)))
124 #define SEE_CHANNEL(s, chptr, b) (!SecretChannel(chptr))
129 * A little spin-marking utility to tell us wich clients we have already
130 * processed and wich not
132 static int who_marker = 0;
133 static void move_marker(void)
137 aClient *cptr = client;
146 #define CheckMark(x, y) ((x == y) ? 0 : (x = y))
147 #define Process(cptr) CheckMark(cptr->marker, who_marker)
150 * The function that actually prints out the WHO reply for a client found
152 static void do_who(aClient *sptr, aClient *acptr, aChannel *repchan,
153 int fields, char *qrt)
156 Reg2 aChannel *chptr;
158 static char buf1[512];
159 /* NOTE: with current fields list and sizes this _cannot_ overrun,
160 and also the message finally sent shouldn't ever be truncated */
166 /* If we don't have a channel and we need one... try to find it,
167 unless the listing is for a channel service, we already know
168 that there are no common channels, thus use PubChannel and not
170 if (!chptr && (!fields || (fields & (WHO_FIELD_CHA | WHO_FIELD_FLA))) &&
171 !IsChannelService(acptr))
174 for (lp = acptr->user->channel; lp && !chptr; lp = lp->next)
175 if (PubChannel(lp->value.chptr) &&
176 (acptr == sptr || !is_zombie(acptr, chptr)))
177 chptr = lp->value.chptr;
180 /* Place the fields one by one in the buffer and send it
181 note that fields == NULL means "default query" */
183 if (fields & WHO_FIELD_QTY) /* Query type */
189 while ((*qrt) && (*(p1++) = *(qrt++)));
192 if (!fields || (fields & WHO_FIELD_CHA))
196 if ((p2 = (chptr ? chptr->chname : NULL)))
197 while ((*p2) && (*(p1++) = *(p2++)));
202 if (!fields || (fields & WHO_FIELD_UID))
204 Reg3 char *p2 = acptr->user->username;
206 while ((*p2) && (*(p1++) = *(p2++)));
209 if (fields & WHO_FIELD_NIP)
212 p2 = inetntoa(acptr->ip);
214 while ((*p2) && (*(p1++) = *(p2++)));
217 if (!fields || (fields & WHO_FIELD_HOS))
219 Reg3 char *p2 = acptr->user->host;
221 while ((*p2) && (*(p1++) = *(p2++)));
224 if (!fields || (fields & WHO_FIELD_SER))
226 Reg3 char *p2 = acptr->user->server->name;
228 while ((*p2) && (*(p1++) = *(p2++)));
231 if (!fields || (fields & WHO_FIELD_NIC))
233 Reg3 char *p2 = acptr->name;
235 while ((*p2) && (*(p1++) = *(p2++)));
238 if (!fields || (fields & WHO_FIELD_FLA))
241 if (acptr->user->away)
247 if (chptr && is_chan_op(acptr, chptr))
249 else if (chptr && has_voice(acptr, chptr))
251 else if (chptr && is_zombie(acptr, chptr))
257 if (IsInvisible(acptr))
259 if (SendWallops(acptr))
261 if (SendDebug(acptr))
266 if (!fields || (fields & WHO_FIELD_DIS))
270 *p1++ = ':'; /* Place colon here for default reply */
271 p1 = sprintf_irc(p1, "%d", acptr->hopcount);
274 if (!fields || (fields & WHO_FIELD_REN))
276 Reg3 char *p2 = acptr->info;
279 *p1++ = ':'; /* Place colon here for special reply */
280 while ((*p2) && (*(p1++) = *(p2++)));
283 /* The first char will always be an useless blank and we
284 need to terminate buf1 */
287 sendto_one(sptr, rpl_str(fields ? RPL_WHOSPCRPL : RPL_WHOREPLY),
288 me.name, sptr->name, ++p1);
294 * parv[0] = sender prefix
295 * parv[1] = nickname mask list
296 * parv[2] = additional selection flag, only 'o' for now.
297 * and %flags to specify what fields to output
298 * plus a ,querytype if the t flag is specified
299 * so the final thing will be like o%tnchu,777
300 * parv[3] = _optional_ parameter that overrides parv[1]
301 * This can be used as "/quote who foo % :The Black Hacker
302 * to find me, parv[3] _can_ contain spaces !.
305 int m_who(aClient *UNUSED(cptr), aClient *sptr, int parc, char *parv[])
307 Reg1 char *mask; /* The mask we are looking for */
308 Reg2 char ch; /* Scratch char register */
310 Reg4 aChannel *chptr; /* Channel to show */
311 Reg5 aClient *acptr; /* Client to show */
313 int bitsel; /* Mask of selectors to apply */
314 int matchsel; /* Wich fields the match should apply on */
315 int counter; /* Query size counter,
316 initially used to count fields */
317 int commas; /* Does our mask contain any comma ?
319 int fields; /* Mask of fields to show */
320 int isthere = 0; /* When this set the user is member of chptr */
321 char *nick; /* Single element extracted from
323 char *p; /* Scratch char pointer */
324 char *qrt; /* Pointer to the query type */
325 static char mymask[512]; /* To save the mask before corrupting it */
327 /* Let's find where is our mask, and if actually contains something */
328 mask = ((parc > 1) ? parv[1] : NULL);
329 if (parc > 3 && parv[3])
331 if (mask && ((mask[0] == '\0') ||
332 (mask[1] == '\0' && ((mask[0] == '0') || (mask[0] == '*')))))
335 /* Evaluate the flags now, we consider the second parameter
336 as "matchFlags%fieldsToInclude,querytype" */
337 bitsel = fields = counter = matchsel = 0;
339 if (parc > 2 && parv[2] && *parv[2])
342 while (((ch = *(p++))) && (ch != '%') && (ch != ','))
347 bitsel |= WHOSELECT_OPER;
351 bitsel |= WHOSELECT_EXTRA;
354 write_log(WPATH, "# " TIME_T_FMT " %s!%s@%s WHO %s %s\n",
355 now, sptr->name, sptr->user->username, sptr->user->host,
356 (BadPtr(parv[3]) ? parv[1] : parv[3]), parv[2]);
361 matchsel |= WHO_FIELD_NIC;
365 matchsel |= WHO_FIELD_UID;
369 matchsel |= WHO_FIELD_HOS;
373 matchsel |= WHO_FIELD_NIP;
377 matchsel |= WHO_FIELD_SER;
381 matchsel |= WHO_FIELD_REN;
385 while ((ch = *p++) && (ch != ','))
392 fields |= WHO_FIELD_CHA;
396 fields |= WHO_FIELD_DIS;
400 fields |= WHO_FIELD_FLA;
404 fields |= WHO_FIELD_HOS;
408 fields |= WHO_FIELD_NIP;
412 fields |= WHO_FIELD_NIC;
416 fields |= WHO_FIELD_REN;
420 fields |= WHO_FIELD_SER;
424 fields |= WHO_FIELD_QTY;
428 fields |= WHO_FIELD_UID;
439 matchsel = WHO_FIELD_DEF;
443 if (qrt && (fields & WHO_FIELD_QTY))
446 if (!((*p > '9') || (*p < '0')))
448 if (!((*p > '9') || (*p < '0')))
450 if (!((*p > '9') || (*p < '0')))
457 /* I'd love to add also a check on the number of matches fields per time */
458 counter = (2048 / (counter + 4));
459 if (mask && (strlen(mask) > 510))
462 commas = (mask && strchr(mask, ','));
464 /* First treat mask as a list of plain nicks/channels */
467 strcpy(mymask, mask);
468 for (p = NULL, nick = strtoken(&p, mymask, ","); nick;
469 nick = strtoken(&p, NULL, ","))
471 if (IsChannelName(nick) && (chptr = FindChannel(nick)))
473 isthere = (IsMember(sptr, chptr) != NULL);
474 if (isthere || SEE_CHANNEL(sptr, chptr, bitsel))
476 for (lp = chptr->members; lp; lp = lp->next)
478 acptr = lp->value.cptr;
479 if ((bitsel & WHOSELECT_OPER) && !(IsAnOper(acptr)))
481 if ((acptr != sptr) && (lp->flags & CHFL_ZOMBIE))
483 if (!(isthere || (SEE_USER(sptr, acptr, bitsel))))
485 if (!Process(acptr)) /* This can't be moved before other checks */
487 if (!(isthere || (SHOW_MORE(sptr, counter))))
489 do_who(sptr, acptr, chptr, fields, qrt);
495 if ((acptr = FindUser(nick)) &&
496 ((!(bitsel & WHOSELECT_OPER)) || IsAnOper(acptr)) &&
497 Process(acptr) && SHOW_MORE(sptr, counter))
499 do_who(sptr, acptr, NULL, fields, qrt);
505 /* If we didn't have any comma in the mask treat it as a
506 real mask and try to match all relevant fields */
507 if (!(commas || (counter < 1)))
510 static struct in_mask imask;
513 matchcomp(mymask, &minlen, &cset, mask);
514 if (matchcompIP(&imask, mask))
515 matchsel &= ~WHO_FIELD_NIP;
516 if ((minlen > NICKLEN) || !(cset & NTL_IRCNK))
517 matchsel &= ~WHO_FIELD_NIC;
518 if ((matchsel & WHO_FIELD_SER) &&
519 ((minlen > HOSTLEN) || (!(cset & NTL_IRCHN))
520 || (!markMatchexServer(mymask, minlen))))
521 matchsel &= ~WHO_FIELD_SER;
522 if ((minlen > USERLEN) || !(cset & NTL_IRCUI))
523 matchsel &= ~WHO_FIELD_UID;
524 if ((minlen > HOSTLEN) || !(cset & NTL_IRCHN))
525 matchsel &= ~WHO_FIELD_HOS;
528 /* First of all loop through the clients in common channels */
529 if ((!(counter < 1)) && matchsel)
530 for (lp = sptr->user->channel; lp; lp = lp->next)
531 for (chptr = lp->value.chptr, lp2 = chptr->members; lp2;
534 acptr = lp2->value.cptr;
535 if (!(IsUser(acptr) && Process(acptr)))
536 continue; /* Now Process() is at the beginning, if we fail
537 we'll never have to show this acptr in this query */
538 if ((bitsel & WHOSELECT_OPER) && !IsAnOper(acptr))
541 ((!(matchsel & WHO_FIELD_NIC))
542 || matchexec(acptr->name, mymask, minlen))
543 && ((!(matchsel & WHO_FIELD_UID))
544 || matchexec(acptr->user->username, mymask, minlen))
545 && ((!(matchsel & WHO_FIELD_SER))
546 || (!(acptr->user->server->flags & FLAGS_MAP)))
547 && ((!(matchsel & WHO_FIELD_HOS))
548 || matchexec(acptr->user->host, mymask, minlen))
549 && ((!(matchsel & WHO_FIELD_REN))
550 || matchexec(acptr->info, mymask, minlen))
551 && ((!(matchsel & WHO_FIELD_NIP))
552 || ((((acptr->ip.s_addr & imask.mask.s_addr) !=
553 imask.bits.s_addr)) || (imask.fall
554 && matchexec(inet_ntoa(acptr->ip), mymask, minlen)))))
556 if (!SHOW_MORE(sptr, counter))
558 do_who(sptr, acptr, chptr, fields, qrt);
561 /* Loop through all clients :-\, if we still have something to match to
562 and we can show more clients */
563 if ((!(counter < 1)) && matchsel)
564 for (acptr = me.prev; acptr; acptr = acptr->prev)
566 if (!(IsUser(acptr) && Process(acptr)))
568 if ((bitsel & WHOSELECT_OPER) && !IsAnOper(acptr))
570 if (!(SEE_USER(sptr, acptr, bitsel)))
573 ((!(matchsel & WHO_FIELD_NIC))
574 || matchexec(acptr->name, mymask, minlen))
575 && ((!(matchsel & WHO_FIELD_UID))
576 || matchexec(acptr->user->username, mymask, minlen))
577 && ((!(matchsel & WHO_FIELD_SER))
578 || (!(acptr->user->server->flags & FLAGS_MAP)))
579 && ((!(matchsel & WHO_FIELD_HOS))
580 || matchexec(acptr->user->host, mymask, minlen))
581 && ((!(matchsel & WHO_FIELD_REN))
582 || matchexec(acptr->info, mymask, minlen))
583 && ((!(matchsel & WHO_FIELD_NIP))
584 || ((((acptr->ip.s_addr & imask.mask.s_addr) != imask.bits.s_addr))
586 && matchexec(inet_ntoa(acptr->ip), mymask, minlen)))))
588 if (!SHOW_MORE(sptr, counter))
590 do_who(sptr, acptr, NULL, fields, qrt);
594 /* Make a clean mask suitable to be sent in the "end of" */
595 if (mask && (p = strchr(mask, ' ')))
597 sendto_one(sptr, rpl_str(RPL_ENDOFWHO),
598 me.name, parv[0], BadPtr(mask) ? "*" : mask);
600 /* Notify the user if we decided that his query was too long */
602 sendto_one(sptr, err_str(ERR_QUERYTOOLONG), me.name, parv[0], "WHO");
607 #define MAX_WHOIS_LINES 50
612 * parv[0] = sender prefix
613 * parv[1] = nickname masklist
617 * parv[1] = target server
618 * parv[2] = nickname masklist
620 int m_whois(aClient *cptr, aClient *sptr, int parc, char *parv[])
624 aClient *acptr, *a2cptr;
626 char *nick, *tmp, *name;
628 int found, len, mlen, total;
629 static char buf[512];
633 sendto_one(sptr, err_str(ERR_NONICKNAMEGIVEN), me.name, parv[0]);
640 /* For convenience: Accept a nickname as first parameter, by replacing
641 it with the correct servername - as is needed by hunt_server() */
642 if (MyUser(sptr) && (acptr = FindUser(parv[1])))
643 parv[1] = acptr->user->server->name;
644 if (hunt_server(0, cptr, sptr, ":%s WHOIS %s :%s", 1, parc, parv) !=
651 for (tmp = parv[1]; (nick = strtoken(&p, tmp, ",")); tmp = NULL)
653 int invis, showperson, member, wilds;
657 wilds = (strchr(nick, '?') || strchr(nick, '*'));
658 /* Do a hash lookup if the nick does not contain wilds */
662 * We're no longer allowing remote users to generate requests with wildcards.
664 if (!MyConnect(sptr))
666 for (acptr = client; (acptr = next_client(acptr, nick));
669 if (!IsRegistered(acptr) || IsServer(acptr) || IsPing(acptr))
672 * I'm always last :-) and acptr->next == NULL!!
677 * 'Rules' established for sending a WHOIS reply:
679 * - if wildcards are being used dont send a reply if
680 * the querier isnt any common channels and the
681 * client in question is invisible and wildcards are
682 * in use (allow exact matches only);
684 * - only send replies about common or public channels
685 * the target user(s) are on;
688 name = (!*acptr->name) ? "?" : acptr->name;
690 invis = acptr != sptr && IsInvisible(acptr);
691 member = (user && user->channel) ? 1 : 0;
692 showperson = (wilds && !invis && !member) || !wilds;
694 for (lp = user->channel; lp; lp = lp->next)
696 chptr = lp->value.chptr;
697 member = IsMember(sptr, chptr) ? 1 : 0;
698 if (invis && !member)
700 if (is_zombie(acptr, chptr))
702 if (member || (!invis && PubChannel(chptr)))
707 if (!invis && HiddenChannel(chptr) && !SecretChannel(chptr))
715 a2cptr = user->server;
716 sendto_one(sptr, rpl_str(RPL_WHOISUSER), me.name,
717 parv[0], name, user->username, user->host, acptr->info);
722 sendto_one(sptr, rpl_str(RPL_WHOISUSER), me.name,
723 parv[0], name, "<unknown>", "<unknown>", "<unknown>");
729 if (user && !IsChannelService(acptr))
731 mlen = strlen(me.name) + strlen(parv[0]) + 12 + strlen(name);
732 for (len = 0, *buf = '\0', lp = user->channel; lp; lp = lp->next)
734 chptr = lp->value.chptr;
735 if (ShowChannel(sptr, chptr) &&
736 (acptr == sptr || !is_zombie(acptr, chptr)))
738 if (len + strlen(chptr->chname) + mlen > BUFSIZE - 5)
740 sendto_one(sptr, ":%s %d %s %s :%s",
741 me.name, RPL_WHOISCHANNELS, parv[0], name, buf);
746 *(buf + len++) = '-';
747 if (is_chan_op(acptr, chptr))
748 *(buf + len++) = '@';
749 else if (has_voice(acptr, chptr))
750 *(buf + len++) = '+';
751 else if (is_zombie(acptr, chptr))
752 *(buf + len++) = '!';
755 strcpy(buf + len, chptr->chname);
756 len += strlen(chptr->chname);
757 strcat(buf + len, " ");
762 sendto_one(sptr, rpl_str(RPL_WHOISCHANNELS),
763 me.name, parv[0], name, buf);
766 sendto_one(sptr, rpl_str(RPL_WHOISSERVER), me.name,
767 parv[0], name, a2cptr->name, a2cptr->info);
772 sendto_one(sptr, rpl_str(RPL_AWAY), me.name,
773 parv[0], name, user->away);
776 sendto_one(sptr, rpl_str(RPL_WHOISOPERATOR),
777 me.name, parv[0], name);
779 if (MyConnect(acptr))
780 sendto_one(sptr, rpl_str(RPL_WHOISIDLE), me.name,
781 parv[0], name, now - user->last, acptr->firsttime);
783 if (found == 2 || total++ >= MAX_WHOIS_LINES)
790 if ((acptr = FindUser(nick)))
792 found = 2; /* Make sure we exit the loop after passing it once */
794 name = (!*acptr->name) ? "?" : acptr->name;
795 a2cptr = user->server;
796 sendto_one(sptr, rpl_str(RPL_WHOISUSER), me.name,
797 parv[0], name, user->username, user->host, acptr->info);
802 sendto_one(sptr, err_str(ERR_NOSUCHNICK), me.name, parv[0], nick);
805 if (!MyConnect(sptr) || total >= MAX_WHOIS_LINES)
808 sendto_one(sptr, rpl_str(RPL_ENDOFWHOIS), me.name, parv[0], parv[1]);