/** * Kill an client identified by its nick name. * * Please note that after killig a client, its CLIENT cond CONNECTION * structures are invalid. So the caller must make sure on its own not to * access data of probably killed clients after calling this function! * * @param Client The client from which the command leading to the KILL has * been received, or NULL. The KILL will no be forwarded in this * direction. Only relevant when From is set, too. * @param From The client from which the command originated, or NULL for the local server. * @param Nick The nick name to kill. * @param Reason Text to send as reason to the client and other servers. */ GLOBAL bool IRC_KillClient(CLIENT *Client, CLIENT *From, const char *Nick, const char *Reason) { const char *msg; CONN_ID my_conn, conn; CLIENT *c; /* Do we know such a client in the network? */ c = Client_Search(Nick); if (!c) { LogDebug("Client with nick \"%s\" is unknown, not forwaring.", Nick); return CONNECTED; } /* Inform other servers */ IRC_WriteStrServersPrefix(From ? Client : NULL, From ? From : Client_ThisServer(), "KILL %s :%s", Nick, Reason); if (Client_Type(c) != CLIENT_USER && Client_Type(c) != CLIENT_GOTNICK) { /* Target of this KILL is not a regular user, this is * invalid! So we ignore this case if we received a * regular KILL from the network and try to kill the * client/connection anyway (but log an error!) if the * origin is the local server. */ if (Client != Client_ThisServer()) { /* Invalid KILL received from remote */ if (Client_Type(c) == CLIENT_SERVER) msg = ERR_CANTKILLSERVER_MSG; else msg = ERR_NOPRIVILEGES_MSG; return IRC_WriteErrClient(Client, msg, Client_ID(Client)); } Log(LOG_ERR, "Got KILL for invalid client type: %d, \"%s\"!", Client_Type(c), Nick); } /* Save ID of this connection */ my_conn = Client_Conn(Client); /* Kill the client NOW: * - Close the local connection (if there is one), * - Destroy the CLIENT structure for remote clients. * Note: Conn_Close() removes the CLIENT structure as well. */ conn = Client_Conn(c); if(conn > NONE) Conn_Close(conn, NULL, Reason, true); else Client_Destroy(c, NULL, Reason, false); /* Are we still connected or were we killed, too? */ if (my_conn > NONE && Conn_GetClient(my_conn)) return CONNECTED; else return DISCONNECTED; }
/** * Finish client registration. * * Introduce the new client to the network and send all "hello messages" * to it after authentication has been succeeded. * * @param Client The client logging in. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool Login_User_PostAuth(CLIENT *Client) { REQUEST Req; char modes[CLIENT_MODE_LEN + 1]; assert(Client != NULL); if (Class_HandleServerBans(Client) != CONNECTED) return DISCONNECTED; Client_Introduce(NULL, Client, CLIENT_USER); if (!IRC_WriteStrClient (Client, RPL_WELCOME_MSG, Client_ID(Client), Client_Mask(Client))) return false; if (!IRC_WriteStrClient (Client, RPL_YOURHOST_MSG, Client_ID(Client), Client_ID(Client_ThisServer()), PACKAGE_VERSION, HOST_CPU, HOST_VENDOR, HOST_OS)) return false; if (!IRC_WriteStrClient (Client, RPL_CREATED_MSG, Client_ID(Client), NGIRCd_StartStr)) return false; if (!IRC_WriteStrClient (Client, RPL_MYINFO_MSG, Client_ID(Client), Client_ID(Client_ThisServer()), PACKAGE_VERSION, USERMODES, CHANMODES)) return false; /* Features supported by this server (005 numeric, ISUPPORT), * see <http://www.irc.org/tech_docs/005.html> for details. */ if (!IRC_Send_ISUPPORT(Client)) return DISCONNECTED; if (!IRC_Send_LUSERS(Client)) return DISCONNECTED; if (!IRC_Show_MOTD(Client)) return DISCONNECTED; /* Set default user modes */ if (Conf_DefaultUserModes[0]) { snprintf(modes, sizeof(modes), "+%s", Conf_DefaultUserModes); Req.prefix = Client_ID(Client_ThisServer()); Req.command = "MODE"; Req.argc = 2; Req.argv[0] = Client_ID(Client); Req.argv[1] = modes; IRC_MODE(Client, &Req); } else IRC_SetPenalty(Client, 1); return CONNECTED; }
/** * Announce new server in the network * @param Client New server * @param Server Existing server in the network */ static bool Announce_Server(CLIENT * Client, CLIENT * Server) { CLIENT *c; if (Client_Conn(Server) > NONE) { /* Announce the new server to the one already registered * which is directly connected to the local server */ if (!IRC_WriteStrClient (Server, "SERVER %s %d %d :%s", Client_ID(Client), Client_Hops(Client) + 1, Client_MyToken(Client), Client_Info(Client))) return DISCONNECTED; } if (Client_Hops(Server) == 1) c = Client_ThisServer(); else c = Client_TopServer(Server); /* Inform new server about the one already registered in the network */ return IRC_WriteStrClientPrefix(Client, c, "SERVER %s %d %d :%s", Client_ID(Server), Client_Hops(Server) + 1, Client_MyToken(Server), Client_Info(Server)); } /* Announce_Server */
/** * Introduce a new user or service client in the network. * * @param From Remote server introducing the client or NULL (local). * @param Client New client. * @param Type Type of the client (CLIENT_USER or CLIENT_SERVICE). */ GLOBAL void Client_Introduce(CLIENT *From, CLIENT *Client, int Type) { /* Set client type (user or service) */ Client_SetType(Client, Type); if (From) { if (Conf_NickIsService(Conf_GetServer(Client_Conn(From)), Client_ID(Client))) Client_SetType(Client, CLIENT_SERVICE); LogDebug("%s \"%s\" (+%s) registered (via %s, on %s, %d hop%s).", Client_TypeText(Client), Client_Mask(Client), Client_Modes(Client), Client_ID(From), Client_ID(Client_Introducer(Client)), Client_Hops(Client), Client_Hops(Client) > 1 ? "s": ""); } else { Log(LOG_NOTICE, "%s \"%s\" registered (connection %d).", Client_TypeText(Client), Client_Mask(Client), Client_Conn(Client)); Log_ServerNotice('c', "Client connecting: %s (%s@%s) [%s] - %s", Client_ID(Client), Client_User(Client), Client_Hostname(Client), Conn_IPA(Client_Conn(Client)), Client_TypeText(Client)); } /* Inform other servers */ IRC_WriteStrServersPrefixFlag_CB(From, From != NULL ? From : Client_ThisServer(), '\0', cb_introduceClient, (void *)Client); } /* Client_Introduce */
/** * 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 */
/** * 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); }
static void Dump_State(void) { Log(LOG_DEBUG, "--- Internal server state: %s ---", Client_ID(Client_ThisServer())); Log(LOG_DEBUG, "time()=%ld", time(NULL)); Conf_DebugDump(); Conn_DebugDump(); Client_DebugDump(); Log(LOG_DEBUG, "--- End of state dump ---"); } /* Dump_State */
/** * 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; }
/** * Check if a client is allowed to send to a specific channel. * * @param Chan The channel to check. * @param From The client that wants to send. * @return true if the client is allowed to send, false otherwise. */ static bool Can_Send_To_Channel(CHANNEL *Chan, CLIENT *From) { bool is_member, has_voice, is_halfop, is_op, is_chanadmin, is_owner; is_member = has_voice = is_halfop = is_op = is_chanadmin = is_owner = false; /* The server itself always can send messages :-) */ if (Client_ThisServer() == From) return true; if (Channel_IsMemberOf(Chan, From)) { is_member = true; if (Channel_UserHasMode(Chan, From, 'v')) has_voice = true; if (Channel_UserHasMode(Chan, From, 'h')) is_halfop = true; if (Channel_UserHasMode(Chan, From, 'o')) is_op = true; if (Channel_UserHasMode(Chan, From, 'a')) is_chanadmin = true; if (Channel_UserHasMode(Chan, From, 'q')) is_owner = true; } /* * Is the client allowed to write to channel? * * If channel mode n set: non-members cannot send to channel. * If channel mode m set: need voice. */ if (Channel_HasMode(Chan, 'n') && !is_member) return false; if (Channel_HasMode(Chan, 'M') && !Client_HasMode(From, 'R') && !Client_HasMode(From, 'o')) return false; if (has_voice || is_halfop || is_op || is_chanadmin || is_owner) return true; if (Channel_HasMode(Chan, 'm')) return false; if (Lists_Check(&Chan->list_excepts, From)) return true; return !Lists_Check(&Chan->list_bans, From); }
/** * 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 */
static void Dump_State(void) { Log(LOG_DEBUG, "--- Internal server state: %s ---", Client_ID(Client_ThisServer())); #ifdef HAVE_LONG_LONG Log(LOG_DEBUG, "time()=%llu", (unsigned long long)time(NULL)); #else Log(LOG_DEBUG, "time()=%lu", (unsigned long)time(NULL)); #endif Conf_DebugDump(); Conn_DebugDump(); Client_DebugDump(); Log(LOG_DEBUG, "--- End of state dump ---"); } /* Dump_State */
/** * Kill all users with a specific nick name in the network. * * @param Nick Nick name. * @param Reason Reason for the KILL. */ static void Kill_Nick(char *Nick, char *Reason) { REQUEST r; assert (Nick != NULL); assert (Reason != NULL); r.prefix = NULL; r.argv[0] = Nick; r.argv[1] = Reason; r.argc = 2; Log(LOG_ERR, "User(s) with nick \"%s\" will be disconnected: %s", Nick, Reason); IRC_KILL(Client_ThisServer(), &r); } /* Kill_Nick */
/** * Update (and generate, if necessary) the cloaked hostname of a client. * * The newly set cloaked hostname is announced in the network using METADATA * commands to peers that support this feature. * * @param Client The client of which the cloaked hostname should be updated. * @param Origin The originator of the hostname change, or NULL if this server. * @param Hostname The new cloaked hostname, or NULL if it should be generated. */ GLOBAL void Client_UpdateCloakedHostname(CLIENT *Client, CLIENT *Origin, const char *Hostname) { char Cloak_Buffer[CLIENT_HOST_LEN]; assert(Client != NULL); if (!Origin) Origin = Client_ThisServer(); if (!Client->cloaked) { Client->cloaked = malloc(CLIENT_HOST_LEN); if (!Client->cloaked) return; } if (!Hostname) { /* Generate new cloaked hostname */ if (*Conf_CloakHostModeX) { strlcpy(Cloak_Buffer, Client->host, sizeof(Cloak_Buffer)); strlcat(Cloak_Buffer, Conf_CloakHostSalt, sizeof(Cloak_Buffer)); snprintf(Client->cloaked, CLIENT_HOST_LEN, Conf_CloakHostModeX, Hash(Cloak_Buffer)); } else strlcpy(Client->cloaked, Client_ID(Client->introducer), CLIENT_HOST_LEN); } else strlcpy(Client->cloaked, Hostname, CLIENT_HOST_LEN); LogDebug("Cloaked hostname of \"%s\" updated to \"%s\"", Client_ID(Client), Client->cloaked); /* Inform other servers in the network */ IRC_WriteStrServersPrefixFlag(Client_NextHop(Origin), Origin, 'M', "METADATA %s cloakhost :%s", Client_ID(Client), Client->cloaked); }
/** * 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 */
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 "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 "PONG" command. * * See RFC 2812, 3.7.3 "Pong message". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_PONG(CLIENT *Client, REQUEST *Req) { CLIENT *target, *from; CONN_ID conn; #ifndef STRICT_RFC long auth_ping; #endif char *s; assert(Client != NULL); assert(Req != NULL); /* Wrong number of arguments? */ if (Req->argc < 1) { if (Client_Type(Client) == CLIENT_USER) return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, Client_ID(Client)); else return CONNECTED; } if (Req->argc > 2) { if (Client_Type(Client) == CLIENT_USER) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); else return CONNECTED; } /* Forward? */ if (Req->argc == 2 && Client_Type(Client) == CLIENT_SERVER) { target = Client_Search(Req->argv[0]); if (!target) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[0]); from = Client_Search(Req->prefix); if (target != Client_ThisServer() && target != from) { /* Ok, we have to forward the message. */ if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); if (Client_Type(Client_NextHop(target)) != CLIENT_SERVER) s = Client_ID(from); else s = Req->argv[0]; return IRC_WriteStrClientPrefix(target, from, "PONG %s :%s", s, Req->argv[1]); } } /* The connection timestamp has already been updated when the data has * been read from so socket, so we don't need to update it here. */ conn = Client_Conn(Client); #ifndef STRICT_RFC /* Check authentication PING-PONG ... */ auth_ping = Conn_GetAuthPing(conn); if (auth_ping) { LogDebug("AUTH PONG: waiting for token \"%ld\", got \"%s\" ...", auth_ping, Req->argv[0]); if (auth_ping == atoi(Req->argv[0])) { Conn_SetAuthPing(conn, 0); if (Client_Type(Client) == CLIENT_WAITAUTHPING) Login_User(Client); } else if (!IRC_WriteStrClient(Client, "To connect, type /QUOTE PONG %ld", auth_ping)) return DISCONNECTED; } #endif if (Client_Type(Client) == CLIENT_SERVER && Conn_LastPing(conn) == 0) { Log(LOG_INFO, "Synchronization with \"%s\" done (connection %d): %ld seconds [%ld users, %ld channels]", Client_ID(Client), conn, time(NULL) - Conn_GetSignon(conn), Client_UserCount(), Channel_CountVisible(NULL)); Conn_UpdatePing(conn); } else LogDebug("Connection %d: received PONG. Lag: %ld seconds.", conn, time(NULL) - Conn_LastPing(conn)); return CONNECTED; } /* IRC_PONG */
/** * Handler for the IRC "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 "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 */
/** * 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 */
/** * 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 "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 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_TRACE( CLIENT *Client, REQUEST *Req ) { CLIENT *from, *target, *c; CONN_ID idx, idx2; char user[CLIENT_USER_LEN]; assert( Client != NULL ); assert( Req != NULL ); /* Bad number of arguments? */ if( Req->argc > 1 ) return IRC_WriteStrClient( Client, ERR_NORECIPIENT_MSG, Client_ID( Client ), Req->command ); /* Search sender */ if( Client_Type( Client ) == CLIENT_SERVER ) from = Client_Search( Req->prefix ); else from = Client; if( ! from ) return IRC_WriteStrClient( Client, ERR_NOSUCHNICK_MSG, Client_ID( Client ), Req->prefix ); /* Search target */ if( Req->argc == 1 ) target = Client_Search( Req->argv[0] ); else target = Client_ThisServer( ); /* Forward command to other server? */ if( target != Client_ThisServer( )) { if(( ! target ) || ( Client_Type( target ) != CLIENT_SERVER )) return IRC_WriteStrClient( from, ERR_NOSUCHSERVER_MSG, Client_ID( from ), Req->argv[0] ); /* Send RPL_TRACELINK back to initiator */ idx = Client_Conn( Client ); assert( idx > NONE ); idx2 = Client_Conn( Client_NextHop( target )); assert( idx2 > NONE ); if( ! IRC_WriteStrClient( from, RPL_TRACELINK_MSG, Client_ID( from ), PACKAGE_NAME, PACKAGE_VERSION, Client_ID( target ), Client_ID( Client_NextHop( target )), Option_String( idx2 ), time( NULL ) - Conn_StartTime( idx2 ), Conn_SendQ( idx ), Conn_SendQ( idx2 ))) return DISCONNECTED; /* Forward command */ IRC_WriteStrClientPrefix( target, from, "TRACE %s", Req->argv[0] ); return CONNECTED; } /* Infos about all connected servers */ c = Client_First( ); while( c ) { if( Client_Conn( c ) > NONE ) { /* Local client */ if( Client_Type( c ) == CLIENT_SERVER ) { /* Server link */ strlcpy( user, Client_User( c ), sizeof( user )); if( user[0] == '~' ) strlcpy( user, "unknown", sizeof( user )); if( ! IRC_WriteStrClient( from, RPL_TRACESERVER_MSG, Client_ID( from ), Client_ID( c ), user, Client_Hostname( c ), Client_Mask( Client_ThisServer( )), Option_String( Client_Conn( c )))) return DISCONNECTED; } if(( Client_Type( c ) == CLIENT_USER ) && ( strchr( Client_Modes( c ), 'o' ))) { /* IRC Operator */ if( ! IRC_WriteStrClient( from, RPL_TRACEOPERATOR_MSG, Client_ID( from ), Client_ID( c ))) return DISCONNECTED; } } c = Client_Next( c ); } IRC_SetPenalty( Client, 3 ); return IRC_WriteStrClient( from, RPL_TRACEEND_MSG, Client_ID( from ), Conf_ServerName, PACKAGE_NAME, PACKAGE_VERSION, NGIRCd_DebugLevel ); } /* IRC_TRACE */
/** * Handler for the IRC "PING" command. * * See RFC 2812, 3.7.2 "Ping message". * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @returns CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_PING(CLIENT *Client, REQUEST *Req) { CLIENT *target, *from; assert(Client != NULL); assert(Req != NULL); if (Req->argc < 1) return IRC_WriteStrClient(Client, ERR_NOORIGIN_MSG, Client_ID(Client)); #ifdef STRICT_RFC /* Don't ignore additional arguments when in "strict" mode */ if (Req->argc > 2) return IRC_WriteStrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); #endif if (Req->argc > 1) { /* A target has been specified ... */ target = Client_Search(Req->argv[1]); if (!target || Client_Type(target) != CLIENT_SERVER) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[1]); if (target != Client_ThisServer()) { /* Ok, we have to forward the PING */ if (Client_Type(Client) == CLIENT_SERVER) from = Client_Search(Req->prefix); else from = Client; if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); return IRC_WriteStrClientPrefix(target, from, "PING %s :%s", Req->argv[0], Req->argv[1] ); } } if (Client_Type(Client) == CLIENT_SERVER) { if (Req->prefix) from = Client_Search(Req->prefix); else from = Client; } else from = Client_ThisServer(); if (!from) return IRC_WriteStrClient(Client, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->prefix); Log(LOG_DEBUG, "Connection %d: got PING, sending PONG ...", Client_Conn(Client)); #ifdef STRICT_RFC return IRC_WriteStrClient(Client, "PONG %s :%s", Client_ID(from), Client_ID(Client)); #else /* Some clients depend on the argument being returned in the PONG * reply (not mentioned in any RFC, though) */ return IRC_WriteStrClient(Client, "PONG %s :%s", Client_ID(from), Req->argv[0]); #endif } /* IRC_PING */