]> arthur.barton.de Git - ngircd-alex.git/blobdiff - src/ngircd/irc-mode.c
ngIRCd Release 27
[ngircd-alex.git] / src / ngircd / irc-mode.c
index 88d2294bad203efe377820bc8f9aaee822056544..89a070429afef1b8645e30c64820db12ad26f8f3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ngIRCd -- The Next Generation IRC Daemon
- * Copyright (c)2001-2013 Alexander Barton (alex@barton.de) and Contributors.
+ * Copyright (c)2001-2023 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
  * IRC commands for mode changes (like MODE, AWAY, etc.)
  */
 
-#include "imp.h"
 #include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "defines.h"
 #include "conn.h"
 #include "channel.h"
 #include "irc-macros.h"
@@ -33,7 +31,6 @@
 #include "messages.h"
 #include "conf.h"
 
-#include "exp.h"
 #include "irc-mode.h"
 
 static bool Client_Mode PARAMS((CLIENT *Client, REQUEST *Req, CLIENT *Origin,
@@ -69,9 +66,15 @@ IRC_MODE( CLIENT *Client, REQUEST *Req )
        assert(Client != NULL);
        assert(Req != NULL);
 
-       _IRC_ARGC_GE_OR_RETURN_(Client, Req, 1)
        _IRC_GET_SENDER_OR_RETURN_(origin, Req, Client)
 
+       /* Test for "fake" MODE commands injected by this local instance,
+        * for example when handling the "DefaultUserModes" settings.
+        * This doesn't harm real commands, because prefixes of regular
+        * clients are checked in Validate_Prefix() and can't be faked. */
+       if (Req->prefix && Client_Search(Req->prefix) == Client_ThisServer())
+               Client = Client_Search(Req->prefix);
+
        /* Channel or user mode? */
        cl = NULL; chan = NULL;
        if (Client_IsValidNick(Req->argv[0]))
@@ -203,6 +206,7 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                case 'b': /* Block private msgs */
                case 'C': /* Only messages from clients sharing a channel */
                case 'i': /* Invisible */
+               case 'I': /* Hide channel list from WHOIS */
                case 's': /* Server messages */
                case 'w': /* Wallops messages */
                        x[0] = *mode_ptr;
@@ -224,11 +228,13 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                        else
                                x[0] = 'B';
                        break;
-               case 'c': /* Receive connect notices
-                          * (only settable by IRC operators!) */
+               case 'c': /* Receive connect notices */
+               case 'q': /* KICK-protected user */
+               case 'F': /* disable flood protection */
+                         /* (only settable by IRC operators!) */
                        if (!set || Client_Type(Client) == CLIENT_SERVER
-                           || Client_OperByMe(Origin))
-                               x[0] = 'c';
+                           || Client_HasMode(Origin, 'o'))
+                               x[0] = *mode_ptr;
                        else
                                ok = IRC_WriteErrClient(Origin,
                                                        ERR_NOPRIVILEGES_MSG,
@@ -236,22 +242,12 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                        break;
                case 'o': /* IRC operator (only unsettable!) */
                        if (!set || Client_Type(Client) == CLIENT_SERVER) {
-                               Client_SetOperByMe(Target, false);
                                x[0] = 'o';
                        } else
                                ok = IRC_WriteErrClient(Origin,
                                                        ERR_NOPRIVILEGES_MSG,
                                                        Client_ID(Origin));
                        break;
-               case 'q': /* KICK-protected user */
-                       if (!set || Client_Type(Client) == CLIENT_SERVER
-                           || Client_OperByMe(Origin))
-                               x[0] = 'q';
-                       else
-                               ok = IRC_WriteErrClient(Origin,
-                                                       ERR_NOPRIVILEGES_MSG,
-                                                       Client_ID(Origin));
-                       break;
                case 'r': /* Restricted (only settable) */
                        if (set || Client_Type(Client) == CLIENT_SERVER)
                                x[0] = 'r';
@@ -275,7 +271,7 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                                                        Client_ID(Origin));
                        else if (!set || Conf_CloakHostModeX[0]
                                 || Client_Type(Client) == CLIENT_SERVER
-                                || Client_OperByMe(Client)) {
+                                || Client_HasMode(Origin, 'o')) {
                                x[0] = 'x';
                                send_RPL_HOSTHIDDEN_MSG = true;
                        } else
@@ -285,7 +281,7 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                        break;
                default:
                        if (Client_Type(Client) != CLIENT_SERVER) {
-                               Log(LOG_DEBUG,
+                               LogDebug(
                                    "Unknown mode \"%c%c\" from \"%s\"!?",
                                    set ? '+' : '-', *mode_ptr,
                                    Client_ID(Origin));
@@ -296,7 +292,7 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                                                        *mode_ptr);
                                x[0] = '\0';
                        } else {
-                               Log(LOG_DEBUG,
+                               LogDebug(
                                    "Handling unknown mode \"%c%c\" from \"%s\" for \"%s\" ...",
                                    set ? '+' : '-', *mode_ptr,
                                    Client_ID(Origin), Client_ID(Target));
@@ -367,7 +363,6 @@ Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target )
                         Client_Modes(Target));
        }
 
