1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-06-30 06:36:38 +02:00
Files
unrealircd/src/send.c
T
Bram Matthys ced1382eab Fix channel messages not showing up on remote servers (6.1.2-rc2 bug).
This required two members on the same server and channel mode +H to be set
(or set::broadcast-channel-messages 'always', then also with -H).

The cause was a (normally harmless) optimization in
1473f52603 which meant we would loop
through remote servers for the case of +H.

And then the real cause a bug in the linecache system, which
caused servers to be seen as LCUT_NORMAL because locally
connected servers are MyConnect()->true.

And then on the wire (S2S) a message would look like..
:nick!user@host PRIVMSG ...
But nick!user@host is not valid in normal S2S traffic and on the receiving
server is seen as a nick@server message (and 'nick!user' is never found
on 'server' where server is actually a user host)... seems like an
old relic, but this aside.
This in turn, causing the message to be dropped (unknown source),
and the PRIVMSG handler is not called at all.

Bug reported by CrazyCat and then PeGaSuS managed to reproduce the
issue later on irc.unrealircd.org. Thanks!

As said, this only affects 6.1.2-rc2 and chmode +H.
2025-11-02 15:53:42 +01:00

1434 lines
44 KiB
C

/*
* Unreal Internet Relay Chat Daemon, src/send.c
* Copyright (C) 1990 Jarkko Oikarinen and
* University of Oulu, Computing Center
*
* 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.
*/
/* send.c 2.32 2/28/94 (C) 1988 University of Oulu, Computing Center and Jarkko Oikarinen */
/** @file
* @brief The sending functions to users, channels, servers.
*/
#include "unrealircd.h"
/* Local structs */
typedef enum LineCacheUserType { LCUT_NORMAL=0, LCUT_OPER=1, LCUT_REMOTE=2 } LineCacheUserType;
typedef struct LineCacheLine LineCacheLine;
struct LineCacheLine
{
LineCacheLine *prev, *next;
LineCacheUserType user_type;
unsigned long caps;
int line_opts; /**< Cached line message options (rare) */
char *line; /**< Entire cached line, including message tags (if appropriate) and \r\n */
int linelen; /**< strlen(line) */
};
typedef struct LineCache LineCache;
struct LineCache
{
// later: possible options?
LineCacheLine *items;
};
/* Some forward declarions are needed */
void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl);
void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
static void vsendto_prefix_one_cached(LineCache *cache, int line_opts, Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,6,0)));
static LineCache *linecache_init(void);
static void linecache_free(LineCache *cache);
static void linecache_add(LineCache *cache, int line_opts, Client *to, const char *line, int linelen);
static LineCacheLine *linecache_get(LineCache *cache, int line_opts, Client *to);
/* These are two local (static) buffers used by the various send functions */
static char sendbuf[MAXLINELENGTH];
static char sendbuf2[MAXLINELENGTH];
static char sendbuf3[MAXLINELENGTH];
/** This is used to ensure no duplicate messages are sent
* to the same server uplink/direction. In send functions
* that deliver to multiple users or servers the value is
* increased by 1 and then for each delivery in the loop
* it is checked if to->direction->local->serial == current_serial
* and if so, sending is skipped.
*/
MODVAR int current_serial;
/** This is a callback function from the event loop.
* All it does is call send_queued().
*/
void send_queued_cb(int fd, int revents, void *data)
{
Client *to = data;
if (IsDeadSocket(to))
return;
send_queued(to);
}
/** This function is called when queued data might be ready to be
* sent to the client. It is called from the event loop and also
* a couple of other places (such as when closing the connection).
*/
int send_queued(Client *to)
{
int len, rlen;
dbufbuf *block;
int want_read;
/* We NEVER write to dead sockets. */
if (IsDeadSocket(to))
return -1;
while (DBufLength(&to->local->sendQ) > 0)
{
block = container_of(to->local->sendQ.dbuf_list.next, dbufbuf, dbuf_node);
len = block->size;
/* Deliver it and check for fatal error.. */
if ((rlen = deliver_it(to, block->data, len, &want_read)) < 0)
{
char buf[256];
snprintf(buf, 256, "Write error: %s", STRERROR(ERRNO));
return dead_socket(to, buf);
}
dbuf_delete(&to->local->sendQ, rlen);
if (want_read)
{
/* SSL_write indicated that it cannot write data at this
* time and needs to READ data first. Let's stop talking
* to the user and ask to notify us when there's data
* to read.
*/
fd_setselect(to->local->fd, FD_SELECT_READ, send_queued_cb, to);
fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
break;
}
/* Restore handling of reads towards read_packet(), since
* it may be overwritten in an earlier call to send_queued(),
* to handle reads by send_queued_cb(), see directly above.
*/
fd_setselect(to->local->fd, FD_SELECT_READ, read_packet, to);
if (rlen < len)
{
/* incomplete write due to EWOULDBLOCK, reschedule */
fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
break;
}
}
/* Nothing left to write, stop asking for write-ready notification. */
if ((DBufLength(&to->local->sendQ) == 0) && (to->local->fd >= 0))
fd_setselect(to->local->fd, FD_SELECT_WRITE, NULL, to);
return (IsDeadSocket(to)) ? -1 : 0;
}
/** Mark "to" with "there is data to be send" */
void mark_data_to_send(Client *to)
{
if (!IsDeadSocket(to) && (to->local->fd >= 0) && (DBufLength(&to->local->sendQ) > 0))
{
fd_setselect(to->local->fd, FD_SELECT_WRITE, send_queued_cb, to);
}
}
/** Send data to clients, servers, channels, IRCOps, etc.
* There are a lot of send functions. The most commonly functions
* are: sendto_one() to send to an individual user,
* sendnumeric() to send a numeric to an individual user
* and sendto_channel() to send a message to a channel.
* @defgroup SendFunctions Send functions
* @{
*/
/** Send a message to a single client.
* This function is used quite a lot, after sendnumeric() it is the most-used send function.
* @param to The client to send to
* @param mtags Any message tags associated with this message (can be NULL)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
* @section sendto_one_examples Examples
* @subsection sendto_one_mode_r Send "MODE -r"
* This will send the `:serv.er.name MODE yournick -r` message.
* Note that it will send only this message to illustrate the sendto_one() function.
* It does *not* set anyone actually -r.
* @code
* sendto_one(client, NULL, ":%s MODE %s :-r", me.name, client->name);
* @endcode
*/
void sendto_one(Client *to, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
va_start(vl, pattern);
vsendto_one(to, mtags, pattern, vl);
va_end(vl);
}
/** Send a message to a single client - va_list variant.
* This function is similar to sendto_one() except that it
* doesn't use varargs but uses a va_list instead.
* Generally this function is NOT used outside send.c, so not by modules.
* @param to The client to send to
* @param mtags Any message tags associated with this message (can be NULL)
* @param pattern The format string / pattern to use.
* @param vl Format string parameters.
*/
void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl)
{
const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
/* Need to ignore -Wformat-nonliteral here */
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
if (BadPtr(mtags_str))
{
/* Simple message without message tags */
sendbufto_one(to, sendbuf, 0);
} else {
/* Message tags need to be prepended */
snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
sendbufto_one(to, sendbuf2, 0);
}
}
/** Prepare a line for sendbufto_one().
* @param to Client to send to
* @param input Pointer to line to be sent.
* @notes The string '*input' can be changed or cut off (this is quite frequent).
* Or it may be replaced entirely, in which case 'input' will be set to a new string.
*/
static int sendbufto_one_prepare_line(Client *to, char **input)
{
char *msg = *input;
char *p;
int len;
/* If we are in UTF8ONLY mode, we first convert the line to be sent
* to valid UTF8. Of course, normally the line would not contain
* invalid UTF8 to begin with (since we already reject it at the
* input side from clients), but some external data may 'poison'
* things like a MOTD or who knows what. Also, since we will be
* cutting off strings, we need to take extra care in this function
* as well when UTF8ONLY is enabled, since we may cut in the middle
* of an UTF8 sequence.
*/
if (UTF8ONLY)
{
/* Note that last parameter strictlen must be 0, otherwise we might
* end up cutting BIGLINES servers and also.. we already do all that
* cutting of 512 etc below anyway, so no need to do double work.
*/
char *ret = unrl_utf8_make_valid(*input, sendbuf3, sizeof(sendbuf3), 0);
if (ret != *input)
{
/* Message had invalid UTF8 or was cut (eg 512 length restriction) */
*input = msg = ret;
}
}
if (*msg == '@')
{
/* The message includes one or more message tags:
* Spec-wise the rules allow about 8K for message tags
* (MAXTAGSIZE) and then 512 bytes for
* the remainder of the message (BUFSIZE).
*/
p = strchr(msg+1, ' ');
if (!p)
{
unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_MALFORMED_MSG", to,
"Malformed message to $client: $buf",
log_data_string("buf", msg));
return 0;
}
if (p - msg > MAXTAGSIZE)
{
unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG", to,
"Oversized message to $client (length $length): $buf",
log_data_integer("length", p - msg),
log_data_string("buf", msg));
return 0;
}
p++; /* skip space character */
} else {
p = msg;
}
len = strlen(p);
if (!len || (p[len - 1] != '\n'))
{
if (!IsServer(to) || !SupportBIGLINES(to->direction))
{
/* Normal case */
if (len > 510)
len = 510;
if (UTF8ONLY)
utf8_valid_cutoff(p, &len);
p[len++] = '\r';
p[len++] = '\n';
p[len] = '\0';
} else {
/* BIGLINES case:
* - first 'if' is about the total line length,
* this is basically an optimized strlen(msg)
* - the 'else' applies to non-mtags part,
* like in the 'Normal case' from above.
*/
if ((p - msg) + len > MAXLINELENGTH-3)
{
len = MAXLINELENGTH-3;
if (UTF8ONLY)
utf8_valid_cutoff(msg, &len);
msg[len++] = '\r';
msg[len++] = '\n';
msg[len] = '\0';
} else {
if (UTF8ONLY)
utf8_valid_cutoff(p, &len);
p[len++] = '\r';
p[len++] = '\n';
p[len] = '\0';
}
}
}
#ifdef DEBUGMODE
if (UTF8ONLY)
{
/* if validation fails it means conversion above resulted
* in an invalid UTF8 string.
*/
if (!unrl_utf8_validate(msg, NULL))
abort();
}
#endif
/* Return length, that is:
* p-msg = message tag len (can be 0)
* len = normal IRC message including \r\n
*/
return (p - msg) + len;
}
/** Send a line buffer to the client.
* This function is used (usually indirectly) for pretty much all
* cases where a line needs to be sent to a client.
* @param to The client to which the buffer should be send.
* @param msg The message.
* @param quick Normally set to 0, see the notes.
* @note
* - Neither 'to' or 'msg' may be NULL.
* - If quick is set to 0 then the length is calculated,
* the string is cut off at 510 bytes if needed, and
* CR+LF is added if needed.
* If quick is >0 then it is assumed the message already
* is within boundaries and passed all safety checks and
* contains CR+LF at the end. This if, for example, used in
* channel broadcasts to save some CPU cycles. It is NOT
* recommended as normal usage since you need to be very
* careful to take everything into account, including side-
* effects not mentioned here.
*/
void sendbufto_one(Client *to, char *msg, unsigned int quick)
{
int len;
Hook *h;
Client *intended_to = to;
if (to->direction)
to = to->direction;
if (IsDeadSocket(to))
return; /* This socket has already
been marked as dead */
if (to->local->fd < 0)
{
/* This is normal when 'to' was being closed (via exit_client
* and close_connection) --Run
*/
return;
}
/* Unless 'quick' is set, we do some safety checks,
* cut the string off at the appropriate place and add
* CR+LF if needed (nearly always).
*/
if (!quick)
{
len = sendbufto_one_prepare_line(to, &msg);
if (len == 0)
return;
} else {
len = quick;
}
if (len >= MAXLINELENGTH)
{
unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_OVERSIZED_MSG2", to,
"Oversized message to $client (length $length): $buf",
log_data_integer("length", len),
log_data_string("buf", msg));
#ifdef DEBUGMODE
abort();
#else
return;
#endif
}
if (IsMe(to))
{
char tmp_msg[500];
strlcpy(tmp_msg, msg, sizeof(tmp_msg));
stripcrlf(tmp_msg);
unreal_log(ULOG_WARNING, "send", "SENDBUFTO_ONE_ME_MESSAGE", to,
"Trying to send data to myself: $buf",
log_data_string("buf", tmp_msg));
return;
}
for (h = Hooks[HOOKTYPE_PACKET]; h; h = h->next)
{
(*(h->func.intfunc))(&me, to, intended_to, &msg, &len);
if (!msg)
return;
}
#if defined(RAWCMDLOGGING)
{
static char copy[16300];
char *p;
strlcpy(copy, msg, len > sizeof(copy) ? sizeof(copy) : len);
stripcrlf(copy);
unreal_log(ULOG_INFO, "rawtraffic", "TRAFFIC_OUT", to,
"-> $client: $data",
log_data_string("data", copy));
}
#endif
if (DBufLength(&to->local->sendQ) > get_sendq(to))
{
unreal_log(ULOG_INFO, "flood", "SENDQ_EXCEEDED", to,
"Flood of queued data to $client.details [$client.ip] exceeds class::sendq ($sendq > $class_sendq) (Too much data queued to be sent to this client)",
log_data_integer("sendq", DBufLength(&to->local->sendQ)),
log_data_integer("class_sendq", get_sendq(to)));
dead_socket(to, "Max SendQ exceeded");
return;
}
dbuf_put(&to->local->sendQ, msg, len);
/*
* Update statistics. The following is slightly incorrect
* because it counts messages even if queued, but bytes
* only really sent. Queued bytes get updated in SendQueued.
*/
// FIXME: something is wrong here, I think we do double counts, either in message or in traffic, I forgot.. CHECK !!!!
to->local->traffic.messages_sent++;
me.local->traffic.messages_sent++;
/* Previously we ran send_queued() here directly, but that is
* a bad idea, CPU-wise. So now we just mark the client indicating
* that there is data to send.
*/
if (IsControl(to))
send_queued(to); /* send this one ASAP */
else
mark_data_to_send(to);
}
/** A single function to send data to a channel.
* Previously there were 6 different functions to send channel data,
* now there is 1 single function. This also means that you most
* likely will pass NULL or 0 as some parameters.
* @param channel The channel to send to
* @param from The source of the message
* @param skip The client to skip (can be NULL).
* Note that if you specify a remote link then
* you usually mean xyz->direction and not xyz.
* @param member_modes Require any of the member_modes to be set (eg: "o"), or NULL to skip this check.
* @param clicap Client capability the recipient should have
* (this only works for local clients, we will
* always send the message to remote clients and
* assume the server there will handle it)
* @param sendflags Determines whether to send the message to local/remote users
* @param mtags The message tags to attach to this message
* @param pattern The pattern (eg: ":%s PRIVMSG %s :%s")
* @param ... The parameters for the pattern.
* @note For all channel messages, it is important to attach the correct
* message tags (mtags) via a new_message() call, as can be seen
* in the example.
* @section sendto_channel_examples Examples
* @subsection sendto_channel_privmsg Send a PRIVMSG to a channel
* This command will send the message "Hello everyone!!!" to the channel when executed.
* @code
* CMD_FUNC(cmd_sayhello)
* {
* MessageTag *mtags = NULL;
* Channel *channel = NULL;
* if ((parc < 2) || BadPtr(parv[1]))
* {
* sendnumeric(client, ERR_NEEDMOREPARAMS, "SAYHELLO");
* return;
* }
* channel = find_channel(parv[1]);
* if (!channel)
* {
* sendnumeric(client, ERR_NOSUCHCHANNEL, parv[1]);
* return;
* }
* new_message(client, recv_mtags, &mtags);
* sendto_channel(channel, client, client->direction, NULL, 0,
* SEND_LOCAL|SEND_REMOTE, mtags,
* ":%s PRIVMSG %s :Hello everyone!!!",
* client->name, channel->name);
* free_message_tags(mtags);
* }
* @endcode
*/
void sendto_channel(Channel *channel, Client *from, Client *skip,
char *member_modes, long clicap, int sendflags,
MessageTag *mtags,
FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
Member *lp;
LocalMember *lm;
Client *acptr;
char member_modes_ext[64];
LineCache *cache;
char check_invisible = 0;
char send_to_all_remote_servers = 0;
long UMODE_CTCP = 0;
if (sendflags & SKIP_CTCP)
UMODE_CTCP = find_user_mode('T');
/* If we need to send to remote servers, then in the past we only sent
* to servers who have at least 1 channel member in their path.
* However, nowadays it is more complex since we have +H history and such:
* broadcast-channel-messages=never: don't send to remote servers that have no users
* broadcast-channel-messages=always: always send to all remote servers
* broadcast-channel-messages=auto: send to all remote servers if channel is +H (history)
* The latter is the default setting.
*/
if ((sendflags & SEND_REMOTE) &&
((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_ALWAYS) ||
((iConf.broadcast_channel_messages == BROADCAST_CHANNEL_MESSAGES_AUTO) && has_channel_mode(channel, 'H'))))
{
/* We need to send to all remote servers. We can simplify this
* case by not interating channel->members since we will send
* to all connected servers at the end of this function anyway.
* And in case SEND_LOCAL is also set, we will go through
* channel->local_members instead which is faster.
*/
sendflags &= ~SEND_REMOTE;
send_to_all_remote_servers = 1;
}
if (member_modes)
{
channel_member_modes_generate_equal_or_greater(member_modes, member_modes_ext, sizeof(member_modes_ext));
member_modes = member_modes_ext;
}
if ((sendflags & CHECK_INVISIBLE) && IsUser(from) && invisible_user_in_channel(from, channel))
check_invisible = 1;
++current_serial;
cache = linecache_init();
if (sendflags & SEND_LOCAL)
{
for (lm = channel->local_members; lm; lm = lm->next)
{
lp = lm->ptr;
acptr = lp->client;
/* Skip sending to 'skip' */
if ((acptr == skip) || (acptr->direction == skip))
continue;
/* Don't send to deaf clients (unless 'senddeaf' is set) */
if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
continue;
/* Don't send to NOCTCP clients (umode +T) */
if ((sendflags & SKIP_CTCP) && (acptr->umodes & UMODE_CTCP))
continue;
/* Sender ('from') is invisible for 'acptr' and we were asked to CHECK_INVISIBLE */
if (check_invisible && !check_channel_access_member(lp, "hoaq") && (from != acptr))
continue;
/* Now deal with 'member_modes' (if not NULL) */
if (member_modes && !check_channel_access_member(lp, member_modes))
continue;
/* Now deal with 'clicap' (if non-zero) */
if (clicap && /*MyUser(acptr) &&*/ ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
continue;
va_start(vl, pattern);
vsendto_prefix_one_cached(cache, 0, acptr, from, mtags, pattern, vl);
va_end(vl);
}
}
if (sendflags & SEND_REMOTE)
{
for (lp = channel->members; lp; lp = lp->next)
{
acptr = lp->client;
if (MyUser(acptr))
continue; /* Already handled xx lines up */
/* Skip sending to 'skip' */
if ((acptr == skip) || (acptr->direction == skip))
continue;
/* Don't send to deaf clients (unless 'senddeaf' is set) */
if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
continue;
/* Don't send to NOCTCP clients (umode +T) */
if ((sendflags & SKIP_CTCP) && (acptr->umodes & UMODE_CTCP))
continue;
/* Sender ('from') is invisible for 'acptr' and we were asked to CHECK_INVISIBLE */
if (check_invisible && !check_channel_access_member(lp, "hoaq") && (from != acptr))
continue;
/* Now deal with 'member_modes' (if not NULL) */
if (member_modes && !check_channel_access_member(lp, member_modes))
continue;
/* Now deal with 'clicap' (if non-zero) */
//if (clicap && MyUser(acptr) && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
// continue;
/* Message already sent to remote link? */
// FIXME: Move this way more up ?
if (acptr->direction->local->serial != current_serial)
{
va_start(vl, pattern);
vsendto_prefix_one_cached(cache, 0, acptr, from, mtags, pattern, vl);
va_end(vl);
acptr->direction->local->serial = current_serial;
}
}
}
if (send_to_all_remote_servers)
{
list_for_each_entry(acptr, &server_list, special_node)
{
if ((acptr == skip) || (acptr->direction == skip))
continue; /* still obey this rule.. */
if (acptr->direction->local->serial != current_serial)
{
va_start(vl, pattern);
vsendto_prefix_one_cached(cache, 0, acptr, from, mtags, pattern, vl);
va_end(vl);
acptr->direction->local->serial = current_serial;
}
}
}
linecache_free(cache);
}
/** Send a message to a server, taking into account server options if needed.
* @param one The client to skip (can be NULL)
* @param servercaps Server capabilities which must be present (OR'd together, if multiple)
* @param noservercaps Server capabilities which must NOT be present (OR'd together, if multiple)
* @param mtags The message tags to attach to this message.
* @param format The format string / pattern, such as ":%s NICK %s".
* @param ... The parameters for the format string
*/
void sendto_server(Client *one, unsigned long servercaps, unsigned long noservercaps, MessageTag *mtags, FORMAT_STRING(const char *format), ...)
{
Client *acptr;
/* noone to send to.. */
if (list_empty(&server_list))
return;
list_for_each_entry(acptr, &server_list, special_node)
{
va_list vl;
if (one && acptr == one->direction)
continue;
if (servercaps && !CHECKSERVERPROTO(acptr, servercaps))
continue;
if (noservercaps && CHECKSERVERPROTO(acptr, noservercaps))
continue;
va_start(vl, format);
vsendto_one(acptr, mtags, format, vl);
va_end(vl);
}
}
/** Send a message to all local users on all channels where
* the user 'user' is on.
* This is used for events such as a nick change and quit.
* @param user The user and source of the message.
* @param skip The client to skip (can be NULL)
* @param clicap Client capability the recipient should have
* (this only works for local clients, we will
* always send the message to remote clients and
* assume the server there will handle it)
* @param mtags The message tags to attach to this message.
* @param pattern The pattern (eg: ":%s NICK %s").
* @param ... The parameters for the pattern.
*/
void sendto_local_common_channels(Client *user, Client *skip, long clicap, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
Membership *channels;
LocalMember *lm;
Client *acptr;
LineCache *cache;
char check_invisible;
if (!user->user)
return;
cache = linecache_init();
++current_serial;
for (channels = user->user->channel; channels; channels = channels->next)
{
check_invisible = invisible_user_in_channel(user, channels->channel); // FIXME: we only have a slow version of this function
for (lm = channels->channel->local_members; lm; lm = lm->next)
{
acptr = lm->ptr->client;
if (acptr->local->serial == current_serial)
continue; /* message already sent to this client */
if (clicap && ((clicap & CAP_INVERT) ? HasCapabilityFast(acptr, clicap) : !HasCapabilityFast(acptr, clicap)))
continue; /* client does not have the specified capability */
if (acptr == skip)
continue; /* the one to skip */
// FIXME: use user_can_see_member_fast()
if (check_invisible && user_can_see_member(acptr, user, channels->channel))
continue; /* the sending user (quit'ing or nick changing) is 'invisible' -- skip */
acptr->local->serial = current_serial;
va_start(vl, pattern);
vsendto_prefix_one_cached(cache, 0, acptr, user, mtags, pattern, vl);
va_end(vl);
}
}
linecache_free(cache);
}
/** Send a QUIT message to all local users on all channels where
* the user 'user' is on.
* This is used for events such as a nick change and quit.
* @param leaving The user who is leaving and the source of the message.
* @param skip The client to skip (can be NULL)
* @param clicap Client capability the recipient should have
* (this only works for local clients, we will
* always send the message to remote clients and
* assume the server there will handle it)
* @param mtags The message tags to attach to this message.
* @param pattern The pattern (eg: ":%s NICK %s").
* @param ... The parameters for the pattern.
*/
void quit_sendto_local_common_channels(Client *leaving, MessageTag *mtags, const char *reason)
{
va_list vl;
Membership *mb;
LocalMember *lm;
Client *acptr;
char sender[512];
MessageTag *m;
const char *real_quit_reason = NULL;
m = find_mtag(mtags, "unrealircd.org/real-quit-reason");
if (m && m->value)
real_quit_reason = m->value;
if (IsUser(leaving))
{
snprintf(sender, sizeof(sender), "%s!%s@%s",
leaving->name, leaving->user->username, GetHost(leaving));
} else {
strlcpy(sender, leaving->name, sizeof(sender));
}
++current_serial;
if (leaving->user)
{
for (mb = leaving->user->channel; mb; mb = mb->next)
{
for (lm = mb->channel->local_members; lm; lm = lm->next)
{
acptr = lm->ptr->client;
if (!MyConnect(acptr))
continue; /* only process local clients */
if (acptr->local->serial == current_serial)
continue; /* message already sent to this client */
if (!user_can_see_member_fast(acptr, leaving, mb->channel, lm->ptr, mb->member_modes))
continue; /* the sending user (QUITing) is 'invisible' -- skip */
acptr->local->serial = current_serial;
if (!reason)
sendto_one(acptr, mtags, ":%s QUIT", sender);
else if (!IsOper(acptr) || !real_quit_reason)
sendto_one(acptr, mtags, ":%s QUIT :%s", sender, reason);
else
sendto_one(acptr, mtags, ":%s QUIT :%s", sender, real_quit_reason);
}
}
}
}
/*
** send a msg to all ppl on servers/hosts that match a specified mask
** (used for enhanced PRIVMSGs)
**
** addition -- Armin, 8jun90 (gruner@informatik.tu-muenchen.de)
*/
static int match_it(Client *one, const char *mask, int what)
{
switch (what)
{
case MATCH_HOST:
return match_simple(mask, one->user->realhost);
case MATCH_SERVER:
default:
return match_simple(mask, one->user->server);
}
}
/** Send to all clients which match the mask.
* This function is rarely used.
* @param one The client to skip
* @param from The sender
* @param mask The mask
* @param what One of MATCH_HOST or MATCH_SERVER
* @param mtags Message tags associated with the message
* @param pattern Format string
* @param ... Parameters to the format string
*/
void sendto_match_butone(Client *one, Client *from, const char *mask, int what,
MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
Client *acptr;
char cansendlocal, cansendglobal;
if (MyConnect(from))
{
cansendlocal = (ValidatePermissionsForPath("chat:notice:local",from,NULL,NULL,NULL)) ? 1 : 0;
cansendglobal = (ValidatePermissionsForPath("chat:notice:global",from,NULL,NULL,NULL)) ? 1 : 0;
}
else
cansendlocal = cansendglobal = 1;
/* To servers... */
if (cansendglobal)
{
char buf[512];
va_start(vl, pattern);
ircvsnprintf(buf, sizeof(buf), pattern, vl);
va_end(vl);
sendto_server(one, 0, 0, mtags, "%s", buf);
}
/* To local clients... */
if (cansendlocal)
{
list_for_each_entry(acptr, &lclient_list, lclient_node)
{
if (!IsMe(acptr) && (acptr != one) && IsUser(acptr) && match_it(acptr, mask, what))
{
va_start(vl, pattern);
vsendto_prefix_one(acptr, from, mtags, pattern, vl);
va_end(vl);
}
}
}
}
/** Send a message to all locally connected users with specified user mode.
* @param umodes The umode that the recipient should have set (one of UMODE_)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void sendto_umode(int umodes, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
Client *acptr;
char nbuf[1024];
list_for_each_entry(acptr, &lclient_list, lclient_node)
if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
{
ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :", me.name, acptr->name);
strlcat(nbuf, pattern, sizeof nbuf);
va_start(vl, pattern);
vsendto_one(acptr, NULL, nbuf, vl);
va_end(vl);
}
}
/** Send a message to all users with specified user mode (local & remote users).
* @param umodes The umode that the recipient should have set (one of UMODE_*)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void sendto_umode_global(int umodes, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
Client *acptr;
Umode *um;
char nbuf[1024];
char modestr[128];
char *p;
/* Convert 'umodes' (int) to 'modestr' (string) */
get_usermode_string_raw_r(umodes, modestr, sizeof(modestr));
list_for_each_entry(acptr, &lclient_list, lclient_node)
{
if (IsUser(acptr) && (acptr->umodes & umodes) == umodes)
{
ircsnprintf(nbuf, sizeof(nbuf), ":%s NOTICE %s :", me.name, acptr->name);
strlcat(nbuf, pattern, sizeof nbuf);
va_start(vl, pattern);
vsendto_one(acptr, NULL, nbuf, vl);
va_end(vl);
} else
if (IsServer(acptr) && *modestr)
{
snprintf(nbuf, sizeof(nbuf), ":%s SENDUMODE %s :%s", me.id, modestr, pattern);
va_start(vl, pattern);
vsendto_one(acptr, NULL, nbuf, vl);
va_end(vl);
}
}
}
/** Send CAP DEL and CAP NEW notification to clients supporting it.
* This function is mostly meant to be used by the CAP and SASL modules.
* @param add Whether the CAP token is added (1) or removed (0)
* @param token The CAP token
*/
void send_cap_notify(int add, const char *token)
{
Client *client;
ClientCapability *clicap = ClientCapabilityFindReal(token);
long CAP_NOTIFY = ClientCapabilityBit("cap-notify");
list_for_each_entry(client, &lclient_list, lclient_node)
{
if (HasCapabilityFast(client, CAP_NOTIFY))
{
if (add)
{
const char *args = NULL;
if (clicap)
{
if (clicap->visible && !clicap->visible(client))
continue; /* invisible CAP, so don't announce it */
if (clicap->parameter && (client->local->cap_protocol >= 302))
args = clicap->parameter(client);
}
if (!args)
{
sendto_one(client, NULL, ":%s CAP %s NEW :%s",
me.name, (*client->name ? client->name : "*"), token);
} else {
sendto_one(client, NULL, ":%s CAP %s NEW :%s=%s",
me.name, (*client->name ? client->name : "*"), token, args);
}
} else {
sendto_one(client, NULL, ":%s CAP %s DEL :%s",
me.name, (*client->name ? client->name : "*"), token);
}
}
}
}
/* Prepare buffer based on format string and 'from' for LOCAL delivery.
* The prefix (:<something>) will be expanded to :nick!user@host if 'from'
* is a person, taking into account the rules for hidden/cloaked host.
* NOTE: Do not send this prepared buffer to remote clients or servers,
* they do not want or need the expanded prefix. In that case, simply
* use ircvsnprintf() directly.
*/
static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl)
{
int len;
/* This expands the ":%s " part of the pattern
* into ":nick!user@host ".
* In case of a non-person (server) it doesn't do
* anything since no expansion is needed.
*/
if (from && from->user && !strncmp(pattern, ":%s ", 4))
{
va_arg(vl, char *); /* eat first parameter */
*buf = ':';
strlcpy(buf+1, from->name, buflen-1);
if (IsUser(from))
{
char *username = from->user->username;
char *host = GetHost(from);
if (*username)
{
strlcat(buf, "!", buflen);
strlcat(buf, username, buflen);
}
if (*host)
{
strlcat(buf, "@", buflen);
strlcat(buf, host, buflen);
}
}
/* Now build the remaining string */
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
ircvsnprintf(buf + strlen(buf), buflen - strlen(buf), &pattern[3], vl);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}
else
{
ircvsnprintf(buf, buflen, pattern, vl);
}
len = strlen(buf);
if (len > 510)
{
len = 510;
if (UTF8ONLY)
utf8_valid_cutoff(buf, &len);
}
buf[len++] = '\r';
buf[len++] = '\n';
buf[len] = '\0';
return len;
}
/** Send a message to a client, expand the sender prefix.
* This is similar to sendto_one() except that it will expand the source part :%s
* to :nick!user@host if needed, while with sendto_one() it will be :nick.
* @param to The client to send to
* @param mtags Any message tags associated with this message (can be NULL)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void sendto_prefix_one(Client *to, Client *from, MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
va_start(vl, pattern);
vsendto_prefix_one(to, from, mtags, pattern, vl);
va_end(vl);
}
/** Send a message to a single client, expand the sender prefix - va_list variant.
* This is similar to vsendto_one() except that it will expand the source part :%s
* to :nick!user@host if needed, while with sendto_one() it will be :nick.
* This function is also similar to sendto_prefix_one(), but this is the va_list
* variant.
* @param to The client to send to
* @param mtags Any message tags associated with this message (can be NULL)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
{
const char *mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
if (to && from && MyUser(to) && from->user)
vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl);
else
ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
if (BadPtr(mtags_str))
{
/* Simple message without message tags */
sendbufto_one(to, sendbuf, 0);
} else {
/* Message tags need to be prepended */
snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
sendbufto_one(to, sendbuf2, 0);
}
}
static LineCache *linecache_init(void)
{
LineCache *e = safe_alloc(sizeof(LineCache));
return e;
}
static void linecache_free(LineCache *cache)
{
LineCacheLine *e, *e_next;
for (e = cache->items; e; e = e_next)
{
e_next = e->next;
safe_free(e->line);
safe_free(e);
}
safe_free(cache);
}
static LineCacheUserType linecache_usertype(Client *to)
{
if (!MyConnect(to) || IsServer(to))
return LCUT_REMOTE;
if (IsOper(to))
return LCUT_OPER;
return LCUT_NORMAL;
}
static unsigned long linecache_caps(Client *to)
{
if (!MyConnect(to))
{
/* Depending on PROTOCTL MTAGS of the directly linked uplink
* we either send all message tags or no message tags
*/
if (SupportMTAGS(to->direction))
return -1; /* 0xffffff... (iotw: all) */
else
return 0; /* none */
} else {
return to->local->caps & clicaps_affecting_mtag;
}
}
static void linecache_add(LineCache *cache, int line_opts, Client *to, const char *line, int linelen)
{
LineCacheLine *e = safe_alloc(sizeof(LineCacheLine));
e->user_type = linecache_usertype(to);
e->caps = linecache_caps(to);
safe_strdup(e->line, line);
e->linelen = linelen ? linelen : strlen(line);
AddListItem(e, cache->items);
}
static LineCacheLine *linecache_get(LineCache *cache, int line_opts, Client *to)
{
LineCacheLine *l;
int user_type = linecache_usertype(to);
int caps = linecache_caps(to);
for (l = cache->items; l; l = l->next)
if ((l->caps == caps) && (l->user_type == user_type) && (l->line_opts == line_opts))
return l;
return NULL;
}
/** Cached version of "send a message to a single client", expand the sender prefix - va_list variant.
* This is the cached version of vsendto_prefix_one()
* @param cache The LineCache to use
* @param line_opts LineCache options for this particular message/line (usually 0)
* @param to The client to send to
* @param mtags Any message tags associated with this message (can be NULL)
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
static void vsendto_prefix_one_cached(LineCache *cache, int line_opts, Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl)
{
const char *mtags_str;
LineCacheLine *l;
int len;
if ((l = linecache_get(cache, line_opts, to)))
{
sendbufto_one(to, l->line, l->linelen);
return;
}
mtags_str = mtags ? mtags_to_string(mtags, to) : NULL;
if (to && from && MyUser(to) && from->user)
vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf)-3, from, pattern, vl);
else
ircvsnprintf(sendbuf, sizeof(sendbuf)-3, pattern, vl);
if (BadPtr(mtags_str))
{
/* Simple message without message tags */
char *out = sendbuf;
len = sendbufto_one_prepare_line(to, &out);
linecache_add(cache, line_opts, to, out, len);
sendbufto_one(to, out, len);
} else {
/* Message tags need to be prepended */
char *out = sendbuf2;
snprintf(sendbuf2, sizeof(sendbuf2)-3, "@%s %s", mtags_str, sendbuf);
len = sendbufto_one_prepare_line(to, &out);
linecache_add(cache, line_opts, to, out, len);
sendbufto_one(to, out, 0);
}
}
/** Introduce user to all other servers, except the one to skip.
* @param one Server to skip (can be NULL)
* @param client Client to introduce
* @param umodes User modes of client
*/
void sendto_serv_butone_nickcmd(Client *one, MessageTag *mtags, Client *client, const char *umodes)
{
Client *acptr;
list_for_each_entry(acptr, &server_list, special_node)
{
if (one && acptr == one->direction)
continue;
sendto_one_nickcmd(acptr, mtags, client, umodes);
}
}
/** Introduce user to a server.
* @param server Server to send to (locally connected!)
* @param client Client to introduce
* @param umodes User modes of client
*/
void sendto_one_nickcmd(Client *server, MessageTag *mtags, Client *client, const char *umodes)
{
char *vhost;
char mtags_generated = 0;
if (!*umodes)
umodes = "+";
if (SupportVHP(server))
{
if (IsHidden(client))
vhost = client->user->virthost;
else
vhost = client->user->realhost;
}
else
{
if (IsHidden(client) && client->umodes & UMODE_SETHOST)
vhost = client->user->virthost;
else
vhost = "*";
}
if (mtags == NULL)
{
moddata_add_s2s_mtags(client, &mtags);
mtags_generated = 1;
}
sendto_one(server, mtags,
":%s UID %s %d %lld %s %s %s %s %s %s %s %s :%s",
client->uplink->id, client->name, client->hopcount,
(long long)client->lastnick,
client->user->username, client->user->realhost, client->id,
client->user->account, umodes, vhost, getcloak(client),
encode_ip(client->ip), client->info);
if (mtags_generated)
safe_free_message_tags(mtags);
}
/* sidenote: sendnotice() and sendtxtnumeric() assume no client or server
* has a % in their nick, which is a safe assumption since % is illegal.
*/
/** Send a server notice to a client.
* @param to The client to send to
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void sendnotice(Client *to, FORMAT_STRING(const char *pattern), ...)
{
static char realpattern[1024];
va_list vl;
char *name = *to->name ? to->name : "*";
ircsnprintf(realpattern, sizeof(realpattern), ":%s NOTICE %s :%s", me.name, name, pattern);
va_start(vl, pattern);
vsendto_one(to, NULL, realpattern, vl);
va_end(vl);
}
/** Send MultiLine list as a notice, one for each line.
* @param client The client to send to
* @param m The MultiLine list.
*/
void sendnotice_multiline(Client *client, MultiLine *m)
{
for (; m; m = m->next)
sendnotice(client, "%s", m->line);
}
/** Send numeric message to a client - format to user specific needs.
* This will ignore the numeric definition of src/numeric.c and always send ":me.name numeric clientname "
* followed by the pattern and format string you choose.
* @param to The recipient
* @param mtags NULL, or NULL-terminated array of message tags
* @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
* @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
*/
void sendtaggednumericfmt(Client *to, MessageTag *mtags, int numeric, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
char realpattern[512];
snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
va_start(vl, pattern);
vsendto_one(to, mtags, realpattern, vl);
va_end(vl);
}
/** Send text numeric message to a client (RPL_TEXT).
* Because this generic output numeric is commonly used it got a special function for it.
* @param to The recipient
* @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
* @note Don't forget to add a colon if you need it (eg `:%%s`), this is a common mistake.
*/
void sendtxtnumeric(Client *to, FORMAT_STRING(const char *pattern), ...)
{
static char realpattern[1024];
va_list vl;
ircsnprintf(realpattern, sizeof(realpattern), ":%s %d %s :%s", me.name, RPL_TEXT, to->name, pattern);
va_start(vl, pattern);
vsendto_one(to, NULL, realpattern, vl);
va_end(vl);
}
/** Build buffer in order to send a numeric message to a client - rarely used.
* @param buf The buffer that should be used
* @param buflen The size of the buffer
* @param to The recipient
* @param numeric The numeric, one of RPL_* or ERR_*, see src/numeric.c
* @param pattern The format string / pattern to use.
* @param ... Format string parameters.
*/
void buildnumericfmt(char *buf, size_t buflen, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
char realpattern[512];
snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
va_start(vl, pattern);
/* Need to ignore -Wformat-nonliteral here */
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
vsnprintf(buf, buflen, realpattern, vl);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
va_end(vl);
}
void add_nvplist_numeric_fmt(NameValuePrioList **lst, int priority, const char *name, Client *to, int numeric, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
char realpattern[512], buf[512];
snprintf(realpattern, sizeof(realpattern), ":%s %.3d %s %s", me.name, numeric, to->name[0] ? to->name : "*", pattern);
va_start(vl, pattern);
/* Need to ignore -Wformat-nonliteral here */
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
vsnprintf(buf, sizeof(buf), realpattern, vl);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
va_end(vl);
add_nvplist(lst, priority, name, buf);
}
/* Send raw data directly to socket, bypassing everything.
* Looks like an interesting function to call? NO! STOP!
* Don't use this function. It may only be used by the initial
* Z-Line check via the codepath to banned_client().
* YOU SHOULD NEVER USE THIS FUNCTION.
* If you want to send raw data (without formatting) to a client
* then have a look at sendbufto_one() instead.
*
* Side-effects:
* Too many to list here. Only in the early accept code the
* "if's" and side-effects are under control.
*
* By the way, did I already mention that you SHOULD NOT USE THIS
* FUNCTION? ;)
*/
void send_raw_direct(Client *user, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
int sendlen;
*sendbuf = '\0';
va_start(vl, pattern);
sendlen = vmakebuf_local_withprefix(sendbuf, sizeof(sendbuf), user, pattern, vl);
va_end(vl);
(void)send(user->local->fd, sendbuf, sendlen, 0);
}
/** @} */