]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/irc.c
PRIVMSG/NOTICE: don't stop list processing on invalid target
[ngircd-alex.git] / src / ngircd / irc.c
index 0bfb3eed6a32e366a8d16d00a5a78348dc3ae647..5af79396ee0add0ef82f5dd8a7905ccdce912636 100644 (file)
@@ -1,20 +1,20 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2004 Alexander Barton <alex@barton.de>
+ * Copyright (c)2001-2012 Alexander Barton (alex@barton.de) and Contributors.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  * Please read the file COPYING, README and AUTHORS for more information.
- *
- * IRC commands
  */
 
-
 #include "portab.h"
 
-static char UNUSED id[] = "$Id: irc.c,v 1.132 2008/01/15 22:28:14 fw Exp $";
+/**
+ * @file
+ * IRC commands
+ */
 
 #include "imp.h"
 #include <assert.h>
@@ -22,10 +22,8 @@ static char UNUSED id[] = "$Id: irc.c,v 1.132 2008/01/15 22:28:14 fw Exp $";
 #include <string.h>
 
 #include "ngircd.h"
-#include "resolve.h"
 #include "conn-func.h"
 #include "conf.h"
-#include "client.h"
 #include "channel.h"
 #include "defines.h"
 #include "irc-write.h"
@@ -47,27 +45,68 @@ static bool Send_Message_Mask PARAMS((CLIENT *from, char *command,
                                      bool SendErrors));
 
 
+/**
+ * Check if a list limit is reached and inform client accordingly.
+ *
+ * @param From The client.
+ * @param Count Reply item count.
+ * @param Limit Reply limit.
+ * @param Name Name of the list.
+ * @return true if list limit has been reached; false otherwise.
+ */
+GLOBAL bool
+IRC_CheckListTooBig(CLIENT *From, const int Count, const int Limit,
+                   const char *Name)
+{
+       assert(From != NULL);
+       assert(Count >= 0);
+       assert(Limit > 0);
+       assert(Name != NULL);
+
+       if (Count < Limit)
+               return false;
+
+       (void)IRC_WriteStrClient(From,
+                                "NOTICE %s :%s list limit (%d) reached!",
+                                Client_ID(From), Name, Limit);
+       IRC_SetPenalty(From, 2);
+       return true;
+}
+
+
 GLOBAL bool
 IRC_ERROR( CLIENT *Client, REQUEST *Req )
 {
        assert( Client != NULL );
        assert( Req != NULL );
 
-       if( Req->argc < 1 ) Log( LOG_NOTICE, "Got ERROR from \"%s\"!", Client_Mask( Client ));
-       else Log( LOG_NOTICE, "Got ERROR from \"%s\": %s!", Client_Mask( Client ), Req->argv[0] );
+       if (Req->argc < 1)
+               Log(LOG_NOTICE, "Got ERROR from \"%s\"!",
+                   Client_Mask(Client));
+       else
+               Log(LOG_NOTICE, "Got ERROR from \"%s\": \"%s\"!",
+                   Client_Mask(Client), Req->argv[0]);
 
        return CONNECTED;
 } /* IRC_ERROR */
 
 
 /**
- * Kill client on request.
+ * Handler for the IRC "KILL" command.
+ *
  * This function implements the IRC command "KILL" wich is used to selectively
  * disconnect clients. It can be used by IRC operators and servers, for example
- * to "solve" nick collisions after netsplits.
+ * to "solve" nick collisions after netsplits. See RFC 2812 section 3.7.1.
+ *
  * Please note that this function is also called internally, without a real
- * KILL command beeing received over the network! Client is Client_ThisServer()
- * in this case. */
+ * KILL command being received over the network! Client is Client_ThisServer()
+ * in this case, and the prefix in Req is NULL.
+ *
+ * @param Client       The client from which this command has been received
+ *                     or Client_ThisServer() when generated interanlly.
+ * @param Req          Request structure with prefix and all parameters.
+ * @returns            CONNECTED or DISCONNECTED.
+ */
 GLOBAL bool
 IRC_KILL( CLIENT *Client, REQUEST *Req )
 {
@@ -75,55 +114,47 @@ IRC_KILL( CLIENT *Client, REQUEST *Req )
        char reason[COMMAND_LEN], *msg;
        CONN_ID my_conn, conn;
 
-       assert( Client != NULL );
-       assert( Req != NULL );
+       assert (Client != NULL);
+       assert (Req != NULL);
 
-       if(( Client_Type( Client ) != CLIENT_SERVER ) &&
-          ( ! Client_OperByMe( Client )))
-       {
-               /* The originator of the KILL is neither an IRC operator of
-                * this server nor a server. */
-               return IRC_WriteStrClient( Client, ERR_NOPRIVILEGES_MSG,
-                                          Client_ID( Client ));
-       }
+       if (Client_Type(Client) != CLIENT_SERVER && !Client_OperByMe(Client))
+               return IRC_WriteStrClient(Client, ERR_NOPRIVILEGES_MSG,
+                                         Client_ID(Client));
 
-       if( Req->argc != 2 )
-       {
-               /* This command requires exactly 2 parameters! */
-               return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG,
-                                          Client_ID( Client ), Req->command );
-       }
+       if (Req->argc != 2)
+               return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG,
+                                         Client_ID(Client), Req->command);
 
