]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/commands/cmd_who.cpp
8438f8cdd0e07d222252eb96c76df49a0d5741f1
[user/henk/code/inspircd.git] / src / commands / cmd_who.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
6  *
7  * This file is part of InspIRCd.  InspIRCd is free software: you can
8  * redistribute it and/or modify it under the terms of the GNU General Public
9  * License as published by the Free Software Foundation, version 2.
10  *
11  * This program is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20
21 #include "inspircd.h"
22
23 /** Handle /WHO. These command handlers can be reloaded by the core,
24  * and handle basic RFC1459 commands. Commands within modules work
25  * the same way, however, they can be fully unloaded, where these
26  * may not.
27  */
28 class CommandWho : public Command
29 {
30         bool CanView(Channel* chan, User* user);
31         bool opt_viewopersonly;
32         bool opt_showrealhost;
33         bool opt_realname;
34         bool opt_mode;
35         bool opt_ident;
36         bool opt_metadata;
37         bool opt_port;
38         bool opt_away;
39         bool opt_local;
40         bool opt_far;
41         bool opt_time;
42
43  public:
44         /** Constructor for who.
45          */
46         CommandWho ( Module* parent) : Command(parent,"WHO", 1) {
47                 syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [ohurmMiaplf]";
48         }
49         void SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string &initial, Channel* ch, User* u, std::vector<std::string> &whoresults);
50         /** Handle command.
51          * @param parameters The parameters to the comamnd
52          * @param pcnt The number of parameters passed to teh command
53          * @param user The user issuing the command
54          * @return A value from CmdResult to indicate command success or failure.
55          */
56         CmdResult Handle(const std::vector<std::string>& parameters, User *user);
57         bool whomatch(User* cuser, User* user, const char* matchtext);
58 };
59
60
61 static Channel* get_first_visible_channel(User *source, User *u)
62 {
63         UCListIter i = u->chans.begin();
64         while (i != u->chans.end())
65         {
66                 Channel* c = *i++;
67
68                 /* XXX move the +I check into m_hidechans */
69                 if (source == u || !(c->IsModeSet('s') || c->IsModeSet('p') || u->IsModeSet('I')) || c->HasUser(source))
70                         return c;
71         }
72         return NULL;
73 }
74
75 bool CommandWho::whomatch(User* cuser, User* user, const char* matchtext)
76 {
77         bool match = false;
78         bool positive = false;
79
80         if (user->registered != REG_ALL)
81                 return false;
82
83         if (opt_local && !IS_LOCAL(user))
84                 return false;
85         else if (opt_far && IS_LOCAL(user))
86                 return false;
87
88         if (opt_mode)
89         {
90                 for (const char* n = matchtext; *n; n++)
91                 {
92                         if (*n == '+')
93                         {
94                                 positive = true;
95                                 continue;
96                         }
97                         else if (*n == '-')
98                         {
99                                 positive = false;
100                                 continue;
101                         }
102                         if (user->IsModeSet(*n) != positive)
103                                 return false;
104                 }
105                 return true;
106         }
107         else
108         {
109                 /*
110                  * This was previously one awesome pile of ugly nested if, when really, it didn't need
111                  * to be, since only one condition was ever checked, a chained if works just fine.
112                  * -- w00t
113                  */
114                 if (opt_metadata)
115                 {
116                         match = false;
117                         const Extensible::ExtensibleStore& list = user->GetExtList();
118                         for(Extensible::ExtensibleStore::const_iterator i = list.begin(); i != list.end(); ++i)
119                                 if (InspIRCd::Match(i->first->name, matchtext))
120                                         match = true;
121                 }
122                 else if (opt_realname)
123                         match = InspIRCd::Match(user->fullname, matchtext);
124                 else if (opt_showrealhost)
125                         match = InspIRCd::Match(user->host, matchtext, ascii_case_insensitive_map);
126                 else if (opt_ident)
127                         match = InspIRCd::Match(user->ident, matchtext, ascii_case_insensitive_map);
128                 else if (opt_port)
129                 {
130                         irc::portparser portrange(matchtext, false);
131                         long portno = -1;
132                         while ((portno = portrange.GetToken()))
133                                 if (IS_LOCAL(user) && portno == IS_LOCAL(user)->GetServerPort())
134                                 {
135                                         match = true;
136                                         break;
137                                 }
138                 }
139                 else if (opt_away)
140                         match = InspIRCd::Match(user->awaymsg, matchtext);
141                 else if (opt_time)
142                 {
143                         long seconds = ServerInstance->Duration(matchtext);
144
145                         // Okay, so time matching, we want all users connected `seconds' ago
146                         if (user->signon >= ServerInstance->Time() - seconds)
147                                 match = true;
148                 }
149
150                 /*
151                  * Once the conditionals have been checked, only check dhost/nick/server
152                  * if they didn't match this user -- and only match if we don't find a match.
153                  *
154                  * This should make things minutely faster, and again, less ugly.
155                  * -- w00t
156                  */
157                 if (!match)
158                         match = InspIRCd::Match(user->dhost, matchtext, ascii_case_insensitive_map);
159
160                 if (!match)
161                         match = InspIRCd::Match(user->nick, matchtext);
162
163                 /* Don't allow server name matches if HideWhoisServer is enabled, unless the command user has the priv */
164                 if (!match && (ServerInstance->Config->HideWhoisServer.empty() || cuser->HasPrivPermission("users/auspex")))
165                         match = InspIRCd::Match(user->server, matchtext);
166
167                 return match;
168         }
169 }
170
171 bool CommandWho::CanView(Channel* chan, User* user)
172 {
173         if (!user || !chan)
174                 return false;
175
176         /* Bug #383 - moved higher up the list, because if we are in the channel
177          * we can see all its users
178          */
179         if (chan->HasUser(user))
180                 return true;
181         /* Opers see all */
182         if (user->HasPrivPermission("users/auspex"))
183                 return true;
184         /* Cant see inside a +s or a +p channel unless we are a member (see above) */
185         else if (!chan->IsModeSet('s') && !chan->IsModeSet('p'))
186                 return true;
187
188         return false;
189 }
190
191 void CommandWho::SendWhoLine(User* user, const std::vector<std::string>& parms, const std::string &initial, Channel* ch, User* u, std::vector<std::string> &whoresults)
192 {
193         if (!ch)
194                 ch = get_first_visible_channel(user, u);
195
196         std::string wholine = initial + (ch ? ch->name : "*") + " " + u->ident + " " +
197                 (opt_showrealhost ? u->host : u->dhost) + " ";
198         if (!ServerInstance->Config->HideWhoisServer.empty() && !user->HasPrivPermission("servers/auspex"))
199                 wholine.append(ServerInstance->Config->HideWhoisServer);
200         else
201                 wholine.append(u->server);
202         
203         wholine.append(" " + u->nick + " ");
204
205         /* away? */
206         if (IS_AWAY(u))
207         {
208                 wholine.append("G");
209         }
210         else
211         {
212                 wholine.append("H");
213         }
214
215         /* oper? */
216         if (IS_OPER(u))
217         {
218                 wholine.push_back('*');
219         }
220
221         if (ch)
222                 wholine.append(ch->GetPrefixChar(u));
223
224         wholine.append(" :0 " + u->fullname);
225
226         FOREACH_MOD(I_OnSendWhoLine, OnSendWhoLine(user, parms, u, wholine));
227
228         if (!wholine.empty())
229                 whoresults.push_back(wholine);
230 }
231
232 CmdResult CommandWho::Handle (const std::vector<std::string>& parameters, User *user)
233 {
234         /*
235          * XXX - RFC says:
236          *   The <name> passed to WHO is matched against users' host, server, real
237          *   name and nickname
238          * Currently, we support WHO #chan, WHO nick, WHO 0, WHO *, and the addition of a 'o' flag, as per RFC.
239          */
240
241         /* WHO options */
242         opt_viewopersonly = false;
243         opt_showrealhost = false;
244         opt_realname = false;
245         opt_mode = false;
246         opt_ident = false;
247         opt_metadata = false;
248         opt_port = false;
249         opt_away = false;
250         opt_local = false;
251         opt_far = false;
252         opt_time = false;
253
254         Channel *ch = NULL;
255         std::vector<std::string> whoresults;
256         std::string initial = "352 " + user->nick + " ";
257
258         char matchtext[MAXBUF];
259         bool usingwildcards = false;
260
261         /* Change '0' into '*' so the wildcard matcher can grok it */
262         if (parameters[0] == "0")
263                 strlcpy(matchtext, "*", MAXBUF);
264         else
265                 strlcpy(matchtext, parameters[0].c_str(), MAXBUF);
266
267         for (const char* check = matchtext; *check; check++)
268         {
269                 if (*check == '*' || *check == '?' || *check == '.')
270                 {
271                         usingwildcards = true;
272                         break;
273                 }
274         }
275
276         if (parameters.size() > 1)
277         {
278                 /* Fix for bug #444, WHO flags count as a wildcard */
279                 usingwildcards = true;
280
281                 for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
282                 {
283                         switch (*iter)
284                         {
285                                 case 'o':
286                                         opt_viewopersonly = true;
287                                         break;
288                                 case 'h':
289                                         if (user->HasPrivPermission("users/auspex"))
290                                                 opt_showrealhost = true;
291                                         break;
292                                 case 'r':
293                                         opt_realname = true;
294                                         break;
295                                 case 'm':
296                                         if (user->HasPrivPermission("users/auspex"))
297                                                 opt_mode = true;
298                                         break;
299                                 case 'M':
300                                         if (user->HasPrivPermission("users/auspex"))
301                                                 opt_metadata = true;
302                                         break;
303                                 case 'i':
304                                         opt_ident = true;
305                                         break;
306                                 case 'p':
307                                         if (user->HasPrivPermission("users/auspex"))
308                                                 opt_port = true;
309                                         break;
310                                 case 'a':
311                                         opt_away = true;
312                                         break;
313                                 case 'l':
314                                         if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty())
315                                                 opt_local = true;
316                                         break;
317                                 case 'f':
318                                         if (user->HasPrivPermission("users/auspex") || ServerInstance->Config->HideWhoisServer.empty())
319                                                 opt_far = true;
320                                         break;
321                                 case 't':
322                                         opt_time = true;
323                                         break;
324                         }
325                 }
326         }
327
328
329         /* who on a channel? */
330         ch = ServerInstance->FindChan(matchtext);
331
332         if (ch)
333         {
334                 if (CanView(ch,user))
335                 {
336                         bool inside = ch->HasUser(user);
337
338                         /* who on a channel. */
339                         const UserMembList *cu = ch->GetUsers();
340
341                         for (UserMembCIter i = cu->begin(); i != cu->end(); i++)
342                         {
343                                 /* None of this applies if we WHO ourselves */
344                                 if (user != i->first)
345                                 {
346                                         /* opers only, please */
347                                         if (opt_viewopersonly && !IS_OPER(i->first))
348                                                 continue;
349
350                                         /* If we're not inside the channel, hide +i users */
351                                         if (i->first->IsModeSet('i') && !inside && !user->HasPrivPermission("users/auspex"))
352                                                 continue;
353                                 }
354
355                                 SendWhoLine(user, parameters, initial, ch, i->first, whoresults);
356                         }
357                 }
358         }
359         else
360         {
361                 /* Match against wildcard of nick, server or host */
362                 if (opt_viewopersonly)
363                 {
364                         /* Showing only opers */
365                         for (std::list<User*>::iterator i = ServerInstance->Users->all_opers.begin(); i != ServerInstance->Users->all_opers.end(); i++)
366                         {
367                                 User* oper = *i;
368
369                                 if (whomatch(user, oper, matchtext))
370                                 {
371                                         if (!user->SharesChannelWith(oper))
372                                         {
373                                                 if (usingwildcards && (oper->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex")))
374                                                         continue;
375                                         }
376
377                                         SendWhoLine(user, parameters, initial, NULL, oper, whoresults);
378                                 }
379                         }
380                 }
381                 else
382                 {
383                         for (user_hash::iterator i = ServerInstance->Users->clientlist->begin(); i != ServerInstance->Users->clientlist->end(); i++)
384                         {
385                                 if (whomatch(user, i->second, matchtext))
386                                 {
387                                         if (!user->SharesChannelWith(i->second))
388                                         {
389                                                 if (usingwildcards && (i->second->IsModeSet('i')) && (!user->HasPrivPermission("users/auspex")))
390                                                         continue;
391                                         }
392
393                                         SendWhoLine(user, parameters, initial, NULL, i->second, whoresults);
394                                 }
395                         }
396                 }
397         }
398         /* Send the results out */
399         for (std::vector<std::string>::const_iterator n = whoresults.begin(); n != whoresults.end(); n++)
400                 user->WriteServ(*n);
401         user->WriteNumeric(315, "%s %s :End of /WHO list.",user->nick.c_str(), *parameters[0].c_str() ? parameters[0].c_str() : "*");
402
403         // Penalize the user a bit for large queries
404         // (add one unit of penalty per 200 results)
405         if (IS_LOCAL(user))
406                 IS_LOCAL(user)->CommandFloodPenalty += whoresults.size() * 5;
407         return CMD_SUCCESS;
408 }
409
410 COMMAND_INIT(CommandWho)