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