-       IRC_SetPenalty(Client, 1);
        return ok;
 } /* Client_Mode */
 
@@ -384,37 +379,44 @@ Channel_Mode_Answer_Request(CLIENT *Origin, CHANNEL *Channel)
        char the_modes[COMMAND_LEN], the_args[COMMAND_LEN], argadd[CLIENT_PASS_LEN];
        const char *mode_ptr;
 
-       /* Member or not? -- That's the question! */
-       if (!Channel_IsMemberOf(Channel, Origin))
-               return IRC_WriteStrClient(Origin, RPL_CHANNELMODEIS_MSG,
-                       Client_ID(Origin), Channel_Name(Channel), Channel_Modes(Channel));
-
-       /* The sender is a member: generate extended reply */
-       strlcpy(the_modes, Channel_Modes(Channel), sizeof(the_modes));
-       mode_ptr = the_modes;
-       the_args[0] = '\0';
-
-       while(*mode_ptr) {
-               switch(*mode_ptr) {
-               case 'l':
-                       snprintf(argadd, sizeof(argadd), " %lu", Channel_MaxUsers(Channel));
-                       strlcat(the_args, argadd, sizeof(the_args));
-                       break;
-               case 'k':
-                       strlcat(the_args, " ", sizeof(the_args));
-                       strlcat(the_args, Channel_Key(Channel), sizeof(the_args));
-                       break;
+       if (!Channel_IsMemberOf(Channel, Origin)) {
+               /* Not a member: "simple" mode reply */
+               if (!IRC_WriteStrClient(Origin, RPL_CHANNELMODEIS_MSG,
+                                       Client_ID(Origin), Channel_Name(Channel),
+                                       Channel_Modes(Channel)))
+                       return DISCONNECTED;
+       } else {
+               /* The sender is a member: generate extended reply */
+               strlcpy(the_modes, Channel_Modes(Channel), sizeof(the_modes));
+               mode_ptr = the_modes;
+               the_args[0] = '\0';
+
+               while(*mode_ptr) {
+                       switch(*mode_ptr) {
+                       case 'l':
+                               snprintf(argadd, sizeof(argadd), " %lu",
+                                        Channel_MaxUsers(Channel));
+                               strlcat(the_args, argadd, sizeof(the_args));
+                               break;
+                       case 'k':
+                               strlcat(the_args, " ", sizeof(the_args));
+                               strlcat(the_args, Channel_Key(Channel),
+                                       sizeof(the_args));
+                               break;
+                       }
+                       mode_ptr++;
                }
-               mode_ptr++;
+               if (the_args[0])
+                       strlcat(the_modes, the_args, sizeof(the_modes));
+
+               if (!IRC_WriteStrClient(Origin, RPL_CHANNELMODEIS_MSG,
+                                       Client_ID(Origin), Channel_Name(Channel),
+                                       the_modes))
+                       return DISCONNECTED;
        }
-       if (the_args[0])
-               strlcat(the_modes, the_args, sizeof(the_modes));
 
-       if (!IRC_WriteStrClient(Origin, RPL_CHANNELMODEIS_MSG,
-                               Client_ID(Origin), Channel_Name(Channel),
-                               the_modes))
-               return DISCONNECTED;
 #ifndef STRICT_RFC
+       /* Channel creation time */
        if (!IRC_WriteStrClient(Origin, RPL_CREATIONTIME_MSG,
                                  Client_ID(Origin), Channel_Name(Channel),
                                  Channel_CreationTime(Channel)))
@@ -456,7 +458,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
 
        /* Check if origin is oper and opers can use mode */
        use_servermode = Conf_OperServerMode;
-       if(Client_OperByMe(Client) && Conf_OperCanMode) {
+       if(Client_HasMode(Client, 'o') && Conf_OperCanMode) {
                is_oper = true;
        }
 
@@ -573,11 +575,13 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                        Client_ID(Origin), Channel_Name(Channel));
                                goto chan_exit;
                        }
+                       /* fall through */
                case 'i': /* Invite only */
                case 'V': /* Invite disallow */
                case 'M': /* Only identified nicks can write */
                case 'm': /* Moderated */
                case 'n': /* Only members can write */
+               case 'N': /* Can't change nick while on this channel */
                case 'Q': /* No kicks */
                case 't': /* Topic locked */
                        if(is_oper || is_machine || is_owner ||
@@ -593,42 +597,56 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                goto chan_exit;
                        if (!set) {
                                if (is_oper || is_machine || is_owner ||
-                                   is_admin || is_op || is_halfop)
+                                   is_admin || is_op || is_halfop) {
                                        x[0] = *mode_ptr;
-                               else
+                                       if (Channel_HasMode(Channel, 'k'))
+                                               strlcpy(argadd, "*", sizeof(argadd));
+                                       if (arg_arg > mode_arg)
+                                               arg_arg++;
+                               } else
                                        connected = IRC_WriteErrClient(Origin,
                                                ERR_CHANOPRIVSNEEDED_MSG,
                                                Client_ID(Origin),
                                                Channel_Name(Channel));
                                break;
                        }
-                       if (arg_arg > mode_arg) {
-                               if (is_oper || is_machine || is_owner ||
-                                   is_admin || is_op || is_halfop) {
-                                       Channel_ModeDel(Channel, 'k');
-                                       Channel_SetKey(Channel,
-                                                      Req->argv[arg_arg]);
-                                       strlcpy(argadd, Channel_Key(Channel),
-                                               sizeof(argadd));
-                                       x[0] = *mode_ptr;
-                               } else {
+                       if (arg_arg <= mode_arg) {
+                               if (is_machine)
+                                       Log(LOG_ERR,
+                                           "Got MODE +k without key for \"%s\" from \"%s\"! Ignored.",
+                                           Channel_Name(Channel), Client_ID(Origin));
+                               else
                                        connected = IRC_WriteErrClient(Origin,
-                                               ERR_CHANOPRIVSNEEDED_MSG,
+                                               ERR_NEEDMOREPARAMS_MSG,
+                                               Client_ID(Origin), Req->command);
+                               goto chan_exit;
+                       }
+                       if (!Req->argv[arg_arg][0] || strchr(Req->argv[arg_arg], ' ')) {
+                               if (is_machine)
+                                       Log(LOG_ERR,
+                                           "Got invalid key on MODE +k for \"%s\" from \"%s\"! Ignored.",
+                                           Channel_Name(Channel), Client_ID(Origin));
+                               else
+                                       connected = IRC_WriteErrClient(Origin,
+                                              ERR_INVALIDMODEPARAM_MSG,
                                                Client_ID(Origin),
-                                               Channel_Name(Channel));
-                               }
-                               Req->argv[arg_arg][0] = '\0';
-                               arg_arg++;
+                                               Channel_Name(Channel), 'k');
+                               goto chan_exit;
+                       }
+                       if (is_oper || is_machine || is_owner ||
+                           is_admin || is_op || is_halfop) {
+                               Channel_ModeDel(Channel, 'k');
+                               Channel_SetKey(Channel, Req->argv[arg_arg]);
+                               strlcpy(argadd, Channel_Key(Channel), sizeof(argadd));
+                               x[0] = *mode_ptr;
                        } else {
-#ifdef STRICT_RFC
-                               /* Only send error message in "strict" mode,
-                                * this is how ircd2.11 and others behave ... */
                                connected = IRC_WriteErrClient(Origin,
-                                       ERR_NEEDMOREPARAMS_MSG,
-                                       Client_ID(Origin), Req->command);
-#endif
-                               goto chan_exit;
+                                       ERR_CHANOPRIVSNEEDED_MSG,
+                                       Client_ID(Origin),
+                                       Channel_Name(Channel));
                        }
