This commit was generated by cvs2svn to compensate for changes in r2,
[ircu2.10.12-pk.git] / ircd / whocmds.c
1
2 /*
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
6  *
7  * See file AUTHORS in IRC package for additional names of
8  * the programmers.
9  *
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)
13  * any later version.
14  *
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.
19  *
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.
23  */
24
25 #include "sys.h"
26 #include <sys/stat.h>
27 #include <utmp.h>
28 #if HAVE_FCNTL_H
29 #include <fcntl.h>
30 #endif
31 #include <stdlib.h>
32 #if HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 #ifdef USE_SYSLOG
36 #include <syslog.h>
37 #endif
38 #include "h.h"
39 #include "struct.h"
40 #include "common.h"
41 #include "s_serv.h"
42 #include "numeric.h"
43 #include "send.h"
44 #include "s_conf.h"
45 #include "s_misc.h"
46 #include "match.h"
47 #include "hash.h"
48 #include "s_bsd.h"
49 #include "whowas.h"
50 #include "list.h"
51 #include "s_err.h"
52 #include "parse.h"
53 #include "ircd.h"
54 #include "s_user.h"
55 #include "support.h"
56 #include "s_user.h"
57 #include "channel.h"
58 #include "random.h"
59 #include "version.h"
60 #include "msg.h"
61 #include "userload.h"
62 #include "numnicks.h"
63 #include "sprintf_irc.h"
64 #include "querycmds.h"
65 #include "IPcheck.h"
66
67 RCSTAG_CC("$Id$");
68
69 /*
70  * m_who() 
71  * m_who with support routines rewritten by Nemesi, August 1997
72  * - Alghoritm have been flattened (no more recursive)
73  * - Several bug fixes
74  * - Strong performance improvement
75  * - Added possibility to have specific fields in the output
76  * See readme.who for further details.
77  */
78
79 /* Macros used only in here by m_who and its support functions */
80
81 #define WHOSELECT_OPER 1
82 #define WHOSELECT_EXTRA 2
83
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
94
95 #define WHO_FIELD_DEF ( WHO_FIELD_NIC | WHO_FIELD_UID | WHO_FIELD_HOS | WHO_FIELD_SER )
96
97 #define IS_VISIBLE_USER(s,ac) ((s==ac) || (!IsInvisible(ac)))
98
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)))
101 #else
102 #define SEE_LUSER(s, ac, b) (IS_VISIBLE_USER(s, ac))
103 #endif
104
105 #ifdef SHOW_ALL_INVISIBLE_USERS
106 #define SEE_USER(s, ac, b) (SEE_LUSER(s, ac, b) || ((b & WHOSELECT_EXTRA) && IsOper(s)))
107 #else
108 #define SEE_USER(s, ac, b) (SEE_LUSER(s, ac, b))
109 #endif
110
111 #ifdef UNLIMIT_OPER_QUERY
112 #define SHOW_MORE(sptr, counter) (IsAnOper(sptr) || (!(counter-- < 0)) )
113 #else
114 #define SHOW_MORE(sptr, counter) (!(counter-- < 0))
115 #endif
116
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)))
120 #else
121 #define SEE_CHANNEL(s, chptr, b) (!SecretChannel(chptr) || ((b & WHOSELECT_EXTRA) && IsOper(s)))
122 #endif
123 #else
124 #define SEE_CHANNEL(s, chptr, b) (!SecretChannel(chptr))
125 #endif
126
127
128 /*
129  * A little spin-marking utility to tell us wich clients we have already
130  * processed and wich not
131  */
132 static int who_marker = 0;
133 static void move_marker(void)
134 {
135   if (!++who_marker)
136   {
137     aClient *cptr = client;
138     while (cptr)
139     {
140       cptr->marker = 0;
141       cptr = cptr->next;
142     }
143     who_marker++;
144   }
145 }
146 #define CheckMark(x, y) ((x == y) ? 0 : (x = y))
147 #define Process(cptr) CheckMark(cptr->marker, who_marker)
148
149 /*
150  * The function that actually prints out the WHO reply for a client found
151  */
152 static void do_who(aClient *sptr, aClient *acptr, aChannel *repchan,
153     int fields, char *qrt)
154 {
155   Reg1 char *p1;
156   Reg2 aChannel *chptr;
157
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 */
161
162   p1 = buf1;
163   chptr = repchan;
164   buf1[1] = '\0';
165
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
169      SeeChannel */
170   if (!chptr && (!fields || (fields & (WHO_FIELD_CHA | WHO_FIELD_FLA))) &&
171       !IsChannelService(acptr))
172   {
173     Reg3 Link *lp;
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;
178   }
179
180   /* Place the fields one by one in the buffer and send it
181      note that fields == NULL means "default query" */
182
183   if (fields & WHO_FIELD_QTY)   /* Query type */
184   {
185     *(p1++) = ' ';
186     if (BadPtr(qrt))
187       *(p1++) = '0';
188     else
189       while ((*qrt) && (*(p1++) = *(qrt++)));
190   }
191
192   if (!fields || (fields & WHO_FIELD_CHA))
193   {
194     Reg3 char *p2;
195     *(p1++) = ' ';
196     if ((p2 = (chptr ? chptr->chname : NULL)))
197       while ((*p2) && (*(p1++) = *(p2++)));
198     else
199       *(p1++) = '*';
200   }
201
202   if (!fields || (fields & WHO_FIELD_UID))
203   {
204     Reg3 char *p2 = acptr->user->username;
205     *(p1++) = ' ';
206     while ((*p2) && (*(p1++) = *(p2++)));
207   }
208
209   if (fields & WHO_FIELD_NIP)
210   {
211     Reg3 char *p2;
212     p2 = inetntoa(acptr->ip);
213     *(p1++) = ' ';
214     while ((*p2) && (*(p1++) = *(p2++)));
215   }
216
217   if (!fields || (fields & WHO_FIELD_HOS))
218   {
219     Reg3 char *p2 = acptr->user->host;
220     *(p1++) = ' ';
221     while ((*p2) && (*(p1++) = *(p2++)));
222   }
223
224   if (!fields || (fields & WHO_FIELD_SER))
225   {
226     Reg3 char *p2 = acptr->user->server->name;
227     *(p1++) = ' ';
228     while ((*p2) && (*(p1++) = *(p2++)));
229   }
230
231   if (!fields || (fields & WHO_FIELD_NIC))
232   {
233     Reg3 char *p2 = acptr->name;
234     *(p1++) = ' ';
235     while ((*p2) && (*(p1++) = *(p2++)));
236   }
237
238   if (!fields || (fields & WHO_FIELD_FLA))
239   {
240     *(p1++) = ' ';
241     if (acptr->user->away)
242       *(p1++) = 'G';
243     else
244       *(p1++) = 'H';
245     if (IsAnOper(acptr))
246       *(p1++) = '*';
247     if (chptr && is_chan_op(acptr, chptr))
248       *(p1++) = '@';
249     else if (chptr && has_voice(acptr, chptr))
250       *(p1++) = '+';
251     else if (chptr && is_zombie(acptr, chptr))
252       *(p1++) = '!';
253     if (IsDeaf(acptr))
254       *(p1++) = 'd';
255     if (IsAnOper(sptr))
256     {
257       if (IsInvisible(acptr))
258         *(p1++) = 'i';
259       if (SendWallops(acptr))
260         *(p1++) = 'w';
261       if (SendDebug(acptr))
262         *(p1++) = 'g';
263     }
264   }
265
266   if (!fields || (fields & WHO_FIELD_DIS))
267   {
268     *p1++ = ' ';
269     if (!fields)
270       *p1++ = ':';              /* Place colon here for default reply */
271     p1 = sprintf_irc(p1, "%d", acptr->hopcount);
272   }
273
274   if (!fields || (fields & WHO_FIELD_REN))
275   {
276     Reg3 char *p2 = acptr->info;
277     *p1++ = ' ';
278     if (fields)
279       *p1++ = ':';              /* Place colon here for special reply */
280     while ((*p2) && (*(p1++) = *(p2++)));
281   }
282
283   /* The first char will always be an useless blank and we 
284      need to terminate buf1 */
285   *p1 = '\0';
286   p1 = buf1;
287   sendto_one(sptr, rpl_str(fields ? RPL_WHOSPCRPL : RPL_WHOREPLY),
288       me.name, sptr->name, ++p1);
289 }
290
291 /*
292  *  m_who
293  *
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 !.
303  */
304
305 int m_who(aClient *UNUSED(cptr), aClient *sptr, int parc, char *parv[])
306 {
307   Reg1 char *mask;              /* The mask we are looking for              */
308   Reg2 char ch;                 /* Scratch char register                    */
309   Reg3 Link *lp, *lp2;
310   Reg4 aChannel *chptr;         /* Channel to show                          */
311   Reg5 aClient *acptr;          /* Client to show                           */
312
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 ?
318                                    If so is a list..                        */
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
322                                    the mask list                            */
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    */
326
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])
330     mask = parv[3];
331   if (mask && ((mask[0] == '\0') ||
332       (mask[1] == '\0' && ((mask[0] == '0') || (mask[0] == '*')))))
333     mask = NULL;
334
335   /* Evaluate the flags now, we consider the second parameter 
336      as "matchFlags%fieldsToInclude,querytype"           */
337   bitsel = fields = counter = matchsel = 0;
338   qrt = NULL;
339   if (parc > 2 && parv[2] && *parv[2])
340   {
341     p = parv[2];
342     while (((ch = *(p++))) && (ch != '%') && (ch != ','))
343       switch (ch)
344       {
345         case 'o':
346         case 'O':
347           bitsel |= WHOSELECT_OPER;
348           continue;
349         case 'x':
350         case 'X':
351           bitsel |= WHOSELECT_EXTRA;
352 #ifdef WPATH
353           if (IsAnOper(sptr))
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]);
357 #endif /* WPATH */
358           continue;
359         case 'n':
360         case 'N':
361           matchsel |= WHO_FIELD_NIC;
362           continue;
363         case 'u':
364         case 'U':
365           matchsel |= WHO_FIELD_UID;
366           continue;
367         case 'h':
368         case 'H':
369           matchsel |= WHO_FIELD_HOS;
370           continue;
371         case 'i':
372         case 'I':
373           matchsel |= WHO_FIELD_NIP;
374           continue;
375         case 's':
376         case 'S':
377           matchsel |= WHO_FIELD_SER;
378           continue;
379         case 'r':
380         case 'R':
381           matchsel |= WHO_FIELD_REN;
382           continue;
383       }
384     if (ch == '%')
385       while ((ch = *p++) && (ch != ','))
386       {
387         counter++;
388         switch (ch)
389         {
390           case 'c':
391           case 'C':
392             fields |= WHO_FIELD_CHA;
393             break;
394           case 'd':
395           case 'D':
396             fields |= WHO_FIELD_DIS;
397             break;
398           case 'f':
399           case 'F':
400             fields |= WHO_FIELD_FLA;
401             break;
402           case 'h':
403           case 'H':
404             fields |= WHO_FIELD_HOS;
405             break;
406           case 'i':
407           case 'I':
408             fields |= WHO_FIELD_NIP;
409             break;
410           case 'n':
411           case 'N':
412             fields |= WHO_FIELD_NIC;
413             break;
414           case 'r':
415           case 'R':
416             fields |= WHO_FIELD_REN;
417             break;
418           case 's':
419           case 'S':
420             fields |= WHO_FIELD_SER;
421             break;
422           case 't':
423           case 'T':
424             fields |= WHO_FIELD_QTY;
425             break;
426           case 'u':
427           case 'U':
428             fields |= WHO_FIELD_UID;
429             break;
430           default:
431             break;
432         }
433       };
434     if (ch)
435       qrt = p;
436   }
437
438   if (!matchsel)
439     matchsel = WHO_FIELD_DEF;
440   if (!fields)
441     counter = 7;
442
443   if (qrt && (fields & WHO_FIELD_QTY))
444   {
445     p = qrt;
446     if (!((*p > '9') || (*p < '0')))
447       p++;
448     if (!((*p > '9') || (*p < '0')))
449       p++;
450     if (!((*p > '9') || (*p < '0')))
451       p++;
452     *p = '\0';
453   }
454   else
455     qrt = NULL;
456
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))
460     mask[510] = '\0';
461   move_marker();
462   commas = (mask && strchr(mask, ','));
463
464   /* First treat mask as a list of plain nicks/channels */
465   if (mask)
466   {
467     strcpy(mymask, mask);
468     for (p = NULL, nick = strtoken(&p, mymask, ","); nick;
469         nick = strtoken(&p, NULL, ","))
470     {
471       if (IsChannelName(nick) && (chptr = FindChannel(nick)))
472       {
473         isthere = (IsMember(sptr, chptr) != NULL);
474         if (isthere || SEE_CHANNEL(sptr, chptr, bitsel))
475         {
476           for (lp = chptr->members; lp; lp = lp->next)
477           {
478             acptr = lp->value.cptr;
479             if ((bitsel & WHOSELECT_OPER) && !(IsAnOper(acptr)))
480               continue;
481             if ((acptr != sptr) && (lp->flags & CHFL_ZOMBIE))
482               continue;
483             if (!(isthere || (SEE_USER(sptr, acptr, bitsel))))
484               continue;
485             if (!Process(acptr))        /* This can't be moved before other checks */
486               continue;
487             if (!(isthere || (SHOW_MORE(sptr, counter))))
488               break;
489             do_who(sptr, acptr, chptr, fields, qrt);
490           }
491         }
492       }
493       else
494       {
495         if ((acptr = FindUser(nick)) &&
496             ((!(bitsel & WHOSELECT_OPER)) || IsAnOper(acptr)) &&
497             Process(acptr) && SHOW_MORE(sptr, counter))
498         {
499           do_who(sptr, acptr, NULL, fields, qrt);
500         }
501       }
502     }
503   }
504
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)))
508   {
509     int minlen, cset;
510     static struct in_mask imask;
511     if (mask)
512     {
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;
526     }
527
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;
532             lp2 = lp2->next)
533         {
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))
539             continue;
540           if ((mask) &&
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)))))
555             continue;
556           if (!SHOW_MORE(sptr, counter))
557             break;
558           do_who(sptr, acptr, chptr, fields, qrt);
559         }
560
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)
565       {
566         if (!(IsUser(acptr) && Process(acptr)))
567           continue;
568         if ((bitsel & WHOSELECT_OPER) && !IsAnOper(acptr))
569           continue;
570         if (!(SEE_USER(sptr, acptr, bitsel)))
571           continue;
572         if ((mask) &&
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))
585             || (imask.fall
586             && matchexec(inet_ntoa(acptr->ip), mymask, minlen)))))
587           continue;
588         if (!SHOW_MORE(sptr, counter))
589           break;
590         do_who(sptr, acptr, NULL, fields, qrt);
591       }
592   }
593
594   /* Make a clean mask suitable to be sent in the "end of" */
595   if (mask && (p = strchr(mask, ' ')))
596     *p = '\0';
597   sendto_one(sptr, rpl_str(RPL_ENDOFWHO),
598       me.name, parv[0], BadPtr(mask) ? "*" : mask);
599
600   /* Notify the user if we decided that his query was too long */
601   if (counter < 0)
602     sendto_one(sptr, err_str(ERR_QUERYTOOLONG), me.name, parv[0], "WHO");
603
604   return 0;
605 }
606
607 #define MAX_WHOIS_LINES 50
608
609 /*
610  * m_whois
611  *
612  * parv[0] = sender prefix
613  * parv[1] = nickname masklist
614  *
615  * or
616  *
617  * parv[1] = target server
618  * parv[2] = nickname masklist
619  */
620 int m_whois(aClient *cptr, aClient *sptr, int parc, char *parv[])
621 {
622   Reg2 Link *lp;
623   Reg3 anUser *user;
624   aClient *acptr, *a2cptr;
625   aChannel *chptr;
626   char *nick, *tmp, *name;
627   char *p = NULL;
628   int found, len, mlen, total;
629   static char buf[512];
630
631   if (parc < 2)
632   {
633     sendto_one(sptr, err_str(ERR_NONICKNAMEGIVEN), me.name, parv[0]);
634     return 0;
635   }
636
637   if (parc > 2)
638   {
639     aClient *acptr;
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) !=
645         HUNTED_ISME)
646       return 0;
647     parv[1] = parv[2];
648   }
649
650   total = 0;
651   for (tmp = parv[1]; (nick = strtoken(&p, tmp, ",")); tmp = NULL)
652   {
653     int invis, showperson, member, wilds;
654
655     found = 0;
656     collapse(nick);
657     wilds = (strchr(nick, '?') || strchr(nick, '*'));
658     /* Do a hash lookup if the nick does not contain wilds */
659     if (wilds)
660     {
661       /*
662        * We're no longer allowing remote users to generate requests with wildcards.
663        */
664       if (!MyConnect(sptr))
665         continue;
666       for (acptr = client; (acptr = next_client(acptr, nick));
667           acptr = acptr->next)
668       {
669         if (!IsRegistered(acptr) || IsServer(acptr) || IsPing(acptr))
670           continue;
671         /*
672          * I'm always last :-) and acptr->next == NULL!!
673          */
674         if (IsMe(acptr))
675           break;
676         /*
677          * 'Rules' established for sending a WHOIS reply:
678          *
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);
683          *
684          * - only send replies about common or public channels
685          *   the target user(s) are on;
686          */
687         user = acptr->user;
688         name = (!*acptr->name) ? "?" : acptr->name;
689
690         invis = acptr != sptr && IsInvisible(acptr);
691         member = (user && user->channel) ? 1 : 0;
692         showperson = (wilds && !invis && !member) || !wilds;
693         if (user)
694           for (lp = user->channel; lp; lp = lp->next)
695           {
696             chptr = lp->value.chptr;
697             member = IsMember(sptr, chptr) ? 1 : 0;
698             if (invis && !member)
699               continue;
700             if (is_zombie(acptr, chptr))
701               continue;
702             if (member || (!invis && PubChannel(chptr)))
703             {
704               showperson = 1;
705               break;
706             }
707             if (!invis && HiddenChannel(chptr) && !SecretChannel(chptr))
708               showperson = 1;
709           }
710         if (!showperson)
711           continue;
712
713         if (user)
714         {
715           a2cptr = user->server;
716           sendto_one(sptr, rpl_str(RPL_WHOISUSER), me.name,
717               parv[0], name, user->username, user->host, acptr->info);
718         }
719         else
720         {
721           a2cptr = &me;
722           sendto_one(sptr, rpl_str(RPL_WHOISUSER), me.name,
723               parv[0], name, "<unknown>", "<unknown>", "<unknown>");
724         }
725
726         found = 1;
727
728       exact_match:
729         if (user && !IsChannelService(acptr))
730         {
731           mlen = strlen(me.name) + strlen(parv[0]) + 12 + strlen(name);
732           for (len = 0, *buf = '\0', lp = user->channel; lp; lp = lp->next)
733           {
734             chptr = lp->value.chptr;
735             if (ShowChannel(sptr, chptr) &&
736                 (acptr == sptr || !is_zombie(acptr, chptr)))
737             {
738               if (len + strlen(chptr->chname) + mlen > BUFSIZE - 5)
739               {
740                 sendto_one(sptr, ":%s %d %s %s :%s",
741                     me.name, RPL_WHOISCHANNELS, parv[0], name, buf);
742                 *buf = '\0';
743                 len = 0;
744               }
745               if (IsDeaf(acptr))
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++) = '!';
753               if (len)
754                 *(buf + len) = '\0';
755               strcpy(buf + len, chptr->chname);
756               len += strlen(chptr->chname);
757               strcat(buf + len, " ");
758               len++;
759             }
760           }
761           if (buf[0] != '\0')
762             sendto_one(sptr, rpl_str(RPL_WHOISCHANNELS),
763                 me.name, parv[0], name, buf);
764         }
765
766         sendto_one(sptr, rpl_str(RPL_WHOISSERVER), me.name,
767             parv[0], name, a2cptr->name, a2cptr->info);
768
769         if (user)
770         {
771           if (user->away)
772             sendto_one(sptr, rpl_str(RPL_AWAY), me.name,
773                 parv[0], name, user->away);
774
775           if (IsAnOper(acptr))
776             sendto_one(sptr, rpl_str(RPL_WHOISOPERATOR),
777                 me.name, parv[0], name);
778
779           if (MyConnect(acptr))
780             sendto_one(sptr, rpl_str(RPL_WHOISIDLE), me.name,
781                 parv[0], name, now - user->last, acptr->firsttime);
782         }
783         if (found == 2 || total++ >= MAX_WHOIS_LINES)
784           break;
785       }
786     }
787     else
788     {
789       /* No wildcards */
790       if ((acptr = FindUser(nick)))
791       {
792         found = 2;              /* Make sure we exit the loop after passing it once */
793         user = acptr->user;
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);
798         goto exact_match;
799       }
800     }
801     if (!found)
802       sendto_one(sptr, err_str(ERR_NOSUCHNICK), me.name, parv[0], nick);
803     if (p)
804       p[-1] = ',';
805     if (!MyConnect(sptr) || total >= MAX_WHOIS_LINES)
806       break;
807   }
808   sendto_one(sptr, rpl_str(RPL_ENDOFWHOIS), me.name, parv[0], parv[1]);
809
810   return 0;
811 }