ircu2/ircd/ircd_relay.c

727 lines
21 KiB
C

/*
* IRC - Internet Relay Chat, ircd/ircd_relay.c
* Copyright (C) 1990 Jarkko Oikarinen and
* University of Oulu, Computing Center
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file
* @brief Helper functions to relay various types of messages.
* @version $Id: ircd_relay.c 1913 2009-07-04 22:46:00Z entrope $
*
* There are four basic types of messages, each with four subtypes.
*
* The basic types are: channel, directed, masked, and private.
* Channel messages are (perhaps obviously) sent directly to a
* channel. Directed messages are sent to "NICK[%host]@server", but
* only allowed if the server is a services server (to avoid
* information leaks for normal clients). Masked messages are sent to
* either *@*host.mask or *.server.mask. Private messages are sent to
* NICK.
*
* The subtypes for each type are: client message, client notice,
* server message, and server notice. Client subtypes are sent by a
* local user, and server subtypes are given to us by a server.
* Notice subtypes correspond to the NOTICE command, and message
* subtypes correspond to the PRIVMSG command.
*
* As a special note, directed messages do not have server subtypes,
* since there is no difference in handling them based on origin.
*/
#include "config.h"
#include "ircd_relay.h"
#include "channel.h"
#include "client.h"
#include "hash.h"
#include "ircd.h"
#include "ircd_chattr.h"
#include "ircd_features.h"
#include "ircd_log.h"
#include "ircd_reply.h"
#include "ircd_string.h"
#include "match.h"
#include "msg.h"
#include "numeric.h"
#include "numnicks.h"
#include "s_debug.h"
#include "s_misc.h"
#include "s_user.h"
#include "send.h"
/* #include <assert.h> -- Now using assert in ircd_log.h */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* This file contains message relaying functions for client and server
* private messages and notices
* TODO: This file contains a lot of cut and paste code, and needs
* to be cleaned up a bit. The idea is to factor out the common checks
* but not introduce any IsOper/IsUser/MyUser/IsServer etc. stuff.
*/
/** Relay a local user's message to a channel.
* Generates an error if the client cannot send to the channel.
* @param[in] sptr Client that originated the message.
* @param[in] name Name of target channel.
* @param[in] text %Message to relay.
* @param[in] targets Number of targets for the initial PRIVMSG.
*/
void relay_channel_message(struct Client* sptr, const char* name, const char* text, int targets)
{
struct Channel* chptr;
const char* mytext = text;
const char* ch;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (0 == (chptr = FindChannel(name))) {
send_reply(sptr, ERR_NOSUCHCHANNEL, name);
return;
}
/*
* This first: Almost never a server/service
*/
if (!client_can_send_to_channel(sptr, chptr, 0)) {
send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
return;
}
if ((chptr->mode.mode & MODE_NOPRIVMSGS) &&
check_target_limit(sptr, chptr, chptr->chname, 0))
return;
if ((chptr->mode.exmode & EXMODE_NOCTCPS) && (*text == '\x01') &&
ircd_strncmp(text+1, "ACTION", 6)) {
send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
return;
}
if (((chptr->mode.exmode & EXMODE_NOMULTITARG) ||
feature_bool(FEAT_NOMULTITARGETS)) && (targets > 1)) {
send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
return;
}
if (chptr->mode.exmode & EXMODE_NOCOLOR)
for (ch=text;*ch;ch++)
if (*ch==COLOR_COLOR || *ch==27 || *ch==COLOR_REVERSE) {
send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
return;
}
if (chptr->mode.exmode & EXMODE_STRIPCOLOR) {
mytext = StripColor(text);
if (EmptyString(mytext)) {
send_reply(sptr, ERR_NOTEXTTOSEND);
return;
}
}
RevealDelayedJoinIfNeeded(sptr, chptr);
sendcmdto_channel_butone(sptr, CMD_PRIVATE, chptr, cli_from(sptr),
SKIP_DEAF | SKIP_BURST, text[0], "%H :%s", chptr, mytext);
}
/** Relay a local user's notice to a channel.
* Silently exits if the client cannot send to the channel.
* @param[in] sptr Client that originated the message.
* @param[in] name Name of target channel.
* @param[in] text %Message to relay.
* @param[in] targets Number of targets for the initial NOTICE
*/
void relay_channel_notice(struct Client* sptr, const char* name, const char* text, int targets)
{
struct Channel* chptr;
const char* mytext = text;
const char* ch;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (0 == (chptr = FindChannel(name)))
return;
/*
* This first: Almost never a server/service
*/
if (!client_can_send_to_channel(sptr, chptr, 0))
return;
if ((chptr->mode.mode & MODE_NOPRIVMSGS) &&
check_target_limit(sptr, chptr, chptr->chname, 0))
return;
if (chptr->mode.exmode & EXMODE_NONOTICES)
return;
if ((chptr->mode.exmode & EXMODE_NOCTCPS) && (*text == '\x01'))
return;
if (((chptr->mode.exmode & EXMODE_NOMULTITARG) ||
feature_bool(FEAT_NOMULTITARGETS)) && (targets > 1))
return;
if (chptr->mode.exmode & EXMODE_NOCOLOR)
for (ch=text;*ch;ch++)
if (*ch==COLOR_COLOR || *ch==27 || *ch==COLOR_REVERSE)
return;
if (chptr->mode.exmode & EXMODE_STRIPCOLOR) {
mytext = StripColor(text);
if (EmptyString(mytext))
return;
}
RevealDelayedJoinIfNeeded(sptr, chptr);
sendcmdto_channel_butone(sptr, CMD_NOTICE, chptr, cli_from(sptr),
SKIP_DEAF | SKIP_BURST, '\0', "%H :%s", chptr, mytext);
}
/** Relay a message to a channel.
* Generates an error if the client cannot send to the channel,
* or if the channel is a local channel
* @param[in] sptr Client that originated the message.
* @param[in] name Name of target channel.
* @param[in] text %Message to relay.
*/
void server_relay_channel_message(struct Client* sptr, const char* name, const char* text)
{
struct Channel* chptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (IsLocalChannel(name) || 0 == (chptr = FindChannel(name))) {
send_reply(sptr, ERR_NOSUCHCHANNEL, name);
return;
}
/*
* This first: Almost never a server/service
* Servers may have channel services, need to check for it here
*/
if (client_can_send_to_channel(sptr, chptr, 1) || IsChannelService(sptr)) {
sendcmdto_channel_butone(sptr, CMD_PRIVATE, chptr, cli_from(sptr),
SKIP_DEAF | SKIP_BURST, text[0], "%H :%s", chptr, text);
}
else
send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
}
/** Relay a notice to a channel.
* Generates an error if the client cannot send to the channel,
* or if the channel is a local channel
* @param[in] sptr Client that originated the message.
* @param[in] name Name of target channel.
* @param[in] text %Message to relay.
*/
void server_relay_channel_notice(struct Client* sptr, const char* name, const char* text)
{
struct Channel* chptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (IsLocalChannel(name) || 0 == (chptr = FindChannel(name)))
return;
/*
* This first: Almost never a server/service
* Servers may have channel services, need to check for it here
*/
if ((client_can_send_to_channel(sptr, chptr, 1) &&
!(chptr->mode.exmode & EXMODE_NONOTICES)) || IsChannelService(sptr)) {
sendcmdto_channel_butone(sptr, CMD_NOTICE, chptr, cli_from(sptr),
SKIP_DEAF | SKIP_BURST, '\0', "%H :%s", chptr, text);
}
}
/** Relay a directed message.
* Generates an error if the named server does not exist, if it is not
* a services server, or if \a name names a local user and a hostmask
* is specified but does not match.
* @param[in] sptr Client that originated the message.
* @param[in] name Target nickname, with optional "%hostname" suffix.
* @param[in] server Name of target server.
* @param[in] text %Message to relay.
*/
void relay_directed_message(struct Client* sptr, char* name, char* server, const char* text)
{
struct Client* acptr;
char* host;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
assert(0 != server);
if ((acptr = FindServer(server + 1)) == NULL || !IsService(acptr))
{
send_reply(sptr, ERR_NOSUCHNICK, name);
return;
}
/*
* NICK[%host]@server addressed? See if <server> is me first
*/
if (!IsMe(acptr))
{
sendcmdto_one(sptr, CMD_PRIVATE, acptr, "%s :%s", name, text);
return;
}
/*
* Look for an user whose NICK is equal to <name> and then
* check if it's hostname matches <host> and if it's a local
* user.
*/
*server = '\0';
if ((host = strchr(name, '%')))
*host++ = '\0';
/* As reported by Vampire-, it's possible to brute force finding users
* by sending a message to each server and see which one succeeded.
* This means we have to remove error reporting. Sigh. Better than
* removing the ability to send directed messages to client servers
* Thanks for the suggestion Vampire=. -- Isomer 2001-08-28
* Argh, /ping nick@server, disallow messages to non +k clients :/ I hate
* this. -- Isomer 2001-09-16
*/
if (!(acptr = FindUser(name)) || !MyUser(acptr) ||
(!EmptyString(host) && 0 != match(host, cli_user(acptr)->host)) ||
!IsChannelService(acptr))
{
/*
* By this stage we might as well not bother because they will
* know that this server is currently linked because of the
* increased lag.
*/
send_reply(sptr, ERR_NOSUCHNICK, name);
return;
}
*server = '@';
if (host)
*--host = '%';
/*
* +R check, if target is +R and we're not +r (or opered) then
* deny the message.
*/
if (IsAccountOnly(acptr) && !IsAccount(sptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_ACCOUNTONLY, cli_name(acptr), "PRIVMSG", cli_name(acptr));
return;
}
/*
* +D check, if target is +D and we're not opered then deny
* the message.
*/
if (IsPrivDeaf(acptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_PRIVDEAF, cli_name(acptr), "PRIVMSG", cli_name(acptr));
return;
}
if ((IsRestrictPrivMsg(sptr) || IsCommonChansOnly(acptr)) && !IsAnOper(sptr) &&
!common_chan_count(acptr, sptr, 1) && (sptr != acptr)) {
send_reply(sptr, ERR_COMMONCHANSONLY, cli_name(acptr), "PRIVMSG");
return;
}
if (!(is_silenced(sptr, acptr, 0)))
sendcmdto_one(sptr, CMD_PRIVATE, acptr, "%s :%s", name, text);
}
/** Relay a directed notice.
* Generates an error if the named server does not exist, if it is not
* a services server, or if \a name names a local user and a hostmask
* is specified but does not match.
* @param[in] sptr Client that originated the message.
* @param[in] name Target nickname, with optional "%hostname" suffix.
* @param[in] server Name of target server.
* @param[in] text %Message to relay.
*/
void relay_directed_notice(struct Client* sptr, char* name, char* server, const char* text)
{
struct Client* acptr;
char* host;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
assert(0 != server);
if (0 == (acptr = FindServer(server + 1)))
return;
/*
* NICK[%host]@server addressed? See if <server> is me first
*/
if (!IsMe(acptr)) {
sendcmdto_one(sptr, CMD_NOTICE, acptr, "%s :%s", name, text);
return;
}
/*
* Look for an user whose NICK is equal to <name> and then
* check if it's hostname matches <host> and if it's a local
* user.
*/
*server = '\0';
if ((host = strchr(name, '%')))
*host++ = '\0';
if (!(acptr = FindUser(name)) || !MyUser(acptr) ||
(!EmptyString(host) && 0 != match(host, cli_user(acptr)->host)))
return;
*server = '@';
if (host)
*--host = '%';
/*
* +R check, if target is +R and we're not +r (or opered) then
* deny the message.
*/
if (IsAccountOnly(acptr) && !IsAccount(sptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_ACCOUNTONLY, cli_name(acptr), "NOTICE", cli_name(acptr));
return;
}
/*
* +D check, if target is +D and we're not opered then deny
* the message.
*/
if (IsPrivDeaf(acptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_PRIVDEAF, cli_name(acptr), "NOTICE", cli_name(acptr));
return;
}
if ((IsRestrictPrivMsg(sptr) || IsCommonChansOnly(acptr)) && !IsAnOper(sptr) &&
!common_chan_count(acptr, sptr, 1) && (sptr != acptr)) {
send_reply(sptr, ERR_COMMONCHANSONLY, cli_name(acptr), "NOTICE");
return;
}
if (!(is_silenced(sptr, acptr, 0)))
sendcmdto_one(sptr, CMD_NOTICE, acptr, "%s :%s", name, text);
}
/** Relay a private message from a local user.
* Returns an error if the user does not exist or sending to him would
* exceed the source's free targets. Sends an AWAY status message if
* the target is marked as away.
* @param[in] sptr Client that originated the message.
* @param[in] name Nickname of target user.
* @param[in] text %Message to relay.
*/
void relay_private_message(struct Client* sptr, const char* name, const char* text)
{
struct Client* acptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (0 == (acptr = FindUser(name))) {
send_reply(sptr, ERR_NOSUCHNICK, name);
return;
}
if ((!IsChannelService(acptr) &&
check_target_limit(sptr, acptr, cli_name(acptr), 0)) ||
is_silenced(sptr, acptr, 0))
return;
/*
* +R check, if target is +R and we're not +r (or opered) then
* deny the message.
*/
if (IsAccountOnly(acptr) && !IsAccount(sptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_ACCOUNTONLY, cli_name(acptr), "PRIVMSG", cli_name(acptr));
return;
}
/*
* +D check, if target is +D and we're not opered then deny
* the message.
*/
if (IsPrivDeaf(acptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_PRIVDEAF, cli_name(acptr), "PRIVMSG", cli_name(acptr));
return;
}
if ((IsRestrictPrivMsg(sptr) || IsCommonChansOnly(acptr)) && !IsAnOper(sptr) &&
!common_chan_count(acptr, sptr, 1) && (sptr != acptr)) {
send_reply(sptr, ERR_COMMONCHANSONLY, cli_name(acptr), "PRIVMSG");
return;
}
/*
* send away message if user away
*/
if (cli_user(acptr) && cli_user(acptr)->away)
send_reply(sptr, RPL_AWAY, cli_name(acptr), cli_user(acptr)->away);
/*
* deliver the message
*/
if (MyUser(acptr))
add_target(acptr, sptr);
sendcmdto_one(sptr, CMD_PRIVATE, acptr, "%C :%s", acptr, text);
}
/** Relay a private notice from a local user.
* Returns an error if the user does not exist or sending to him would
* exceed the source's free targets. Sends an AWAY status message if
* the target is marked as away.
* @param[in] sptr Client that originated the message.
* @param[in] name Nickname of target user.
* @param[in] text %Message to relay.
*/
void relay_private_notice(struct Client* sptr, const char* name, const char* text)
{
struct Client* acptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
if (0 == (acptr = FindUser(name)))
return;
if ((!IsChannelService(acptr) &&
check_target_limit(sptr, acptr, cli_name(acptr), 0)) ||
is_silenced(sptr, acptr, 0))
return;
/*
* +R check, if target is +R and we're not +r (or opered) then
* deny the message.
*/
if (IsAccountOnly(acptr) && !IsAccount(sptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_ACCOUNTONLY, cli_name(acptr), "NOTICE", cli_name(acptr));
return;
}
/*
* +D check, if target is +D and we're not opered then deny
* the message.
*/
if (IsPrivDeaf(acptr) && !IsOper(sptr) && (acptr != sptr)) {
send_reply(sptr, ERR_PRIVDEAF, cli_name(acptr), "NOTICE", cli_name(acptr));
return;
}
if ((IsRestrictPrivMsg(sptr) || IsCommonChansOnly(acptr)) && !IsAnOper(sptr) &&
!common_chan_count(acptr, sptr, 1) && (sptr != acptr)) {
send_reply(sptr, ERR_COMMONCHANSONLY, cli_name(acptr), "NOTICE");
return;
}
/*
* deliver the message
*/
if (MyUser(acptr))
add_target(acptr, sptr);
sendcmdto_one(sptr, CMD_NOTICE, acptr, "%C :%s", acptr, text);
}
/** Relay a private message that arrived from a server.
* Returns an error if the user does not exist.
* @param[in] sptr Client that originated the message.
* @param[in] name Nickname of target user.
* @param[in] text %Message to relay.
*/
void server_relay_private_message(struct Client* sptr, const char* name, const char* text)
{
struct Client* acptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
/*
* nickname addressed?
*/
if (0 == (acptr = findNUser(name)) || !IsUser(acptr)) {
send_reply(sptr, SND_EXPLICIT | ERR_NOSUCHNICK, "* :Target left %s. "
"Failed to deliver: [%.20s]", feature_str(FEAT_NETWORK),
text);
return;
}
if (is_silenced(sptr, acptr, 0))
return;
if (MyUser(acptr))
add_target(acptr, sptr);
sendcmdto_one(sptr, CMD_PRIVATE, acptr, "%C :%s", acptr, text);
}
/** Relay a private notice that arrived from a server.
* Returns an error if the user does not exist.
* @param[in] sptr Client that originated the message.
* @param[in] name Nickname of target user.
* @param[in] text %Message to relay.
*/
void server_relay_private_notice(struct Client* sptr, const char* name, const char* text)
{
struct Client* acptr;
assert(0 != sptr);
assert(0 != name);
assert(0 != text);
/*
* nickname addressed?
*/
if (0 == (acptr = findNUser(name)) || !IsUser(acptr))
return;
if (is_silenced(sptr, acptr, 0))
return;
if (MyUser(acptr))
add_target(acptr, sptr);
sendcmdto_one(sptr, CMD_NOTICE, acptr, "%C :%s", acptr, text);
}
/** Relay a masked message from a local user.
* Sends an error response if there is no top-level domain label in \a
* mask, or if that TLD contains a wildcard.
* @param[in] sptr Client that originated the message.
* @param[in] mask Target mask for the message.
* @param[in] text %Message to relay.
*/
void relay_masked_message(struct Client* sptr, const char* mask, const char* text)
{
const char* s;
int host_mask = 0;
assert(0 != sptr);
assert(0 != mask);
assert(0 != text);
/*
* look for the last '.' in mask and scan forward
*/
if (0 == (s = strrchr(mask, '.'))) {
send_reply(sptr, ERR_NOTOPLEVEL, mask);
return;
}
while (*++s) {
if (*s == '.' || *s == '*' || *s == '?')
break;
}
if (*s == '*' || *s == '?') {
send_reply(sptr, ERR_WILDTOPLEVEL, mask);
return;
}
s = mask;
if ('@' == *++s) {
host_mask = 1;
++s;
}
sendcmdto_match_butone(sptr, CMD_PRIVATE, s,
IsServer(cli_from(sptr)) ? cli_from(sptr) : 0,
host_mask ? MATCH_HOST : MATCH_SERVER,
"%s :%s", mask, text);
}
/** Relay a masked notice from a local user.
* Sends an error response if there is no top-level domain label in \a
* mask, or if that TLD contains a wildcard.
* @param[in] sptr Client that originated the message.
* @param[in] mask Target mask for the message.
* @param[in] text %Message to relay.
*/
void relay_masked_notice(struct Client* sptr, const char* mask, const char* text)
{
const char* s;
int host_mask = 0;
assert(0 != sptr);
assert(0 != mask);
assert(0 != text);
/*
* look for the last '.' in mask and scan forward
*/
if (0 == (s = strrchr(mask, '.'))) {
send_reply(sptr, ERR_NOTOPLEVEL, mask);
return;
}
while (*++s) {
if (*s == '.' || *s == '*' || *s == '?')
break;
}
if (*s == '*' || *s == '?') {
send_reply(sptr, ERR_WILDTOPLEVEL, mask);
return;
}
s = mask;
if ('@' == *++s) {
host_mask = 1;
++s;
}
sendcmdto_match_butone(sptr, CMD_NOTICE, s,
IsServer(cli_from(sptr)) ? cli_from(sptr) : 0,
host_mask ? MATCH_HOST : MATCH_SERVER,
"%s :%s", mask, text);
}
/** Relay a masked message that arrived from a server.
* @param[in] sptr Client that originated the message.
* @param[in] mask Target mask for the message.
* @param[in] text %Message to relay.
*/
void server_relay_masked_message(struct Client* sptr, const char* mask, const char* text)
{
const char* s = mask;
int host_mask = 0;
assert(0 != sptr);
assert(0 != mask);
assert(0 != text);
if ('@' == *++s) {
host_mask = 1;
++s;
}
sendcmdto_match_butone(sptr, CMD_PRIVATE, s,
IsServer(cli_from(sptr)) ? cli_from(sptr) : 0,
host_mask ? MATCH_HOST : MATCH_SERVER,
"%s :%s", mask, text);
}
/** Relay a masked notice that arrived from a server.
* @param[in] sptr Client that originated the message.
* @param[in] mask Target mask for the message.
* @param[in] text %Message to relay.
*/
void server_relay_masked_notice(struct Client* sptr, const char* mask, const char* text)
{
const char* s = mask;
int host_mask = 0;
assert(0 != sptr);
assert(0 != mask);
assert(0 != text);
if ('@' == *++s) {
host_mask = 1;
++s;
}
sendcmdto_match_butone(sptr, CMD_NOTICE, s,
IsServer(cli_from(sptr)) ? cli_from(sptr) : 0,
host_mask ? MATCH_HOST : MATCH_SERVER,
"%s :%s", mask, text);
}