]> arthur.barton.de Git - ngircd-alex.git/blob - src/ngircd/irc.c
968bcea6e58998a7d4d56096f727fa887bc7b37a
[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 /**
454  * Get pointer to a static string representing the connection "options".
455  *
456  * @param Idx Connection index.
457  * @return Pointer to static (global) string buffer.
458  */
459 static char *
460 #ifdef ZLIB
461 Option_String(CONN_ID Idx)
462 #else
463 Option_String(UNUSED CONN_ID Idx)
464 #endif
465 {
466         static char option_txt[8];
467 #ifdef ZLIB
468         UINT16 options;
469 #endif
470
471         assert(Idx != NONE);
472
473         options = Conn_Options(Idx);
474         strcpy(option_txt, "F");        /* No idea what this means, but the
475                                          * original ircd sends it ... */
476 #ifdef SSL_SUPPORT
477         if(options & CONN_SSL)          /* SSL encrypted link */
478                 strlcat(option_txt, "s", sizeof(option_txt));
479 #endif
480 #ifdef ZLIB
481         if(options & CONN_ZIP)          /* zlib compression enabled */
482                 strlcat(option_txt, "z", sizeof(option_txt));
483 #endif
484         LogDebug(" *** %d: %d = %s", Idx, options, option_txt);
485
486         return option_txt;
487 } /* Option_String */
488
489 static bool
490 Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
491 {
492         CLIENT *cl, *from;
493         CL2CHAN *cl2chan;
494         CHANNEL *chan;
495         char *currentTarget = Req->argv[0];
496         char *lastCurrentTarget = NULL;
497         char *message = NULL;
498
499         assert(Client != NULL);
500         assert(Req != NULL);
501
502         if (Req->argc == 0) {
503                 if (!SendErrors)
504                         return CONNECTED;
505                 return IRC_WriteErrClient(Client, ERR_NORECIPIENT_MSG,
506                                           Client_ID(Client), Req->command);
507         }
508         if (Req->argc == 1) {
509                 if (!SendErrors)
510                         return CONNECTED;
511                 return IRC_WriteErrClient(Client, ERR_NOTEXTTOSEND_MSG,
512                                           Client_ID(Client));
513         }
514         if (Req->argc > 2) {
515                 if (!SendErrors)
516                         return CONNECTED;
517                 return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG,
518                                           Client_ID(Client), Req->command);
519         }
520
521         if (Client_Type(Client) == CLIENT_SERVER)
522                 from = Client_Search(Req->prefix);
523         else
524                 from = Client;
525         if (!from)
526                 return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG,
527                                           Client_ID(Client), Req->prefix);
528
529 #ifdef ICONV
530         if (Client_Conn(Client) > NONE)
531                 message = Conn_EncodingFrom(Client_Conn(Client), Req->argv[1]);
532         else
533 #endif
534                 message = Req->argv[1];
535
536         /* handle msgtarget = msgto *("," msgto) */
537         currentTarget = strtok_r(currentTarget, ",", &lastCurrentTarget);
538         ngt_UpperStr(Req->command);
539
540         while (currentTarget) {
541                 /* Check for and handle valid <msgto> of form:
542                  * RFC 2812 2.3.1:
543                  *   msgto =  channel / ( user [ "%" host ] "@" servername )
544                  *   msgto =/ ( user "%" host ) / targetmask
545                  *   msgto =/ nickname / ( nickname "!" user "@" host )
546                  */
547                 if (strchr(currentTarget, '!') == NULL)
548                         /* nickname */
549                         cl = Client_Search(currentTarget);
550                 else
551                         cl = NULL;
552
553                 if (cl == NULL) {
554                         /* If currentTarget isn't a nickname check for:
555                          * user ["%" host] "@" servername
556                          * user "%" host
557                          * nickname "!" user "@" host
558                          */
559                         char target[COMMAND_LEN];
560                         char * nick = NULL;
561                         char * user = NULL;
562                         char * host = NULL;
563                         char * server = NULL;
564
565                         strlcpy(target, currentTarget, COMMAND_LEN);
566                         server = strchr(target, '@');
567                         if (server) {
568                                 *server = '\0';
569                                 server++;
570                         }
571                         host = strchr(target, '%');
572                         if (host) {
573                                 *host = '\0';
574                                 host++;
575                         }
576                         user = strchr(target, '!');
577                         if (user) {
578                                 /* msgto form: nick!user@host */
579                                 *user = '\0';
580                                 user++;
581                                 nick = target;
582                                 host = server; /* not "@server" but "@host" */
583                         } else {
584                                 user = target;
585                         }
586
587                         for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
588                                 if (Client_Type(cl) != CLIENT_USER &&
589                                     Client_Type(cl) != CLIENT_SERVICE)
590                                         continue;
591                                 if (nick != NULL && host != NULL) {
592                                         if (strcasecmp(nick, Client_ID(cl)) == 0 &&
593                                             strcasecmp(user, Client_User(cl)) == 0 &&
594                                             strcasecmp(host, Client_HostnameDisplayed(cl)) == 0)
595                                                 break;
596                                         else
597                                                 continue;
598                                 }
599                                 if (strcasecmp(user, Client_User(cl)) != 0)
600                                         continue;
601                                 if (host != NULL && strcasecmp(host,
602                                                 Client_HostnameDisplayed(cl)) != 0)
603                                         continue;
604                                 if (server != NULL && strcasecmp(server,
605                                                 Client_ID(Client_Introducer(cl))) != 0)
606                                         continue;
607                                 break;
608                         }
609                 }
610
611                 if (cl) {
612                         /* Target is a user, enforce type */
613 #ifndef STRICT_RFC
614                         if (Client_Type(cl) != ForceType &&
615                             !(ForceType == CLIENT_USER &&
616                               (Client_Type(cl) == CLIENT_USER ||
617                                Client_Type(cl) == CLIENT_SERVICE))) {
618 #else
619                         if (Client_Type(cl) != ForceType) {
620 #endif
621                                 if (SendErrors && !IRC_WriteErrClient(
622                                     from, ERR_NOSUCHNICK_MSG,Client_ID(from),
623                                     currentTarget))
624                                         return DISCONNECTED;
625                                 goto send_next_target;
626                         }
627
628 #ifndef STRICT_RFC
629                         if (ForceType == CLIENT_SERVICE &&
630                             (Conn_Options(Client_Conn(Client_NextHop(cl)))
631                              & CONN_RFC1459)) {
632                                 /* SQUERY command but RFC 1459 link: convert
633                                  * request to PRIVMSG command */
634                                 Req->command = "PRIVMSG";
635                         }
636 #endif
637                         if (Client_HasMode(cl, 'b') &&
638                             !Client_HasMode(from, 'R') &&
639                             !Client_HasMode(from, 'o') &&
640                             !(Client_Type(from) == CLIENT_SERVER) &&
641                             !(Client_Type(from) == CLIENT_SERVICE)) {
642                                 if (SendErrors && !IRC_WriteErrClient(from,
643                                                 ERR_NONONREG_MSG,
644                                                 Client_ID(from), Client_ID(cl)))
645                                         return DISCONNECTED;
646                                 goto send_next_target;
647                         }
648
649                         if (Client_HasMode(cl, 'C')) {
650                                 cl2chan = Channel_FirstChannelOf(cl);
651                                 while (cl2chan) {
652                                         chan = Channel_GetChannel(cl2chan);
653                                         if (Channel_IsMemberOf(chan, from))
654                                                 break;
655                                         cl2chan = Channel_NextChannelOf(cl, cl2chan);
656                                 }
657                                 if (!cl2chan) {
658                                         if (SendErrors && !IRC_WriteErrClient(
659                                             from, ERR_NOTONSAMECHANNEL_MSG,
660                                             Client_ID(from), Client_ID(cl)))
661                                                 return DISCONNECTED;
662                                         goto send_next_target;
663                                 }
664                         }
665
666                         if (SendErrors && (Client_Type(Client) != CLIENT_SERVER)
667                             && Client_HasMode(cl, 'a')) {
668                                 /* Target is away */
669                                 if (!IRC_WriteStrClient(from, RPL_AWAY_MSG,
670                                                         Client_ID(from),
671                                                         Client_ID(cl),
672                                                         Client_Away(cl)))
673                                         return DISCONNECTED;
674                         }
675                         if (Client_Conn(from) > NONE) {
676                                 Conn_UpdateIdle(Client_Conn(from));
677                         }
678                         if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
679                                                       Req->command, Client_ID(cl),
680                                                       message))
681                                 return DISCONNECTED;
682                 } else if (ForceType != CLIENT_SERVICE
683                            && (chan = Channel_Search(currentTarget))) {
684                         if (!Channel_Write(chan, from, Client, Req->command,
685                                            SendErrors, message))
686                                         return DISCONNECTED;
687                 } else if (ForceType != CLIENT_SERVICE
688                         /* $#: server/target mask, RFC 2812, sec. 3.3.1 */
689                            && strchr("$#", currentTarget[0])
690                            && strchr(currentTarget, '.')) {
691                         /* targetmask */
692                         if (!Send_Message_Mask(from, Req->command, currentTarget,
693                                                message, SendErrors))
694                                 return DISCONNECTED;
695                 } else {
696                         if (!SendErrors)
697                                 return CONNECTED;
698                         if (!IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG,
699                                                 Client_ID(from), currentTarget))
700                                 return DISCONNECTED;
701                 }
702
703         send_next_target:
704                 currentTarget = strtok_r(NULL, ",", &lastCurrentTarget);
705                 if (currentTarget)
706                         Conn_SetPenalty(Client_Conn(Client), 1);
707         }
708
709         return CONNECTED;
710 } /* Send_Message */
711
712 static bool
713 Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
714                   char * message, bool SendErrors)
715 {
716         CLIENT *cl;
717         bool client_match;
718         char *mask = targetMask + 1;
719         const char *check_wildcards;
720
721         cl = NULL;
722
723         if (!Client_HasMode(from, 'o')) {
724                 if (!SendErrors)
725                         return true;
726                 return IRC_WriteErrClient(from, ERR_NOPRIVILEGES_MSG,
727                                           Client_ID(from));
728         }
729
730         /*
731          * RFC 2812, sec. 3.3.1 requires that targetMask have at least one
732          * dot (".") and no wildcards ("*", "?") following the last one.
733          */
734         check_wildcards = strrchr(targetMask, '.');
735         assert(check_wildcards != NULL);
736         if (check_wildcards &&
737                 check_wildcards[strcspn(check_wildcards, "*?")])
738         {
739                 if (!SendErrors)
740                         return true;
741                 return IRC_WriteErrClient(from, ERR_WILDTOPLEVEL, targetMask);
742         }
743
744         /* #: hostmask, see RFC 2812, sec. 3.3.1 */
745         if (targetMask[0] == '#') {
746                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
747                         if (Client_Type(cl) != CLIENT_USER)
748                                 continue;
749                         client_match = MatchCaseInsensitive(mask, Client_Hostname(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         } else {
756                 assert(targetMask[0] == '$'); /* $: server mask, see RFC 2812, sec. 3.3.1 */
757                 for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
758                         if (Client_Type(cl) != CLIENT_USER)
759                                 continue;
760                         client_match = MatchCaseInsensitive(mask,
761                                         Client_ID(Client_Introducer(cl)));
762                         if (client_match)
763                                 if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s",
764                                                 command, Client_ID(cl), message))
765                                         return false;
766                 }
767         }
768         return CONNECTED;
769 } /* Send_Message_Mask */
770
771 /* -eof- */