diff options
| -rw-r--r-- | src/kike.c | 318 | 
1 files changed, 279 insertions, 39 deletions
| @@ -225,6 +225,12 @@ irc_is_valid_key (const char *key)  #undef SP  static bool +irc_is_valid_user_mask (const char *mask) +{ +	return irc_regex_match ("^[^!@]+![^!@]+@[^@!]+$", mask); +} + +static bool  irc_is_valid_fingerprint (const char *fp)  {  	return irc_regex_match ("^[a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){19}$", fp); @@ -934,7 +940,9 @@ enum  	IRC_ERR_NOTREGISTERED         = 451,  	IRC_ERR_NEEDMOREPARAMS        = 461,  	IRC_ERR_ALREADYREGISTERED     = 462, +	IRC_ERR_KEYSET                = 467,  	IRC_ERR_CHANNELISFULL         = 471, +	IRC_ERR_UNKNOWNMODE           = 472,  	IRC_ERR_INVITEONLYCHAN        = 473,  	IRC_ERR_BANNEDFROMCHAN        = 474,  	IRC_ERR_BADCHANNELKEY         = 475, @@ -1012,7 +1020,9 @@ static const char *g_default_replies[] =  	[IRC_ERR_NOTREGISTERED] = ":You have not registered",  	[IRC_ERR_NEEDMOREPARAMS] = "%s :Not enough parameters",  	[IRC_ERR_ALREADYREGISTERED] = ":Unauthorized command (already registered)", +	[IRC_ERR_KEYSET] = "%s :Channel key already set",  	[IRC_ERR_CHANNELISFULL] = "%s :Cannot join channel (+l)", +	[IRC_ERR_UNKNOWNMODE] = "%c :is unknown mode char to me for %s",  	[IRC_ERR_INVITEONLYCHAN] = "%s :Cannot join channel (+i)",  	[IRC_ERR_BANNEDFROMCHAN] = "%s :Cannot join channel (+b)",  	[IRC_ERR_BADCHANNELKEY] = "%s :Cannot join channel (+k)", @@ -1329,43 +1339,15 @@ irc_channel_multicast (struct channel *chan, const char *message,  	}  } -static void -irc_send_channel_list (struct client *c, const char *channel_name, -	const struct str_vector *list, int reply, int end_reply) -{ -	for (size_t i = 0; i < list->len; i++) -		irc_send_reply (c, reply, channel_name, list->vector[i]); -	irc_send_reply (c, end_reply); -} -  static bool -irc_maybe_send_channel_list (struct client *c, struct channel *chan, -	const char *mode_string) -{ -	if (*mode_string == '+') -		mode_string++; - -	if (!strcmp (mode_string, "b")) -		irc_send_channel_list (c, chan->name, &chan->ban_list, -			IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST); -	else if (!strcmp (mode_string, "e")) -		irc_send_channel_list (c, chan->name, &chan->exception_list, -			IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST); -	else if (!strcmp (mode_string, "I")) -		irc_send_channel_list (c, chan->name, &chan->invite_list, -			IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST); -	else -		return false; -	return true; -} - -static void  irc_modify_mode (unsigned *mask, unsigned mode, bool add)  { +	unsigned orig = *mask;  	if (add)  		*mask |= mode;  	else  		*mask &= ~mode; +	return *mask != orig;  }  static void @@ -1419,15 +1401,16 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string)  		irc_modify_mode (&new_mode, IRC_USER_MODE_RX_WALLOPS, adding);  		break;  	case 'r': +		// It's not possible to un-restrict yourself  		if (adding) -			irc_modify_mode (&new_mode, IRC_USER_MODE_RESTRICTED, true); +			new_mode |= IRC_USER_MODE_RESTRICTED;  		break;  	case 'o':  		if (!adding) -			irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, false); +			new_mode &= ~IRC_USER_MODE_OPERATOR;  		else if (c->ssl_cert_fingerprint  			&& str_map_find (&c->ctx->operators, c->ssl_cert_fingerprint)) -			irc_modify_mode (&new_mode, IRC_USER_MODE_OPERATOR, true); +			new_mode |= IRC_USER_MODE_OPERATOR;  		else  			irc_send (c, ":%s NOTICE %s :Either you're not using an SSL"  				" client certificate, or the fingerprint doesn't match", @@ -1443,6 +1426,267 @@ irc_handle_user_mode_change (struct client *c, const char *mode_string)  }  static void +irc_send_channel_list (struct client *c, const char *channel_name, +	const struct str_vector *list, int reply, int end_reply) +{ +	for (size_t i = 0; i < list->len; i++) +		irc_send_reply (c, reply, channel_name, list->vector[i]); +	irc_send_reply (c, end_reply); +} + +static char * +irc_check_expand_user_mask (const char *mask) +{ +	struct str result; +	str_init (&result); +	str_append (&result, mask); + +	// Make sure it is a complete mask +	if (!strchr (result.str, '!')) +		str_append (&result, "!*"); +	if (!strchr (result.str, '@')) +		str_append (&result, "@*"); + +	// And validate whatever the result is +	if (!irc_is_valid_user_mask (result.str)) +	{ +		str_free (&result); +		return NULL; +	} +	return str_steal (&result); +} + +static void +irc_handle_chan_mode_change (struct client *c, +	struct channel *chan, char *params[]) +{ +	struct channel_user *user = channel_get_user (chan, c); + +	// This is by far the worst command to implement from the whole RFC; +	// don't blame me if it doesn't work exactly as expected. + +	struct str added;     struct str_vector added_params; +	struct str removed;   struct str_vector removed_params; + +	str_init (&added);    str_vector_init (&added_params); +	str_init (&removed);  str_vector_init (&removed_params); + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +	// TODO: try to convert this madness into functions; that will most +	//   likely require creating a special parser class +#define NEEDS_OPER                                                             \ +	if (!user || (!(user->modes & IRC_CHAN_MODE_OPERATOR)                      \ +	 && !(c->mode & IRC_USER_MODE_OPERATOR)))                                  \ +	{                                                                          \ +		irc_send_reply (c, IRC_ERR_CHANOPRIVSNEEDED, chan->name);              \ +		continue;                                                              \ +	} + +#define HANDLE_USER(mode)                                                      \ +	if (!(target = *params))                                                   \ +		continue;                                                              \ +	params++;                                                                  \ +	NEEDS_OPER                                                                 \ +	if (!(client = str_map_find (&c->ctx->users, target)))                     \ +		irc_send_reply (c, IRC_ERR_NOSUCHNICK, target);                        \ +	else if (!(target_user = channel_get_user (chan, client)))                 \ +		irc_send_reply (c, IRC_ERR_USERNOTINCHANNEL,                           \ +			target, chan->name);                                               \ +	else if (irc_modify_mode (&target_user->modes, (mode), adding))            \ +	{                                                                          \ +		str_append_c (output, mode_char);                                      \ +		str_vector_add (output_params, target_user->nickname);                 \ +	} + +#define HANDLE_LIST(list, list_msg, end_msg)                                   \ +{                                                                              \ +	if (!(target = *params))                                                   \ +	{                                                                          \ +		if (adding)                                                            \ +			irc_send_channel_list (c, chan->name, list, list_msg, end_msg);    \ +		continue;                                                              \ +	}                                                                          \ +	params++;                                                                  \ +	NEEDS_OPER                                                                 \ +	char *mask = irc_check_expand_user_mask (target);                          \ +	if (!mask)                                                                 \ +		continue;                                                              \ +	size_t i;                                                                  \ +	for (i = 0; i < (list)->len; i++)                                          \ +		if (!irc_strcmp ((list)->vector[i], mask))                             \ +			break;                                                             \ +	if (!((i != (list)->len) ^ adding))                                        \ +	{                                                                          \ +		free (mask);                                                           \ +		continue;                                                              \ +	}                                                                          \ +	if (adding)                                                                \ +		str_vector_add ((list), mask);                                         \ +	else                                                                       \ +		str_vector_remove ((list), i);                                         \ +	str_append_c (output, mode_char);                                          \ +	str_vector_add (output_params, mask);                                      \ +	free (mask);                                                               \ +} + +#define HANDLE_MODE(mode)                                                      \ +	NEEDS_OPER                                                                 \ +	if (irc_modify_mode (&chan->modes, (mode), adding))                        \ +		str_append_c (output, mode_char); + +#define REMOVE_MODE(removed_mode, removed_char)                                \ +	if (adding && irc_modify_mode (&chan->modes, (removed_mode), false))       \ +		str_append_c (&removed, (removed_char)); + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +	const char *mode_string; +	while ((mode_string = *params++)) +	{ +		bool adding = true; +		struct str *output = &added; +		struct str_vector *output_params = &added_params; + +		const char *target; +		struct channel_user *target_user; +		struct client *client; + +		char mode_char; +		while (*mode_string) +		switch ((mode_char = *mode_string++)) +		{ +		case '+': +			adding = true; +			output = &added; +			output_params = &added_params; +			break; +		case '-': +			adding = false; +			output = &removed; +			output_params = &removed_params; +			break; + +		case 'o':  HANDLE_USER (IRC_CHAN_MODE_OPERATOR)         break; +		case 'v':  HANDLE_USER (IRC_CHAN_MODE_VOICE)            break; + +		case 'i':  HANDLE_MODE (IRC_CHAN_MODE_INVITE_ONLY)      break; +		case 'm':  HANDLE_MODE (IRC_CHAN_MODE_MODERATED)        break; +		case 'n':  HANDLE_MODE (IRC_CHAN_MODE_NO_OUTSIDE_MSGS)  break; +		case 'q':  HANDLE_MODE (IRC_CHAN_MODE_QUIET)            break; +		case 't':  HANDLE_MODE (IRC_CHAN_MODE_PROTECTED_TOPIC)  break; + +		case 'p': +			HANDLE_MODE (IRC_CHAN_MODE_PRIVATE) +			REMOVE_MODE (IRC_CHAN_MODE_SECRET, 's') +			break; +		case 's': +			HANDLE_MODE (IRC_CHAN_MODE_SECRET) +			REMOVE_MODE (IRC_CHAN_MODE_PRIVATE, 'p') +			break; + +		case 'b': +			HANDLE_LIST (&chan->ban_list, +				IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST) +			break; +		case 'e': +			HANDLE_LIST (&chan->exception_list, +				IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST) +			break; +		case 'I': +			HANDLE_LIST (&chan->invite_list, +				IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST) +			break; + +		case 'k': +			NEEDS_OPER +			if (!adding) +			{ +				if (!(target = *params)) +					continue; +				params++; +				if (!chan->key || irc_strcmp (target, chan->key)) +					continue; + +				str_append_c (&removed, mode_char); +				str_vector_add (&removed_params, chan->key); +				free (chan->key); +				chan->key = NULL; +			} +			else if (!(target = *params)) +				continue; +			else +			{ +				params++; +				if (chan->key) +					irc_send_reply (c, IRC_ERR_KEYSET, chan->name); +				else +				{ +					chan->key = xstrdup (target); +					str_append_c (&added, mode_char); +					str_vector_add (&added_params, chan->key); +				} +			} +			break; +		case 'l': +			NEEDS_OPER +			if (!adding) +			{ +				if (chan->user_limit == -1) +					continue; + +				chan->user_limit = -1; +				str_append_c (&removed, mode_char); +			} +			else if (!(target = *params)) +				continue; +			else +			{ +				params++; +				unsigned long x; +				if (xstrtoul (&x, target, 10) && x > 0 && x <= LONG_MAX) +				{ +					chan->user_limit = x; +					str_append_c (&added, mode_char); +					str_vector_add (&added_params, target); +				} +			} +			break; + +		default: +			RETURN_WITH_REPLY (c, IRC_ERR_UNKNOWNMODE); +		} +	} + +#undef NEEDS_OPER +#undef HANDLE_USER +#undef HANDLE_LIST +#undef HANDLE_MODE +#undef REMOVE_MODE + +	if (added.len || removed.len) +	{ +		struct str message; +		str_init (&message); +		str_append_printf (&message, ":%s!%s@%s MODE %s ", +			c->nickname, c->username, c->hostname, chan->name); +		if (added.len) +			str_append_printf (&message, "+%s", added.str); +		if (removed.len) +			str_append_printf (&message, "-%s", removed.str); +		for (size_t i = 0; i < added_params.len; i++) +			str_append_printf (&message, " %s", added_params.vector[i]); +		for (size_t i = 0; i < removed_params.len; i++) +			str_append_printf (&message, " %s", removed_params.vector[i]); +		irc_channel_multicast (chan, message.str, NULL); +		str_free (&message); +	} + +	str_free (&added);    str_vector_free (&added_params); +	str_free (&removed);  str_vector_free (&removed_params); +} + +static void  irc_handle_mode (const struct irc_message *msg, struct client *c)  {  	if (msg->params.len < 1) @@ -1474,13 +1718,9 @@ irc_handle_mode (const struct irc_message *msg, struct client *c)  			char *mode = channel_get_mode (chan, channel_get_user (chan, c));  			irc_send_reply (c, IRC_RPL_CHANNELMODEIS, target, mode);  			free (mode); -			return;  		} -		if (msg->params.len < 3 -		 && irc_maybe_send_channel_list (c, chan, msg->params.vector[1])) -			return; - -		// TODO: mode modification +		else +			irc_handle_chan_mode_change (c, chan, &msg->params.vector[1]);  		return;  	} | 