+                       Req->argv[arg_arg][0] = '\0';
+                       arg_arg++;
                        break;
                case 'l': /* Member limit */
                        if (Mode_Limit_Reached(Client, mode_arg_count++))
@@ -644,35 +662,44 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                                Channel_Name(Channel));
                                break;
                        }
-                       if (arg_arg > mode_arg) {
-                               if (is_oper || is_machine || is_owner ||
-                                   is_admin || is_op || is_halfop) {
-                                       l = atol(Req->argv[arg_arg]);
-                                       if (l > 0 && l < 0xFFFF) {
-                                               Channel_ModeDel(Channel, 'l');
-                                               Channel_SetMaxUsers(Channel, l);
-                                               snprintf(argadd, sizeof(argadd),
-                                                        "%ld", l);
-                                               x[0] = *mode_ptr;
-                                       }
-                               } else {
+                       if (arg_arg <= mode_arg) {
+                               if (is_machine)
+                                       Log(LOG_ERR,
+                                           "Got MODE +l without limit for \"%s\" from \"%s\"! Ignored.",
+                                           Channel_Name(Channel), Client_ID(Origin));
+                               else
                                        connected = IRC_WriteErrClient(Origin,
-                                               ERR_CHANOPRIVSNEEDED_MSG,
+                                               ERR_NEEDMOREPARAMS_MSG,
+                                               Client_ID(Origin), Req->command);
+                               goto chan_exit;
+                       }
+                       l = atol(Req->argv[arg_arg]);
+                       if (l <= 0 || l >= 0xFFFF) {
+                               if (is_machine)
+                                       Log(LOG_ERR,
+                                           "Got MODE +l with invalid limit for \"%s\" from \"%s\"! Ignored.",
+                                           Channel_Name(Channel), Client_ID(Origin));
+                               else
+                                       connected = IRC_WriteErrClient(Origin,
+                                               ERR_INVALIDMODEPARAM_MSG,
                                                Client_ID(Origin),
-                                               Channel_Name(Channel));
-                               }
-                               Req->argv[arg_arg][0] = '\0';
-                               arg_arg++;
+                                               Channel_Name(Channel), 'l');
+                               goto chan_exit;
+                       }
+                       if (is_oper || is_machine || is_owner ||
+                           is_admin || is_op || is_halfop) {
+                               Channel_ModeDel(Channel, 'l');
+                               Channel_SetMaxUsers(Channel, l);
+                               snprintf(argadd, sizeof(argadd), "%ld", l);
+                               x[0] = *mode_ptr;
                        } else {
-#ifdef STRICT_RFC
-                               /* Only send error message in "strict" mode,
-                                * this is how ircd2.11 and others behave ... */
                                connected = IRC_WriteErrClient(Origin,
-                                       ERR_NEEDMOREPARAMS_MSG,
-                                       Client_ID(Origin), Req->command);
-#endif
-                               goto chan_exit;
+                                       ERR_CHANOPRIVSNEEDED_MSG,
+                                       Client_ID(Origin),
+                                       Channel_Name(Channel));
                        }
