]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc.c
e99f42ff23633d06deaf5b10f4c4ffa46f824688
[ngircd-alex.git] / src / ngircd / irc.c
1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * Please read the file COPYING, README and AUTHORS for more information.
10  */
11
12 #include "portab.h"
13
14 /**
15  * @file
16  * IRC commands
17  */
18
19 #include <assert.h>
20 #include <stdio.h>
21 #include <string.h>
22
23 #include "ngircd.h"
24 #include "conn-func.h"
25 #include "conf.h"
26 #include "channel.h"
27 #include "conn-encoding.h"
28 #include "defines.h"
29 #include "irc-macros.h"
30 #include "irc-write.h"
31 #include "log.h"
32 #include "match.h"
33 #include "messages.h"
34 #include "parse.h"
35 #include "op.h"
36 #include "tool.h"
37
38 #include "irc.h"
39
40 static char *Option_String PARAMS((CONN_ID Idx));
41 static bool Send_Message PARAMS((CLIENT *Client, REQUEST *Req, int ForceType,
42                                  bool SendErrors));
43 static bool Send_Message_Mask PARAMS((CLIENT *from, char *command,
44                                       char *targetMask, char *message,
45                                       bool SendErrors));
46 static bool Help PARAMS((CLIENT *Client, const char *Topic));
47
48 /**
49  * Check if a list limit is reached and inform client accordingly.
50  *
51  * @param From The client.
52  * @param Count Reply item count.
53  * @param Limit Reply limit.
54  * @param Name Name of the list.
55  * @return true if list limit has been reached; false otherwise.
56  */
57 GLOBAL bool
58 IRC_CheckListTooBig(CLIENT *From, const int Count, const int Limit,
59                     const char *Name)
60 {
61         assert(From != NULL);
62         assert(Count >= 0);
63         assert(Limit > 0);
64         assert(Name != NULL);
65
66         if (Count < Limit)
67                 return false;
68
69         (void)IRC_WriteStrClient(From,
70                                  "NOTICE %s :%s list limit (%d) reached!",
71                                  Client_ID(From), Name, Limit);
72         IRC_SetPenalty(From, 2);
73         return true;
74 }
75
76 /**
77  * Handler for the IRC "ERROR" command.
78  *
79  * @param Client The client from which this command has been received.
80  * @param Req Request structure with prefix and all parameters.
81  * @return CONNECTED or DISCONNECTED.
82 */
83 GLOBAL bool
84 IRC_ERROR(CLIENT *Client, REQUEST *Req)
85 {
86         assert( Client != NULL );
87         assert( Req != NULL );
88
89         if (Client_Type(Client) != CLIENT_GOTPASS
90             && Client_Type(Client) != CLIENT_GOTPASS_2813
91             && Client_Type(Client) != CLIENT_UNKNOWNSERVER
92             && Client_Type(Client) != CLIENT_SERVER
93             && Client_Type(Client) != CLIENT_SERVICE) {
94                 LogDebug("Ignored ERROR command from \"%s\" ...",
95                          Client_Mask(Client));
96                 IRC_SetPenalty(Client, 2);
97                 return CONNECTED;
98         }
99
100         if (Req->argc < 1)
101                 Log(LOG_NOTICE, "Got ERROR from \"%s\"!",
102                     Client_Mask(Client));
103         else
104                 Log(LOG_NOTICE, "Got ERROR from \"%s\": \"%s\"!",
105                     Client_Mask(Client), Req->argv[0]);
106
107         return CONNECTED;
108 } /* IRC_ERROR */
109
110 /**
111  * Handler for the IRC "KILL" command.
112  *
113  * This function implements the IRC command "KILL" which is used to selectively
114  * disconnect clients. It can be used by IRC operators and servers, for example
115  * to "solve" nick collisions after netsplits. See RFC 2812 section 3.7.1.
116  *
117  * Please note that this function is also called internally, without a real
118  * KILL command being received over the network! Client is Client_ThisServer()
119  * in this case, and the prefix in Req is NULL.
120  *
121  * @param Client The client from which this command has been received or
122  * Client_ThisServer() when generated interanlly.
123  * @param Req Request structure with prefix and all parameters.
124  * @return CONNECTED or DISCONNECTED.
125  */
126 GLOBAL bool
127 IRC_KILL(CLIENT *Client, REQUEST *Req)
128 {
129         CLIENT *prefix;
130         char reason[COMMAND_LEN];
131
132         assert (Client != NULL);
133         assert (Req != NULL);
134
135         if (Client_Type(Client) != CLIENT_SERVER && !Op_Check(Client, Req))
136                 return Op_NoPrivileges(Client, Req);
137
138         /* Get prefix (origin); use the client if no prefix is given. */
139         if (Req->prefix)
140                 prefix = Client_Search(Req->prefix);
141         else
142                 prefix = Client;
143
144         /* Log a warning message and use this server as origin when the
145          * prefix (origin) is invalid. And this is the reason why we don't
146          * use the _IRC_GET_SENDER_OR_RETURN_ macro above! */
147         if (!prefix) {
148                 Log(LOG_WARNING, "Got KILL with invalid prefix: \"%s\"!",
149                     Req->prefix );
150                 prefix = Client_ThisServer();
151         }
152
153         if (Client != Client_ThisServer())
154                 Log(LOG_NOTICE|LOG_snotice,
155                     "Got KILL command from \"%s\" for \"%s\": \"%s\".",
156                     Client_Mask(prefix), Req->argv[0], Req->argv[1]);
157
158         /* Build reason string: Prefix the "reason" if the originator is a
159          * regular user, so users can't spoof KILLs of servers. */
160         if (Client_Type(Client) == CLIENT_USER)
161                 snprintf(reason, sizeof(reason), "KILLed by %s: %s",
162                          Client_ID(Client), Req->argv[1]);
163         else
164                 strlcpy(reason, Req->argv[1], sizeof(reason));
165
166         return IRC_KillClient(Client, prefix, Req->argv[0], reason);
167 }
168
169 /**
170  * Handler for the IRC "NOTICE" command.
171  *
172  * @param Client The client from which this command has been received.
173  * @param Req Request structure with prefix and all parameters.
174  * @return CONNECTED or DISCONNECTED.
175 */
176 GLOBAL bool
177 IRC_NOTICE(CLIENT *Client, REQUEST *Req)
178 {
179         return Send_Message(Client, Req, CLIENT_USER, false);
180 } /* IRC_NOTICE */
181
182 /**
183  * Handler for the IRC "PRIVMSG" command.
184  *
185  * @param Client The client from which this command has been received.
186  * @param Req Request structure with prefix and all parameters.
187  * @return CONNECTED or DISCONNECTED.
188  */
189 GLOBAL bool
190 IRC_PRIVMSG(CLIENT *Client, REQUEST *Req)
191 {
192         return Send_Message(Client, Req, CLIENT_USER, true);
193 } /* IRC_PRIVMSG */
194
195 /**
196  * Handler for the IRC "SQUERY" command.
197  *
198  * @param Client The client from which this command has been received.
199  * @param Req Request structure with prefix and all parameters.
200  * @return CONNECTED or DISCONNECTED.
201  */
202 GLOBAL bool
203 IRC_SQUERY(CLIENT *Client, REQUEST *Req)
204 {
205         return Send_Message(Client, Req, CLIENT_SERVICE, true);
206 } /* IRC_SQUERY */
207
208 /*
209  * Handler for the IRC "TRACE" command.
210  *
211  * @param Client The client from which this command has been received.
212  * @param Req Request structure with prefix and all parameters.
213  * @return CONNECTED or DISCONNECTED.
214  */
215  GLOBAL bool
216 IRC_TRACE(CLIENT *Client, REQUEST *Req)
217 {
218         CLIENT *from, *target, *c;
219         CONN_ID idx, idx2;
220         char user[CLIENT_USER_LEN];
221
222         assert(Client != NULL);
223         assert(Req != NULL);
224
225         _IRC_GET_SENDER_OR_RETURN_(from, Req, Client)
226         _IRC_GET_TARGET_SERVER_OR_RETURN_(target, Req, 0, from)
227
228         /* Forward command to other server? */
229         if (target != Client_ThisServer()) {
230                 /* Send RPL_TRACELINK back to initiator */
231                 idx = Client_Conn(Client);
232                 assert(idx > NONE);
233                 idx2 = Client_Conn(Client_NextHop(target));
234                 assert(idx2 > NONE);
235
236                 if (!IRC_WriteStrClient(from, RPL_TRACELINK_MSG,
237                                         Client_ID(from), PACKAGE_NAME,
238                                         PACKAGE_VERSION, Client_ID(target),
239                                         Client_ID(Client_NextHop(target)),
240                                         Option_String(idx2),
241                                         time(NULL) - Conn_StartTime(idx2),
242                                         Conn_SendQ(idx), Conn_SendQ(idx2)))
243                         return DISCONNECTED;
244
245                 /* Forward command */
246                 IRC_WriteStrClientPrefix(target, from, "TRACE %s", Req->argv[0]);
247                 return CONNECTED;
248         }
249
250         /* Infos about all connected servers */
251         c = Client_First();
252         while (c) {
253                 if (Client_Conn(c) > NONE) {
254                         /* Local client */
255                         if (Client_Type(c) == CLIENT_SERVER) {
256                                 /* Server link */
257                                 strlcpy(user, Client_User(c), sizeof(user));
258                                 if (user[0] == '~')
259                                         strlcpy(user, "unknown", sizeof(user));
260                                 if (!IRC_WriteStrClient(from,
261                                                 RPL_TRACESERVER_MSG,
262                                                 Client_ID(from), Client_ID(c),
263                                                 user, Client_Hostname(c),
264                                                 Client_Mask(Client_ThisServer()),
265                                                 Option_String(Client_Conn(c))))
266                                         return DISCONNECTED;
267                         }
268                         if (Client_Type(c) == CLIENT_USER
269                             && Client_HasMode(c, 'o')) {
270                                 /* IRC Operator */
271                                 if (!IRC_WriteStrClient(from,
272                                                 RPL_TRACEOPERATOR_MSG,
273                                                 Client_ID(from), Client_ID(c)))
274                                         return DISCONNECTED;
275                         }
276                 }
277                 c = Client_Next( c );
278         }
279
280         return IRC_WriteStrClient(from, RPL_TRACEEND_MSG, Client_ID(from),
281                                   Conf_ServerName, PACKAGE_NAME,
282                                   PACKAGE_VERSION, NGIRCd_DebugLevel);
283 } /* IRC_TRACE */
284
285 /**
286  * Handler for the IRC "HELP" command.
287  *
288  * @param Client The client from which this command has been received.
289  * @param Req Request structure with prefix and all parameters.
290  * @return CONNECTED or DISCONNECTED.
291  */
292 GLOBAL bool
293 IRC_HELP(CLIENT *Client, REQUEST *Req)
294 {
295         COMMAND *cmd;
296
297         assert(Client != NULL);
298         assert(Req != NULL);
299
300         if ((Req->argc == 0 && array_bytes(&Conf_Helptext) > 0)
301             || (Req->argc >= 1 && strcasecmp(Req->argv[0], "Commands") != 0)) {
302                 /* Help text available and requested */
303                 if (Req->argc >= 1)
304                         return Help(Client, Req->argv[0]);
305
306                 if (!Help(Client, "Intro"))
307                         return DISCONNECTED;
308                 return CONNECTED;
309         }
310
311         cmd = Parse_GetCommandStruct();
312         while(cmd->name) {
313                 if (!IRC_WriteStrClient(Client, "NOTICE %s :%s",
314                                         Client_ID(Client), cmd->name))
315                         return DISCONNECTED;
316                 cmd++;
317         }
318         return CONNECTED;
319 } /* IRC_HELP */
320
321 /**
322  * Kill an client identified by its nick name.
323  *
324  * Please note that after killig a client, its CLIENT cond CONNECTION
325  * structures are invalid. So the caller must make sure on its own not to
326  * access data of probably killed clients after calling this function!
327  *
328  * @param Client The client from which the command leading to the KILL has
329  *              been received, or NULL. The KILL will no be forwarded in this
330  *              direction. Only relevant when From is set, too.
331  * @param From The client from which the command originated, or NULL for
332                 the local server.
333  * @param Nick The nick name to kill.
334  * @param Reason Text to send as reason to the client and other servers.
335  */
336 GLOBAL bool
337 IRC_KillClient(CLIENT *Client, CLIENT *From, const char *Nick, const char *Reason)
338 {
339         const char *msg;
340         CONN_ID my_conn, conn;
341         CLIENT *c;
342
343         /* Do we know such a client in the network? */
344         c = Client_Search(Nick);
345         if (!c) {
346                 LogDebug("Client with nick \"%s\" is unknown, not forwaring.", Nick);
347                 return CONNECTED;
348         }
349
350         /* Inform other servers */
351         IRC_WriteStrServersPrefix(From ? Client : NULL,
352                                   From ? From : Client_ThisServer(),
353                                   "KILL %s :%s", Nick, Reason);
354
355         if (Client_Type(c) != CLIENT_USER && Client_Type(c) != CLIENT_GOTNICK) {
356                 /* Target of this KILL is not a regular user, this is
357                  * invalid! So we ignore this case if we received a
358                  * regular KILL from the network and try to kill the
359                  * client/connection anyway (but log an error!) if the
360                  * origin is the local server. */
361
362                 if (Client != Client_ThisServer()) {
363                         /* Invalid KILL received from remote */
364                         if (Client_Type(c) == CLIENT_SERVER)
365                                 msg = ERR_CANTKILLSERVER_MSG;
366                         else
367                                 msg = ERR_NOPRIVILEGES_MSG;
368                         return IRC_WriteErrClient(Client, msg, Client_ID(Client));
369                 }
370
371                 Log(LOG_ERR,
372                     "Got KILL for invalid client type: %d, \"%s\"!",
373                     Client_Type(c), Nick);
374         }
375
376         /* Save ID of this connection */
377         my_conn = Client_Conn(Client);
378
379         /* Kill the client NOW:
380          *  - Close the local connection (if there is one),
381          *  - Destroy the CLIENT structure for remote clients.
382          * Note: Conn_Close() removes the CLIENT structure as well. */
383         conn = Client_Conn(c);
384         if(conn > NONE)
385                 Conn_Close(conn, NULL, Reason, true);
386         else
387                 Client_Destroy(c, NULL, Reason, false);
388
389         /* Are we still connected or were we killed, too? */
390         if (my_conn > NONE && Conn_GetClient(my_conn))
391                 return CONNECTED;
392         else
393                 return DISCONNECTED;
394 }
395
396 /**
397  * Send help for a given topic to the client.
398  *
399  * @param Client The client requesting help.
400  * @param Topoc The help topic requested.
401  * @return CONNECTED or DISCONNECTED.
402  */
403 static bool
404 Help(CLIENT *Client, const char *Topic)
405 {
406         char *line;
407         size_t helptext_len, len_str, idx_start, lines = 0;
408         bool in_article = false;
409
410         assert(Client != NULL);
411         assert(Topic != NULL);
412
413         helptext_len = array_bytes(&Conf_Helptext);
414         line = array_start(&Conf_Helptext);
415         while (helptext_len > 0) {
416                 len_str = strlen(line) + 1;
417                 assert(helptext_len >= len_str);
418                 helptext_len -= len_str;
419
420                 if (in_article) {
421                         /* The first character in each article text line must
422                          * be a TAB (ASCII 9) character which will be stripped
423                          * in the output. If it is not a TAB, the end of the
424                          * article has been reached. */
425                         if (line[0] != '\t') {
426                                 if (lines > 0)
427                                         return CONNECTED;
428                                 else
429                                         break;
430                         }
431
432                         /* A single '.' character indicates an empty line */
433                         if (line[1] == '.' && line[2] == '\0')
434                                 idx_start = 2;
435                         else
436                                 idx_start = 1;
437
438                         if (!IRC_WriteStrClient(Client, "NOTICE %s :%s",
439                                                 Client_ID(Client),
440                                                 &line[idx_start]))
441                                 return DISCONNECTED;
442                         lines++;
443
444                 } else {
445                         if (line[0] == '-' && line[1] == ' '
446                             && strcasecmp(&line[2], Topic) == 0)
447                                 in_article = true;
448                 }
449
450                 line += len_str;
451         }
452
453         /* Help topic not found (or empty)! */
454         if (!IRC_WriteStrClient(Client, "NOTICE %s :No help for \"%s\" found!",
455                                 Client_ID(Client), Topic))
456                 return DISCONNECTED;
457
458         return CONNECTED;
459 }
460
461 /**
462  * Get pointer to a static string representing the connection "options".
463  *
464  * @param Idx Connection index.
465  * @return Pointer to static (global) string buffer.
466  */
467 static char *
468 #ifdef ZLIB
469 Option_String(CONN_ID Idx)
470 #else
471 Option_String(UNUSED CONN_ID Idx)
472 #endif
473 {
474         static char option_txt[8];
475         UINT16 options;
476
477         assert(Idx != NONE);
478
479         options = Conn_Options(Idx);
480         strcpy(option_txt, "F");        /* No idea what this means, but the
481                                          * original ircd sends it ... */
482 #ifdef SSL_SUPPORT
483         if(options & CONN_SSL)          /* SSL encrypted link */
484                 strlcat(option_txt, "s", sizeof(option_txt));
485 #endif
486 #ifdef ZLIB
487         if(options & CONN_ZIP)          /* zlib compression enabled */
488                 strlcat(option_txt, "z", sizeof(option_txt));
489 #endif
490         LogDebug(" *** %d: %d = %s", Idx, options, option_txt);
491
492         return option_txt;
493 } /* Option_String */
494
495 static bool
496 Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
497 {
498         CLIENT *cl, *from;
499         CL2CHAN *cl2chan;
500         CHANNEL *chan;
501         char *currentTarget = Req->argv[0];
502         char *lastCurrentTarget = NULL;
503         char *message = NULL;
504
505         assert(Client != NULL);
506         assert(Req != NULL);
507
508         if (Req->argc == 0) {
509                 if (!SendErrors)
510                         return CONNECTED;
511                 return IRC_WriteErrClient(Client, ERR_NORECIPIENT_MSG,
512                                           Client_ID(Client), Req->command);
513         }
514         if (Req->argc == 1) {
515                 if (!SendErrors)
516                         return CONNECTED;
517                 return IRC_WriteErrClient(Client, ERR_NOTEXTTOSEND_MSG,
518                                           Client_ID(Client));
519         }
520         if (Req->argc > 2) {
521                 if (!SendErrors)
522                         return CONNECTED;
523                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
524                                           Client_ID(Client), Req->command);
525         }
526
527         if (Client_Type(Client) == CLIENT_SERVER)
528                 from = Client_Search(Req->prefix);
529         else
530                 from = Client;
531         if (!from)
532                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
533                                           Client_ID(Client), Req->prefix);
534
535 #ifdef ICONV
536         if (Client_Conn(Client) > NONE)
537                 message = Conn_EncodingFrom(Client_Conn(Client), Req->argv[1]);
538         else
539 #endif
540                 message = Req->argv[1];
541
542         /* handle msgtarget = msgto *("," msgto) */
543         currentTarget = strtok_r(currentTarget, ",", &lastCurrentTarget);
544         ngt_UpperStr(Req->command);
545
546         while (currentTarget) {
547                 /* Check for and handle valid <msgto> of form:
548                  * RFC 2812 2.3.1:
549                  *   msgto =  channel / ( user [ "%" host ] "@" servername )
550                  *   msgto =/ ( user "%" host ) / targetmask
551                  *   msgto =/ nickname / ( nickname "!" user "@" host )
552                  */
553                 if (strchr(currentTarget, '!') == NULL)
554                         /* nickname */
555                         cl = Client_Search(currentTarget);
556                 else
557                         cl = NULL;
558
559                 if (cl == NULL) {
560                         /* If currentTarget isn't a nickname check for:
561                          * user ["%" host] "@" servername
562                          * user "%" host
563                          * nickname "!" user "@" host
564                          */
565                         char target[COMMAND_LEN];
566                         char * nick = NULL;
567                         char * user = NULL;
568                         char * host = NULL;
569                         char * server = NULL;
570
571                         strlcpy(target, currentTarget, COMMAND_LEN);
572                         server = strchr(target, '@');
573                         if (server) {
574                                 *server = '\0';
575                                 server++;
576                         }
577                         host = strchr(target, '%');
578                         if (host) {
579                                 *host = '\0';
580                                 host++;
581                         }
582                         user = strchr(target, '!');
583                         if (user) {
584                                 /* msgto form: nick!user@host */
585                                 *user = '\0';
586                                 user++;
587                                 nick = target;
588                                 host = server; /* not "@server" but "@host" */
589                         } else {
590                                 user = target;
591                         }
592
593                         for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
594                                 if (Client_Type(cl) != CLIENT_USER &&
595                                     Client_Type(cl) != CLIENT_SERVICE)
596                                         continue;
597                                 if (nick != NULL && host != NULL) {
598                                         if (strcasecmp(nick, Client_ID(cl)) == 0 &&
599                                             strcasecmp(user, Client_User(cl)) == 0 &&
600                                             strcasecmp(host, Client_HostnameDisplayed(cl)) == 0)
601                                                 break;
602                                         else
603                                                 continue;
604                                 }
605                                 if (strcasecmp(user, Client_User(cl)) != 0)
606                                         continue;
607                                 if (host != NULL && strcasecmp(host,
608                                                 Client_HostnameDisplayed(cl)) != 0)
609                                         continue;
610                                 if (server != NULL && strcasecmp(server,
611                                                 Client_ID(Client_Introducer(cl))) != 0)
612                                         continue;
613                                 break;
614                         }
615                 }
616
617                 if (cl) {
618                         /* Target is a user, enforce type */
619 #ifndef STRICT_RFC
620                         if (Client_Type(cl) != ForceType &&
621                             !(ForceType == CLIENT_USER &&
622                               (Client_Type(cl) == CLIENT_USER ||
623                                Client_Type(cl) == CLIENT_SERVICE))) {
624 #else
625                         if (Client_Type(cl) != ForceType) {
626 #endif
627                                 if (SendErrors && !IRC_WriteErrClient(
628                                     from, ERR_NOSUCHNICK_MSG,Client_ID(from),
629                                     currentTarget))
630                                         return DISCONNECTED;
631                                 goto send_next_target;
632                         }
633
634 #ifndef STRICT_RFC
635                         if (ForceType == CLIENT_SERVICE &&
636                             (Conn_Options(Client_Conn(Client_NextHop(cl)))
637                              & CONN_RFC1459)) {
638                                 /* SQUERY command but RFC 1459 link: convert
639                                  * request to PRIVMSG command */
640                                 Req->command = "PRIVMSG";
641                         }
642 #endif
643                         if (Client_HasMode(cl, 'b') &&
644                             !Client_HasMode(from, 'R') &&
645                             !Client_HasMode(from, 'o') &&
646                             !(Client_Type(from) == CLIENT_SERVER) &&
647                             !(Client_Type(from) == CLIENT_SERVICE)) {
648                                 if (SendErrors && !IRC_WriteErrClient(from,
649                                                 ERR_NONONREG_MSG,
650                                                 Client_ID(from), Client_ID(cl)))
651                                         return DISCONNECTED;
652                                 goto send_next_target;
653                         }
654
655                         if (Client_HasMode(cl, 'C')) {
656                                 cl2chan = Channel_FirstChannelOf(cl);
657                                 while (cl2chan) {
658                                         chan = Channel_GetChannel(cl2chan);
659                                         if (Channel_IsMemberOf(chan, from))
660                                                 break;
661                                         cl2chan = Channel_NextChannelOf(cl, cl2chan);
662                                 }
663                                 if (!cl2chan) {
664                                         if (SendErrors && !IRC_WriteErrClient(
665                                             from, ERR_NOTONSAMECHANNEL_MSG,
666                                             Client_ID(from), Client_ID(cl)))
667                                                 return DISCONNECTED;
668                                         goto send_next_target;
669                                 }
670                         }
671
672                         if (SendErrors && (Client_Type(Client) != CLIENT_SERVER)
673                             && Client_HasMode(cl, 'a')) {
674                                 /* Target is away */
675                                 if (!IRC_WriteStrClient(from, RPL_AWAY_MSG,
676                                                         Client_ID(from),
677                                                         Client_ID(cl),
678                                                         Client_Away(cl)))
679                                         return DISCONNECTED;
680                         }
681                         if (Client_Conn(from) > NONE) {
682                                 Conn_UpdateIdle(Client_Conn(from));
683                         }
684                         if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
685                                                       Req->command, Client_ID(cl),
686                                                       message))
687                                 return DISCONNECTED;
688                 } else if (ForceType != CLIENT_SERVICE
689                            && (chan = Channel_Search(currentTarget))) {
690                         if (!Channel_Write(chan, from, Client, Req->command,
691                                            SendErrors, message))
692                                         return DISCONNECTED;
693                 } else if (ForceType != CLIENT_SERVICE
694                         /* $#: server/target mask, RFC 2812, sec. 3.3.1 */
695                            && strchr("$#", currentTarget[0])
696                            && strchr(currentTarget, '.')) {
697                         /* targetmask */
698                         if (!Send_Message_Mask(from, Req->command, currentTarget,
699                                                message, SendErrors))
700                                 return DISCONNECTED;
701                 } else {
702                         if (!SendErrors)
703                                 return CONNECTED;
704                         if (!IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG,
705                                                 Client_ID(from), currentTarget))
706                                 return DISCONNECTED;
707                 }
708
709         send_next_target:
710                 currentTarget = strtok_r(NULL, ",", &lastCurrentTarget);
711                 if (currentTarget)
712                         Conn_SetPenalty(Client_Conn(Client), 1);
713         }
714
715         return CONNECTED;
716 } /* Send_Message */
717
718 static bool
719 Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
720                   char * message, bool SendErrors)
721 {
722         CLIENT *cl;
723         bool client_match;
724         char *mask = targetMask + 1;
725         const char *check_wildcards;
726
727         cl = NULL;
728
729         if (!Client_HasMode(from, 'o')) {
730                 if (!SendErrors)
731                         return true;
732                 return IRC_WriteErrClient(from, ERR_NOPRIVILEGES_MSG,
733                                           Client_ID(from));
734         }
735
736         /*
737          * RFC 2812, sec. 3.3.1 requires that targetMask have at least one
738          * dot (".") and no wildcards ("*", "?") following the last one.
739          */
740         check_wildcards = strrchr(targetMask, '.');
741         assert(check_wildcards != NULL);
742         if (check_wildcards &&
743                 check_wildcards[strcspn(check_wildcards, "*?")])
744         {
745                 if (!SendErrors)
746                         return true;
747                 return IRC_WriteErrClient(from, ERR_WILDTOPLEVEL, targetMask);
748         }
749
750         /* #: hostmask, see RFC 2812, sec. 3.3.1 */
751         if (targetMask[0] == '#') {
752                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
753                         if (Client_Type(cl) != CLIENT_USER)
754                                 continue;
755                         client_match = MatchCaseInsensitive(mask, Client_Hostname(cl));
756                         if (client_match)
757                                 if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
758                                                 command, Client_ID(cl), message))
759                                         return false;
760                 }
761         } else {
762                 assert(targetMask[0] == '$'); /* $: server mask, see RFC 2812, sec. 3.3.1 */
763                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
764                         if (Client_Type(cl) != CLIENT_USER)
765                                 continue;
766                         client_match = MatchCaseInsensitive(mask,
767                                         Client_ID(Client_Introducer(cl)));
768                         if (client_match)
769                                 if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
770                                                 command, Client_ID(cl), message))
771                                         return false;
772                 }
773         }
774         return CONNECTED;
775 } /* Send_Message_Mask */
776
777 /* -eof- */