/** * Handler for the IRC "MODE" command. * * This function detects whether user or channel modes should be modified * and calls the appropriate sub-functions. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_MODE( CLIENT *Client, REQUEST *Req ) { CLIENT *cl, *origin; CHANNEL *chan; assert(Client != NULL); assert(Req != NULL); _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])) cl = Client_Search(Req->argv[0]); if (Channel_IsValidName(Req->argv[0])) chan = Channel_Search(Req->argv[0]); if (cl) return Client_Mode(Client, Req, origin, cl); if (chan) return Channel_Mode(Client, Req, origin, chan); /* No target found! */ return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->argv[0]); } /* IRC_MODE */
/** * Check that the originator of a request is an IRC operator and allowed * to administer this server. * * @param CLient Client from which the command has been received. * @param Req Request structure. * @return CLIENT structure of the client that initiated the command or * NULL if client is not allowed to execute operator commands. */ GLOBAL CLIENT * Op_Check(CLIENT * Client, REQUEST * Req) { CLIENT *c; assert(Client != NULL); assert(Req != NULL); if (Client_Type(Client) == CLIENT_SERVER && Req->prefix) c = Client_Search(Req->prefix); else c = Client; if (!c) return NULL; if (Client_Type(Client) == CLIENT_SERVER && Client_Type(c) == CLIENT_SERVER) return c; if (!Client_HasMode(c, 'o')) return NULL; if (Client_Conn(c) <= NONE && !Conf_AllowRemoteOper) return NULL; /* The client is an local IRC operator, or this server is configured * to trust remote operators. */ return c; } /* Op_Check */
/** * Kill an client identified by its nick name. * * Please note that after killig a client, its CLIENT cond CONNECTION * structures are invalid. So the caller must make sure on its own not to * access data of probably killed clients after calling this function! * * @param Client The client from which the command leading to the KILL has * been received, or NULL. The KILL will no be forwarded in this * direction. Only relevant when From is set, too. * @param From The client from which the command originated, or NULL for the local server. * @param Nick The nick name to kill. * @param Reason Text to send as reason to the client and other servers. */ GLOBAL bool IRC_KillClient(CLIENT *Client, CLIENT *From, const char *Nick, const char *Reason) { const char *msg; CONN_ID my_conn, conn; CLIENT *c; /* Do we know such a client in the network? */ c = Client_Search(Nick); if (!c) { LogDebug("Client with nick \"%s\" is unknown, not forwaring.", Nick); return CONNECTED; } /* Inform other servers */ IRC_WriteStrServersPrefix(From ? Client : NULL, From ? From : Client_ThisServer(), "KILL %s :%s", Nick, Reason); if (Client_Type(c) != CLIENT_USER && Client_Type(c) != CLIENT_GOTNICK) { /* Target of this KILL is not a regular user, this is * invalid! So we ignore this case if we received a * regular KILL from the network and try to kill the * client/connection anyway (but log an error!) if the * origin is the local server. */ if (Client != Client_ThisServer()) { /* Invalid KILL received from remote */ if (Client_Type(c) == CLIENT_SERVER) msg = ERR_CANTKILLSERVER_MSG; else msg = ERR_NOPRIVILEGES_MSG; return IRC_WriteErrClient(Client, msg, Client_ID(Client)); } Log(LOG_ERR, "Got KILL for invalid client type: %d, \"%s\"!", Client_Type(c), Nick); } /* Save ID of this connection */ my_conn = Client_Conn(Client); /* 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); if(conn > NONE) Conn_Close(conn, NULL, Reason, true); else Client_Destroy(c, NULL, Reason, false); /* Are we still connected or were we killed, too? */ if (my_conn > NONE && Conn_GetClient(my_conn)) return CONNECTED; else return DISCONNECTED; }
static bool try_kick(CLIENT *peer, CLIENT* from, const char *nick, const char *channel, const char *reason) { CLIENT *target = Client_Search(nick); if (!target) return IRC_WriteStrClient(from, ERR_NOSUCHNICK_MSG, Client_ID(from), nick); Channel_Kick(peer, target, from, channel, reason); return true; }
/** * Handler for the IRC "MODE" command. * * See RFC 2812 section 3.1.5 ("user mode message") and section 3.2.3 * ("channel mode message"), and RFC 2811 section 4 ("channel modes"). * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_MODE( CLIENT *Client, REQUEST *Req ) { CLIENT *cl, *origin; CHANNEL *chan; assert(Client != NULL); assert(Req != NULL); /* No parameters? */ if (Req->argc < 1) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); /* Origin for answers */ if (Client_Type(Client) == CLIENT_SERVER) { origin = Client_Search(Req->prefix); if (!origin) return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); } else origin = Client; /* Channel or user mode? */ cl = NULL; chan = NULL; if (Client_IsValidNick(Req->argv[0])) cl = Client_Search(Req->argv[0]); if (Channel_IsValidName(Req->argv[0])) chan = Channel_Search(Req->argv[0]); if (cl) return Client_Mode(Client, Req, origin, cl); if (chan) return Channel_Mode(Client, Req, origin, chan); /* No target found! */ return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->argv[0]); } /* IRC_MODE */
/** * Handler for the IRC "QUIT" command. * * See RFC 2812, 3.1.7 "Quit", and RFC 2813, 4.1.5 "Quit". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_QUIT( CLIENT *Client, REQUEST *Req ) { CLIENT *target; char quitmsg[LINE_LEN]; assert(Client != NULL); assert(Req != NULL); /* Wrong number of arguments? */ if (Req->argc > 1) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); if (Req->argc == 1) strlcpy(quitmsg, Req->argv[0], sizeof quitmsg); if (Client_Type(Client) == CLIENT_SERVER) { /* Server */ target = Client_Search(Req->prefix); if (!target) { Log(LOG_WARNING, "Got QUIT from %s for unknown client!?", Client_ID(Client)); return CONNECTED; } if (target != Client) { Client_Destroy(target, "Got QUIT command.", Req->argc == 1 ? quitmsg : NULL, true); return CONNECTED; } else { Conn_Close(Client_Conn(Client), "Got QUIT command.", Req->argc == 1 ? quitmsg : NULL, true); return DISCONNECTED; } } else { if (Req->argc == 1 && quitmsg[0] != '\"') { /* " " to avoid confusion */ strlcpy(quitmsg, "\"", sizeof quitmsg); strlcat(quitmsg, Req->argv[0], sizeof quitmsg-1); strlcat(quitmsg, "\"", sizeof quitmsg ); } /* User, Service, or not yet registered */ Conn_Close(Client_Conn(Client), "Got QUIT command.", Req->argc == 1 ? quitmsg : NULL, true); return DISCONNECTED; } } /* IRC_QUIT */
/** * Handler for the IRC "KILL" command. * * This function implements the IRC command "KILL" which is used to selectively * disconnect clients. It can be used by IRC operators and servers, for example * 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 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. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_KILL(CLIENT *Client, REQUEST *Req) { CLIENT *prefix; char reason[COMMAND_LEN]; assert (Client != NULL); assert (Req != NULL); if (Client_Type(Client) != CLIENT_SERVER && !Op_Check(Client, Req)) return Op_NoPrivileges(Client, Req); /* 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. And this is the reason why we don't * use the _IRC_GET_SENDER_OR_RETURN_ macro above! */ if (!prefix) { Log(LOG_WARNING, "Got KILL with invalid prefix: \"%s\"!", Req->prefix ); prefix = Client_ThisServer(); } 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: 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)); return IRC_KillClient(Client, prefix, Req->argv[0], reason); }
/** * Return and log a "no privileges" message. */ GLOBAL bool Op_NoPrivileges(CLIENT * Client, REQUEST * Req) { CLIENT *from = NULL; if (Req->prefix) from = Client_Search(Req->prefix); if (from) { Log(LOG_ERR|LOG_snotice, "No privileges: client \"%s\" (%s), command \"%s\"!", Req->prefix, Client_Mask(Client), Req->command); return IRC_WriteErrClient(from, ERR_NOPRIVILEGES_MSG, Client_ID(from)); } else { Log(LOG_ERR|LOG_snotice, "No privileges: client \"%s\", command \"%s\"!", Client_Mask(Client), Req->command); return IRC_WriteErrClient(Client, ERR_NOPRIVILEGES_MSG, Client_ID(Client)); } } /* Op_NoPrivileges */
/** * Make sure that a given nickname is valid. * * If the nickname is not valid for the given client, this function sends back * the appropriate error messages. * * @param Client Client that wants to change the nickname. * @param Nick New nickname. * @returns true if nickname is valid, false otherwise. */ GLOBAL bool Client_CheckNick(CLIENT *Client, char *Nick) { assert(Client != NULL); assert(Nick != NULL); if (!Client_IsValidNick(Nick)) { if (strlen(Nick ) >= Conf_MaxNickLength) IRC_WriteErrClient(Client, ERR_NICKNAMETOOLONG_MSG, Client_ID(Client), Nick, Conf_MaxNickLength - 1); else IRC_WriteErrClient(Client, ERR_ERRONEUSNICKNAME_MSG, Client_ID(Client), Nick); return false; } if (Client_Type(Client) != CLIENT_SERVER && Client_Type(Client) != CLIENT_SERVICE) { /* Make sure that this isn't a restricted/forbidden nickname */ if (Conf_NickIsBlocked(Nick)) { IRC_WriteErrClient(Client, ERR_FORBIDDENNICKNAME_MSG, Client_ID(Client), Nick); return false; } } /* Nickname already registered? */ if (Client_Search(Nick)) { IRC_WriteErrClient(Client, ERR_NICKNAMEINUSE_MSG, Client_ID(Client), Nick); return false; } return true; } /* Client_CheckNick */
/** * 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. See RFC 2812 section 3.7.1. * * Please note that this function is also called internally, without a real * 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 ) { CLIENT *prefix, *c; char reason[COMMAND_LEN], *msg; CONN_ID my_conn, conn; assert (Client != NULL); assert (Req != NULL); if (Client_Type(Client) != CLIENT_SERVER && !Client_OperByMe(Client)) return IRC_WriteStrClient(Client, ERR_NOPRIVILEGES_MSG, Client_ID(Client)); if (Req->argc != 2) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); /* 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()) 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: 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)); /* Inform other servers */ IRC_WriteStrServersPrefix(Client, prefix, "KILL %s :%s", Req->argv[0], reason); /* Save ID of this connection */ my_conn = Client_Conn( Client ); /* Do we host such a client? */ c = Client_Search( Req->argv[0] ); if( c ) { if(( Client_Type( c ) != CLIENT_USER ) && ( Client_Type( c ) != CLIENT_GOTNICK )) { /* Target of this KILL is not a regular user, this is * invalid! So we ignore this case if we received a * regular KILL from the network and try to kill the * client/connection anyway (but log an error!) if the * origin is the local server. */ if( Client != Client_ThisServer( )) { /* Invalid KILL received from remote */ if( Client_Type( c ) == CLIENT_SERVER ) msg = ERR_CANTKILLSERVER_MSG; else msg = ERR_NOPRIVILEGES_MSG; return IRC_WriteStrClient( Client, msg, Client_ID( Client )); } Log( LOG_ERR, "Got KILL for invalid client type: %d, \"%s\"!", Client_Type( c ), Req->argv[0] ); } /* 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 ); 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] ); /* Are we still connected or were we killed, too? */ if(( my_conn > NONE ) && ( Conn_GetClient( my_conn ))) return CONNECTED; else return DISCONNECTED; } /* IRC_KILL */
/** * Handler for the IRC "SERVICE" command. * * This function implements IRC Services registration using the SERVICE command * defined in RFC 2812 3.1.6 and RFC 2813 4.1.4. * * At the moment ngIRCd doesn't support directly linked services, so this * function returns ERR_ERRONEUSNICKNAME when the SERVICE command has not been * received from a peer server. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED.. */ GLOBAL bool IRC_SERVICE(CLIENT *Client, REQUEST *Req) { CLIENT *c, *intr_c; char *nick, *user, *host, *info, *modes, *ptr; int token, hops; assert(Client != NULL); assert(Req != NULL); if (Client_Type(Client) != CLIENT_GOTPASS && Client_Type(Client) != CLIENT_SERVER) return IRC_WriteStrClient(Client, ERR_ALREADYREGISTRED_MSG, Client_ID(Client)); if (Req->argc != 6) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); if (Client_Type(Client) != CLIENT_SERVER) return IRC_WriteStrClient(Client, ERR_ERRONEUSNICKNAME_MSG, Client_ID(Client), Req->argv[0]); /* Bad number of parameters? */ if (Req->argc != 6) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); nick = Req->argv[0]; user = NULL; host = NULL; token = atoi(Req->argv[1]); hops = atoi(Req->argv[4]); info = Req->argv[5]; /* Validate service name ("nick name") */ c = Client_Search(nick); if(c) { /* Nick name collission: disconnect (KILL) both clients! */ Log(LOG_ERR, "Server %s introduces already registered service \"%s\"!", Client_ID(Client), nick); Kill_Nick(nick, "Nick collision"); return CONNECTED; } /* Get the server to which the service is connected */ intr_c = Client_GetFromToken(Client, token); if (! intr_c) { Log(LOG_ERR, "Server %s introduces service \"%s\" on unknown server!?", Client_ID(Client), nick); Kill_Nick(nick, "Unknown server"); return CONNECTED; } /* Get user and host name */ ptr = strchr(nick, '@'); if (ptr) { *ptr = '\0'; host = ++ptr; } if (!host) host = Client_Hostname(intr_c); ptr = strchr(nick, '!'); if (ptr) { *ptr = '\0'; user = ++ptr; } if (!user) user = nick; /* According to RFC 2812/2813 parameter 4 <type> "is currently reserved * for future usage"; but we use it to transfer the modes and check * that the first character is a '+' sign and ignore it otherwise. */ modes = (Req->argv[3][0] == '+') ? ++Req->argv[3] : ""; c = Client_NewRemoteUser(intr_c, nick, hops, user, host, token, modes, info, true); if (! c) { /* Couldn't create client structure, so KILL the service to * keep network status consistent ... */ Log(LOG_ALERT, "Can't create client structure! (on connection %d)", Client_Conn(Client)); Kill_Nick(nick, "Server error"); return CONNECTED; } Client_Introduce(Client, c, CLIENT_SERVICE); return CONNECTED; } /* IRC_SERVICE */
/** * Handle channel mode and channel-user mode changes * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @param Origin The originator of the MODE command (prefix). * @param Channel The target channel of this MODE command. * @return CONNECTED or DISCONNECTED. */ static bool Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel) { char the_modes[COMMAND_LEN], the_args[COMMAND_LEN], x[2], argadd[CLIENT_PASS_LEN], *mode_ptr; bool connected, set, skiponce, retval, use_servermode, is_halfop, is_op, is_admin, is_owner, is_machine, is_oper; int mode_arg, arg_arg, mode_arg_count = 0; CLIENT *client; long l; size_t len; is_halfop = is_op = is_admin = is_owner = is_machine = is_oper = false; if (Channel_IsModeless(Channel)) return IRC_WriteErrClient(Client, ERR_NOCHANMODES_MSG, Client_ID(Client), Channel_Name(Channel)); /* Mode request: let's answer it :-) */ if (Req->argc <= 1) return Channel_Mode_Answer_Request(Origin, Channel); /* Check if origin is oper and opers can use mode */ use_servermode = Conf_OperServerMode; if(Client_HasMode(Client, 'o') && Conf_OperCanMode) { is_oper = true; } /* Check if client is a server/service */ if(Client_Type(Client) == CLIENT_SERVER || Client_Type(Client) == CLIENT_SERVICE) { is_machine = true; } /* Check if client is member of channel or an oper or an server/service */ if(!Channel_IsMemberOf(Channel, Client) && !is_oper && !is_machine) return IRC_WriteErrClient(Origin, ERR_NOTONCHANNEL_MSG, Client_ID(Origin), Channel_Name(Channel)); mode_arg = 1; mode_ptr = Req->argv[mode_arg]; if (Req->argc > mode_arg + 1) arg_arg = mode_arg + 1; else arg_arg = -1; /* Initial state: set or unset modes? */ skiponce = false; switch (*mode_ptr) { case '-': set = false; break; case '+': set = true; break; default: set = true; skiponce = true; } /* Prepare reply string */ strcpy(the_modes, set ? "+" : "-"); the_args[0] = '\0'; x[1] = '\0'; connected = CONNECTED; while (mode_ptr) { if (!skiponce) mode_ptr++; if (!*mode_ptr) { /* Try next argument if there's any */ if (arg_arg < 0) break; if (arg_arg > mode_arg) mode_arg = arg_arg; else mode_arg++; if (mode_arg >= Req->argc) break; mode_ptr = Req->argv[mode_arg]; if (Req->argc > mode_arg + 1) arg_arg = mode_arg + 1; else arg_arg = -1; } skiponce = false; switch (*mode_ptr) { case '+': case '-': if (((*mode_ptr == '+') && !set) || ((*mode_ptr == '-') && set)) { /* Action modifier ("+"/"-") must be changed ... */ len = strlen(the_modes) - 1; if (the_modes[len] == '+' || the_modes[len] == '-') { /* Adjust last action modifier in result */ the_modes[len] = *mode_ptr; } else { /* Append modifier character to result string */ x[0] = *mode_ptr; strlcat(the_modes, x, sizeof(the_modes)); } set = *mode_ptr == '+'; } continue; } /* Are there arguments left? */ if (arg_arg >= Req->argc) arg_arg = -1; if(!is_machine && !is_oper) { if (Channel_UserHasMode(Channel, Client, 'q')) is_owner = true; if (Channel_UserHasMode(Channel, Client, 'a')) is_admin = true; if (Channel_UserHasMode(Channel, Client, 'o')) is_op = true; if (Channel_UserHasMode(Channel, Client, 'h')) is_halfop = true; } /* Validate modes */ x[0] = '\0'; argadd[0] = '\0'; client = NULL; switch (*mode_ptr) { /* --- Channel modes --- */ case 'R': /* Registered users only */ case 's': /* Secret channel */ case 'z': /* Secure connections only */ if(!is_oper && !is_machine && !is_owner && !is_admin && !is_op) { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); goto chan_exit; } 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 || is_admin || is_op || is_halfop) x[0] = *mode_ptr; else connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); break; case 'k': /* Channel key */ if (Mode_Limit_Reached(Client, mode_arg_count++)) goto chan_exit; if (!set) { if (is_oper || is_machine || is_owner || is_admin || is_op || is_halfop) { x[0] = *mode_ptr; 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 { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); } Req->argv[arg_arg][0] = '\0'; arg_arg++; } 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; } break; case 'l': /* Member limit */ if (Mode_Limit_Reached(Client, mode_arg_count++)) goto chan_exit; if (!set) { if (is_oper || is_machine || is_owner || is_admin || is_op || is_halfop) x[0] = *mode_ptr; 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) { 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 { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); } Req->argv[arg_arg][0] = '\0'; arg_arg++; } 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; } break; case 'O': /* IRC operators only */ if (set) { /* Only IRC operators are allowed to * set the 'O' channel mode! */ if(is_oper || is_machine) x[0] = 'O'; else connected = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); } else if(is_oper || is_machine || is_owner || is_admin || is_op) x[0] = 'O'; else connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); break; case 'P': /* Persistent channel */ if (set) { /* Only IRC operators are allowed to * set the 'P' channel mode! */ if(is_oper || is_machine) x[0] = 'P'; else connected = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); } else if(is_oper || is_machine || is_owner || is_admin || is_op) x[0] = 'P'; else connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); break; /* --- Channel user modes --- */ case 'q': /* Owner */ case 'a': /* Channel admin */ if(!is_oper && !is_machine && !is_owner && !is_admin) { connected = IRC_WriteErrClient(Origin, ERR_CHANOPPRIVTOOLOW_MSG, Client_ID(Origin), Channel_Name(Channel)); goto chan_exit; } case 'o': /* Channel operator */ if(!is_oper && !is_machine && !is_owner && !is_admin && !is_op) { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); goto chan_exit; } case 'h': /* Half Op */ if(!is_oper && !is_machine && !is_owner && !is_admin && !is_op) { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); goto chan_exit; } case 'v': /* Voice */ if (arg_arg > mode_arg) { if (is_oper || is_machine || is_owner || is_admin || is_op || is_halfop) { client = Client_Search(Req->argv[arg_arg]); if (client) x[0] = *mode_ptr; else connected = IRC_WriteErrClient(Origin, ERR_NOSUCHNICK_MSG, Client_ID(Origin), Req->argv[arg_arg]); } else { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); } Req->argv[arg_arg][0] = '\0'; arg_arg++; } else { #ifdef STRICT_RFC /* Report an error to the client, when a user * mode should be changed but no nickname is * given. But don't do it when not in "strict" * mode, because most other servers don't do * it as well and some clients send "wired" * MODE commands like "MODE #chan -ooo nick". */ connected = IRC_WriteErrClient(Origin, ERR_NEEDMOREPARAMS_MSG, Client_ID(Origin), Req->command); #endif goto chan_exit; } break; /* --- Channel lists --- */ case 'I': /* Invite lists */ case 'b': /* Ban lists */ case 'e': /* Channel exception lists */ if (Mode_Limit_Reached(Client, mode_arg_count++)) goto chan_exit; if (arg_arg > mode_arg) { /* modify list */ if (is_oper || is_machine || is_owner || is_admin || is_op || is_halfop) { connected = set ? Add_To_List(*mode_ptr, Origin, Client, Channel, Req->argv[arg_arg]) : Del_From_List(*mode_ptr, Origin, Client, Channel, Req->argv[arg_arg]); } else { connected = IRC_WriteErrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(Origin), Channel_Name(Channel)); } Req->argv[arg_arg][0] = '\0'; arg_arg++; } else { switch (*mode_ptr) { case 'I': Channel_ShowInvites(Origin, Channel); break; case 'b': Channel_ShowBans(Origin, Channel); break; case 'e': Channel_ShowExcepts(Origin, Channel); break; } } break; default: if (Client_Type(Client) != CLIENT_SERVER) { Log(LOG_DEBUG, "Unknown mode \"%c%c\" from \"%s\" on %s!?", set ? '+' : '-', *mode_ptr, Client_ID(Origin), Channel_Name(Channel)); connected = IRC_WriteErrClient(Origin, ERR_UNKNOWNMODE_MSG, Client_ID(Origin), *mode_ptr, Channel_Name(Channel)); x[0] = '\0'; } else { Log(LOG_DEBUG, "Handling unknown mode \"%c%c\" from \"%s\" on %s ...", set ? '+' : '-', *mode_ptr, Client_ID(Origin), Channel_Name(Channel)); x[0] = *mode_ptr; } } if (!connected) break; /* Is there a valid mode change? */ if (!x[0]) continue; /* Validate target client */ if (client && (!Channel_IsMemberOf(Channel, client))) { if (!IRC_WriteErrClient(Origin, ERR_USERNOTINCHANNEL_MSG, Client_ID(Origin), Client_ID(client), Channel_Name(Channel))) break; continue; } if (client) { /* Channel-User-Mode */ retval = set ? Channel_UserModeAdd(Channel, client, x[0]) : Channel_UserModeDel(Channel, client, x[0]); if (retval) { strlcat(the_args, " ", sizeof(the_args)); strlcat(the_args, Client_ID(client), sizeof(the_args)); strlcat(the_modes, x, sizeof(the_modes)); LogDebug ("User \"%s\": Mode change on %s, now \"%s\"", Client_Mask(client), Channel_Name(Channel), Channel_UserModes(Channel, client)); } } else { /* Channel-Mode */ retval = set ? Channel_ModeAdd(Channel, x[0]) : Channel_ModeDel(Channel, x[0]); if (retval) { strlcat(the_modes, x, sizeof(the_modes)); LogDebug("Channel %s: Mode change, now \"%s\".", Channel_Name(Channel), Channel_Modes(Channel)); } } /* Are there additional arguments to add? */ if (argadd[0]) { strlcat(the_args, " ", sizeof(the_args)); strlcat(the_args, argadd, sizeof(the_args)); } } chan_exit: /* Are there changed modes? */ if (the_modes[1]) { /* Clean up mode string */ len = strlen(the_modes) - 1; if ((the_modes[len] == '+') || (the_modes[len] == '-')) the_modes[len] = '\0'; if (Client_Type(Client) == CLIENT_SERVER) { /* MODE requests for local channels from other servers * are definitely invalid! */ if (Channel_IsLocal(Channel)) { Log(LOG_ALERT, "Got remote MODE command for local channel!? Ignored."); return CONNECTED; } /* Forward mode changes to channel users and all the * other remote servers: */ IRC_WriteStrServersPrefix(Client, Origin, "MODE %s %s%s", Channel_Name(Channel), the_modes, the_args); IRC_WriteStrChannelPrefix(Client, Channel, Origin, false, "MODE %s %s%s", Channel_Name(Channel), the_modes, the_args); } else { if (use_servermode) Origin = Client_ThisServer(); /* Send reply to client and inform other servers and channel users */ connected = IRC_WriteStrClientPrefix(Client, Origin, "MODE %s %s%s", Channel_Name(Channel), the_modes, the_args); /* Only forward requests for non-local channels */ if (!Channel_IsLocal(Channel)) IRC_WriteStrServersPrefix(Client, Origin, "MODE %s %s%s", Channel_Name(Channel), the_modes, the_args); IRC_WriteStrChannelPrefix(Client, Channel, Origin, false, "MODE %s %s%s", Channel_Name(Channel), the_modes, the_args); } } return connected; } /* Channel_Mode */
/** * Handler for the IRC "PING" command. * * See RFC 2812, 3.7.2 "Ping message". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_PING(CLIENT *Client, REQUEST *Req) { CLIENT *target, *from; assert(Client != NULL); assert(Req != NULL); if (Req->argc < 1) return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, Client_ID(Client)); #ifdef STRICT_RFC /* Don't ignore additional arguments when in "strict" mode */ if (Req->argc > 2) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); #endif if (Req->argc > 1) { /* A target has been specified ... */ target = Client_Search(Req->argv[1]); if (!target || Client_Type(target) != CLIENT_SERVER) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[1]); if (target != Client_ThisServer()) { /* Ok, we have to forward the PING */ if (Client_Type(Client) == CLIENT_SERVER) from = Client_Search(Req->prefix); else from = Client; if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); return IRC_WriteStrClientPrefix(target, from, "PING %s :%s", Req->argv[0], Req->argv[1] ); } } if (Client_Type(Client) == CLIENT_SERVER) { if (Req->prefix) from = Client_Search(Req->prefix); else from = Client; } else from = Client_ThisServer(); if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); Log(LOG_DEBUG, "Connection %d: got PING, sending PONG ...", Client_Conn(Client)); #ifdef STRICT_RFC return IRC_WriteStrClient(Client, "PONG %s :%s", Client_ID(from), Client_ID(Client)); #else /* Some clients depend on the argument being returned in the PONG * reply (not mentioned in any RFC, though) */ return IRC_WriteStrClient(Client, "PONG %s :%s", Client_ID(from), Req->argv[0]); #endif } /* IRC_PING */
/** * Handler for the IRC "PONG" command. * * See RFC 2812, 3.7.3 "Pong message". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_PONG(CLIENT *Client, REQUEST *Req) { CLIENT *target, *from; CONN_ID conn; #ifndef STRICT_RFC long auth_ping; #endif char *s; assert(Client != NULL); assert(Req != NULL); /* Wrong number of arguments? */ if (Req->argc < 1) { if (Client_Type(Client) == CLIENT_USER) return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, Client_ID(Client)); else return CONNECTED; } if (Req->argc > 2) { if (Client_Type(Client) == CLIENT_USER) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); else return CONNECTED; } /* Forward? */ if (Req->argc == 2 && Client_Type(Client) == CLIENT_SERVER) { target = Client_Search(Req->argv[0]); if (!target) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[0]); from = Client_Search(Req->prefix); if (target != Client_ThisServer() && target != from) { /* Ok, we have to forward the message. */ if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); if (Client_Type(Client_NextHop(target)) != CLIENT_SERVER) s = Client_ID(from); else s = Req->argv[0]; return IRC_WriteStrClientPrefix(target, from, "PONG %s :%s", s, Req->argv[1]); } } /* The connection timestamp has already been updated when the data has * been read from so socket, so we don't need to update it here. */ conn = Client_Conn(Client); #ifndef STRICT_RFC /* Check authentication PING-PONG ... */ auth_ping = Conn_GetAuthPing(conn); if (auth_ping) { LogDebug("AUTH PONG: waiting for token \"%ld\", got \"%s\" ...", auth_ping, Req->argv[0]); if (auth_ping == atoi(Req->argv[0])) { Conn_SetAuthPing(conn, 0); if (Client_Type(Client) == CLIENT_WAITAUTHPING) Login_User(Client); } else if (!IRC_WriteStrClient(Client, "To connect, type /QUOTE PONG %ld", auth_ping)) return DISCONNECTED; } #endif if (Client_Type(Client) == CLIENT_SERVER && Conn_LastPing(conn) == 0) { Log(LOG_INFO, "Synchronization with \"%s\" done (connection %d): %ld seconds [%ld users, %ld channels]", Client_ID(Client), conn, time(NULL) - Conn_GetSignon(conn), Client_UserCount(), Channel_CountVisible(NULL)); Conn_UpdatePing(conn); } else LogDebug("Connection %d: received PONG. Lag: %ld seconds.", conn, time(NULL) - Conn_LastPing(conn)); return CONNECTED; } /* IRC_PONG */
/** * Handler for the IRC command "INVITE". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_INVITE(CLIENT *Client, REQUEST *Req) { CHANNEL *chan; CLIENT *target, *from; const char *colon_if_necessary; bool remember = false; assert( Client != NULL ); assert( Req != NULL ); _IRC_ARGC_EQ_OR_RETURN_(Client, Req, 2) _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) /* Search user */ target = Client_Search(Req->argv[0]); if (!target || (Client_Type(target) != CLIENT_USER)) return IRC_WriteStrClient(from, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->argv[0]); chan = Channel_Search(Req->argv[1]); if (chan) { /* Channel exists. Is the user a valid member of the channel? */ if (!Channel_IsMemberOf(chan, from)) return IRC_WriteStrClient(from, ERR_NOTONCHANNEL_MSG, Client_ID(Client), Req->argv[1]); /* Is the channel "invite-disallow"? */ if (strchr(Channel_Modes(chan), 'V')) return IRC_WriteStrClient(from, ERR_NOINVITE_MSG, Client_ID(from), Channel_Name(chan)); /* Is the channel "invite-only"? */ if (strchr(Channel_Modes(chan), 'i')) { /* Yes. The user must be channel owner/admin/operator/halfop! */ if (!strchr(Channel_UserModes(chan, from), 'q') && !strchr(Channel_UserModes(chan, from), 'a') && !strchr(Channel_UserModes(chan, from), 'o') && !strchr(Channel_UserModes(chan, from), 'h')) return IRC_WriteStrClient(from, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(from), Channel_Name(chan)); remember = true; } /* Is the target user already member of the channel? */ if (Channel_IsMemberOf(chan, target)) return IRC_WriteStrClient(from, ERR_USERONCHANNEL_MSG, Client_ID(from), Req->argv[0], Req->argv[1]); /* If the target user is banned on that channel: remember invite */ if (Lists_Check(Channel_GetListBans(chan), target)) remember = true; if (remember) { /* We must remember this invite */ if (!Channel_AddInvite(chan, Client_Mask(target), true)) return CONNECTED; } } LogDebug("User \"%s\" invites \"%s\" to \"%s\" ...", Client_Mask(from), Req->argv[0], Req->argv[1]); /* * RFC 2812 says: * 'There is no requirement that the channel [..] must exist or be a valid channel' * The problem with this is that this allows the "channel" to contain spaces, * in which case we must prefix its name with a colon to make it clear that * it is only a single argument. */ colon_if_necessary = strchr(Req->argv[1], ' ') ? ":":""; /* Inform target client */ IRC_WriteStrClientPrefix(target, from, "INVITE %s %s%s", Req->argv[0], colon_if_necessary, Req->argv[1]); if (Client_Conn(target) > NONE) { /* The target user is local, so we have to send the status code */ if (!IRC_WriteStrClientPrefix(from, target, RPL_INVITING_MSG, Client_ID(from), Req->argv[0], colon_if_necessary, Req->argv[1])) return DISCONNECTED; if (strchr(Client_Modes(target), 'a') && !IRC_WriteStrClient(from, RPL_AWAY_MSG, Client_ID(from), Client_ID(target), Client_Away(target))) return DISCONNECTED; } return CONNECTED; } /* IRC_INVITE */
/** * Get pointer to a static string representing the connection "options". * * @param Idx Connection index. * @return Pointer to static (global) string buffer. */ static char * #if defined(SSL_SUPPORT) || defined(ZLIB) Option_String(CONN_ID Idx) { static char option_txt[8]; UINT16 options; assert(Idx != NONE); options = Conn_Options(Idx); strcpy(option_txt, "F"); /* No idea what this means, but the * original ircd sends it ... */ #ifdef SSL_SUPPORT if(options & CONN_SSL) /* SSL encrypted link */ strlcat(option_txt, "s", sizeof(option_txt)); #endif #ifdef ZLIB if(options & CONN_ZIP) /* zlib compression enabled */ strlcat(option_txt, "z", sizeof(option_txt)); #endif return option_txt; #else Option_String(UNUSED CONN_ID Idx) { return ""; #endif } /* Option_String */ /** * Send a message to target(s). * * This function is used by IRC_{PRIVMSG|NOTICE|SQUERY} to actualy * send the message(s). * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @param ForceType Required type of the destination of the message(s). * @param SendErrors Whether to report errors back to the client or not. * @return CONNECTED or DISCONNECTED. */ 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 *strtok_last = NULL; char *message = NULL; char *targets[MAX_HNDL_TARGETS]; int i, target_nr = 0; assert(Client != NULL); assert(Req != NULL); if (Req->argc == 0) { if (!SendErrors) return CONNECTED; return IRC_WriteErrClient(Client, ERR_NORECIPIENT_MSG, Client_ID(Client), Req->command); } if (Req->argc == 1) { if (!SendErrors) return CONNECTED; return IRC_WriteErrClient(Client, ERR_NOTEXTTOSEND_MSG, Client_ID(Client)); } if (Req->argc > 2) { if (!SendErrors) return CONNECTED; return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); } if (Client_Type(Client) == CLIENT_SERVER && Req->prefix) from = Client_Search(Req->prefix); else from = Client; if (!from) return IRC_WriteErrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); #ifdef ICONV if (Client_Conn(Client) > NONE) message = Conn_EncodingFrom(Client_Conn(Client), Req->argv[1]); else #endif message = Req->argv[1]; /* handle msgtarget = msgto *("," msgto) */ currentTarget = strtok_r(currentTarget, ",", &strtok_last); ngt_UpperStr(Req->command); /* Please note that "currentTarget" is NULL when the target contains * the separator character only, e. g. "," or ",,,," etc.! */ while (currentTarget) { /* Make sure that there hasn't been such a target already: */ targets[target_nr++] = currentTarget; for(i = 0; i < target_nr - 1; i++) { if (strcasecmp(currentTarget, targets[i]) == 0) goto send_next_target; } /* Check for and handle valid <msgto> of form: * RFC 2812 2.3.1: * msgto = channel / ( user [ "%" host ] "@" servername ) * msgto =/ ( user "%" host ) / targetmask * msgto =/ nickname / ( nickname "!" user "@" host ) */ if (strchr(currentTarget, '!') == NULL) /* nickname */ cl = Client_Search(currentTarget); else cl = NULL; if (cl == NULL) { /* If currentTarget isn't a nickname check for: * user ["%" host] "@" servername * user "%" host * nickname "!" user "@" host */ char target[COMMAND_LEN]; char * nick = NULL; char * user = NULL; char * host = NULL; char * server = NULL; strlcpy(target, currentTarget, COMMAND_LEN); server = strchr(target, '@'); if (server) { *server = '\0'; server++; } host = strchr(target, '%'); if (host) { *host = '\0'; host++; } user = strchr(target, '!'); if (user) { /* msgto form: nick!user@host */ *user = '******'; user++; nick = target; host = server; /* not "@server" but "@host" */ } else { user = target; } for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { if (Client_Type(cl) != CLIENT_USER && Client_Type(cl) != CLIENT_SERVICE) continue; if (nick != NULL && host != NULL) { if (strcasecmp(nick, Client_ID(cl)) == 0 && strcasecmp(user, Client_User(cl)) == 0 && strcasecmp(host, Client_HostnameDisplayed(cl)) == 0) break; else continue; } if (strcasecmp(user, Client_User(cl)) != 0) continue; if (host != NULL && strcasecmp(host, Client_HostnameDisplayed(cl)) != 0) continue; if (server != NULL && strcasecmp(server, Client_ID(Client_Introducer(cl))) != 0) continue; break; } } if (cl) { /* Target is a user, enforce type */ #ifndef STRICT_RFC if (Client_Type(cl) != ForceType && !(ForceType == CLIENT_USER && (Client_Type(cl) == CLIENT_USER || Client_Type(cl) == CLIENT_SERVICE))) { #else if (Client_Type(cl) != ForceType) { #endif if (SendErrors && !IRC_WriteErrClient( from, ERR_NOSUCHNICK_MSG,Client_ID(from), currentTarget)) return DISCONNECTED; goto send_next_target; } #ifndef STRICT_RFC if (ForceType == CLIENT_SERVICE && (Conn_Options(Client_Conn(Client_NextHop(cl))) & CONN_RFC1459)) { /* SQUERY command but RFC 1459 link: convert * request to PRIVMSG command */ Req->command = "PRIVMSG"; } #endif if (Client_HasMode(cl, 'b') && !Client_HasMode(from, 'R') && !Client_HasMode(from, 'o') && !(Client_Type(from) == CLIENT_SERVER) && !(Client_Type(from) == CLIENT_SERVICE)) { if (SendErrors && !IRC_WriteErrClient(from, ERR_NONONREG_MSG, Client_ID(from), Client_ID(cl))) return DISCONNECTED; goto send_next_target; } if (Client_HasMode(cl, 'C') && !Client_HasMode(from, 'o') && !(Client_Type(from) == CLIENT_SERVER) && !(Client_Type(from) == CLIENT_SERVICE)) { 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_WriteErrClient( from, ERR_NOTONSAMECHANNEL_MSG, Client_ID(from), Client_ID(cl))) return DISCONNECTED; goto send_next_target; } } if (SendErrors && (Client_Type(Client) != CLIENT_SERVER) && Client_HasMode(cl, 'a')) { /* Target is away */ if (!IRC_WriteStrClient(from, RPL_AWAY_MSG, Client_ID(from), Client_ID(cl), Client_Away(cl))) return DISCONNECTED; } if (Client_Conn(from) > NONE) { Conn_UpdateIdle(Client_Conn(from)); } if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", Req->command, Client_ID(cl), message)) return DISCONNECTED; } else if (ForceType != CLIENT_SERVICE && (chan = Channel_Search(currentTarget))) { /* Target is a channel */ if (!Channel_Write(chan, from, Client, Req->command, SendErrors, message)) return DISCONNECTED; } else if (ForceType != CLIENT_SERVICE && strchr("$#", currentTarget[0]) && strchr(currentTarget, '.')) { /* $#: server/host mask, RFC 2812, sec. 3.3.1 */ if (!Send_Message_Mask(from, Req->command, currentTarget, message, SendErrors)) return DISCONNECTED; } else { if (!SendErrors) return CONNECTED; if (!IRC_WriteErrClient(from, ERR_NOSUCHNICK_MSG, Client_ID(from), currentTarget)) return DISCONNECTED; } send_next_target: currentTarget = strtok_r(NULL, ",", &strtok_last); if (!currentTarget) break; Conn_SetPenalty(Client_Conn(Client), 1); if (target_nr >= MAX_HNDL_TARGETS) { /* Too many targets given! */ return IRC_WriteErrClient(Client, ERR_TOOMANYTARGETS_MSG, currentTarget); } } return CONNECTED; } /* Send_Message */ /** * Send a message to "target mask" target(s). * * See RFC 2812, sec. 3.3.1 for details. * * @param from The client from which this command has been received. * @param command The command to use (PRIVMSG, NOTICE, ...). * @param targetMask The "target mask" (will be verified by this function). * @param message The message to send. * @param SendErrors Whether to report errors back to the client or not. * @return CONNECTED or DISCONNECTED. */ static bool Send_Message_Mask(CLIENT * from, char * command, char * targetMask, char * message, bool SendErrors) { CLIENT *cl; bool client_match; char *mask = targetMask + 1; const char *check_wildcards; cl = NULL; if (!Client_HasMode(from, 'o')) { if (!SendErrors) return true; return IRC_WriteErrClient(from, ERR_NOPRIVILEGES_MSG, 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, '.'); if (!check_wildcards || check_wildcards[strcspn(check_wildcards, "*?")]) { if (!SendErrors) return true; return IRC_WriteErrClient(from, ERR_WILDTOPLEVEL_MSG, targetMask); } if (targetMask[0] == '#') { /* #: hostmask, see RFC 2812, sec. 3.3.1 */ for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { if (Client_Type(cl) != CLIENT_USER) continue; client_match = MatchCaseInsensitive(mask, Client_Hostname(cl)); if (client_match) if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", command, Client_ID(cl), message)) return false; } } else { /* $: server mask, see RFC 2812, sec. 3.3.1 */ assert(targetMask[0] == '$'); for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { if (Client_Type(cl) != CLIENT_USER) continue; client_match = MatchCaseInsensitive(mask, Client_ID(Client_Introducer(cl))); if (client_match) if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", command, Client_ID(cl), message)) return false; } } return CONNECTED; } /* Send_Message_Mask */
GLOBAL bool IRC_KICK(CLIENT *Client, REQUEST *Req) { CLIENT *from; char *itemList = Req->argv[0]; const char* currentNick, *currentChannel, *reason; unsigned int channelCount = 1; unsigned int nickCount = 1; assert( Client != NULL ); assert( Req != NULL ); if ((Req->argc < 2) || (Req->argc > 3)) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); while (*itemList) { if (*itemList == ',') { *itemList = '\0'; channelCount++; } itemList++; } itemList = Req->argv[1]; while (*itemList) { if (*itemList == ',') { *itemList = '\0'; nickCount++; } itemList++; } if (Client_Type(Client) == CLIENT_SERVER) from = Client_Search(Req->prefix); else from = Client; if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); reason = Req->argc == 3 ? Req->argv[2] : Client_ID(from); currentNick = Req->argv[1]; currentChannel = Req->argv[0]; if (channelCount == 1) { while (nickCount > 0) { if (!try_kick(Client, from, currentNick, currentChannel, reason)) return false; while (*currentNick) currentNick++; currentNick++; nickCount--; } } else if (channelCount == nickCount) { while (nickCount > 0) { if (!try_kick(Client, from, currentNick, currentChannel, reason)) return false; while (*currentNick) currentNick++; while (*currentChannel) currentChannel++; currentNick++; currentChannel++; nickCount--; } } else { return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); } return true; } /* IRC_KICK */
/** * Handler for the IRC "LIST" command. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_LIST( CLIENT *Client, REQUEST *Req ) { char *pattern; CHANNEL *chan; CLIENT *from, *target; int count = 0; assert(Client != NULL); assert(Req != NULL); _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) if (Req->argc > 0) pattern = strtok(Req->argv[0], ","); else pattern = "*"; if (Req->argc == 2) { /* Forward to other server? */ target = Client_Search(Req->argv[1]); if (! target || Client_Type(target) != CLIENT_SERVER) return IRC_WriteErrClient(from, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[1]); if (target != Client_ThisServer()) { /* Target is indeed an other server, forward it! */ return IRC_WriteStrClientPrefix(target, from, "LIST %s :%s", Req->argv[0], Req->argv[1]); } } while (pattern) { /* Loop through all the channels */ if (Req->argc > 0) ngt_LowerStr(pattern); chan = Channel_First(); while (chan) { /* Check search pattern */ if (MatchCaseInsensitive(pattern, Channel_Name(chan))) { /* Gotcha! */ if (!Channel_HasMode(chan, 's') || Channel_IsMemberOf(chan, from) || (!Conf_MorePrivacy && Client_HasMode(Client, 'o') && Client_Conn(Client) > NONE)) { if ((Conf_MaxListSize > 0) && IRC_CheckListTooBig(from, count, Conf_MaxListSize, "LIST")) break; if (!IRC_WriteStrClient(from, RPL_LIST_MSG, Client_ID(from), Channel_Name(chan), Channel_MemberCount(chan), Channel_Topic( chan ))) return DISCONNECTED; count++; } } chan = Channel_Next(chan); } /* Get next name ... */ if(Req->argc > 0) pattern = strtok(NULL, ","); else pattern = NULL; } return IRC_WriteStrClient(from, RPL_LISTEND_MSG, Client_ID(from)); } /* IRC_LIST */
/** * Handler for the IRC "USER" command. * * See RFC 2812, 3.1.3 "User message". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_USER(CLIENT * Client, REQUEST * Req) { CLIENT *c; char *ptr; assert(Client != NULL); assert(Req != NULL); if (Client_Type(Client) == CLIENT_GOTNICK || #ifndef STRICT_RFC Client_Type(Client) == CLIENT_UNKNOWN || #endif Client_Type(Client) == CLIENT_GOTPASS) { /* New connection */ if (Req->argc != 4) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); /* User name: only alphanumeric characters and limited punctuation is allowed.*/ ptr = Req->argv[0]; while (*ptr) { if (!isalnum(*ptr) && *ptr != '+' && *ptr != '-' && *ptr != '.' && *ptr != '_') { Conn_Close(Client_Conn(Client), NULL, "Invalid user name", true); return DISCONNECTED; } ptr++; } #ifdef IDENTAUTH ptr = Client_User(Client); if (!ptr || !*ptr || *ptr == '~') Client_SetUser(Client, Req->argv[0], false); #else Client_SetUser(Client, Req->argv[0], false); #endif Client_SetOrigUser(Client, Req->argv[0]); /* "Real name" or user info text: Don't set it to the empty * string, the original ircd can't deal with such "real names" * (e. g. "USER user * * :") ... */ if (*Req->argv[3]) Client_SetInfo(Client, Req->argv[3]); else Client_SetInfo(Client, "-"); LogDebug("Connection %d: got valid USER command ...", Client_Conn(Client)); if (Client_Type(Client) == CLIENT_GOTNICK) return Login_User(Client); else Client_SetType(Client, CLIENT_GOTUSER); return CONNECTED; } else if (Client_Type(Client) == CLIENT_SERVER || Client_Type(Client) == CLIENT_SERVICE) { /* Server/service updating an user */ if (Req->argc != 4) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); c = Client_Search(Req->prefix); if (!c) return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); Client_SetUser(c, Req->argv[0], true); Client_SetOrigUser(c, Req->argv[0]); Client_SetHostname(c, Req->argv[1]); Client_SetInfo(c, Req->argv[3]); LogDebug("Connection %d: got valid USER command for \"%s\".", Client_Conn(Client), Client_Mask(c)); /* RFC 1459 style user registration? * Introduce client to network: */ if (Client_Type(c) == CLIENT_GOTNICK) Client_Introduce(Client, c, CLIENT_USER); return CONNECTED; } else if (Client_Type(Client) == CLIENT_USER) { /* Already registered connection */ return IRC_WriteStrClient(Client, ERR_ALREADYREGISTRED_MSG, Client_ID(Client)); } else { /* Unexpected/invalid connection state? */ return IRC_WriteStrClient(Client, ERR_NOTREGISTERED_MSG, Client_ID(Client)); } } /* IRC_USER */
GLOBAL bool IRC_TRACE( CLIENT *Client, REQUEST *Req ) { CLIENT *from, *target, *c; CONN_ID idx, idx2; char user[CLIENT_USER_LEN]; assert( Client != NULL ); assert( Req != NULL ); /* Bad number of arguments? */ if( Req->argc > 1 ) return IRC_WriteStrClient( Client, ERR_NORECIPIENT_MSG, Client_ID( Client ), Req->command ); /* Search sender */ if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_Search( Req->prefix ); else from = Client; if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix ); /* Search target */ if( Req->argc == 1 ) target = Client_Search( Req->argv[0] ); else target = Client_ThisServer( ); /* Forward command to other server? */ if( target != Client_ThisServer( )) { if(( ! target ) || ( Client_Type( target ) != CLIENT_SERVER )) return IRC_WriteStrClient( from, ERR_NOSUCHSERVER_MSG, Client_ID( from ), Req->argv[0] ); /* Send RPL_TRACELINK back to initiator */ idx = Client_Conn( Client ); assert( idx > NONE ); idx2 = Client_Conn( Client_NextHop( target )); assert( idx2 > NONE ); if( ! IRC_WriteStrClient( from, RPL_TRACELINK_MSG, Client_ID( from ), PACKAGE_NAME, PACKAGE_VERSION, Client_ID( target ), Client_ID( Client_NextHop( target )), Option_String( idx2 ), time( NULL ) - Conn_StartTime( idx2 ), Conn_SendQ( idx ), Conn_SendQ( idx2 ))) return DISCONNECTED; /* Forward command */ IRC_WriteStrClientPrefix( target, from, "TRACE %s", Req->argv[0] ); return CONNECTED; } /* Infos about all connected servers */ c = Client_First( ); while( c ) { if( Client_Conn( c ) > NONE ) { /* Local client */ if( Client_Type( c ) == CLIENT_SERVER ) { /* Server link */ strlcpy( user, Client_User( c ), sizeof( user )); if( user[0] == '~' ) strlcpy( user, "unknown", sizeof( user )); if( ! IRC_WriteStrClient( from, RPL_TRACESERVER_MSG, Client_ID( from ), Client_ID( c ), user, Client_Hostname( c ), Client_Mask( Client_ThisServer( )), Option_String( Client_Conn( c )))) return DISCONNECTED; } if(( Client_Type( c ) == CLIENT_USER ) && ( strchr( Client_Modes( c ), 'o' ))) { /* IRC Operator */ if( ! IRC_WriteStrClient( from, RPL_TRACEOPERATOR_MSG, Client_ID( from ), Client_ID( c ))) return DISCONNECTED; } } c = Client_Next( c ); } IRC_SetPenalty( Client, 3 ); return IRC_WriteStrClient( from, RPL_TRACEEND_MSG, Client_ID( from ), Conf_ServerName, PACKAGE_NAME, PACKAGE_VERSION, NGIRCd_DebugLevel ); } /* IRC_TRACE */
/** * Handler for the IRC command "SERVER". * See RFC 2813 section 4.1.2. */ GLOBAL bool IRC_SERVER( CLIENT *Client, REQUEST *Req ) { char str[LINE_LEN]; CLIENT *from, *c; int i; CONN_ID con; assert( Client != NULL ); assert( Req != NULL ); /* Return an error if this is not a local client */ if (Client_Conn(Client) <= NONE) return IRC_WriteStrClient(Client, ERR_UNKNOWNCOMMAND_MSG, Client_ID(Client), Req->command); if (Client_Type(Client) == CLIENT_GOTPASS || Client_Type(Client) == CLIENT_GOTPASS_2813) { /* We got a PASS command from the peer, and now a SERVER * command: the peer tries to register itself as a server. */ LogDebug("Connection %d: got SERVER command (new server link) ...", Client_Conn(Client)); if(( Req->argc != 2 ) && ( Req->argc != 3 )) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); /* Ist this server configured on out side? */ for( i = 0; i < MAX_SERVERS; i++ ) if( strcasecmp( Req->argv[0], Conf_Server[i].name ) == 0 ) break; if( i >= MAX_SERVERS ) { Log( LOG_ERR, "Connection %d: Server \"%s\" not configured here!", Client_Conn( Client ), Req->argv[0] ); Conn_Close( Client_Conn( Client ), NULL, "Server not configured here", true); return DISCONNECTED; } if( strcmp( Client_Password( Client ), Conf_Server[i].pwd_in ) != 0 ) { /* wrong password */ Log( LOG_ERR, "Connection %d: Got bad password from server \"%s\"!", Client_Conn( Client ), Req->argv[0] ); Conn_Close( Client_Conn( Client ), NULL, "Bad password", true); return DISCONNECTED; } /* Is there a registered server with this ID? */ if( ! Client_CheckID( Client, Req->argv[0] )) return DISCONNECTED; Client_SetID( Client, Req->argv[0] ); Client_SetHops( Client, 1 ); Client_SetInfo( Client, Req->argv[Req->argc - 1] ); /* Is this server registering on our side, or are we connecting to * a remote server? */ con = Client_Conn(Client); if (Client_Token(Client) != TOKEN_OUTBOUND) { /* Incoming connection, send user/pass */ if (!IRC_WriteStrClient(Client, "PASS %s %s", Conf_Server[i].pwd_out, NGIRCd_ProtoID) || !IRC_WriteStrClient(Client, "SERVER %s 1 :%s", Conf_ServerName, Conf_ServerInfo)) { Conn_Close(con, "Unexpected server behavior!", NULL, false); return DISCONNECTED; } Client_SetIntroducer(Client, Client); Client_SetToken(Client, 1); } else { /* outgoing connect, we already sent a SERVER and PASS * command to the peer */ Client_SetToken(Client, atoi(Req->argv[1])); } /* Mark this connection as belonging to an configured server */ Conf_SetServer(i, con); /* Check protocol level */ if (Client_Type(Client) == CLIENT_GOTPASS) { /* We got a "simple" PASS command, so the peer is * using the protocol as defined in RFC 1459. */ if (! (Conn_Options(con) & CONN_RFC1459)) Log(LOG_INFO, "Switching connection %d (\"%s\") to RFC 1459 compatibility mode.", con, Client_ID(Client)); Conn_SetOption(con, CONN_RFC1459); } Client_SetType(Client, CLIENT_UNKNOWNSERVER); #ifdef ZLIB if (strchr(Client_Flags(Client), 'Z') && !Zip_InitConn(con)) { Conn_Close( con, "Can't inizialize compression (zlib)!", NULL, false ); return DISCONNECTED; } #endif #ifdef IRCPLUS if (strchr(Client_Flags(Client), 'H')) { LogDebug("Peer supports IRC+ extended server handshake ..."); if (!IRC_Send_ISUPPORT(Client)) return DISCONNECTED; return IRC_WriteStrClient(Client, RPL_ENDOFMOTD_MSG, Client_ID(Client)); } else { #endif if (Conf_MaxNickLength != CLIENT_NICK_LEN_DEFAULT) Log(LOG_CRIT, "Attention: this server uses a non-standard nick length, but the peer doesn't support the IRC+ extended server handshake!"); #ifdef IRCPLUS } #endif return IRC_Num_ENDOFMOTD(Client, Req); } else if( Client_Type( Client ) == CLIENT_SERVER ) { /* New server is being introduced to the network */ if( Req->argc != 4 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); /* check for existing server with same ID */ if( ! Client_CheckID( Client, Req->argv[0] )) return DISCONNECTED; from = Client_Search( Req->prefix ); if( ! from ) { /* Uh, Server, that introduced the new server is unknown?! */ Log( LOG_ALERT, "Unknown ID in prefix of SERVER: \"%s\"! (on connection %d)", Req->prefix, Client_Conn( Client )); Conn_Close( Client_Conn( Client ), NULL, "Unknown ID in prefix of SERVER", true); return DISCONNECTED; } c = Client_NewRemoteServer(Client, Req->argv[0], from, atoi(Req->argv[1]), atoi(Req->argv[2]), Req->argv[3], true); if (!c) { Log( LOG_ALERT, "Can't create client structure for server! (on connection %d)", Client_Conn( Client )); Conn_Close( Client_Conn( Client ), NULL, "Can't allocate client structure for remote server", true); return DISCONNECTED; } if(( Client_Hops( c ) > 1 ) && ( Req->prefix[0] )) snprintf( str, sizeof( str ), "connected to %s, ", Client_ID( from )); else strcpy( str, "" ); Log( LOG_NOTICE|LOG_snotice, "Server \"%s\" registered (via %s, %s%d hop%s).", Client_ID( c ), Client_ID( Client ), str, Client_Hops( c ), Client_Hops( c ) > 1 ? "s": "" ); /* notify other servers */ IRC_WriteStrServersPrefix( Client, from, "SERVER %s %d %d :%s", Client_ID( c ), Client_Hops( c ) + 1, Client_MyToken( c ), Client_Info( c )); return CONNECTED; } else return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); } /* IRC_SERVER */
/** * Handler for the IRC command "SQUIT". * See RFC 2813 section 4.1.2 and RFC 2812 section 3.1.8. */ GLOBAL bool IRC_SQUIT(CLIENT * Client, REQUEST * Req) { char msg[COMMAND_LEN], logmsg[COMMAND_LEN]; CLIENT *from, *target; CONN_ID con; int loglevel; assert(Client != NULL); assert(Req != NULL); if (Client_Type(Client) != CLIENT_SERVER && !Client_HasMode(Client, 'o')) return Op_NoPrivileges(Client, Req); /* Bad number of arguments? */ if (Req->argc != 2) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); if (Client_Type(Client) == CLIENT_SERVER && Req->prefix) { from = Client_Search(Req->prefix); if (Client_Type(from) != CLIENT_SERVER && !Op_Check(Client, Req)) return Op_NoPrivileges(Client, Req); } else from = Client; if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); if (Client_Type(Client) == CLIENT_USER) loglevel = LOG_NOTICE | LOG_snotice; else loglevel = LOG_DEBUG; Log(loglevel, "Got SQUIT from %s for \"%s\": \"%s\" ...", Client_ID(from), Req->argv[0], Req->argv[1]); target = Client_Search(Req->argv[0]); if (Client_Type(Client) != CLIENT_SERVER && target == Client_ThisServer()) return Op_NoPrivileges(Client, Req); if (!target) { /* The server is (already) unknown */ Log(LOG_WARNING, "Got SQUIT from %s for unknown server \"%s\"!?", Client_ID(Client), Req->argv[0]); return CONNECTED; } con = Client_Conn(target); if (Req->argv[1][0]) if (Client_NextHop(from) != Client || con > NONE) snprintf(msg, sizeof(msg), "%s (SQUIT from %s)", Req->argv[1], Client_ID(from)); else strlcpy(msg, Req->argv[1], sizeof(msg)); else snprintf(msg, sizeof(msg), "Got SQUIT from %s", Client_ID(from)); if (con > NONE) { /* We are directly connected to the target server, so we * have to tear down the connection and to inform all the * other remaining servers in the network */ IRC_SendWallops(Client_ThisServer(), Client_ThisServer(), "Received SQUIT %s from %s: %s", Req->argv[0], Client_ID(from), Req->argv[1][0] ? Req->argv[1] : "-"); Conn_Close(con, NULL, msg, true); if (con == Client_Conn(Client)) return DISCONNECTED; } else { /* This server is not directly connected, so the SQUIT must * be forwarded ... */ if (Client_Type(from) != CLIENT_SERVER) { /* The origin is not an IRC server, so don't evaluate * this SQUIT but simply forward it */ IRC_WriteStrClientPrefix(Client_NextHop(target), from, "SQUIT %s :%s", Req->argv[0], Req->argv[1]); } else { /* SQUIT has been generated by another server, so * remove the target server from the network! */ logmsg[0] = '\0'; if (!strchr(msg, '(')) snprintf(logmsg, sizeof(logmsg), "%s (SQUIT from %s)", Req->argv[1], Client_ID(from)); Client_Destroy(target, logmsg[0] ? logmsg : msg, msg, false); } } return CONNECTED; } /* IRC_SQUIT */
GLOBAL bool IRC_NJOIN( CLIENT *Client, REQUEST *Req ) { char nick_in[COMMAND_LEN], nick_out[COMMAND_LEN], *channame, *ptr, modes[8]; bool is_op, is_voiced; CHANNEL *chan; CLIENT *c; assert( Client != NULL ); assert( Req != NULL ); if( Req->argc != 2 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); strlcpy( nick_in, Req->argv[1], sizeof( nick_in )); strcpy( nick_out, "" ); channame = Req->argv[0]; ptr = strtok( nick_in, "," ); while( ptr ) { is_op = is_voiced = false; /* cut off prefixes */ while(( *ptr == '@' ) || ( *ptr == '+' )) { if( *ptr == '@' ) is_op = true; if( *ptr == '+' ) is_voiced = true; ptr++; } c = Client_Search( ptr ); if( c ) { Channel_Join( c, channame ); chan = Channel_Search( channame ); assert( chan != NULL ); if( is_op ) Channel_UserModeAdd( chan, c, 'o' ); if( is_voiced ) Channel_UserModeAdd( chan, c, 'v' ); /* announce to channel... */ IRC_WriteStrChannelPrefix( Client, chan, c, false, "JOIN :%s", channame ); /* set Channel-User-Modes */ strlcpy( modes, Channel_UserModes( chan, c ), sizeof( modes )); if( modes[0] ) { /* send modes to channel */ IRC_WriteStrChannelPrefix( Client, chan, Client, false, "MODE %s +%s %s", channame, modes, Client_ID( c )); } if( nick_out[0] != '\0' ) strlcat( nick_out, ",", sizeof( nick_out )); if( is_op ) strlcat( nick_out, "@", sizeof( nick_out )); if( is_voiced ) strlcat( nick_out, "+", sizeof( nick_out )); strlcat( nick_out, ptr, sizeof( nick_out )); } else Log( LOG_ERR, "Got NJOIN for unknown nick \"%s\" for channel \"%s\"!", ptr, channame ); /* search for next Nick */ ptr = strtok( NULL, "," ); } /* forward to other servers */ if( nick_out[0] != '\0' ) IRC_WriteStrServersPrefix( Client, Client_ThisServer( ), "NJOIN %s :%s", Req->argv[0], nick_out ); return CONNECTED; } /* IRC_NJOIN */
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; assert(Client != NULL); assert(Req != NULL); if (Req->argc == 0) { if (!SendErrors) return CONNECTED; return IRC_WriteStrClient(Client, ERR_NORECIPIENT_MSG, Client_ID(Client), Req->command); } if (Req->argc == 1) { if (!SendErrors) return CONNECTED; return IRC_WriteStrClient(Client, ERR_NOTEXTTOSEND_MSG, Client_ID(Client)); } if (Req->argc > 2) { if (!SendErrors) return CONNECTED; return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); } if (Client_Type(Client) == CLIENT_SERVER) from = Client_Search(Req->prefix); else from = Client; if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHNICK_MSG, Client_ID(Client), Req->prefix); /* handle msgtarget = msgto *("," msgto) */ currentTarget = strtok_r(currentTarget, ",", &lastCurrentTarget); ngt_UpperStr(Req->command); while (currentTarget) { /* Check for and handle valid <msgto> of form: * RFC 2812 2.3.1: * msgto = channel / ( user [ "%" host ] "@" servername ) * msgto =/ ( user "%" host ) / targetmask * msgto =/ nickname / ( nickname "!" user "@" host ) */ if (strchr(currentTarget, '!') == NULL) /* nickname */ cl = Client_Search(currentTarget); else cl = NULL; if (cl == NULL) { /* If currentTarget isn't a nickname check for: * user ["%" host] "@" servername * user "%" host * nickname "!" user "@" host */ char target[COMMAND_LEN]; char * nick = NULL; char * user = NULL; char * host = NULL; char * server = NULL; strlcpy(target, currentTarget, COMMAND_LEN); server = strchr(target, '@'); if (server) { *server = '\0'; server++; } host = strchr(target, '%'); if (host) { *host = '\0'; host++; } user = strchr(target, '!'); if (user) { /* msgto form: nick!user@host */ *user = '******'; user++; nick = target; host = server; /* not "@server" but "@host" */ } else { user = target; } for (cl = Client_First(); cl != NULL; cl = Client_Next(cl)) { if (Client_Type(cl) != CLIENT_USER && Client_Type(cl) != CLIENT_SERVICE) continue; if (nick != NULL && host != NULL) { if (strcasecmp(nick, Client_ID(cl)) == 0 && strcasecmp(user, Client_User(cl)) == 0 && strcasecmp(host, Client_HostnameCloaked(cl)) == 0) break; else continue; } if (strcasecmp(user, Client_User(cl)) != 0) continue; if (host != NULL && strcasecmp(host, Client_HostnameCloaked(cl)) != 0) continue; if (server != NULL && strcasecmp(server, Client_ID(Client_Introducer(cl))) != 0) continue; break; } } if (cl) { /* Target is a user, enforce type */ #ifndef STRICT_RFC if (Client_Type(cl) != ForceType && !(ForceType == CLIENT_USER && (Client_Type(cl) == CLIENT_USER || Client_Type(cl) == CLIENT_SERVICE))) { #else if (Client_Type(cl) != ForceType) { #endif if (SendErrors && !IRC_WriteStrClient( from, ERR_NOSUCHNICK_MSG,Client_ID(from), currentTarget)) return DISCONNECTED; goto send_next_target; } #ifndef STRICT_RFC if (ForceType == CLIENT_SERVICE && (Conn_Options(Client_Conn(Client_NextHop(cl))) & CONN_RFC1459)) { /* SQUERY command but RFC 1459 link: convert * request to PRIVMSG command */ Req->command = "PRIVMSG"; } #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 */ if (!IRC_WriteStrClient(from, RPL_AWAY_MSG, Client_ID(from), Client_ID(cl), Client_Away(cl))) return DISCONNECTED; } if (Client_Conn(from) > NONE) { Conn_UpdateIdle(Client_Conn(from)); } if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", Req->command, Client_ID(cl), 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 (!SendErrors) return CONNECTED; if (!IRC_WriteStrClient(from, ERR_NOSUCHNICK_MSG, Client_ID(from), currentTarget)) return DISCONNECTED; } send_next_target: currentTarget = strtok_r(NULL, ",", &lastCurrentTarget); if (currentTarget) Conn_SetPenalty(Client_Conn(Client), 1); } return CONNECTED; } /* Send_Message */ static bool Send_Message_Mask(CLIENT * from, char * command, char * targetMask, char * message, bool SendErrors) { CLIENT *cl; bool client_match; char *mask = targetMask + 1; const char *check_wildcards; cl = NULL; if (strchr(Client_Modes(from), 'o') == NULL) { if (!SendErrors) return true; return IRC_WriteStrClient(from, ERR_NOPRIVILEGES_MSG, 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) continue; client_match = MatchCaseInsensitive(mask, Client_Hostname(cl)); if (client_match) if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", command, Client_ID(cl), message)) 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; client_match = MatchCaseInsensitive(mask, Client_ID(Client_Introducer(cl))); if (client_match) if (!IRC_WriteStrClientPrefix(cl, from, "%s %s :%s", command, Client_ID(cl), message)) return false; } } return CONNECTED; } /* Send_Message_Mask */
/** * Handler for the IRC "NICK" command. * * See RFC 2812, 3.1.2 "Nick message", and RFC 2813, 4.1.3 "Nick". * * This function implements the IRC command "NICK" which is used to register * with the server, to change already registered nicknames and to introduce * new users which are connected to other servers. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_NICK( CLIENT *Client, REQUEST *Req ) { CLIENT *intr_c, *target, *c; char *nick, *user, *hostname, *modes, *info; int token, hops; assert( Client != NULL ); assert( Req != NULL ); /* Some IRC clients, for example BitchX, send the NICK and USER * commands in the wrong order ... */ if(Client_Type(Client) == CLIENT_UNKNOWN || Client_Type(Client) == CLIENT_GOTPASS || Client_Type(Client) == CLIENT_GOTNICK #ifndef STRICT_RFC || Client_Type(Client) == CLIENT_GOTUSER #endif || Client_Type(Client) == CLIENT_USER || Client_Type(Client) == CLIENT_SERVICE || (Client_Type(Client) == CLIENT_SERVER && Req->argc == 1)) { /* User registration or change of nickname */ /* Wrong number of arguments? */ if( Req->argc != 1 ) return IRC_WriteStrClient( Client, ERR_NEEDMOREPARAMS_MSG, Client_ID( Client ), Req->command ); /* Search "target" client */ if( Client_Type( Client ) == CLIENT_SERVER ) { target = Client_Search( Req->prefix ); if( ! target ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->argv[0] ); } else { /* Is this a restricted client? */ if( Client_HasMode( Client, 'r' )) return IRC_WriteStrClient( Client, ERR_RESTRICTED_MSG, Client_ID( Client )); target = Client; } #ifndef STRICT_RFC /* If the clients tries to change to its own nickname we won't * do anything. This is how the original ircd behaves and some * clients (for example Snak) expect it to be like this. * But I doubt that this is "really the right thing" ... */ if( strcmp( Client_ID( target ), Req->argv[0] ) == 0 ) return CONNECTED; #endif /* Check that the new nickname is available. Special case: * the client only changes from/to upper to lower case. */ if( strcasecmp( Client_ID( target ), Req->argv[0] ) != 0 ) { if( ! Client_CheckNick( target, Req->argv[0] )) return CONNECTED; } if (Client_Type(target) != CLIENT_USER && Client_Type(target) != CLIENT_SERVICE && Client_Type(target) != CLIENT_SERVER) { /* New client */ LogDebug("Connection %d: got valid NICK command ...", Client_Conn( Client )); /* Register new nickname of this client */ Client_SetID( target, Req->argv[0] ); #ifndef STRICT_RFC if (Conf_AuthPing) { Conn_SetAuthPing(Client_Conn(Client), rand()); IRC_WriteStrClient(Client, "PING :%ld", Conn_GetAuthPing(Client_Conn(Client))); LogDebug("Connection %d: sent AUTH PING %ld ...", Client_Conn(Client), Conn_GetAuthPing(Client_Conn(Client))); } #endif /* If we received a valid USER command already then * register the new client! */ if( Client_Type( Client ) == CLIENT_GOTUSER ) return Login_User( Client ); else Client_SetType( Client, CLIENT_GOTNICK ); } else { /* Nickname change */ if (Client_Conn(target) > NONE) { /* Local client */ Log(LOG_INFO, "%s \"%s\" changed nick (connection %d): \"%s\" -> \"%s\".", Client_TypeText(target), Client_Mask(target), Client_Conn(target), Client_ID(target), Req->argv[0]); Conn_UpdateIdle(Client_Conn(target)); } else { /* Remote client */ LogDebug("%s \"%s\" changed nick: \"%s\" -> \"%s\".", Client_TypeText(target), Client_Mask(target), Client_ID(target), Req->argv[0]); } /* Inform all users and servers (which have to know) * of this nickname change */ if( Client_Type( Client ) == CLIENT_USER ) IRC_WriteStrClientPrefix( Client, Client, "NICK :%s", Req->argv[0] ); IRC_WriteStrServersPrefix( Client, target, "NICK :%s", Req->argv[0] ); IRC_WriteStrRelatedPrefix( target, target, false, "NICK :%s", Req->argv[0] ); /* Register old nickname for WHOWAS queries */ Client_RegisterWhowas( target ); /* Save new nickname */ Client_SetID( target, Req->argv[0] ); IRC_SetPenalty( target, 2 ); } return CONNECTED; } else if(Client_Type(Client) == CLIENT_SERVER || Client_Type(Client) == CLIENT_SERVICE) { /* Server or service introduces new client */ /* Bad number of parameters? */ if (Req->argc != 2 && Req->argc != 7) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); if (Req->argc >= 7) { /* RFC 2813 compatible syntax */ nick = Req->argv[0]; hops = atoi(Req->argv[1]); user = Req->argv[2]; hostname = Req->argv[3]; token = atoi(Req->argv[4]); modes = Req->argv[5] + 1; info = Req->argv[6]; } else { /* RFC 1459 compatible syntax */ nick = Req->argv[0]; hops = 1; user = Req->argv[0]; hostname = Client_ID(Client); token = atoi(Req->argv[1]); modes = ""; info = Req->argv[0]; } c = Client_Search(nick); if(c) { /* * the new nick is already present on this server: * the new and the old one have to be disconnected now. */ Log( LOG_ERR, "Server %s introduces already registered nick \"%s\"!", Client_ID( Client ), Req->argv[0] ); Kill_Nick( Req->argv[0], "Nick collision" ); return CONNECTED; } /* Find the Server this client is connected to */ intr_c = Client_GetFromToken(Client, token); if( ! intr_c ) { Log( LOG_ERR, "Server %s introduces nick \"%s\" on unknown server!?", Client_ID( Client ), Req->argv[0] ); Kill_Nick( Req->argv[0], "Unknown server" ); return CONNECTED; } c = Client_NewRemoteUser(intr_c, nick, hops, user, hostname, token, modes, info, true); if( ! c ) { /* out of memory, need to disconnect client to keep network state consistent */ Log( LOG_ALERT, "Can't create client structure! (on connection %d)", Client_Conn( Client )); Kill_Nick( Req->argv[0], "Server error" ); return CONNECTED; } /* RFC 2813: client is now fully registered, inform all the * other servers about the new user. * RFC 1459: announce the new client only after receiving the * USER command, first we need more information! */ if (Req->argc < 7) { LogDebug("Client \"%s\" is being registered (RFC 1459) ...", Client_Mask(c)); Client_SetType(c, CLIENT_GOTNICK); } else Client_Introduce(Client, c, CLIENT_USER); return CONNECTED; } else return IRC_WriteStrClient( Client, ERR_ALREADYREGISTRED_MSG, Client_ID( Client )); } /* IRC_NICK */