-       if( Req->prefix ) prefix = Client_Search( Req->prefix );
-       else prefix = Client;
-       if( ! prefix )
-       {
-               Log( LOG_WARNING, "Got KILL with invalid prefix: \"%s\"!",
-                    Req->prefix );
-               prefix = Client_ThisServer( );
+       /* Get prefix (origin); use the client if no prefix is given. */
+       if (Req->prefix)
+               prefix = Client_Search(Req->prefix);
+       else
+               prefix = Client;
+
+       /* Log a warning message and use this server as origin when the
+        * prefix (origin) is invalid. */
+       if (!prefix) {
+               Log(LOG_WARNING, "Got KILL with invalid prefix: \"%s\"!",
+                   Req->prefix );
+               prefix = Client_ThisServer();
        }
 
-       if( Client != Client_ThisServer( ))
-       {
-               /* This is a "real" KILL received from the network. */
-               Log( LOG_NOTICE|LOG_snotice, "Got KILL command from \"%s\" for \"%s\": %s",
-                    Client_Mask( prefix ), Req->argv[0], Req->argv[1] );
-       }
+       if (Client != Client_ThisServer())
+               Log(LOG_NOTICE|LOG_snotice,
+                   "Got KILL command from \"%s\" for \"%s\": %s",
+                   Client_Mask(prefix), Req->argv[0], Req->argv[1]);
 
-       /* Build reason string */
-       if( Client_Type( Client ) == CLIENT_USER )
-       {
-               /* Prefix the "reason" if the originator is a regular user,
-                * so users can't spoof KILLs of servers. */
-               snprintf( reason, sizeof( reason ), "KILLed by %s: %s",
-                         Client_ID( Client ), Req->argv[1] );
-       }
+       /* Build reason string: Prefix the "reason" if the originator is a
+        * regular user, so users can't spoof KILLs of servers. */
+       if (Client_Type(Client) == CLIENT_USER)
+               snprintf(reason, sizeof(reason), "KILLed by %s: %s",
+                        Client_ID(Client), Req->argv[1]);
        else
-               strlcpy( reason, Req->argv[1], sizeof( reason ));
+               strlcpy(reason, Req->argv[1], sizeof(reason));
 
        /* Inform other servers */
-       IRC_WriteStrServersPrefix( Client, prefix, "KILL %s :%s",
-                                  Req->argv[0], reason );
+       IRC_WriteStrServersPrefix(Client, prefix, "KILL %s :%s",
+                                 Req->argv[0], reason);
 
        /* Save ID of this connection */
        my_conn = Client_Conn( Client );
@@ -156,11 +187,15 @@ IRC_KILL( CLIENT *Client, REQUEST *Req )
                             Client_Type( c ), Req->argv[0] );
                }
 
-               /* Kill client NOW! */
+               /* Kill the client NOW:
+                *  - Close the local connection (if there is one),
+                *  - Destroy the CLIENT structure for remote clients.
+                * Note: Conn_Close() removes the CLIENT structure as well. */
                conn = Client_Conn( c );
-               Client_Destroy( c, NULL, reason, false );
-               if( conn > NONE )
-                       Conn_Close( conn, NULL, reason, true );
+               if(conn > NONE)
+                       Conn_Close(conn, NULL, reason, true);
+               else
+                       Client_Destroy(c, NULL, reason, false);
        }
        else
                Log( LOG_NOTICE, "Client with nick \"%s\" is unknown here.", Req->argv[0] );
@@ -314,6 +349,7 @@ static bool
 Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
 {
        CLIENT *cl, *from;
+       CL2CHAN *cl2chan;
        CHANNEL *chan;
        char *currentTarget = Req->argv[0];
        char *lastCurrentTarget = NULL;
@@ -400,12 +436,13 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
                        }
 
                        for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
-                               if (Client_Type(cl) != CLIENT_USER)
+                               if (Client_Type(cl) != CLIENT_USER &&
+                                   Client_Type(cl) != CLIENT_SERVICE)
                                        continue;
                                if (nick != NULL && host != NULL) {
                                        if (strcmp(nick, Client_ID(cl)) == 0 &&
                                            strcmp(user, Client_User(cl)) == 0 &&
-                                           strcasecmp(host, Client_Hostname(cl)) == 0)
+                                           strcasecmp(host, Client_HostnameCloaked(cl)) == 0)
                                                break;
                                        else
                                                continue;
