/** * Destroy user or service client. */ static void Destroy_UserOrService(CLIENT *Client, const char *Txt, const char *FwdMsg, bool SendQuit) { if(Client->conn_id != NONE) { /* Local (directly connected) client */ Log(LOG_NOTICE, "%s \"%s\" unregistered (connection %d): %s.", Client_TypeText(Client), Client_Mask(Client), Client->conn_id, Txt); Log_ServerNotice('c', "Client exiting: %s (%s@%s) [%s]", Client_ID(Client), Client_User(Client), Client_Hostname(Client), Txt); if (SendQuit) { /* Inforam all the other servers */ if (FwdMsg) IRC_WriteStrServersPrefix(NULL, Client, "QUIT :%s", FwdMsg ); else IRC_WriteStrServersPrefix(NULL, Client, "QUIT :"); } } else { /* Remote client */ LogDebug("%s \"%s\" unregistered: %s.", Client_TypeText(Client), Client_Mask(Client), Txt); if(SendQuit) { /* Inform all the other servers, but the ones in the * direction we got the QUIT from */ if(FwdMsg) IRC_WriteStrServersPrefix(Client_NextHop(Client), Client, "QUIT :%s", FwdMsg ); else IRC_WriteStrServersPrefix(Client_NextHop(Client), Client, "QUIT :" ); } } /* Unregister client from channels */ Channel_Quit(Client, FwdMsg ? FwdMsg : Client->id); /* Register client in My_Whowas structure */ Client_RegisterWhowas(Client); } /* Destroy_UserOrService */
/** * Check if a list limit is reached and inform client accordingly. * * @param From The client. * @param Count Reply item count. * @param Limit Reply limit. * @param Name Name of the list. * @return true if list limit has been reached; false otherwise. */ GLOBAL bool IRC_CheckListTooBig(CLIENT *From, const int Count, const int Limit, const char *Name) { assert(From != NULL); assert(Count >= 0); assert(Limit > 0); assert(Name != NULL); if (Count < Limit) return false; (void)IRC_WriteStrClient(From, "NOTICE %s :%s list limit (%d) reached!", Client_ID(From), Name, Limit); IRC_SetPenalty(From, 2); return true; }
/** * 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); }
/** * Forward JOIN command to a specific server * * This function differentiates between servers using RFC 2813 mode that * support the JOIN command with appended ASCII 7 character and channel * modes, and servers using RFC 1459 protocol which require separate JOIN * and MODE commands. * * @param To Forward JOIN (and MODE) command to this peer server * @param Prefix Client used to prefix the genrated commands * @param Data Parameters of JOIN command to forward, probably * containing channel modes separated by ASCII 7. */ static void cb_join_forward(CLIENT *To, CLIENT *Prefix, void *Data) { CONN_ID conn; char str[COMMAND_LEN], *ptr = NULL; strlcpy(str, (char *)Data, sizeof(str)); conn = Client_Conn(To); if (Conn_Options(conn) & CONN_RFC1459) { /* RFC 1459 compatibility mode, appended modes are NOT * supported, so strip them off! */ ptr = strchr(str, 0x7); if (ptr) *ptr++ = '\0'; } IRC_WriteStrClientPrefix(To, Prefix, "JOIN %s", str); if (ptr && *ptr) IRC_WriteStrClientPrefix(To, Prefix, "MODE %s +%s %s", str, ptr, Client_ID(Prefix)); } /* cb_join_forward */
/** * Search first CLIENT structure matching a given mask of a server. * * The order of servers is arbitrary, but this function makes sure that the * local server is always returned if the mask matches it. * * @return Pointer to CLIENT structure or NULL if no server could be found. */ GLOBAL CLIENT * Client_SearchServer(const char *Mask) { CLIENT *c; assert(Mask != NULL); /* First check if mask matches the local server */ if (MatchCaseInsensitive(Mask, Client_ID(Client_ThisServer()))) return Client_ThisServer(); c = My_Clients; while (c) { if (Client_Type(c) == CLIENT_SERVER) { /* This is a server: check if Mask matches */ if (MatchCaseInsensitive(Mask, c->id)) return c; } c = (CLIENT *)c->next; } return NULL; }
/** * 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 */
static bool Remove_Client( int Type, CHANNEL *Chan, CLIENT *Client, CLIENT *Origin, const char *Reason, bool InformServer ) { CL2CHAN *cl2chan, *last_cl2chan; CHANNEL *c; assert( Chan != NULL ); assert( Client != NULL ); assert( Origin != NULL ); assert( Reason != NULL ); /* Do not inform other servers if the channel is local to this server, * regardless of what the caller requested! */ if(InformServer) InformServer = !Channel_IsLocal(Chan); last_cl2chan = NULL; cl2chan = My_Cl2Chan; while( cl2chan ) { if(( cl2chan->channel == Chan ) && ( cl2chan->client == Client )) break; last_cl2chan = cl2chan; cl2chan = cl2chan->next; } if( ! cl2chan ) return false; c = cl2chan->channel; assert( c != NULL ); /* maintain cl2chan list */ if( last_cl2chan ) last_cl2chan->next = cl2chan->next; else My_Cl2Chan = cl2chan->next; free( cl2chan ); switch( Type ) { case REMOVE_QUIT: /* QUIT: other servers have already been notified, * see Client_Destroy(); so only inform other clients * in same channel. */ assert( InformServer == false ); LogDebug("User \"%s\" left channel \"%s\" (%s).", Client_Mask( Client ), c->name, Reason ); break; case REMOVE_KICK: /* User was KICKed: inform other servers (public * channels) and all users in the channel */ if( InformServer ) IRC_WriteStrServersPrefix( Client_NextHop( Origin ), Origin, "KICK %s %s :%s", c->name, Client_ID( Client ), Reason); IRC_WriteStrChannelPrefix(Client, c, Origin, false, "KICK %s %s :%s", c->name, Client_ID( Client ), Reason ); if ((Client_Conn(Client) > NONE) && (Client_Type(Client) == CLIENT_USER)) { IRC_WriteStrClientPrefix(Client, Origin, "KICK %s %s :%s", c->name, Client_ID( Client ), Reason); } LogDebug("User \"%s\" has been kicked off \"%s\" by \"%s\": %s.", Client_Mask( Client ), c->name, Client_ID(Origin), Reason); break; default: /* PART */ if (Conf_MorePrivacy) Reason = ""; if (InformServer) IRC_WriteStrServersPrefix(Origin, Client, "PART %s :%s", c->name, Reason); IRC_WriteStrChannelPrefix(Origin, c, Client, false, "PART %s :%s", c->name, Reason); if ((Client_Conn(Origin) > NONE) && (Client_Type(Origin) == CLIENT_USER)) { IRC_WriteStrClientPrefix( Origin, Client, "PART %s :%s", c->name, Reason); LogDebug("User \"%s\" left channel \"%s\" (%s).", Client_Mask(Client), c->name, Reason); } } /* When channel is empty and is not pre-defined, delete */ if( ! Channel_HasMode( Chan, 'P' )) { if( ! Get_First_Cl2Chan( NULL, Chan )) Delete_Channel( Chan ); } return true; } /* Remove_Client */
/** * 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 */
/** * 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+ "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 */
/** * Handle client mode requests * * @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 Target The target (client) of this MODE command. * @return CONNECTED or DISCONNECTED. */ static bool Client_Mode( CLIENT *Client, REQUEST *Req, CLIENT *Origin, CLIENT *Target ) { char the_modes[COMMAND_LEN], x[2], *mode_ptr; bool ok, set; bool send_RPL_HOSTHIDDEN_MSG = false; int mode_arg; size_t len; /* Is the client allowed to request or change the modes? */ if (Client_Type(Client) == CLIENT_USER) { /* Users are only allowed to manipulate their own modes! */ if (Target != Client) return IRC_WriteErrClient(Client, ERR_USERSDONTMATCH_MSG, Client_ID(Client)); } /* Mode request: let's answer it :-) */ if (Req->argc == 1) return IRC_WriteStrClient(Origin, RPL_UMODEIS_MSG, Client_ID(Target), Client_Modes(Target)); mode_arg = 1; mode_ptr = Req->argv[mode_arg]; /* Initial state: set or unset modes? */ if (*mode_ptr == '+') { set = true; strcpy(the_modes, "+"); } else if (*mode_ptr == '-') { set = false; strcpy(the_modes, "-"); } else return IRC_WriteErrClient(Origin, ERR_UMODEUNKNOWNFLAG_MSG, Client_ID(Origin)); x[1] = '\0'; ok = CONNECTED; while (mode_ptr) { mode_ptr++; if (!*mode_ptr) { /* Try next argument if there's any */ mode_arg++; if (mode_arg < Req->argc) mode_ptr = Req->argv[mode_arg]; else break; } 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] == '-') { /* Last character in the "result * string" was an "action", so just * overwrite it with the new action */ the_modes[len] = *mode_ptr; } else { /* Append new modifier character to * the resulting mode string */ x[0] = *mode_ptr; strlcat(the_modes, x, sizeof(the_modes)); } if (*mode_ptr == '+') set = true; else set = false; } continue; } /* Validate modes */ x[0] = '\0'; switch (*mode_ptr) { case 'b': /* Block private msgs */ case 'C': /* Only messages from clients sharing a channel */ case 'i': /* Invisible */ case 'I': /* Hide channel list from WHOIS */ case 's': /* Server messages */ case 'w': /* Wallops messages */ x[0] = *mode_ptr; break; case 'a': /* Away */ if (Client_Type(Client) == CLIENT_SERVER) { x[0] = 'a'; Client_SetAway(Origin, DEFAULT_AWAY_MSG); } else ok = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); break; case 'B': /* Bot */ if (Client_HasMode(Client, 'r')) ok = IRC_WriteErrClient(Origin, ERR_RESTRICTED_MSG, Client_ID(Origin)); else x[0] = 'B'; break; case 'c': /* Receive connect notices */ case 'q': /* KICK-protected user */ case 'F': /* disable flood protection */ /* (only settable by IRC operators!) */ if (!set || Client_Type(Client) == CLIENT_SERVER || Client_HasMode(Origin, 'o')) x[0] = *mode_ptr; else ok = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); break; case 'o': /* IRC operator (only unsettable!) */ if (!set || Client_Type(Client) == CLIENT_SERVER) { x[0] = 'o'; } else ok = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); break; case 'r': /* Restricted (only settable) */ if (set || Client_Type(Client) == CLIENT_SERVER) x[0] = 'r'; else ok = IRC_WriteErrClient(Origin, ERR_RESTRICTED_MSG, Client_ID(Origin)); break; case 'R': /* Registered (not [un]settable by clients) */ if (Client_Type(Client) == CLIENT_SERVER) x[0] = 'R'; else ok = IRC_WriteErrClient(Origin, ERR_NICKREGISTER_MSG, Client_ID(Origin)); break; case 'x': /* Cloak hostname */ if (Client_HasMode(Client, 'r')) ok = IRC_WriteErrClient(Origin, ERR_RESTRICTED_MSG, Client_ID(Origin)); else if (!set || Conf_CloakHostModeX[0] || Client_Type(Client) == CLIENT_SERVER || Client_HasMode(Origin, 'o')) { x[0] = 'x'; send_RPL_HOSTHIDDEN_MSG = true; } else ok = IRC_WriteErrClient(Origin, ERR_NOPRIVILEGES_MSG, Client_ID(Origin)); break; default: if (Client_Type(Client) != CLIENT_SERVER) { Log(LOG_DEBUG, "Unknown mode \"%c%c\" from \"%s\"!?", set ? '+' : '-', *mode_ptr, Client_ID(Origin)); ok = IRC_WriteErrClient(Origin, ERR_UMODEUNKNOWNFLAG2_MSG, Client_ID(Origin), set ? '+' : '-', *mode_ptr); x[0] = '\0'; } else { Log(LOG_DEBUG, "Handling unknown mode \"%c%c\" from \"%s\" for \"%s\" ...", set ? '+' : '-', *mode_ptr, Client_ID(Origin), Client_ID(Target)); x[0] = *mode_ptr; } } if (!ok) break; /* Is there a valid mode change? */ if (!x[0]) continue; if (set) { if (Client_ModeAdd(Target, x[0])) strlcat(the_modes, x, sizeof(the_modes)); } else { if (Client_ModeDel(Target, x[0])) strlcat(the_modes, x, sizeof(the_modes)); } } /* Are there changed modes? */ if (the_modes[1]) { /* Remove needless action modifier characters */ len = strlen(the_modes) - 1; if (the_modes[len] == '+' || the_modes[len] == '-') the_modes[len] = '\0'; if (Client_Type(Client) == CLIENT_SERVER) { /* Forward modes to other servers */ if (Client_Conn(Target) != NONE) { /* Remote server (service?) changed modes * for one of our clients. Inform it! */ IRC_WriteStrClientPrefix(Target, Origin, "MODE %s :%s", Client_ID(Target), the_modes); } IRC_WriteStrServersPrefix(Client, Origin, "MODE %s :%s", Client_ID(Target), the_modes); } else { /* Send reply to client and inform other servers */ ok = IRC_WriteStrClientPrefix(Client, Origin, "MODE %s :%s", Client_ID(Target), the_modes); IRC_WriteStrServersPrefix(Client, Origin, "MODE %s :%s", Client_ID(Target), the_modes); } if (send_RPL_HOSTHIDDEN_MSG && Client_Conn(Target) > NONE) { /* A new (cloaked) hostname must be announced */ IRC_WriteStrClientPrefix(Target, Origin, RPL_HOSTHIDDEN_MSG, Client_ID(Target), Client_HostnameDisplayed(Target)); } LogDebug("%s \"%s\": Mode change, now \"%s\".", Client_TypeText(Target), Client_Mask(Target), Client_Modes(Target)); } return ok; } /* Client_Mode */
/** * 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 */
/** * Announce a channel and its users in the network. */ static bool Announce_Channel(CLIENT *Client, CHANNEL *Chan) { CL2CHAN *cl2chan; CLIENT *cl; char str[COMMAND_LEN], *ptr; bool njoin, xop; /* Check features of remote server */ njoin = Conn_Options(Client_Conn(Client)) & CONN_RFC1459 ? false : true; xop = Client_HasFlag(Client, 'X') ? true : false; /* Get all the members of this channel */ cl2chan = Channel_FirstMember(Chan); snprintf(str, sizeof(str), "NJOIN %s :", Channel_Name(Chan)); while (cl2chan) { cl = Channel_GetClient(cl2chan); assert(cl != NULL); if (njoin) { /* RFC 2813: send NJOIN with nicknames and modes * (if user is channel operator or has voice) */ if (str[strlen(str) - 1] != ':') strlcat(str, ",", sizeof(str)); /* Prepare user prefix (ChanOp, voiced, ...) */ if (xop && Channel_UserHasMode(Chan, cl, 'q')) strlcat(str, "~", sizeof(str)); if (xop && Channel_UserHasMode(Chan, cl, 'a')) strlcat(str, "&", sizeof(str)); if (Channel_UserHasMode(Chan, cl, 'o')) strlcat(str, "@", sizeof(str)); if (xop && Channel_UserHasMode(Chan, cl, 'h')) strlcat(str, "%", sizeof(str)); if (Channel_UserHasMode(Chan, cl, 'v')) strlcat(str, "+", sizeof(str)); strlcat(str, Client_ID(cl), sizeof(str)); /* Send the data if the buffer is "full" */ if (strlen(str) > (sizeof(str) - CLIENT_NICK_LEN - 8)) { if (!IRC_WriteStrClient(Client, "%s", str)) return DISCONNECTED; snprintf(str, sizeof(str), "NJOIN %s :", Channel_Name(Chan)); } } else { /* RFC 1459: no NJOIN, send JOIN and MODE */ if (!IRC_WriteStrClientPrefix(Client, cl, "JOIN %s", Channel_Name(Chan))) return DISCONNECTED; ptr = Channel_UserModes(Chan, cl); while (*ptr) { if (!IRC_WriteStrClientPrefix(Client, cl, "MODE %s +%c %s", Channel_Name(Chan), ptr[0], Client_ID(cl))) return DISCONNECTED; ptr++; } } cl2chan = Channel_NextMember(Chan, cl2chan); } /* Data left in the buffer? */ if (str[strlen(str) - 1] != ':') { /* Yes, send it ... */ if (!IRC_WriteStrClient(Client, "%s", str)) return DISCONNECTED; } return CONNECTED; } /* Announce_Channel */
/** * Handle ENDOFMOTD (376) numeric and login remote server. * The peer is either an IRC server (no IRC+ protocol), or we got the * ENDOFMOTD numeric from an IRC+ server. We have to register the new server. */ GLOBAL bool IRC_Num_ENDOFMOTD(CLIENT * Client, UNUSED REQUEST * Req) { int max_hops, i; CLIENT *c; CHANNEL *chan; Client_SetType(Client, CLIENT_SERVER); Log(LOG_NOTICE | LOG_snotice, "Server \"%s\" registered (connection %d, 1 hop - direct link).", Client_ID(Client), Client_Conn(Client)); /* Get highest hop count */ max_hops = 0; c = Client_First(); while (c) { if (Client_Hops(c) > max_hops) max_hops = Client_Hops(c); c = Client_Next(c); } /* Inform the new server about all other servers, and announce the * new server to all the already registered ones. Important: we have * to do this "in order" and can't introduce servers of which the * "toplevel server" isn't known already. */ for (i = 0; i < (max_hops + 1); i++) { for (c = Client_First(); c != NULL; c = Client_Next(c)) { if (Client_Type(c) != CLIENT_SERVER) continue; /* not a server */ if (Client_Hops(c) != i) continue; /* not actual "nesting level" */ if (c == Client || c == Client_ThisServer()) continue; /* that's us or the peer! */ if (!Announce_Server(Client, c)) return DISCONNECTED; } } /* Announce all the users to the new server */ c = Client_First(); while (c) { if (Client_Type(c) == CLIENT_USER || Client_Type(c) == CLIENT_SERVICE) { if (!Client_Announce(Client, Client_ThisServer(), c)) return DISCONNECTED; } c = Client_Next(c); } /* Announce all channels to the new server */ chan = Channel_First(); while (chan) { if (Channel_IsLocal(chan)) { chan = Channel_Next(chan); continue; } #ifdef IRCPLUS /* Send CHANINFO if the peer supports it */ if (Client_HasFlag(Client, 'C')) { if (!Send_CHANINFO(Client, chan)) return DISCONNECTED; } #endif if (!Announce_Channel(Client, chan)) return DISCONNECTED; /* Get next channel ... */ chan = Channel_Next(chan); } #ifdef IRCPLUS if (Client_HasFlag(Client, 'L')) { LogDebug("Synchronizing INVITE- and BAN-lists ..."); if (!Synchronize_Lists(Client)) return DISCONNECTED; } #endif if (!IRC_WriteStrClient(Client, "PING :%s", Client_ID(Client_ThisServer()))) return DISCONNECTED; return CONNECTED; } /* IRC_Num_ENDOFMOTD */
/** * 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 */
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 */
/** * Check weather a local client is allowed to join an already existing * channel or not. * * @param Client Client that sent the JOIN command * @param chan Channel to check * @param channame Name of the channel * @param key Provided channel key (or NULL) * @returns true if client is allowed to join, false otherwise */ static bool join_allowed(CLIENT *Client, CHANNEL *chan, const char *channame, const char *key) { bool is_invited, is_banned, is_exception; /* Allow IRC operators to overwrite channel limits */ if (Client_HasMode(Client, 'o')) return true; is_banned = Lists_Check(Channel_GetListBans(chan), Client); is_exception = Lists_Check(Channel_GetListExcepts(chan), Client); is_invited = Lists_Check(Channel_GetListInvites(chan), Client); if (is_banned && !is_invited && !is_exception) { /* Client is banned from channel (and not on invite list) */ IRC_WriteErrClient(Client, ERR_BANNEDFROMCHAN_MSG, Client_ID(Client), channame); return false; } if (Channel_HasMode(chan, 'i') && !is_invited) { /* Channel is "invite-only" and client is not on invite list */ IRC_WriteErrClient(Client, ERR_INVITEONLYCHAN_MSG, Client_ID(Client), channame); return false; } if (!Channel_CheckKey(chan, Client, key ? key : "")) { /* Channel is protected by a channel key and the client * didn't specify the correct one */ IRC_WriteErrClient(Client, ERR_BADCHANNELKEY_MSG, Client_ID(Client), channame); return false; } if (Channel_HasMode(chan, 'l') && (Channel_MaxUsers(chan) <= Channel_MemberCount(chan))) { /* There are more clints joined to this channel than allowed */ IRC_WriteErrClient(Client, ERR_CHANNELISFULL_MSG, Client_ID(Client), channame); return false; } if (Channel_HasMode(chan, 'z') && !Conn_UsesSSL(Client_Conn(Client))) { /* Only "secure" clients are allowed, but clients doesn't * use SSL encryption */ IRC_WriteErrClient(Client, ERR_SECURECHANNEL_MSG, Client_ID(Client), channame); return false; } if (Channel_HasMode(chan, 'O') && !Client_HasMode(Client, 'o')) { /* Only IRC operators are allowed! */ IRC_WriteErrClient(Client, ERR_OPONLYCHANNEL_MSG, Client_ID(Client), channame); return false; } if (Channel_HasMode(chan, 'R') && !Client_HasMode(Client, 'R')) { /* Only registered users are allowed! */ IRC_WriteErrClient(Client, ERR_REGONLYCHANNEL_MSG, Client_ID(Client), channame); return false; } return true; } /* join_allowed */
/** * Announce an user or service to a server. * * This function differentiates between RFC1459 and RFC2813 server links and * generates the appropriate commands to register the user or service. * * @param Client Server * @param Prefix Prefix for the generated commands * @param User User to announce */ GLOBAL bool Client_Announce(CLIENT * Client, CLIENT * Prefix, CLIENT * User) { CONN_ID conn; char *modes, *user, *host; modes = Client_Modes(User); user = Client_User(User) ? Client_User(User) : "-"; host = Client_Hostname(User) ? Client_Hostname(User) : "-"; conn = Client_Conn(Client); if (Conn_Options(conn) & CONN_RFC1459) { /* RFC 1459 mode: separate NICK and USER commands */ if (! Conn_WriteStr(conn, "NICK %s :%d", Client_ID(User), Client_Hops(User) + 1)) return DISCONNECTED; if (! Conn_WriteStr(conn, ":%s USER %s %s %s :%s", Client_ID(User), user, host, Client_ID(Client_Introducer(User)), Client_Info(User))) return DISCONNECTED; if (modes[0]) { if (! Conn_WriteStr(conn, ":%s MODE %s +%s", Client_ID(User), Client_ID(User), modes)) return DISCONNECTED; } } else { /* RFC 2813 mode: one combined NICK or SERVICE command */ if (Client_Type(User) == CLIENT_SERVICE && Client_HasFlag(Client, 'S')) { if (!IRC_WriteStrClientPrefix(Client, Prefix, "SERVICE %s %d * +%s %d :%s", Client_Mask(User), Client_MyToken(Client_Introducer(User)), modes, Client_Hops(User) + 1, Client_Info(User))) return DISCONNECTED; } else { if (!IRC_WriteStrClientPrefix(Client, Prefix, "NICK %s %d %s %s %d +%s :%s", Client_ID(User), Client_Hops(User) + 1, user, host, Client_MyToken(Client_Introducer(User)), modes, Client_Info(User))) return DISCONNECTED; } } if (Client_HasFlag(Client, 'M')) { /* Synchronize metadata */ if (Client_HostnameCloaked(User)) { if (!IRC_WriteStrClientPrefix(Client, Prefix, "METADATA %s cloakhost :%s", Client_ID(User), Client_HostnameCloaked(User))) return DISCONNECTED; } if (Client_AccountName(User)) { if (!IRC_WriteStrClientPrefix(Client, Prefix, "METADATA %s accountname :%s", Client_ID(User), Client_AccountName(User))) return DISCONNECTED; } if (Conn_GetCertFp(Client_Conn(User))) { if (!IRC_WriteStrClientPrefix(Client, Prefix, "METADATA %s certfp :%s", Client_ID(User), Conn_GetCertFp(Client_Conn(User)))) return DISCONNECTED; } } return CONNECTED; } /* Client_Announce */
/** * 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 */
/** * 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 command "KICK". * * @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_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 ); _IRC_ARGC_BETWEEN_OR_RETURN_(Client, Req, 2, 3) _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) while (*itemList) { if (*itemList == ',') { *itemList = '\0'; channelCount++; } itemList++; } itemList = Req->argv[1]; while (*itemList) { if (*itemList == ',') { *itemList = '\0'; nickCount++; } itemList++; } 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 */
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 */
/** * 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 "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 */
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 */
/** * 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 = NONE, conn; CLIENT *c; assert(Nick != NULL); assert(Reason != NULL); /* 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; } if (Client_Type(c) != CLIENT_USER && Client_Type(c) != CLIENT_GOTNICK && Client_Type(c) != CLIENT_SERVICE) { /* 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); } /* Inform other servers */ IRC_WriteStrServersPrefix(From ? Client : NULL, From ? From : Client_ThisServer(), "KILL %s :%s", Nick, Reason); /* Save ID of this connection */ if (Client) 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; }
/* * Handler for the IRC "TRACE" 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_TRACE(CLIENT *Client, REQUEST *Req) { CLIENT *from, *target, *c; CONN_ID idx, idx2; char user[CLIENT_USER_LEN]; assert(Client != NULL); assert(Req != NULL); _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) _IRC_GET_TARGET_SERVER_OR_RETURN_(target, Req, 0, from) /* Forward command to other server? */ if (target != Client_ThisServer()) { /* 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), (long)(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 && Client_HasMode(c, 'o')) { /* IRC Operator */ if (!IRC_WriteStrClient(from, RPL_TRACEOPERATOR_MSG, Client_ID(from), Client_ID(c))) return DISCONNECTED; } } c = Client_Next( c ); } return IRC_WriteStrClient(from, RPL_TRACEEND_MSG, Client_ID(from), Conf_ServerName, PACKAGE_NAME, PACKAGE_VERSION, NGIRCd_DebugLevel); } /* IRC_TRACE */
/** * 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 */