/** * 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); }
/** * Send CHANINFO commands to a new server (inform it about existing channels). * @param Client New server * @param Chan Channel */ static bool Send_CHANINFO(CLIENT * Client, CHANNEL * Chan) { char *modes, *topic; bool has_k, has_l; #ifdef DEBUG Log(LOG_DEBUG, "Sending CHANINFO commands for \"%s\" ...", Channel_Name(Chan)); #endif modes = Channel_Modes(Chan); topic = Channel_Topic(Chan); if (!*modes && !*topic) return CONNECTED; has_k = Channel_HasMode(Chan, 'k'); has_l = Channel_HasMode(Chan, 'l'); /* send CHANINFO */ if (!has_k && !has_l) { if (!*topic) { /* "CHANINFO <chan> +<modes>" */ return IRC_WriteStrClient(Client, "CHANINFO %s +%s", Channel_Name(Chan), modes); } /* "CHANINFO <chan> +<modes> :<topic>" */ return IRC_WriteStrClient(Client, "CHANINFO %s +%s :%s", Channel_Name(Chan), modes, topic); } /* "CHANINFO <chan> +<modes> <key> <limit> :<topic>" */ return IRC_WriteStrClient(Client, "CHANINFO %s +%s %s %lu :%s", Channel_Name(Chan), modes, has_k ? Channel_Key(Chan) : "*", has_l ? Channel_MaxUsers(Chan) : 0, topic); } /* Send_CHANINFO */
GLOBAL bool Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const char *Key) { char *file_name, line[COMMAND_LEN], *nick, *pass; FILE *fd; assert(Chan != NULL); assert(Client != NULL); assert(Key != NULL); if (!Channel_HasMode(Chan, 'k')) return true; if (*Key == '\0') return false; if (strcmp(Chan->key, Key) == 0) return true; file_name = array_start(&Chan->keyfile); if (!file_name) return false; fd = fopen(file_name, "r"); if (!fd) { Log(LOG_ERR, "Can't open channel key file \"%s\" for %s: %s", file_name, Chan->name, strerror(errno)); return false; } while (fgets(line, (int)sizeof(line), fd) != NULL) { ngt_TrimStr(line); if (! (nick = strchr(line, ':'))) continue; *nick++ = '\0'; if (!Match(line, Client_User(Client))) continue; if (! (pass = strchr(nick, ':'))) continue; *pass++ = '\0'; if (!Match(nick, Client_ID(Client))) continue; if (strcmp(Key, pass) != 0) continue; fclose(fd); return true; } fclose(fd); return false; } /* Channel_CheckKey */
/** * Set user channel modes. * * @param chan Channel * @param target User to set modes for * @param flags Channel modes to add */ static void join_set_channelmodes(CHANNEL *chan, CLIENT *target, const char *flags) { if (flags) { while (*flags) { Channel_UserModeAdd(chan, target, *flags); flags++; } } /* If the channel is persistent (+P) and client is an IRC op: * make client chanop, if not disabled in configuration. */ if (Channel_HasMode(chan, 'P') && Conf_OperChanPAutoOp && Client_HasMode(target, 'o')) Channel_UserModeAdd(chan, target, 'o'); } /* join_set_channelmodes */
/** * Get number of channels this server knows and that are "visible" to * the given client. If no client is given, all channels will be counted. * * @param Client The client to check or NULL. * @return Number of channels visible to the client. */ GLOBAL unsigned long Channel_CountVisible (CLIENT *Client) { CHANNEL *c; unsigned long count = 0; c = My_Channels; while(c) { if (Client) { if (!Channel_HasMode(c, 's') || Channel_IsMemberOf(c, Client)) count++; } else count++; c = c->next; } return count; }
GLOBAL bool Channel_ModeAdd( CHANNEL *Chan, char Mode ) { /* set Mode. * If the channel already had this mode, return false. * If the channel mode was newly set return true. */ char x[2]; assert( Chan != NULL ); x[0] = Mode; x[1] = '\0'; if( ! Channel_HasMode( Chan, x[0] )) { /* Channel does not have this mode yet, set it */ strlcat( Chan->modes, x, sizeof( Chan->modes )); return true; } else return false; } /* Channel_ModeAdd */
GLOBAL bool Channel_Write(CHANNEL *Chan, CLIENT *From, CLIENT *Client, const char *Command, bool SendErrors, const char *Text) { if (!Can_Send_To_Channel(Chan, From)) { if (! SendErrors) return CONNECTED; /* no error, see RFC 2812 */ if (Channel_HasMode(Chan, 'M')) return IRC_WriteErrClient(From, ERR_NEEDREGGEDNICK_MSG, Client_ID(From), Channel_Name(Chan)); else return IRC_WriteErrClient(From, ERR_CANNOTSENDTOCHAN_MSG, Client_ID(From), Channel_Name(Chan)); } if (Client_Conn(From) > NONE) Conn_UpdateIdle(Client_Conn(From)); IRC_WriteStrChannelPrefix(Client, Chan, From, true, "%s %s :%s", Command, Channel_Name(Chan), Text); return CONNECTED; }
/** * 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 */
/** * 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 */
/** * 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_GET_SENDER_OR_RETURN_(from, Req, Client) /* Search user */ target = Client_Search(Req->argv[0]); if (!target || (Client_Type(target) != CLIENT_USER)) return IRC_WriteErrClient(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_WriteErrClient(from, ERR_NOTONCHANNEL_MSG, Client_ID(Client), Req->argv[1]); /* Is the channel "invite-disallow"? */ if (Channel_HasMode(chan, 'V')) return IRC_WriteErrClient(from, ERR_NOINVITE_MSG, Client_ID(from), Channel_Name(chan)); /* Is the channel "invite-only"? */ if (Channel_HasMode(chan, 'i')) { /* Yes. The user issuing the INVITE command must be * channel owner/admin/operator/halfop! */ if (!Channel_UserHasMode(chan, from, 'q') && !Channel_UserHasMode(chan, from, 'a') && !Channel_UserHasMode(chan, from, 'o') && !Channel_UserHasMode(chan, from, 'h')) return IRC_WriteErrClient(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_WriteErrClient(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_MaskCloaked(target), true)) return CONNECTED; } } LogDebug("User \"%s\" invites \"%s\" to \"%s\" ...", Client_Mask(from), Req->argv[0], Req->argv[1]); /* * RFC 2812 states: * '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 (Client_HasMode(target, 'a') && !IRC_WriteStrClient(from, RPL_AWAY_MSG, Client_ID(from), Client_ID(target), Client_Away(target))) return DISCONNECTED; } return CONNECTED; } /* IRC_INVITE */
/** * 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 */
/** * Handler for the IRC+ "CHANINFO" command. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_CHANINFO( CLIENT *Client, REQUEST *Req ) { char modes_add[COMMAND_LEN], l[16]; CLIENT *from; CHANNEL *chan; int arg_topic; assert( Client != NULL ); assert( Req != NULL ); /* Bad number of parameters? */ if (Req->argc < 2 || Req->argc == 4 || Req->argc > 5) return IRC_WriteErrClient(Client, ERR_NEEDMOREPARAMS_MSG, Client_ID(Client), Req->command); /* Compatibility kludge */ if (Req->argc == 5) arg_topic = 4; else if(Req->argc == 3) arg_topic = 2; else arg_topic = -1; _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) /* Search or create channel */ chan = Channel_Search( Req->argv[0] ); if (!chan) chan = Channel_Create( Req->argv[0] ); if (!chan) return CONNECTED; if (Req->argv[1][0] == '+') { if (!*Channel_Modes(chan)) { /* OK, this channel doesn't have modes yet, * set the received ones: */ Channel_SetModes(chan, &Req->argv[1][1]); if(Req->argc == 5) { if(Channel_HasMode(chan, 'k')) Channel_SetKey(chan, Req->argv[2]); if(Channel_HasMode(chan, 'l')) Channel_SetMaxUsers(chan, atol(Req->argv[3])); } else { /* Delete modes which we never want to inherit */ Channel_ModeDel(chan, 'l'); Channel_ModeDel(chan, 'k'); } strcpy(modes_add, ""); if (Channel_HasMode(chan, 'l')) { snprintf(l, sizeof(l), " %lu", Channel_MaxUsers(chan)); strlcat(modes_add, l, sizeof(modes_add)); } if (Channel_HasMode(chan, 'k')) { strlcat(modes_add, " ", sizeof(modes_add)); strlcat(modes_add, Channel_Key(chan), sizeof(modes_add)); } /* Inform members of this channel */ IRC_WriteStrChannelPrefix(Client, chan, from, false, "MODE %s +%s%s", Req->argv[0], Channel_Modes(chan), modes_add); } } else Log(LOG_WARNING, "CHANINFO: invalid MODE format ignored!"); if (arg_topic > 0) { /* We got a topic */ if (!*Channel_Topic(chan) && Req->argv[arg_topic][0]) { /* OK, there is no topic jet */ Channel_SetTopic(chan, Client, Req->argv[arg_topic]); IRC_WriteStrChannelPrefix(Client, chan, from, false, "TOPIC %s :%s", Req->argv[0], Channel_Topic(chan)); } } /* Forward CHANINFO to other servers */ if (Req->argc == 5) IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s %s %s :%s", Req->argv[0], Req->argv[1], Req->argv[2], Req->argv[3], Req->argv[4]); else if (Req->argc == 3) IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s :%s", Req->argv[0], Req->argv[1], Req->argv[2]); else IRC_WriteStrServersPrefixFlag(Client, from, 'C', "CHANINFO %s %s", Req->argv[0], Req->argv[1]); return CONNECTED; } /* IRC_CHANINFO */
/** * Handler for the IRC "LIST" command. * * @param Client The client from which this command has been received. * @param Req Request structure with prefix and all parameters. * @return CONNECTED or DISCONNECTED. */ GLOBAL bool IRC_LIST( CLIENT *Client, REQUEST *Req ) { char *pattern; CHANNEL *chan; CLIENT *from, *target; int count = 0; assert(Client != NULL); assert(Req != NULL); _IRC_GET_SENDER_OR_RETURN_(from, Req, Client) if (Req->argc > 0) pattern = strtok(Req->argv[0], ","); else pattern = "*"; if (Req->argc == 2) { /* Forward to other server? */ target = Client_Search(Req->argv[1]); if (! target || Client_Type(target) != CLIENT_SERVER) return IRC_WriteErrClient(from, ERR_NOSUCHSERVER_MSG, Client_ID(Client), Req->argv[1]); if (target != Client_ThisServer()) { /* Target is indeed an other server, forward it! */ return IRC_WriteStrClientPrefix(target, from, "LIST %s :%s", Req->argv[0], Req->argv[1]); } } while (pattern) { /* Loop through all the channels */ if (Req->argc > 0) ngt_LowerStr(pattern); chan = Channel_First(); while (chan) { /* Check search pattern */ if (MatchCaseInsensitive(pattern, Channel_Name(chan))) { /* Gotcha! */ if (!Channel_HasMode(chan, 's') || Channel_IsMemberOf(chan, from) || (!Conf_MorePrivacy && Client_HasMode(Client, 'o') && Client_Conn(Client) > NONE)) { if ((Conf_MaxListSize > 0) && IRC_CheckListTooBig(from, count, Conf_MaxListSize, "LIST")) break; if (!IRC_WriteStrClient(from, RPL_LIST_MSG, Client_ID(from), Channel_Name(chan), Channel_MemberCount(chan), Channel_Topic( chan ))) return DISCONNECTED; count++; } } chan = Channel_Next(chan); } /* Get next name ... */ if(Req->argc > 0) pattern = strtok(NULL, ","); else pattern = NULL; } return IRC_WriteStrClient(from, RPL_LISTEND_MSG, Client_ID(from)); } /* IRC_LIST */
/** * Handler for the IRC "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 */