/** * 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 */
/** * Join Channel * This function lets a client join a channel. First, the function * checks that the specified channel name is valid and that the client * isn't already a member. If the specified channel doesn't exist, * a new channel is created. Client is added to channel by function * Add_Client(). */ GLOBAL bool Channel_Join( CLIENT *Client, const char *Name ) { CHANNEL *chan; assert(Client != NULL); assert(Name != NULL); /* Check that the channel name is valid */ if (! Channel_IsValidName(Name)) { IRC_WriteErrClient(Client, ERR_NOSUCHCHANNEL_MSG, Client_ID(Client), Name); return false; } chan = Channel_Search(Name); if(chan) { /* Check if the client is already in the channel */ if (Get_Cl2Chan(chan, Client)) return false; } else { /* If the specified channel does not exist, the channel * is now created */ chan = Channel_Create(Name); if (!chan) return false; } /* Add user to Channel */ if (! Add_Client(chan, Client)) return false; return true; } /* Channel_Join */
/** * Part client from channel. * This function lets a client part from a channel. First, the function checks * if the channel exists and the client is a member of it and sends out * appropriate error messages if not. The real work is done by the function * Remove_Client(). */ GLOBAL bool Channel_Part(CLIENT * Client, CLIENT * Origin, const char *Name, const char *Reason) { CHANNEL *chan; assert(Client != NULL); assert(Name != NULL); assert(Reason != NULL); /* Check that specified channel exists */ chan = Channel_Search(Name); if (!chan) { IRC_WriteErrClient(Client, ERR_NOSUCHCHANNEL_MSG, Client_ID(Client), Name); return false; } /* Check that the client is in the channel */ if (!Get_Cl2Chan(chan, Client)) { IRC_WriteErrClient(Client, ERR_NOTONCHANNEL_MSG, Client_ID(Client), Name); return false; } if (Conf_MorePrivacy) Reason = ""; /* Part client from channel */ if (!Remove_Client(REMOVE_PART, chan, Client, Origin, Reason, true)) return false; else return true; } /* Channel_Part */
/** * Log a message to the local &SERVER channel, if it exists. */ GLOBAL void Channel_LogServer(const char *msg) { CHANNEL *sc; CLIENT *c; assert(msg != NULL); sc = Channel_Search("&SERVER"); if (!sc) return; c = Client_ThisServer(); Channel_Write(sc, c, c, "PRIVMSG", false, msg); } /* Channel_LogServer */
/** * 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 */
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 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 */
/** * Generate predefined persistent channels and &SERVER */ GLOBAL void Channel_InitPredefined( void ) { CHANNEL *new_chan; const struct Conf_Channel *conf_chan; const char *c; size_t i, channel_count = array_length(&Conf_Channels, sizeof(*conf_chan)); conf_chan = array_start(&Conf_Channels); assert(channel_count == 0 || conf_chan != NULL); for (i = 0; i < channel_count; i++, conf_chan++) { if (!conf_chan->name[0]) continue; if (!Channel_IsValidName(conf_chan->name)) { Log(LOG_ERR, "Can't create pre-defined channel: invalid name: \"%s\"", conf_chan->name); continue; } new_chan = Channel_Search(conf_chan->name); if (new_chan) { Log(LOG_INFO, "Can't create pre-defined channel \"%s\": name already in use.", conf_chan->name); Set_KeyFile(new_chan, conf_chan->keyfile); continue; } new_chan = Channel_Create(conf_chan->name); if (!new_chan) { Log(LOG_ERR, "Can't create pre-defined channel \"%s\"!", conf_chan->name); continue; } Log(LOG_INFO, "Created pre-defined channel \"%s\".", conf_chan->name); Channel_ModeAdd(new_chan, 'P'); if (conf_chan->topic[0]) Channel_SetTopic(new_chan, NULL, conf_chan->topic); c = conf_chan->modes; while (*c) Channel_ModeAdd(new_chan, *c++); Channel_SetKey(new_chan, conf_chan->key); Channel_SetMaxUsers(new_chan, conf_chan->maxusers); Set_KeyFile(new_chan, conf_chan->keyfile); } if (channel_count) array_free(&Conf_Channels); /* Make sure the local &SERVER channel exists */ if (!Channel_Search("&SERVER")) { new_chan = Channel_Create("&SERVER"); if (new_chan) { Channel_SetModes(new_chan, "mnPt"); Channel_SetTopic(new_chan, Client_ThisServer(), "Server Messages"); } else Log(LOG_ERR, "Failed to create \"&SERVER\" channel!"); } else LogDebug("Required channel \"&SERVER\" already exists, ok."); } /* Channel_InitPredefined */
/** * Kick user from Channel */ GLOBAL void Channel_Kick(CLIENT *Peer, CLIENT *Target, CLIENT *Origin, const char *Name, const char *Reason ) { CHANNEL *chan; bool can_kick = false; assert(Peer != NULL); assert(Target != NULL); assert(Origin != NULL); assert(Name != NULL); assert(Reason != NULL); /* Check that channel exists */ chan = Channel_Search( Name ); if (!chan) { IRC_WriteErrClient(Origin, ERR_NOSUCHCHANNEL_MSG, Client_ID(Origin), Name); return; } if (Client_Type(Peer) != CLIENT_SERVER && Client_Type(Origin) != CLIENT_SERVICE) { /* Check that user is on the specified channel */ if (!Channel_IsMemberOf(chan, Origin)) { IRC_WriteErrClient(Origin, ERR_NOTONCHANNEL_MSG, Client_ID(Origin), Name); return; } } /* Check that the client to be kicked is on the specified channel */ if (!Channel_IsMemberOf(chan, Target)) { IRC_WriteErrClient(Origin, ERR_USERNOTINCHANNEL_MSG, Client_ID(Origin), Client_ID(Target), Name ); return; } if(Client_Type(Peer) == CLIENT_USER) { /* Channel mode 'Q' and user mode 'q' on target: nobody but * IRC Operators and servers can kick the target user */ if ((Channel_HasMode(chan, 'Q') || Client_HasMode(Target, 'q') || Client_Type(Target) == CLIENT_SERVICE) && !Client_HasMode(Origin, 'o')) { IRC_WriteErrClient(Origin, ERR_KICKDENY_MSG, Client_ID(Origin), Name, Client_ID(Target)); return; } /* Check if client has the rights to kick target */ /* Owner can kick everyone */ if (Channel_UserHasMode(chan, Peer, 'q')) can_kick = true; /* Admin can't kick owner */ else if (Channel_UserHasMode(chan, Peer, 'a') && !Channel_UserHasMode(chan, Target, 'q')) can_kick = true; /* Op can't kick owner | admin */ else if (Channel_UserHasMode(chan, Peer, 'o') && !Channel_UserHasMode(chan, Target, 'q') && !Channel_UserHasMode(chan, Target, 'a')) can_kick = true; /* Half Op can't kick owner | admin | op */ else if (Channel_UserHasMode(chan, Peer, 'h') && !Channel_UserHasMode(chan, Target, 'q') && !Channel_UserHasMode(chan, Target, 'a') && !Channel_UserHasMode(chan, Target, 'o')) can_kick = true; /* IRC operators & IRCd with OperCanMode enabled * can kick anyways regardless of privilege */ else if(Client_HasMode(Origin, 'o') && Conf_OperCanMode) can_kick = true; if(!can_kick) { IRC_WriteErrClient(Origin, ERR_CHANOPPRIVTOOLOW_MSG, Client_ID(Origin), Name); return; } } /* Kick Client from channel */ Remove_Client( REMOVE_KICK, chan, Target, Origin, Reason, true); } /* Channel_Kick */
/** * 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 */
/** * Kick user from Channel */ GLOBAL void Channel_Kick(CLIENT *Peer, CLIENT *Target, CLIENT *Origin, const char *Name, const char *Reason ) { CHANNEL *chan; char *ptr, *target_modes; bool can_kick = false; assert(Peer != NULL); assert(Target != NULL); assert(Origin != NULL); assert(Name != NULL); assert(Reason != NULL); /* Check that channel exists */ chan = Channel_Search( Name ); if( ! chan ) { IRC_WriteStrClient( Origin, ERR_NOSUCHCHANNEL_MSG, Client_ID( Origin ), Name ); return; } if (Client_Type(Peer) != CLIENT_SERVER && Client_Type(Origin) != CLIENT_SERVICE) { /* Check that user is on the specified channel */ if (!Channel_IsMemberOf(chan, Origin)) { IRC_WriteStrClient( Origin, ERR_NOTONCHANNEL_MSG, Client_ID(Origin), Name); return; } } if(Client_Type(Peer) == CLIENT_USER) { /* Channel mode 'Q' and user mode 'q' on target: nobody but * IRC Operators and servers can kick the target user */ if ((strchr(Channel_Modes(chan), 'Q') || Client_HasMode(Target, 'q') || Client_Type(Target) == CLIENT_SERVICE) && !Client_HasMode(Origin, 'o')) { IRC_WriteStrClient(Origin, ERR_KICKDENY_MSG, Client_ID(Origin), Name, Client_ID(Target)); return; } /* Check if client has the rights to kick target */ ptr = Channel_UserModes(chan, Peer); target_modes = Channel_UserModes(chan, Target); while(*ptr) { /* Owner can kick everyone */ if ( *ptr == 'q') { can_kick = true; break; } /* Admin can't kick owner */ if ( *ptr == 'a' ) { if (!strchr(target_modes, 'q')) { can_kick = true; break; } } /* Op can't kick owner | admin */ if ( *ptr == 'o' ) { if (!strchr(target_modes, 'q') && !strchr(target_modes, 'a')) { can_kick = true; break; } } /* Half Op can't kick owner | admin | op */ if ( *ptr == 'h' ) { if (!strchr(target_modes, 'q') && !strchr(target_modes, 'a') && !strchr(target_modes, 'o')) { can_kick = true; break; } } ptr++; } if(!can_kick) { IRC_WriteStrClient(Origin, ERR_CHANOPPRIVTOOLOW_MSG, Client_ID(Origin), Name); return; } } /* Check that the client to be kicked is on the specified channel */ if (!Channel_IsMemberOf(chan, Target)) { IRC_WriteStrClient(Origin, ERR_USERNOTINCHANNEL_MSG, Client_ID(Origin), Client_ID(Target), Name ); return; } /* Kick Client from channel */ Remove_Client( REMOVE_KICK, chan, Target, Origin, Reason, true); } /* Channel_Kick */
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 */
/** * Handler for the IRC+ "CHANINFO" 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_CHANINFO( CLIENT *Client, REQUEST *Req ) { char modes_add[COMMAND_LEN], l[16]; CLIENT *from; CHANNEL *chan; int arg_topic; assert( Client != NULL ); assert( Req != NULL ); /* Bad number of parameters? */ if (Req->argc < 2 || Req->argc == 4 || Req->argc > 5) return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); /* Compatibility kludge */ if (Req->argc == 5) arg_topic = 4; else if(Req->argc == 3) arg_topic = 2; else arg_topic = -1; _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) /* Search or create channel */ chan = Channel_Search( Req->argv[0] ); if (!chan) chan = Channel_Create( Req->argv[0] ); if (!chan) return CONNECTED; if (Req->argv[1][0] == '+') { if (!*Channel_Modes(chan)) { /* OK, this channel doesn't have modes yet, * set the received ones: */ Channel_SetModes(chan, &Req->argv[1][1]); if(Req->argc == 5) { if(Channel_HasMode(chan, 'k')) Channel_SetKey(chan, Req->argv[2]); if(Channel_HasMode(chan, 'l')) Channel_SetMaxUsers(chan, atol(Req->argv[3])); } else { /* Delete modes which we never want to inherit */ Channel_ModeDel(chan, 'l'); Channel_ModeDel(chan, 'k'); } strcpy(modes_add, ""); if (Channel_HasMode(chan, 'l')) { snprintf(l, sizeof(l), " %lu", Channel_MaxUsers(chan)); strlcat(modes_add, l, sizeof(modes_add)); } if (Channel_HasMode(chan, 'k')) { strlcat(modes_add, " ", sizeof(modes_add)); strlcat(modes_add, Channel_Key(chan), sizeof(modes_add)); } /* Inform members of this channel */ IRC_WriteStrChannelPrefix(Client, chan, from, false, "MODE %s +%s%s", Req->argv[0], Channel_Modes(chan), modes_add); } } else Log(LOG_WARNING, "CHANINFO: invalid MODE format ignored!"); if (arg_topic > 0) { /* We got a topic */ if (!*Channel_Topic(chan) && Req->argv[arg_topic][0]) { /* OK, there is no topic jet */ Channel_SetTopic(chan, Client, Req->argv[arg_topic]); IRC_WriteStrChannelPrefix(Client, chan, from, false, "TOPIC %s :%s", Req->argv[0], Channel_Topic(chan)); } } /* Forward CHANINFO to other servers */ if (Req->argc == 5) IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s %s %s :%s", Req->argv[0], Req->argv[1], Req->argv[2], Req->argv[3], Req->argv[4]); else if (Req->argc == 3) IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s :%s", Req->argv[0], Req->argv[1], Req->argv[2]); else IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s", Req->argv[0], Req->argv[1]); return CONNECTED; } /* IRC_CHANINFO */
/** * Handler for the IRC "TOPIC" 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_TOPIC( CLIENT *Client, REQUEST *Req ) { CHANNEL *chan; CLIENT *from; char *topic; bool r, topic_power; assert( Client != NULL ); assert( Req != NULL ); _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) chan = Channel_Search(Req->argv[0]); if (!chan) return IRC_WriteErrClient(from, ERR_NOSUCHCHANNEL_MSG, Client_ID(from), Req->argv[0]); /* Only remote servers and channel members are allowed to change the * channel topic, and IRC operators when the Conf_OperCanMode option * is set in the server configuration. */ if (Client_Type(Client) != CLIENT_SERVER) { topic_power = Client_HasMode(from, 'o'); if (!Channel_IsMemberOf(chan, from) && !(Conf_OperCanMode && topic_power)) return IRC_WriteErrClient(from, ERR_NOTONCHANNEL_MSG, Client_ID(from), Req->argv[0]); } else topic_power = true; if (Req->argc == 1) { /* Request actual topic */ topic = Channel_Topic(chan); if (*topic) { r = IRC_WriteStrClient(from, RPL_TOPIC_MSG, Client_ID(Client), Channel_Name(chan), topic); #ifndef STRICT_RFC if (!r) return r; r = IRC_WriteStrClient(from, RPL_TOPICSETBY_MSG, Client_ID(Client), Channel_Name(chan), Channel_TopicWho(chan), Channel_TopicTime(chan)); #endif return r; } else return IRC_WriteStrClient(from, RPL_NOTOPIC_MSG, Client_ID(from), Channel_Name(chan)); } if (Channel_HasMode(chan, 't')) { /* Topic Lock. Is the user a channel op or IRC operator? */ if(!topic_power && !Channel_UserHasMode(chan, from, 'h') && !Channel_UserHasMode(chan, from, 'o') && !Channel_UserHasMode(chan, from, 'a') && !Channel_UserHasMode(chan, from, 'q')) return IRC_WriteErrClient(from, ERR_CHANOPRIVSNEEDED_MSG, Client_ID(from), Channel_Name(chan)); } /* Set new topic */ Channel_SetTopic(chan, from, Req->argv[1]); LogDebug("%s \"%s\" set topic on \"%s\": %s", Client_TypeText(from), Client_Mask(from), Channel_Name(chan), Req->argv[1][0] ? Req->argv[1] : "<none>"); if (Conf_OperServerMode) from = Client_ThisServer(); /* Update channel and forward new topic to other servers */ if (!Channel_IsLocal(chan)) IRC_WriteStrServersPrefix(Client, from, "TOPIC %s :%s", Req->argv[0], Req->argv[1]); IRC_WriteStrChannelPrefix(Client, chan, from, false, "TOPIC %s :%s", Req->argv[0], Req->argv[1]); if (Client_Type(Client) == CLIENT_USER) return IRC_WriteStrClientPrefix(Client, Client, "TOPIC %s :%s", Req->argv[0], Req->argv[1]); else return CONNECTED; } /* IRC_TOPIC */
/** * Handler for the IRC "JOIN" 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_JOIN( CLIENT *Client, REQUEST *Req ) { char *channame, *key = NULL, *flags, *lastkey = NULL, *lastchan = NULL; CLIENT *target; CHANNEL *chan; assert (Client != NULL); assert (Req != NULL); _IRC_GET_SENDER_OR_RETURN_(target, Req, Client) /* Is argument "0"? */ if (Req->argc == 1 && !strncmp("0", Req->argv[0], 2)) return part_from_all_channels(Client, target); /* Are channel keys given? */ if (Req->argc > 1) key = strtok_r(Req->argv[1], ",", &lastkey); channame = Req->argv[0]; channame = strtok_r(channame, ",", &lastchan); /* Make sure that "channame" is not the empty string ("JOIN :") */ if (!channame) return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); while (channame) { flags = NULL; /* Did the server include channel-user-modes? */ if (Client_Type(Client) == CLIENT_SERVER) { flags = strchr(channame, 0x7); if (flags) { *flags = '\0'; flags++; } } chan = Channel_Search(channame); /* Local client? */ if (Client_Type(Client) == CLIENT_USER) { if (chan) { /* Already existing channel: already member? */ if (Channel_IsMemberOf(chan, Client)) goto join_next; } else { /* Channel must be created */ if (!strchr(Conf_AllowedChannelTypes, channame[0])) { /* ... but channel type is not allowed! */ IRC_WriteErrClient(Client, ERR_NOSUCHCHANNEL_MSG, Client_ID(Client), channame); goto join_next; } } /* Test if the user has reached the channel limit */ if ((Conf_MaxJoins > 0) && (Channel_CountForUser(Client) >= Conf_MaxJoins)) { if (!IRC_WriteErrClient(Client, ERR_TOOMANYCHANNELS_MSG, Client_ID(Client), channame)) return DISCONNECTED; goto join_next; } if (chan) { /* Already existing channel: check if the * client is allowed to join */ if (!join_allowed(Client, chan, channame, key)) goto join_next; } else { /* New channel: first user will become channel * operator unless this is a modeless channel */ if (*channame != '+') flags = "o"; } /* Local client: update idle time */ Conn_UpdateIdle(Client_Conn(Client)); } else { /* Remote server: we don't need to know whether the * client is invited or not, but we have to make sure * that the "one shot" entries (generated by INVITE * commands) in this list become deleted when a user * joins a channel this way. */ if (chan) (void)Lists_Check(Channel_GetListInvites(chan), target); } /* Join channel (and create channel if it doesn't exist) */ if (!Channel_Join(target, channame)) goto join_next; if (!chan) { /* channel is new; it has been created above */ chan = Channel_Search(channame); assert(chan != NULL); if (Channel_IsModeless(chan)) { Channel_ModeAdd(chan, 't'); /* /TOPIC not allowed */ Channel_ModeAdd(chan, 'n'); /* no external msgs */ } } assert(chan != NULL); join_set_channelmodes(chan, target, flags); join_forward(Client, target, chan, channame); if (!join_send_topic(Client, target, chan, channame)) break; /* write error */ join_next: /* next channel? */ channame = strtok_r(NULL, ",", &lastchan); if (channame && key) key = strtok_r(NULL, ",", &lastkey); } return CONNECTED; } /* IRC_JOIN */