cb8bf65e2680623580b3417b669d4b52af18a74f
[ircu2.10.12-pk.git] / ircd / m_who.c
1 /*
2  * IRC - Internet Relay Chat, ircd/m_who.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * See file AUTHORS in IRC package for additional names of
7  * the programmers.
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 1, or (at your option)
12  * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
22  *
23  * $Id$
24  */
25
26 /*
27  * m_functions execute protocol messages on this server:
28  *
29  *    cptr    is always NON-NULL, pointing to a *LOCAL* client
30  *            structure (with an open socket connected!). This
31  *            identifies the physical socket where the message
32  *            originated (or which caused the m_function to be
33  *            executed--some m_functions may call others...).
34  *
35  *    sptr    is the source of the message, defined by the
36  *            prefix part of the message if present. If not
37  *            or prefix not found, then sptr==cptr.
38  *
39  *            (!IsServer(cptr)) => (cptr == sptr), because
40  *            prefixes are taken *only* from servers...
41  *
42  *            (IsServer(cptr))
43  *                    (sptr == cptr) => the message didn't
44  *                    have the prefix.
45  *
46  *                    (sptr != cptr && IsServer(sptr) means
47  *                    the prefix specified servername. (?)
48  *
49  *                    (sptr != cptr && !IsServer(sptr) means
50  *                    that message originated from a remote
51  *                    user (not local).
52  *
53  *            combining
54  *
55  *            (!IsServer(sptr)) means that, sptr can safely
56  *            taken as defining the target structure of the
57  *            message in this server.
58  *
59  *    *Always* true (if 'parse' and others are working correct):
60  *
61  *    1)      sptr->from == cptr  (note: cptr->from == cptr)
62  *
63  *    2)      MyConnect(sptr) <=> sptr == cptr (e.g. sptr
64  *            *cannot* be a local connection, unless it's
65  *            actually cptr!). [MyConnect(x) should probably
66  *            be defined as (x == x->from) --msa ]
67  *
68  *    parc    number of variable parameter strings (if zero,
69  *            parv is allowed to be NULL)
70  *
71  *    parv    a NULL terminated list of parameter pointers,
72  *
73  *                    parv[0], sender (prefix string), if not present
74  *                            this points to an empty string.
75  *                    parv[1]...parv[parc-1]
76  *                            pointers to additional parameters
77  *                    parv[parc] == NULL, *always*
78  *
79  *            note:   it is guaranteed that parv[0]..parv[parc-1] are all
80  *                    non-NULL pointers.
81  */
82 #include "config.h"
83
84 #include "channel.h"
85 #include "client.h"
86 #include "hash.h"
87 #include "ircd.h"
88 #include "ircd_chattr.h"
89 #include "ircd_log.h"
90 #include "ircd_policy.h"
91 #include "ircd_reply.h"
92 #include "ircd_string.h"
93 #include "match.h"
94 #include "numeric.h"
95 #include "numnicks.h"
96 #include "send.h"
97 #include "support.h"
98 #include "whocmds.h"
99
100 #include <assert.h>
101 #include <string.h>
102
103
104 /*
105  * A little spin-marking utility to tell us wich clients we have already
106  * processed and wich not
107  */
108 static int who_marker = 0;
109 static void move_marker(void)
110 {
111   if (!++who_marker)
112   {
113     struct Client *cptr = GlobalClientList;
114     while (cptr)
115     {
116       cli_marker(cptr) = 0;
117       cptr = cli_next(cptr);
118     }
119     who_marker++;
120   }
121 }
122
123 #define CheckMark(x, y) ((x == y) ? 0 : (x = y))
124 #define Process(cptr) CheckMark(cli_marker(cptr), who_marker)
125
126 /*
127  * m_who - generic message handler
128  *
129  *  parv[0] = sender prefix
130  *  parv[1] = nickname mask list
131  *  parv[2] = additional selection flag, only 'o' for now.
132  *            and %flags to specify what fields to output
133  *            plus a ,querytype if the t flag is specified
134  *            so the final thing will be like o%tnchu,777
135  *  parv[3] = _optional_ parameter that overrides parv[1]
136  *            This can be used as "/quote who foo % :The Black Hacker
137  *            to find me, parv[3] _can_ contain spaces !.
138  */
139 int m_who(struct Client* cptr, struct Client* sptr, int parc, char* parv[])
140 {
141   char *mask;           /* The mask we are looking for              */
142   char ch;                      /* Scratch char register                    */
143   struct Channel *chptr;                /* Channel to show                          */
144   struct Client *acptr;         /* Client to show                           */
145
146   int bitsel;                   /* Mask of selectors to apply               */
147   int matchsel;                 /* Wich fields the match should apply on    */
148   int counter;                  /* Query size counter,
149                                    initially used to count fields           */
150   int commas;                   /* Does our mask contain any comma ?
151                                    If so is a list..                        */
152   int fields;                   /* Mask of fields to show                   */
153   int isthere = 0;              /* When this set the user is member of chptr */
154   char *nick;                   /* Single element extracted from
155                                    the mask list                            */
156   char *p;                      /* Scratch char pointer                     */
157   char *qrt;                    /* Pointer to the query type                */
158   static char mymask[512];      /* To save the mask before corrupting it    */
159
160   /* Let's find where is our mask, and if actually contains something */
161   mask = ((parc > 1) ? parv[1] : 0);
162   if (parc > 3 && parv[3])
163     mask = parv[3];
164   if (mask && ((mask[0] == '\0') ||
165       (mask[1] == '\0' && ((mask[0] == '0') || (mask[0] == '*')))))
166     mask = 0;
167
168   /* Evaluate the flags now, we consider the second parameter 
169      as "matchFlags%fieldsToInclude,querytype"           */
170   bitsel = fields = counter = matchsel = 0;
171   qrt = 0;
172   if (parc > 2 && parv[2] && *parv[2])
173   {
174     p = parv[2];
175     while (((ch = *(p++))) && (ch != '%') && (ch != ','))
176       switch (ch)
177       {
178         case 'o':
179         case 'O':
180           bitsel |= WHOSELECT_OPER;
181           continue;
182         case 'x':
183         case 'X':
184           bitsel |= WHOSELECT_EXTRA;
185           if (HasPriv(sptr, PRIV_WHOX))
186             log_write(LS_WHO, L_INFO, LOG_NOSNOTICE, "%#C WHO %s %s", sptr,
187                       (BadPtr(parv[3]) ? parv[1] : parv[3]), parv[2]);
188           continue;
189         case 'n':
190         case 'N':
191           matchsel |= WHO_FIELD_NIC;
192           continue;
193         case 'u':
194         case 'U':
195           matchsel |= WHO_FIELD_UID;
196           continue;
197         case 'h':
198         case 'H':
199           matchsel |= WHO_FIELD_HOS;
200           continue;
201         case 'i':
202         case 'I':
203           matchsel |= WHO_FIELD_NIP;
204           continue;
205         case 's':
206         case 'S':
207           matchsel |= WHO_FIELD_SER;
208           continue;
209         case 'r':
210         case 'R':
211           matchsel |= WHO_FIELD_REN;
212           continue;
213       }
214     if (ch == '%')
215       while ((ch = *p++) && (ch != ','))
216       {
217         counter++;
218         switch (ch)
219         {
220           case 'c':
221           case 'C':
222             fields |= WHO_FIELD_CHA;
223             break;
224           case 'd':
225           case 'D':
226             fields |= WHO_FIELD_DIS;
227             break;
228           case 'f':
229           case 'F':
230             fields |= WHO_FIELD_FLA;
231             break;
232           case 'h':
233           case 'H':
234             fields |= WHO_FIELD_HOS;
235             break;
236           case 'i':
237           case 'I':
238             fields |= WHO_FIELD_NIP;
239             break;
240           case 'l':
241           case 'L':
242             fields |= WHO_FIELD_IDL;
243           case 'n':
244           case 'N':
245             fields |= WHO_FIELD_NIC;
246             break;
247           case 'r':
248           case 'R':
249             fields |= WHO_FIELD_REN;
250             break;
251           case 's':
252           case 'S':
253             fields |= WHO_FIELD_SER;
254             break;
255           case 't':
256           case 'T':
257             fields |= WHO_FIELD_QTY;
258             break;
259           case 'u':
260           case 'U':
261             fields |= WHO_FIELD_UID;
262             break;
263           default:
264             break;
265         }
266       };
267     if (ch)
268       qrt = p;
269   }
270
271   if (!matchsel)
272     matchsel = WHO_FIELD_DEF;
273   if (!fields)
274     counter = 7;
275
276 #ifdef HEAD_IN_SAND_WHO_SERVERNAME
277   if (!IsAnOper(sptr))
278     matchsel &= ~WHO_FIELD_SER;
279 #endif
280
281   if (qrt && (fields & WHO_FIELD_QTY))
282   {
283     p = qrt;
284     if (!((*p > '9') || (*p < '0')))
285       p++;
286     if (!((*p > '9') || (*p < '0')))
287       p++;
288     if (!((*p > '9') || (*p < '0')))
289       p++;
290     *p = '\0';
291   }
292   else
293     qrt = 0;
294
295   /* I'd love to add also a check on the number of matches fields per time */
296   counter = (2048 / (counter + 4));
297   if (mask && (strlen(mask) > 510))
298     mask[510] = '\0';
299   move_marker();
300   commas = (mask && strchr(mask, ','));
301
302   /* First treat mask as a list of plain nicks/channels */
303   if (mask)
304   {
305     strcpy(mymask, mask);
306     for (p = 0, nick = ircd_strtok(&p, mymask, ","); nick;
307         nick = ircd_strtok(&p, 0, ","))
308     {
309       if (IsChannelName(nick) && (chptr = FindChannel(nick)))
310       {
311         isthere = (find_channel_member(sptr, chptr) != 0);
312         if (isthere || SEE_CHANNEL(sptr, chptr, bitsel))
313         {
314           struct Membership* member;
315           for (member = chptr->members; member; member = member->next_member)
316           {
317             acptr = member->user;
318             if ((bitsel & WHOSELECT_OPER) &&
319                 !(IsAnOper(acptr) && (HasPriv(acptr, PRIV_DISPLAY) ||
320                                       HasPriv(sptr, PRIV_SEE_OPERS))))
321               continue;
322             if ((acptr != sptr) && (member->status & CHFL_ZOMBIE))
323               continue;
324             if (!(isthere || (SEE_USER(sptr, acptr, bitsel))))
325               continue;
326             if (!Process(acptr))        /* This can't be moved before other checks */
327               continue;
328             if (!(isthere || (SHOW_MORE(sptr, counter))))
329               break;
330             do_who(sptr, acptr, chptr, fields, qrt);
331           }
332         }
333       }
334       else
335       {
336         if ((acptr = FindUser(nick)) &&
337             ((!(bitsel & WHOSELECT_OPER)) ||
338              (IsAnOper(acptr) && (HasPriv(acptr, PRIV_DISPLAY) ||
339                                   HasPriv(sptr, PRIV_SEE_OPERS)))) &&
340             Process(acptr) && SHOW_MORE(sptr, counter))
341         {
342           do_who(sptr, acptr, 0, fields, qrt);
343         }
344       }
345     }
346   }
347
348   /* If we didn't have any comma in the mask treat it as a
349      real mask and try to match all relevant fields */
350   if (!(commas || (counter < 1)))
351   {
352     int minlen, cset;
353     static struct in_mask imask;
354     if (mask)
355     {
356       matchcomp(mymask, &minlen, &cset, mask);
357       if (matchcompIP(&imask, mask))
358         matchsel &= ~WHO_FIELD_NIP;
359       if ((minlen > NICKLEN) || !(cset & NTL_IRCNK))
360         matchsel &= ~WHO_FIELD_NIC;
361       if ((matchsel & WHO_FIELD_SER) &&
362           ((minlen > HOSTLEN) || (!(cset & NTL_IRCHN))
363           || (!markMatchexServer(mymask, minlen))))
364         matchsel &= ~WHO_FIELD_SER;
365       if ((minlen > USERLEN) || !(cset & NTL_IRCUI))
366         matchsel &= ~WHO_FIELD_UID;
367       if ((minlen > HOSTLEN) || !(cset & NTL_IRCHN))
368         matchsel &= ~WHO_FIELD_HOS;
369     }
370
371     /* First of all loop through the clients in common channels */
372     if ((!(counter < 1)) && matchsel) {
373       struct Membership* member;
374       struct Membership* chan;
375       for (chan = cli_user(sptr)->channel; chan; chan = chan->next_channel) {
376         chptr = chan->channel;
377         for (member = chptr->members; member; member = member->next_member)
378         {
379           acptr = member->user;
380           if (!(IsUser(acptr) && Process(acptr)))
381             continue;           /* Now Process() is at the beginning, if we fail
382                                    we'll never have to show this acptr in this query */
383           if ((bitsel & WHOSELECT_OPER) &&
384               !(IsAnOper(acptr) && (HasPriv(acptr, PRIV_DISPLAY) ||
385                                     HasPriv(sptr, PRIV_SEE_OPERS))))
386             continue;
387           if ((mask) &&
388               ((!(matchsel & WHO_FIELD_NIC))
389               || matchexec(cli_name(acptr), mymask, minlen))
390               && ((!(matchsel & WHO_FIELD_UID))
391               || matchexec(cli_user(acptr)->username, mymask, minlen))
392               && ((!(matchsel & WHO_FIELD_SER))
393               || (!(cli_flags(cli_user(acptr)->server) & FLAGS_MAP)))
394               && ((!(matchsel & WHO_FIELD_HOS))
395               || matchexec(cli_user(acptr)->host, mymask, minlen))
396               && ((!(matchsel & WHO_FIELD_HOS))
397               || !HasHiddenHost(acptr)
398               || !IsAnOper(sptr)
399               || matchexec(cli_user(acptr)->realhost, mymask, minlen))
400               && ((!(matchsel & WHO_FIELD_REN))
401               || matchexec(cli_info(acptr), mymask, minlen))
402               && ((!(matchsel & WHO_FIELD_NIP))
403               || (HasHiddenHost(acptr) && !IsAnOper(sptr))
404               || ((((cli_ip(acptr).s_addr & imask.mask.s_addr) !=
405               imask.bits.s_addr)) || (imask.fall
406               && matchexec(ircd_ntoa((const char*) &(cli_ip(acptr))), mymask, minlen)))))
407             continue;
408           if (!SHOW_MORE(sptr, counter))
409             break;
410           do_who(sptr, acptr, chptr, fields, qrt);
411         }
412       }
413     }
414     /* Loop through all clients :-\, if we still have something to match to 
415        and we can show more clients */
416     if ((!(counter < 1)) && matchsel)
417       for (acptr = cli_prev(&me); acptr; acptr = cli_prev(acptr))
418       {
419         if (!(IsUser(acptr) && Process(acptr)))
420           continue;
421         if ((bitsel & WHOSELECT_OPER) &&
422             !(IsAnOper(acptr) && (HasPriv(acptr, PRIV_DISPLAY) ||
423                                   HasPriv(sptr, PRIV_SEE_OPERS))))
424           continue;
425         if (!(SEE_USER(sptr, acptr, bitsel)))
426           continue;
427         if ((mask) &&
428             ((!(matchsel & WHO_FIELD_NIC))
429             || matchexec(cli_name(acptr), mymask, minlen))
430             && ((!(matchsel & WHO_FIELD_UID))
431             || matchexec(cli_user(acptr)->username, mymask, minlen))
432             && ((!(matchsel & WHO_FIELD_SER))
433             || (!(cli_flags(cli_user(acptr)->server) & FLAGS_MAP)))
434             && ((!(matchsel & WHO_FIELD_HOS))
435             || matchexec(cli_user(acptr)->host, mymask, minlen))
436             && ((!(matchsel & WHO_FIELD_HOS))
437             || !HasHiddenHost(acptr)
438             || !IsAnOper(sptr)
439             || matchexec(cli_user(acptr)->realhost, mymask, minlen))
440             && ((!(matchsel & WHO_FIELD_REN))
441             || matchexec(cli_info(acptr), mymask, minlen))
442             && ((!(matchsel & WHO_FIELD_NIP))
443             || (HasHiddenHost(acptr) && !IsAnOper(sptr))
444             || ((((cli_ip(acptr).s_addr & imask.mask.s_addr) != imask.bits.s_addr))
445             || (imask.fall
446             && matchexec(ircd_ntoa((const char*) &(cli_ip(acptr))), mymask, minlen)))))
447           continue;
448         if (!SHOW_MORE(sptr, counter))
449           break;
450         do_who(sptr, acptr, 0, fields, qrt);
451       }
452   }
453
454   /* Make a clean mask suitable to be sent in the "end of" */
455   if (mask && (p = strchr(mask, ' ')))
456     *p = '\0';
457   send_reply(sptr, RPL_ENDOFWHO, BadPtr(mask) ? "*" : mask);
458
459   /* Notify the user if we decided that his query was too long */
460   if (counter < 0)
461     send_reply(sptr, ERR_QUERYTOOLONG, "WHO");
462
463   return 0;
464 }