@@ -413,7 +450,7 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
                                if (strcasecmp(user, Client_User(cl)) != 0)
                                        continue;
                                if (host != NULL && strcasecmp(host,
-                                               Client_Hostname(cl)) != 0)
+                                               Client_HostnameCloaked(cl)) != 0)
                                        continue;
                                if (server != NULL && strcasecmp(server,
                                                Client_ID(Client_Introducer(cl))) != 0)
@@ -432,11 +469,11 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
 #else
                        if (Client_Type(cl) != ForceType) {
 #endif
-                               if (!SendErrors)
-                                       return CONNECTED;
-                               return IRC_WriteStrClient(from, ERR_NOSUCHNICK_MSG,
-                                                         Client_ID(from),
-                                                         currentTarget);
+                               if (SendErrors && !IRC_WriteStrClient(
+                                   from, ERR_NOSUCHNICK_MSG,Client_ID(from),
+                                   currentTarget))
+                                       return DISCONNECTED;
+                               goto send_next_target;
                        }
 
 #ifndef STRICT_RFC
@@ -449,6 +486,23 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
                        }
 #endif
 
+                       if (Client_HasMode(cl, 'C')) {
+                               cl2chan = Channel_FirstChannelOf(cl);
+                               while (cl2chan) {
+                                       chan = Channel_GetChannel(cl2chan);
+                                       if (Channel_IsMemberOf(chan, from))
+                                               break;
+                                       cl2chan = Channel_NextChannelOf(cl, cl2chan);
+                               }
+                               if (!cl2chan) {
+                                       if (SendErrors && !IRC_WriteStrClient(
+                                           from, ERR_NOTONSAMECHANNEL_MSG,
+                                           Client_ID(from), Client_ID(cl)))
+                                               return DISCONNECTED;
+                                       goto send_next_target;
+                               }
+                       }
+
                        if (SendErrors && (Client_Type(Client) != CLIENT_SERVER)
                            && strchr(Client_Modes(cl), 'a')) {
                                /* Target is away */
@@ -466,18 +520,18 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
                                                      Req->argv[1]))
                                return DISCONNECTED;
                } else if (ForceType != CLIENT_SERVICE
+                          && (chan = Channel_Search(currentTarget))) {
+                       if (!Channel_Write(chan, from, Client, Req->command,
+                                          SendErrors, Req->argv[1]))
+                                       return DISCONNECTED;
+               } else if (ForceType != CLIENT_SERVICE
+                       /* $#: server/target mask, RFC 2812, sec. 3.3.1 */
                           && strchr("$#", currentTarget[0])
                           && strchr(currentTarget, '.')) {
                        /* targetmask */
                        if (!Send_Message_Mask(from, Req->command, currentTarget,
                                               Req->argv[1], SendErrors))
                                return DISCONNECTED;
-               } else if (ForceType != CLIENT_SERVICE
-                          && (chan = Channel_Search(currentTarget))) {
-                       /* channel */
-                       if (!Channel_Write(chan, from, Client, Req->command,
-                                          SendErrors, Req->argv[1]))
-                                       return DISCONNECTED;
                } else {
                        if (!SendErrors)
                                return CONNECTED;
@@ -486,7 +540,10 @@ Send_Message(CLIENT * Client, REQUEST * Req, int ForceType, bool SendErrors)
                                return DISCONNECTED;
                }
 
+       send_next_target:
                currentTarget = strtok_r(NULL, ",", &lastCurrentTarget);
+               if (currentTarget)
+                       Conn_SetPenalty(Client_Conn(Client), 1);
        }
 
        return CONNECTED;
@@ -500,6 +557,7 @@ Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
        CLIENT *cl;
        bool client_match;
        char *mask = targetMask + 1;
+       const char *check_wildcards;
 
        cl = NULL;
 
@@ -510,6 +568,21 @@ Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
                                          Client_ID(from));
        }
 
+       /*
+        * RFC 2812, sec. 3.3.1 requires that targetMask have at least one
+        * dot (".") and no wildcards ("*", "?") following the last one.
+        */
+       check_wildcards = strrchr(targetMask, '.');
+       assert(check_wildcards != NULL);
+       if (check_wildcards &&
+               check_wildcards[strcspn(check_wildcards, "*?")])
+       {
+               if (!SendErrors)
+                       return true;
+               return IRC_WriteStrClient(from, ERR_WILDTOPLEVEL, targetMask);
+       }
+
+       /* #: hostmask, see RFC 2812, sec. 3.3.1 */
        if (targetMask[0] == '#') {
                for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
                        if (Client_Type(cl) != CLIENT_USER)
@@ -521,6 +594,7 @@ Send_Message_Mask(CLIENT * from, char * command, char * targetMask,
                                        return false;
                }
        } else {
+               assert(targetMask[0] == '$'); /* $: server mask, see RFC 2812, sec. 3.3.1 */
                for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) {
                        if (Client_Type(cl) != CLIENT_USER)
                                continue;