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