+                       Req->argv[arg_arg][0] = '\0';
+                       arg_arg++;
                        break;
                case 'O': /* IRC operators only */
                        if (set) {
@@ -714,6 +741,14 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                        break;
                /* --- Channel user modes --- */
                case 'q': /* Owner */
+                       if(!is_oper && !is_machine && !is_owner) {
+                               connected = IRC_WriteErrClient(Origin,
+                                       ERR_CHANOPPRIVTOOLOW_MSG,
+                                       Client_ID(Origin),
+                                       Channel_Name(Channel));
+                               goto chan_exit;
+                       }
+                       /* fall through */
                case 'a': /* Channel admin */
                        if(!is_oper && !is_machine && !is_owner && !is_admin) {
                                connected = IRC_WriteErrClient(Origin,
@@ -722,6 +757,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                        Channel_Name(Channel));
                                goto chan_exit;
                        }
+                       /* fall through */
                case 'o': /* Channel operator */
                        if(!is_oper && !is_machine && !is_owner &&
                           !is_admin && !is_op) {
@@ -731,6 +767,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                        Channel_Name(Channel));
                                goto chan_exit;
                        }
+                       /* fall through */
                case 'h': /* Half Op */
                        if(!is_oper && !is_machine && !is_owner &&
                           !is_admin && !is_op) {
@@ -740,6 +777,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                        Channel_Name(Channel));
                                goto chan_exit;
                        }
