/*
 *   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"


/* 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 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);
	}
}

LineCache *linecache_init(void)
{
	LineCache *e = safe_alloc(sizeof(LineCache));
	return e;
}

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);
	}
}

/** Cached version of sendto_prefix_one() - varargs wrapper.
 * @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 from		The sender
 * @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_cached(LineCache *cache, int line_opts, Client *to, Client *from,
                              MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
{
	va_list vl;
	va_start(vl, pattern);
	vsendto_prefix_one_cached(cache, line_opts, to, from, mtags, pattern, vl);
	va_end(vl);
}

/** 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);
}

/** @} */
