]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc.c
7dafaf0539b005bdf808dcefb870838dfae8dfcc
[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                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
514                                           Client_ID(Client), Req->command);
515         }
516
517         if (Client_Type(Client) == CLIENT_SERVER)
518                 from = Client_Search(Req->prefix);
519         else
520                 from = Client;
521         if (!from)
522                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
523                                           Client_ID(Client), Req->prefix);
524
525 #ifdef ICONV
526         if (Client_Conn(Client) > NONE)
527                 message = Conn_EncodingFrom(Client_Conn(Client), Req->argv[1]);
528         else
529 #endif
530                 message = Req->argv[1];
531
532         /* handle msgtarget = msgto *("," msgto) */
533         currentTarget = strtok_r(currentTarget, ",", &lastCurrentTarget);
534         ngt_UpperStr(Req->command);
535
536         while (currentTarget) {
537                 /* Check for and handle valid <msgto> of form:
538                  * RFC 2812 2.3.1:
539                  *   msgto =  channel / ( user [ "%" host ] "@" servername )
540                  *   msgto =/ ( user "%" host ) / targetmask
541                  *   msgto =/ nickname / ( nickname "!" user "@" host )
542                  */
543                 if (strchr(currentTarget, '!') == NULL)
544                         /* nickname */
545                         cl = Client_Search(currentTarget);
546                 else
547                         cl = NULL;
548
549                 if (cl == NULL) {
550                         /* If currentTarget isn't a nickname check for:
551                          * user ["%" host] "@" servername
552                          * user "%" host
553                          * nickname "!" user "@" host
554                          */
555                         char target[COMMAND_LEN];
556                         char * nick = NULL;
557                         char * user = NULL;
558                         char * host = NULL;
559                         char * server = NULL;
560
561                         strlcpy(target, currentTarget, COMMAND_LEN);
562                         server = strchr(target, '@');
563                         if (server) {
564                                 *server = '\0';
565                                 server++;
566                         }
567                         host = strchr(target, '%');
568                         if (host) {
569                                 *host = '\0';
570                                 host++;
571                         }
572                         user = strchr(target, '!');
573                         if (user) {
574                                 /* msgto form: nick!user@host */
575                                 *user = '\0';
576                                 user++;
577                                 nick = target;
578                                 host = server; /* not "@server" but "@host" */
579                         } else {
580                                 user = target;
581                         }
582
583                         for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
584                                 if (Client_Type(cl) != CLIENT_USER &&
585                                     Client_Type(cl) != CLIENT_SERVICE)
586                                         continue;
587                                 if (nick != NULL && host != NULL) {
588                                         if (strcasecmp(nick, Client_ID(cl)) == 0 &&
589                                             strcasecmp(user, Client_User(cl)) == 0 &&
590                                             strcasecmp(host, Client_HostnameDisplayed(cl)) == 0)
591                                                 break;
592                                         else
593                                                 continue;
594                                 }
595                                 if (strcasecmp(user, Client_User(cl)) != 0)
596                                         continue;
597                                 if (host != NULL && strcasecmp(host,
598                                                 Client_HostnameDisplayed(cl)) != 0)
599                                         continue;
600                                 if (server != NULL && strcasecmp(server,
601                                                 Client_ID(Client_Introducer(cl))) != 0)
602                                         continue;
603                                 break;
604                         }
605                 }
606
607                 if (cl) {
608                         /* Target is a user, enforce type */
609 #ifndef STRICT_RFC
610                         if (Client_Type(cl) != ForceType &&
611                             !(ForceType == CLIENT_USER &&
612                               (Client_Type(cl) == CLIENT_USER ||
613                                Client_Type(cl) == CLIENT_SERVICE))) {
614 #else
615                         if (Client_Type(cl) != ForceType) {
616 #endif
617                                 if (SendErrors && !IRC_WriteErrClient(
618                                     from, ERR_NOSUCHNICK_MSG,Client_ID(from),
619                                     currentTarget))
620                                         return DISCONNECTED;
621                                 goto send_next_target;
622                         }
623
624 #ifndef STRICT_RFC
625                         if (ForceType == CLIENT_SERVICE &&
626                             (Conn_Options(Client_Conn(Client_NextHop(cl)))
627                              & CONN_RFC1459)) {
628                                 /* SQUERY command but RFC 1459 link: convert
629                                  * request to PRIVMSG command */
630                                 Req->command = "PRIVMSG";
631                         }
632 #endif
633                         if (Client_HasMode(cl, 'b') &&
634                             !Client_HasMode(from, 'R') &&
635                             !Client_HasMode(from, 'o') &&
636                             !(Client_Type(from) == CLIENT_SERVER) &&
637                             !(Client_Type(from) == CLIENT_SERVICE)) {
638                                 if (SendErrors && !IRC_WriteErrClient(from,
639                                                 ERR_NONONREG_MSG,
640                                                 Client_ID(from), Client_ID(cl)))
641                                         return DISCONNECTED;
642                                 goto send_next_target;
643                         }
644
645                         if (Client_HasMode(cl, 'C')) {
646                                 cl2chan = Channel_FirstChannelOf(cl);
647                                 while (cl2chan) {
648                                         chan = Channel_GetChannel(cl2chan);
649                                         if (Channel_IsMemberOf(chan, from))
650                                                 break;
651                                         cl2chan = Channel_NextChannelOf(cl, cl2chan);
652                                 }
653                                 if (!cl2chan) {
654                                         if (SendErrors && !IRC_WriteErrClient(
655                                             from, ERR_NOTONSAMECHANNEL_MSG,
656                                             Client_ID(from), Client_ID(cl)))
657                                                 return DISCONNECTED;
658                                         goto send_next_target;
659                                 }
660                         }
661
662                         if (SendErrors && (Client_Type(Client) != CLIENT_SERVER)
663                             && Client_HasMode(cl, 'a')) {
664                                 /* Target is away */
665                                 if (!IRC_WriteStrClient(from, RPL_AWAY_MSG,
666                                                         Client_ID(from),
667                                                         Client_ID(cl),
668                                                         Client_Away(cl)))
669                                         return DISCONNECTED;
670                         }
671                         if (Client_Conn(from) > NONE) {
672                                 Conn_UpdateIdle(Client_Conn(from));
673                         }
674                         if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
675                                                       Req->command, Client_ID(cl),
676                                                       message))
677                                 return DISCONNECTED;
678                 } else if (ForceType != CLIENT_SERVICE
679                            && (chan = Channel_Search(currentTarget))) {
680                         if (!Channel_Write(chan, from, Client, Req->command,
681                                            SendErrors, message))
682                                         return DISCONNECTED;
683                 } else if (ForceType != CLIENT_SERVICE
684                         /* $#: server/target mask, RFC 2812, sec. 3.3.1 */
685                            && strchr("$#", currentTarget[0])
686                            && strchr(currentTarget, '.')) {
687                         /* targetmask */
688                         if (!Send_Message_Mask(from, Req->command, currentTarget,
689                                                message, SendErrors))
690                                 return DISCONNECTED;
691                 } else {
692                         if (!SendErrors)
693                                 return CONNECTED;
694                         if (!IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG,
695                                                 Client_ID(from), currentTarget))
696                                 return DISCONNECTED;
697                 }
698
699         send_next_target:
700                 currentTarget = strtok_r(NULL, ",", &lastCurrentTarget);
701                 if (currentTarget)
702                         Conn_SetPenalty(Client_Conn(Client), 1);
703         }
704
705         return CONNECTED;
706 } /* Send_Message */
707
708 static bool
709 Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
710                   char * message, bool SendErrors)
711 {
712         CLIENT *cl;
713         bool client_match;
714         char *mask = targetMask + 1;
715         const char *check_wildcards;
716
717         cl = NULL;
718
719         if (!Client_HasMode(from, 'o')) {
720                 if (!SendErrors)
721                         return true;
722                 return IRC_WriteErrClient(from, ERR_NOPRIVILEGES_MSG,
723                                           Client_ID(from));
724         }
725
726         /*
727          * RFC 2812, sec. 3.3.1 requires that targetMask have at least one
728          * dot (".") and no wildcards ("*", "?") following the last one.
729          */
730         check_wildcards = strrchr(targetMask, '.');
731         assert(check_wildcards != NULL);
732         if (check_wildcards &&
733                 check_wildcards[strcspn(check_wildcards, "*?")])
734         {
735                 if (!SendErrors)
736                         return true;
737                 return IRC_WriteErrClient(from, ERR_WILDTOPLEVEL, targetMask);
738         }
739
740         /* #: hostmask, see RFC 2812, sec. 3.3.1 */
741         if (targetMask[0] == '#') {
742                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
743                         if (Client_Type(cl) != CLIENT_USER)
744                                 continue;
745                         client_match = MatchCaseInsensitive(mask, Client_Hostname(cl));
746                         if (client_match)
747                                 if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
748                                                 command, Client_ID(cl), message))
749                                         return false;
750                 }
751         } else {
752                 assert(targetMask[0] == '$'); /* $: server mask, see RFC 2812, sec. 3.3.1 */
753                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
754                         if (Client_Type(cl) != CLIENT_USER)
755                                 continue;
756                         client_match = MatchCaseInsensitive(mask,
757                                         Client_ID(Client_Introducer(cl)));
758                         if (client_match)
759                                 if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
760                                                 command, Client_ID(cl), message))
761                                         return false;
762                 }
763         }
764         return CONNECTED;
765 } /* Send_Message_Mask */
766
767 /* -eof- */