+                       /* fall through */
                case 'v': /* Voice */
                        if (arg_arg > mode_arg) {
                                if (is_oper || is_machine || is_owner ||
@@ -816,7 +854,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                        break;
                default:
                        if (Client_Type(Client) != CLIENT_SERVER) {
-                               Log(LOG_DEBUG,
+                               LogDebug(
                                    "Unknown mode \"%c%c\" from \"%s\" on %s!?",
                                    set ? '+' : '-', *mode_ptr,
                                    Client_ID(Origin), Channel_Name(Channel));
@@ -826,7 +864,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                                        Channel_Name(Channel));
                                x[0] = '\0';
                        } else {
-                               Log(LOG_DEBUG,
+                               LogDebug(
                                    "Handling unknown mode \"%c%c\" from \"%s\" on %s ...",
                                    set ? '+' : '-', *mode_ptr,
                                    Client_ID(Origin), Channel_Name(Channel));
@@ -897,7 +935,7 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                if (Client_Type(Client) == CLIENT_SERVER) {
                        /* MODE requests for local channels from other servers
                         * are definitely invalid! */
-                       if (Channel_IsLocal(Channel)) {
+                       if (Channel_IsLocal(Channel) && Client != Client_ThisServer()) {
                                Log(LOG_ALERT, "Got remote MODE command for local channel!? Ignored.");
                                return CONNECTED;
                        }
@@ -928,7 +966,6 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
                }
        }
 
-       IRC_SetPenalty(Client, 1);
        return connected;
 } /* Channel_Mode */
 
@@ -945,8 +982,6 @@ IRC_AWAY( CLIENT *Client, REQUEST *Req )
        assert (Client != NULL);
        assert (Req != NULL);
 
-       _IRC_ARGC_LE_OR_RETURN_(Client, Req, 1)
-
        if (Req->argc == 1 && Req->argv[0][0]) {
                Client_SetAway(Client, Req->argv[0]);
                Client_ModeAdd(Client, 'a');
@@ -1014,15 +1049,15 @@ Add_To_List(char what, CLIENT *Prefix, CLIENT *Client, CHANNEL *Channel,
 
        switch (what) {
                case 'I':
-                       if (!Channel_AddInvite(Channel, mask, false))
+                       if (!Channel_AddInvite(Channel, mask, false, Client_ID(Client)))
                                return CONNECTED;
                        break;
                case 'b':
-                       if (!Channel_AddBan(Channel, mask))
+                       if (!Channel_AddBan(Channel, mask, Client_ID(Client)))
                                return CONNECTED;
                        break;
                case 'e':
-                       if (!Channel_AddExcept(Channel, mask))
+                       if (!Channel_AddExcept(Channel, mask, Client_ID(Client)))
                                return CONNECTED;
                        break;
        }