/*
 *   IRC - Internet Relay Chat, common/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.
 */

#ifndef lint
static  char rcsid[] = "@(#)$Id: send.c,v 1.34 1999/06/07 21:01:57 kalt Exp $";
#endif

#include "os.h"
#ifndef CLIENT_COMPILE
# include "s_defines.h"
#else
# include "c_defines.h"
#endif
#define SEND_C
#ifndef CLIENT_COMPILE
# include "s_externs.h"
#else
# include "c_externs.h"
#endif
#undef SEND_C

static	char	sendbuf[2048];
static	int	send_message __P((aClient *, char *, int));

#if USE_STDARG
static void	vsendto_prefix_one(aClient *, aClient *, char *, va_list);
#endif


#ifndef CLIENT_COMPILE
static	char	psendbuf[2048];
static	int	sentalong[MAXCONNECTIONS];
#endif

/*
** dead_link
**	An error has been detected. The link *must* be closed,
**	but *cannot* call ExitClient (m_bye) from here.
**	Instead, mark it with FLAGS_DEADSOCKET. This should
**	generate ExitClient from the main loop.
**
**	If 'notice' is not NULL, it is assumed to be a format
**	for a message to local opers. It can contain only one
**	'%s', which will be replaced by the sockhost field of
**	the failing link.
**
**	Also, the notice is skipped for "uninteresting" cases,
**	like Persons and yet unknown connections...
*/
static	int	dead_link(to, notice)
aClient *to;
char	*notice;
{
	if (IsHeld(to))
		return -1;
	to->flags |= FLAGS_DEADSOCKET;
	/*
	 * If because of BUFFERPOOL problem then clean dbufs now so that
	 * notices don't hurt operators below.
	 */
	DBufClear(&to->recvQ);
	DBufClear(&to->sendQ);
#ifndef CLIENT_COMPILE
	if (!IsPerson(to) && !IsUnknown(to) && !(to->flags & FLAGS_CLOSING))
		sendto_flag(SCH_ERROR, notice, get_client_name(to, FALSE));
	Debug((DEBUG_ERROR, notice, get_client_name(to, FALSE)));
#endif
	return -1;
}

#ifndef CLIENT_COMPILE
/*
** flush_fdary
**      Used to empty all output buffers for connections in fdary.
*/
void    flush_fdary(fdp)
FdAry   *fdp;
{
        int     i;
        aClient *cptr;

        for (i = 0; i <= fdp->highest; i++)
            {
                if (!(cptr = local[fdp->fd[i]]))
                        continue;
                if (!IsRegistered(cptr)) /* is this needed?? -kalt */
                        continue;
                if (DBufLength(&cptr->sendQ) > 0)
                        (void)send_queued(cptr);
            }
}

/*
** flush_connections
**	Used to empty all output buffers for all connections. Should only
**	be called once per scan of connections. There should be a select in
**	here perhaps but that means either forcing a timeout or doing a poll.
**	When flushing, all we do is empty the obuffer array for each local
**	client and try to send it. if we can't send it, it goes into the sendQ
**	-avalon
*/
void	flush_connections(fd)
int	fd;
{
	Reg	int	i;
	Reg	aClient *cptr;

	if (fd == me.fd)
	    {
		for (i = highest_fd; i >= 0; i--)
			if ((cptr = local[i]) && DBufLength(&cptr->sendQ) > 0)
				(void)send_queued(cptr);
	    }
	else if (fd >= 0 && (cptr = local[fd]) && DBufLength(&cptr->sendQ) > 0)
		(void)send_queued(cptr);
}
#endif

/*
** send_message
**	Internal utility which delivers one message buffer to the
**	socket. Takes care of the error handling and buffering, if
**	needed.
**	if ZIP_LINKS is defined, the message will eventually be compressed,
**	anything stored in the sendQ is compressed.
*/
static	int	send_message(to, msg, len)
aClient	*to;
char	*msg;	/* if msg is a null pointer, we are flushing connection */
int	len;
#if !defined(CLIENT_COMPILE)
{
	int i;

	Debug((DEBUG_SEND,"Sending %s %d [%s] ", to->name, to->fd, msg));

	if (to->from)
		to = to->from;
	if (to->fd < 0)
	    {
		Debug((DEBUG_ERROR,
		       "Local socket %s with negative fd... AARGH!",
		      to->name));
	    }
	if (IsMe(to))
	    {
		sendto_flag(SCH_ERROR, "Trying to send to myself! [%s]", msg);
		return 0;
	    }
	if (IsDead(to))
		return 0; /* This socket has already been marked as dead */
	if (DBufLength(&to->sendQ) > get_sendq(to))
	    {
# ifdef HUB
		if (CBurst(to))
		    {
			aConfItem	*aconf = to->serv->nline;

			poolsize -= MaxSendq(aconf->class) >> 1;
			IncSendq(aconf->class);
			poolsize += MaxSendq(aconf->class) >> 1;
			sendto_flag(SCH_NOTICE,
				    "New poolsize %d. (sendq adjusted)",
				    poolsize);
			istat.is_dbufmore++;
		    }
		else if (IsServer(to) || IsService(to))
			sendto_flag(SCH_ERROR,
				"Max SendQ limit exceeded for %s: %d > %d",
			   	get_client_name(to, FALSE),
				DBufLength(&to->sendQ), get_sendq(to));
		if (!CBurst(to))
		    {
			to->exitc = EXITC_SENDQ;
			return dead_link(to, "Max Sendq exceeded");
		    }
# else /* HUB */
		if (IsService(to) || IsServer(to))
			sendto_flag(SCH_ERROR,
				"Max SendQ limit exceeded for %s: %d > %d",
			   	get_client_name(to, FALSE),
				DBufLength(&to->sendQ), get_sendq(to));
		to->exitc = EXITC_SENDQ;
		return dead_link(to, "Max Sendq exceeded");
# endif /* HUB */
	    }
	else
	    {
tryagain:
# ifdef	ZIP_LINKS
	        /*
		** data is first stored in to->zip->outbuf until
		** it's big enough to be compressed and stored in the sendq.
		** send_queued is then responsible to never let the sendQ
		** be empty and to->zip->outbuf not empty.
		*/
		if (to->flags & FLAGS_ZIP)
			msg = zip_buffer(to, msg, &len, 0);

		if (len && (i = dbuf_put(&to->sendQ, msg, len)) < 0)
# else 	/* ZIP_LINKS */
		if ((i = dbuf_put(&to->sendQ, msg, len)) < 0)
# endif	/* ZIP_LINKS */
			if (i == -2 && CBurst(to))
			    {	/* poolsize was exceeded while connect burst */
				aConfItem	*aconf = to->serv->nline;

				poolsize -= MaxSendq(aconf->class) >> 1;
				IncSendq(aconf->class);
				poolsize += MaxSendq(aconf->class) >> 1;
				sendto_flag(SCH_NOTICE,
					    "New poolsize %d. (reached)",
					    poolsize);
				istat.is_dbufmore++;
				goto tryagain;
			    }
			else
			    {
				to->exitc = EXITC_MBUF;
				return dead_link(to,
					"Buffer allocation error for %s");
			    }
	    }
	/*
	** 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.
	*/
	to->sendM += 1;
	me.sendM += 1;
	if (to->acpt != &me)
		to->acpt->sendM += 1;
	/*
	** This little bit is to stop the sendQ from growing too large when
	** there is no need for it to. Thus we call send_queued() every time
	** 2k has been added to the queue since the last non-fatal write.
	** Also stops us from deliberately building a large sendQ and then
	** trying to flood that link with data (possible during the net
	** relinking done by servers with a large load).
	*/
	if (DBufLength(&to->sendQ)/1024 > to->lastsq)
		send_queued(to);
	return 0;
}
#else /* CLIENT_COMPILE */
{
	int	rlen = 0, i;

	Debug((DEBUG_SEND,"Sending %s %d [%s] ", to->name, to->fd, msg));

	if (to->from)
		to = to->from;
	if (to->fd < 0)
	    {
		Debug((DEBUG_ERROR,
		      "Local socket %s with negative fd... AARGH!",
		      to->name));
	    }
	if (IsDead(to))
		return 0; /* This socket has already been marked as dead */

	if ((rlen = deliver_it(to, msg, len)) < 0 && rlen < len)
		return dead_link(to,"Write error to %s, closing link");
	/*
	** 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.
	*/
	to->sendM += 1;
	me.sendM += 1;
	if (to->acpt != &me)
		to->acpt->sendM += 1;
	return 0;
}
#endif

/*
** send_queued
**	This function is called from the main select-loop (or whatever)
**	when there is a chance the some output would be possible. This
**	attempts to empty the send queue as far as possible...
*/
int	send_queued(to)
aClient *to;
{
	char	*msg;
	int	len, rlen, more = 0;

	/*
	** Once socket is marked dead, we cannot start writing to it,
	** even if the error is removed...
	*/
	if (IsDead(to))
	    {
		/*
		** Actually, we should *NEVER* get here--something is
		** not working correct if send_queued is called for a
		** dead socket... --msa
		*/
		return -1;
	    }
#ifdef	ZIP_LINKS
	/*
	** Here, we must make sure than nothing will be left in to->zip->outbuf
	** This buffer needs to be compressed and sent if all the sendQ is sent
	*/
	if ((to->flags & FLAGS_ZIP) && to->zip->outcount)
	    {
		if (DBufLength(&to->sendQ) > 0)
			more = 1;
		else
		    {
			msg = zip_buffer(to, NULL, &len, 1);
			
			if (len == -1)
			       return dead_link(to,
						"fatal error in zip_buffer()");

			if (dbuf_put(&to->sendQ, msg, len) < 0)
			    {
				to->exitc = EXITC_MBUF;
				return dead_link(to,
					 "Buffer allocation error for %s");
			    }
		    }
	    }
#endif
	while (DBufLength(&to->sendQ) > 0 || more)
	    {
		msg = dbuf_map(&to->sendQ, &len);
					/* Returns always len > 0 */
		if ((rlen = deliver_it(to, msg, len)) < 0)
			return dead_link(to,"Write error to %s, closing link");
		(void)dbuf_delete(&to->sendQ, rlen);
		to->lastsq = DBufLength(&to->sendQ)/1024;
		if (rlen < len) /* ..or should I continue until rlen==0? */
			break;

#ifdef	ZIP_LINKS
		if (DBufLength(&to->sendQ) == 0 && more)
		    {
			/*
			** The sendQ is now empty, compress what's left
			** uncompressed and try to send it too
			*/
			more = 0;
			msg = zip_buffer(to, NULL, &len, 1);

			if (len == -1)
			       return dead_link(to,
						"fatal error in zip_buffer()");

			if (dbuf_put(&to->sendQ, msg, len) < 0)
			    {
				to->exitc = EXITC_MBUF;
				return dead_link(to,
					 "Buffer allocation error for %s");
			    }
		    }
#endif
	    }

	return (IsDead(to)) ? -1 : 0;
}


#ifndef CLIENT_COMPILE
static	anUser	ausr = { NULL, NULL, NULL, NULL, 0, 0, 0, 0, NULL,
# ifndef NO_USRTOP			 
			 NULL, NULL,
# endif
			 NULL, "anonymous", "anonymous.", "anonymous."};

static	aClient	anon = { NULL, NULL, NULL, &ausr, NULL, NULL, 0, 0,/*flags*/
			 &anon, -2, 0, STAT_CLIENT, "anonymous", "anonymous",
			 "anonymous identity hider", 0, "",
# ifdef	ZIP_LINKS
			 NULL,
# endif
			 0, {0, 0, NULL }, {0, 0, NULL },
			 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, 0, NULL, 0
# if defined(__STDC__)	/* hack around union{} initialization	-Vesa */
			 ,{0}, NULL, "", ""
# endif
			};
#endif

/*
 *
 */
#if ! USE_STDARG
static	int	sendprep(pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
static	int	vsendprep(char *pattern, va_list va)
#endif
{
	int	len;

	Debug((DEBUG_L10, "sendprep(%s)", pattern));
#if ! USE_STDARG
	len = irc_sprintf(sendbuf, pattern, p1, p2, p3, p4, p5, p6,
		p7, p8, p9, p10, p11);
	if (len == -1)
		len = strlen(sendbuf);
#else
	len = vsprintf(sendbuf, pattern, va);
#endif
	if (len > 510)
#ifdef	IRCII_KLUDGE
		len = 511;
#else
		len = 510;
	sendbuf[len++] = '\r';
#endif
	sendbuf[len++] = '\n';
	sendbuf[len] = '\0';
	return len;
}

#ifndef CLIENT_COMPILE
#if ! USE_STDARG
static	int	sendpreprep(to, from, pattern,
			    p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
aClient	*to, *from;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
{
	static	char	sender[HOSTLEN+NICKLEN+USERLEN+5];
	Reg	anUser	*user;
	char	*par;
	int	flag = 0, len;

	Debug((DEBUG_L10, "sendpreprep(%#x(%s),%#x(%s),%s)",
		to, to->name, from, from->name, pattern));
	par = p1;
	if (to && from && MyClient(to) && IsPerson(from) &&
	    !mycmp(par, from->name))
	    {
		user = from->user;
		(void)strcpy(sender, from->name);
		if (user)
		    {
			if (*user->username)
			    {
				(void)strcat(sender, "!");
				(void)strcat(sender, user->username);
			    }
			if (*user->host && !MyConnect(from))
			    {
				(void)strcat(sender, "@");
				(void)strcat(sender, user->host);
				flag = 1;
			    }
		    }
		/*
		** flag is used instead of index(sender, '@') for speed and
		** also since username/nick may have had a '@' in them. -avalon
		*/
		if (!flag && MyConnect(from) && *user->host)
		    {
			(void)strcat(sender, "@");
			if (IsUnixSocket(from))
				(void)strcat(sender, user->host);
			else
				(void)strcat(sender, from->sockhost);
		    }
		par = sender;
	    }
	len = irc_sprintf(psendbuf, pattern, par, p2, p3, p4, p5, p6,
		p7, p8, p9, p10, p11);
	if (len == -1)
		len = strlen(psendbuf);
	if (len > 510)
#ifdef	IRCII_KLUDGE
		len = 511;
#else
		len = 510;
	psendbuf[len++] = '\r';
#endif
	psendbuf[len++] = '\n';
	psendbuf[len] = '\0';
	return len;
}

#else /* USE_STDARG */

static	int	vsendpreprep(aClient *to, aClient *from, char *pattern, va_list va)
{
	Reg	anUser	*user;
	int	flag = 0, len;

	Debug((DEBUG_L10, "sendpreprep(%#x(%s),%#x(%s),%s)",
		to, to->name, from, from->name, pattern));
	if (to && from && MyClient(to) && IsPerson(from) &&
	    !strncmp(pattern, ":%s", 3))
	    {
		char	*par = va_arg(va, char *);
		if (from == &anon || !mycmp(par, from->name))
		    {
			user = from->user;
			(void)strcpy(psendbuf, ":");
			(void)strcat(psendbuf, from->name);
			if (user)
			    {
				if (*user->username)
				    {
					(void)strcat(psendbuf, "!");
					(void)strcat(psendbuf, user->username);
				    }
				if (*user->host && !MyConnect(from))
				    {
					(void)strcat(psendbuf, "@");
					(void)strcat(psendbuf, user->host);
					flag = 1;
				    }
			    }
			/*
			** flag is used instead of index(newpat, '@') for speed and
			** also since username/nick may have had a '@' in them. -avalon
			*/
			if (!flag && MyConnect(from) && *user->host)
			    {
				(void)strcat(psendbuf, "@");
				if (IsUnixSocket(from))
				    (void)strcat(psendbuf, user->host);
				else
				    (void)strcat(psendbuf, from->sockhost);
			    }
		    }
		else
		    {
			(void)strcpy(psendbuf, ":");
			(void)strcat(psendbuf, par);
		    }

		len = strlen(psendbuf);
		len += vsprintf(psendbuf+len, pattern+3, va);
	    }
	else
		len = vsprintf(psendbuf, pattern, va);

	if (len > 510)
#ifdef	IRCII_KLUDGE
		len = 511;
#else
		len = 510;
	psendbuf[len++] = '\r';
#endif
	psendbuf[len++] = '\n';
	psendbuf[len] = '\0';
	return len;
}
#endif /* USE_STDARG */
#endif /* CLIENT_COMPILE */

/*
** send message to single client
*/
#if ! USE_STDARG
int	sendto_one(to, pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
aClient *to;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
int	vsendto_one(aClient *to, char *pattern, va_list va)
#endif
{
	int	len;

#if ! USE_STDARG
	len = sendprep(pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11);
#else
	len = vsendprep(pattern, va);
#endif

	(void)send_message(to, sendbuf, len);
	return len;
}

#if USE_STDARG
int	sendto_one(aClient *to, char *pattern, ...)
{
	int	len;
	va_list	va;
	va_start(va, pattern);
	len = vsendto_one(to, pattern, va);
	va_end(va);
	return len;
}
#endif

#ifndef CLIENT_COMPILE
#if ! USE_STDARG
/*VARARGS*/
void	sendto_channel_butone(one, from, chptr, pattern,
			      p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
aClient *one, *from;
aChannel *chptr;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_channel_butone(aClient *one, aClient *from, aChannel *chptr, char *pattern, ...)
#endif
{
	Reg	Link	*lp;
	Reg	aClient *acptr, *lfrm = from;
	int	len1, len2 = 0;

	if (IsAnonymous(chptr) && IsClient(from))
	    {
#if ! USE_STDARG
		if (p1 && *p1 && !mycmp(p1, from->name))
			p1 = anon.name;
#endif
		lfrm = &anon;
	    }

	if (one != from && MyConnect(from) && IsRegisteredUser(from))
	    {
		/* useless junk? */
#if ! USE_STDARG
		sendto_prefix_one(from, from, pattern, p1, p2, p3, p4,
				  p5, p6, p7, p8, p9, p10, p11);
#else
		va_list	va;
		va_start(va, pattern);
		vsendto_prefix_one(from, from, pattern, va);
		va_end(va);
#endif
	    }

#if ! USE_STDARG
	len1 = sendprep(pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11);
#else
	{
		va_list	va;
		va_start(va, pattern);
		len1 = vsendprep(pattern, va);
		va_end(va);
	}
#endif


	for (lp = chptr->clist; lp; lp = lp->next)
	    {
		acptr = lp->value.cptr;
		if (acptr->from == one || IsMe(acptr))
			continue;	/* ...was the one I should skip */
		if (MyConnect(acptr) && IsRegisteredUser(acptr))
		    {
			if (!len2)
			    {
#if ! USE_STDARG
				len2 = sendpreprep(acptr, lfrm, pattern, p1,
						   p2, p3, p4, p5, p6, p7, p8,
						   p9, p10, p11);
#else
				va_list	va;
				va_start(va, pattern);
				len2 = vsendpreprep(acptr, lfrm, pattern, va);
				va_end(va);
#endif
			    }

			if (acptr != from)
				(void)send_message(acptr, psendbuf, len2);
		    }
		else
			(void)send_message(acptr, sendbuf, len1);
	    }
	return;
}

/*
 * sendto_server_butone
 *
 * Send a message to all connected servers except the client 'one'.
 */
#if ! USE_STDARG
/*VARARGS*/
void	sendto_serv_butone(one, pattern, p1, p2, p3, p4,p5,p6,p7,p8,p9,p10,p11)
aClient *one;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_serv_butone(aClient *one, char *pattern, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient *cptr;

	for (i = fdas.highest; i >= 0; i--)
		if ((cptr = local[fdas.fd[i]]) &&
		    (!one || cptr != one->from) && !IsMe(cptr)) {
			if (!len)
			    {
#if ! USE_STDARG
				len = sendprep(pattern, p1, p2, p3, p4, p5,
					       p6, p7, p8, p9, p10, p11);
#else
				va_list	va;
				va_start(va, pattern);
				len = vsendprep(pattern, va);
				va_end(va);
#endif
			    }
			(void)send_message(cptr, sendbuf, len);
	}
	return;
}

#if ! USE_STDARG
void	sendto_serv_v(one, ver, pattern, p1, p2, p3, p4,p5,p6,p7,p8,p9,p10,p11)
aClient *one;
int	ver;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_serv_v(aClient *one, int ver, char *pattern, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient *cptr;

	for (i = fdas.highest; i >= 0; i--)
		if ((cptr = local[fdas.fd[i]]) &&
		    (!one || cptr != one->from) && !IsMe(cptr) &&
		    (cptr->serv->version & ver)) {
			if (!len)
			    {
#if ! USE_STDARG
				len = sendprep(pattern, p1, p2, p3, p4, p5,
					       p6, p7, p8, p9, p10, p11);
#else
				va_list	va;
				va_start(va, pattern);
				len = vsendprep(pattern, va);
				va_end(va);
#endif
			    }
			(void)send_message(cptr, sendbuf, len);
	}
	return;
}

#if ! USE_STDARG
void	sendto_serv_notv(one, ver, pattern, p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11)
aClient *one;
int	ver;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_serv_notv(aClient *one, int ver, char *pattern, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient *cptr;

	for (i = fdas.highest; i >= 0; i--)
		if ((cptr = local[fdas.fd[i]]) &&
		    (!one || cptr != one->from) && !IsMe(cptr) &&
		    ((cptr->serv->version & ver) == 0)) {
			if (!len)
			    {
#if ! USE_STDARG
				len = sendprep(pattern, p1, p2, p3, p4, p5,
					       p6, p7, p8, p9, p10, p11);
#else
				va_list	va;
				va_start(va, pattern);
				len = vsendprep(pattern, va);
				va_end(va);
#endif
			    }
			(void)send_message(cptr, sendbuf, len);
	}
	return;
}

/*
 * sendto_common_channels()
 *
 * Sends a message to all people (inclusing user) on local server who are
 * in same channel with user.
 */
#if ! USE_STDARG
/*VARARGS*/
void	sendto_common_channels(user,pattern,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11)
aClient *user;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_common_channels(aClient *user, char *pattern, ...)
#endif
{
	Reg	int	i;
	Reg	aClient *cptr;
	Reg	Link	*channels, *lp;
	int	len = 0;

/*      This is kind of funky, but should work.  The first part below
	is optimized for HUB servers or servers with few clients on
	them.  The second part is optimized for bigger client servers
	where looping through the whole client list is bad.  I'm not
	really certain of the point at which each function equals
	out...but I do know the 2nd part will help big client servers
	fairly well... - Comstud 97/04/24
*/
     
	if (highest_fd < 50) /* This part optimized for HUB servers... */
	    {
		if (MyConnect(user))
		    {
#if ! USE_STDARG
			len = sendpreprep(user, user, pattern,
				  p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,p11);

#else
			va_list	va;
			va_start(va, pattern);
			len = vsendpreprep(user, user, pattern, va);
			va_end(va);
#endif
			(void)send_message(user, psendbuf, len);
		    }
		for (i = 0; i <= highest_fd; i++)
		    {
			if (!(cptr = local[i]) || IsServer(cptr) ||
			    user == cptr || !user->user)
				continue;
			for (lp = user->user->channel; lp; lp = lp->next)
				if (IsMember(cptr, lp->value.chptr) &&
				    !IsQuiet(lp->value.chptr))
				    {
#ifndef DEBUGMODE
					if (!len) /* This saves little cpu,
						     but breaks the debug code.. */
#endif
					    {
#if ! USE_STDARG
					      len = sendpreprep(cptr, user, pattern,
								p1, p2, p3, p4, p5,
								p6, p7, p8, p9, p10,
								p11);
#else
					      va_list	va;
					      va_start(va, pattern);
					      len = vsendpreprep(cptr, user, pattern, va);
					      va_end(va);
#endif
					    }
					(void)send_message(cptr, psendbuf,
							   len);
					break;
				    }
		    }
	    }
	else
	    {
		/* This part optimized for client servers */
		bzero((char *)&sentalong[0], sizeof(int) * MAXCONNECTIONS);
		if (MyConnect(user))
		    {
#if ! USE_STDARG
			len = sendpreprep(user, user, pattern, p1, p2, p3, p4,
					  p5, p6, p7, p8, p9, p10, p11);
#else
			va_list	va;
			va_start(va, pattern);
			len = vsendpreprep(user, user, pattern, va);
			va_end(va);
#endif
			(void)send_message(user, psendbuf, len);
			sentalong[user->fd] = 1;
		    }
		if (!user->user)
			return;
		for (channels=user->user->channel; channels;
		     channels=channels->next)
		    {
			if (IsQuiet(channels->value.chptr))
				continue;
			for (lp=channels->value.chptr->members;lp;
			     lp=lp->next)
			    {
				cptr = lp->value.cptr;
				if (user == cptr)
					continue;
				if (!MyConnect(cptr) || sentalong[cptr->fd])
					continue;
				sentalong[cptr->fd]++;
#ifndef DEBUGMODE
				if (!len) /* This saves little cpu,
					     but breaks the debug code.. */
#endif
				    {
#if ! USE_STDARG
					len = sendpreprep(cptr, user, pattern,
							  p1, p2, p3, p4, p5,
							  p6, p7, p8, p9, p10,
							  p11);
#else
					va_list	va;
					va_start(va, pattern);
					len = vsendpreprep(cptr, user, pattern, va);
					va_end(va);
#endif
				    }
				(void)send_message(cptr, psendbuf, len);
			    }
		    }
	    }
	return;
}

/*
 * sendto_channel_butserv
 *
 * Send a message to all members of a channel that are connected to this
 * server.
 */
#if ! USE_STDARG
/*VARARGS*/
void	sendto_channel_butserv(chptr, from, pattern, p1, p2, p3,
			       p4, p5, p6, p7, p8, p9, p10, p11)
aChannel *chptr;
aClient *from;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_channel_butserv(aChannel *chptr, aClient *from, char *pattern, ...)
#endif
{
	Reg	Link	*lp;
	Reg	aClient	*acptr, *lfrm = from;
	int	len = 0;

	if (MyClient(from))
	    {	/* Always send to the client itself */
#if ! USE_STDARG
		sendto_prefix_one(from, from, pattern, p1, p2, p3, p4,
				  p5, p6, p7, p8, p9, p10, p11);
#else
		va_list	va;
		va_start(va, pattern);
		vsendto_prefix_one(from, from, pattern, va);
		va_end(va);
#endif
		if (IsQuiet(chptr))	/* Really shut up.. */
			return;
	    }
	if (IsAnonymous(chptr) && IsClient(from))
	    {
#if ! USE_STDARG
		if (p1 && *p1 && !mycmp(p1, from->name))
			p1 = anon.name;
#endif
		lfrm = &anon;
	    }

	for (lp = chptr->members; lp; lp = lp->next)
		if (MyClient(acptr = lp->value.cptr) && acptr != from)
		    {
			if (!len)
			    {
#if ! USE_STDARG
				len = sendpreprep(acptr, lfrm, pattern, p1, p2,
						  p3, p4, p5, p6, p7, p8, p9,
						  p10, p11);
#else
				va_list	va;
				va_start(va, pattern);
				len = vsendpreprep(acptr, lfrm, pattern, va);
				va_end(va);
#endif
			    }
			(void)send_message(acptr, psendbuf, len);
		    }

	return;
}

/*
** 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(one, mask, what)
aClient *one;
char	*mask;
int	what;
{
	switch (what)
	{
	case MATCH_HOST:
		return (match(mask, one->user->host)==0);
	case MATCH_SERVER:
	default:
		return (match(mask, one->user->server)==0);
	}
}

/*
 * sendto_match_servs
 *
 * send to all servers which match the mask at the end of a channel name
 * (if there is a mask present) or to all if no mask.
 */
#if ! USE_STDARG
/*VARARGS*/
void	sendto_match_servs(chptr, from, format, p1, p2, p3, p4, p5, p6, p7,
			   p8, p9, p10, p11)
aChannel *chptr;
aClient	*from;
char	*format, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_match_servs(aChannel *chptr, aClient *from, char *format, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient	*cptr;
	char	*mask;

	if (chptr)
	    {
		if (*chptr->chname == '&')
			return;
		if ((mask = (char *)rindex(chptr->chname, ':')))
			mask++;
	    }
	else
		mask = (char *)NULL;

	for (i = fdas.highest; i >= 0; i--)
	    {
		if (!(cptr = local[fdas.fd[i]]) || (cptr == from) ||
		    IsMe(cptr))
			continue;
		if (!BadPtr(mask) && match(mask, cptr->name))
			continue;
		if (chptr &&
		    *chptr->chname == '!' && !(cptr->serv->version & SV_NJOIN))
			continue;
		if (!len)
		    {
#if ! USE_STDARG
			len = sendprep(format, p1, p2, p3, p4, p5, p6, p7,
				       p8, p9, p10, p11);
#else
			va_list	va;
			va_start(va, format);
			len = vsendprep(format, va);
			va_end(va);
#endif
		    }
		(void)send_message(cptr, sendbuf, len);
	    }
}

#if ! USE_STDARG
/*VARARGS*/
void	sendto_match_servs_v(chptr, from, ver, format, p1, p2, p3, p4, p5, p6,
			     p7, p8, p9, p10, p11)
aChannel *chptr;
aClient	*from;
char	*format, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
int	ver;
#else
void	sendto_match_servs_v(aChannel *chptr, aClient *from, int ver,
			     char *format, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient	*cptr;
	char	*mask;

	if (chptr)
	    {
		if (*chptr->chname == '&')
			return;
		if ((mask = (char *)rindex(chptr->chname, ':')))
			mask++;
	    }
	else
		mask = (char *)NULL;

	for (i = fdas.highest; i >= 0; i--)
	    {
		if (!(cptr = local[fdas.fd[i]]) || (cptr == from) ||
		    IsMe(cptr))
			continue;
		if (!BadPtr(mask) && match(mask, cptr->name))
			continue;
		if ((ver & cptr->serv->version) == 0)
			continue;
		if (chptr &&
		    *chptr->chname == '!' && !(cptr->serv->version & SV_NJOIN))
			continue;
		if (!len)
		    {
#if ! USE_STDARG
			len = sendprep(format, p1, p2, p3, p4, p5, p6, p7,
				       p8, p9, p10, p11);
#else
			va_list	va;
			va_start(va, format);
			len = vsendprep(format, va);
			va_end(va);
#endif
		    }
		(void)send_message(cptr, sendbuf, len);
	    }
}

#if ! USE_STDARG
/*VARARGS*/
void	sendto_match_servs_notv(chptr, from, ver, format, p1, p2, p3, p4, p5,
				p6, p7, p8, p9, p10, p11)
aChannel *chptr;
aClient	*from;
char	*format, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
int	ver;
#else
void	sendto_match_servs_notv(aChannel *chptr, aClient *from, int ver,
				char *format, ...)
#endif
{
	Reg	int	i, len=0;
	Reg	aClient	*cptr;
	char	*mask;

	if (chptr)
	    {
		if (*chptr->chname == '&')
			return;
		if ((mask = (char *)rindex(chptr->chname, ':')))
			mask++;
	    }
	else
		mask = (char *)NULL;

	for (i = fdas.highest; i >= 0; i--)
	    {
		if (!(cptr = local[fdas.fd[i]]) || (cptr == from) ||
		    IsMe(cptr))
			continue;
		if (!BadPtr(mask) && match(mask, cptr->name))
			continue;
		if ((ver & cptr->serv->version) != 0)
			continue;
		if (chptr &&
		    *chptr->chname == '!' && !(cptr->serv->version & SV_NJOIN))
			continue;
		if (!len)
		    {
#if ! USE_STDARG
			len = sendprep(format, p1, p2, p3, p4, p5, p6, p7,
				       p8, p9, p10, p11);
#else
			va_list	va;
			va_start(va, format);
			len = vsendprep(format, va);
			va_end(va);
#endif
		    }
		(void)send_message(cptr, sendbuf, len);
	    }
}

/*
 * sendto_match_butone
 *
 * Send to all clients which match the mask in a way defined on 'what';
 * either by user hostname or user servername.
 */
/*VARARGS*/
#if ! USE_STDARG
void	sendto_match_butone(one, from, mask, what, pattern,
			    p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
aClient *one, *from;
int	what;
char	*mask, *pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9,*p10,*p11;
#else
void	sendto_match_butone(aClient *one, aClient *from, char *mask, int what, char *pattern, ...)

#endif
{
	int	i;
	aClient *cptr,
#ifndef NO_USRTOP
		*acptr = NULL;
	anUser	*user;
#else
		*srch;
#endif
  
	for (i = 0; i <= highest_fd; i++)
	    {
		if (!(cptr = local[i]))
			continue;       /* that clients are not mine */
 		if (cptr == one)	/* must skip the origin !! */
			continue;
		if (IsServer(cptr))
		    {
#ifndef NO_USRTOP			
			for (user = usrtop; user; user = user->nextu)
				if (IsRegisteredUser(acptr = user->bcptr) &&
				    acptr->from == cptr &&
				    match_it(acptr, mask, what))
					break;
			if (!user)
				continue;
#else
			/*
			** we can save some CPU here by not searching the
			** entire list of users since it is ordered!
			** original idea/code from pht.
			** it could be made better by looping on the list of
			** servers to avoid non matching blocks in the list
			** (srch->from != cptr), but then again I never
			** bothered to worry or optimize this routine -kalt
			*/
			for (srch = cptr->prev; srch; srch = srch->prev)
			{
				if (!IsRegisteredUser(srch))
					continue;
				if (srch->from == cptr &&
				    match_it(srch, mask, what))
					break;
			}
#endif
		    }
		/* my client, does he match ? */
		else if (!(IsRegisteredUser(cptr) && 
			   match_it(cptr, mask, what)))
			continue;
#if ! USE_STDARG
		sendto_prefix_one(cptr, from, pattern,
				  p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,p11);
#else
		{
			va_list	va;
			va_start(va, pattern);
			vsendto_prefix_one(cptr, from, pattern, va);
			va_end(va);
		}
#endif

	    }
	return;
}

/*
** sendto_ops_butone
**	Send message to all operators.
** one - client not to send message to
** from- client which message is from *NEVER* NULL!!
*/
#if ! USE_STDARG
/*VARARGS*/
void	sendto_ops_butone(one, from, pattern,
			  p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
aClient *one, *from;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_ops_butone(aClient *one, aClient *from, char *pattern, ...)
#endif
{
	Reg	int	i;
	Reg	aClient *cptr;

	bzero((char *)&sentalong[0], sizeof(int) * MAXCONNECTIONS);
	for (cptr = client; cptr; cptr = cptr->next)
	    {
		if (IsService(cptr) || !IsRegistered(cptr))
			continue;
		if ((IsPerson(cptr) && !SendWallops(cptr)) || IsMe(cptr))
			continue;
		if (MyClient(cptr) && !(IsServer(from) || IsMe(from)))
			continue;
		i = cptr->from->fd;	/* find connection oper is on */
		if (sentalong[i])	/* sent message along it already ? */
			continue;
		if (cptr->from == one)
			continue;	/* ...was the one I should skip */
		sentalong[i] = 1;
#if ! USE_STDARG
      		sendto_prefix_one(cptr->from, from, pattern,
				  p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,p11);
#else
		{
			va_list	va;
			va_start(va, pattern);
			vsendto_prefix_one(cptr->from, from, pattern, va);
			va_end(va);
		}
#endif
	    }
	return;
}

/*
 * to - destination client
 * from - client which message is from
 *
 * NOTE: NEITHER OF THESE SHOULD *EVER* BE NULL!!
 * -avalon
 */
#if ! USE_STDARG
/*VARARGS*/
void	sendto_prefix_one(to, from, pattern,
			  p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11)
Reg	aClient *to;
Reg	aClient *from;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_prefix_one(aClient *to, aClient *from, char *pattern, ...)
#endif
{
	int	len;

#if ! USE_STDARG
	len = sendpreprep(to, from, pattern, p1, p2, p3, p4, p5, p6, p7, p8,
			  p9, p10, p11);
#else
	va_list	va;
	va_start(va, pattern);
	len = vsendpreprep(to, from, pattern, va);
	va_end(va);
#endif
	send_message(to, psendbuf, len);
	return;
}

#if USE_STDARG
static void	vsendto_prefix_one(aClient *to, aClient *from, char *pattern, va_list va)
{
	int	len;

	len = vsendpreprep(to, from, pattern, va);
	send_message(to, psendbuf, len);
	return;
}
#endif


/*
 * sends a message to a server-owned channel
 */
static	SChan	svchans[SCH_MAX] = {
	{ SCH_ERROR,	"&ERRORS",	NULL },
	{ SCH_NOTICE,	"&NOTICES",	NULL },
	{ SCH_KILL,	"&KILLS",	NULL },
	{ SCH_CHAN,	"&CHANNEL",	NULL },
	{ SCH_NUM,	"&NUMERICS",	NULL },
	{ SCH_SERVER,	"&SERVERS",	NULL },
	{ SCH_HASH,	"&HASH",	NULL },
	{ SCH_LOCAL,	"&LOCAL",	NULL },
	{ SCH_SERVICE,	"&SERVICES",	NULL },
	{ SCH_DEBUG,	"&DEBUG",	NULL },
	{ SCH_AUTH,	"&AUTH",	NULL },
};


void	setup_svchans()
{
	int	i;
	SChan	*shptr;

	for (i = SCH_MAX, shptr = svchans + (i - 1); i > 0; i--, shptr--)
		shptr->svc_ptr = find_channel(shptr->svc_chname, NULL);
}

#if ! USE_STDARG
void	sendto_flag(chan, pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10,p11)
u_int	chan;
char	*pattern, *p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10, *p11;
#else
void	sendto_flag(u_int chan, char *pattern, ...)
#endif
{
	Reg	aChannel *chptr = NULL;
	SChan	*shptr;
	char	nbuf[1024];

	if (chan < 1 || chan > SCH_MAX)
		chan = SCH_NOTICE;
	shptr = svchans + (chan - 1);

	if ((chptr = shptr->svc_ptr))
	    {
#if ! USE_STDARG
		(void)sprintf(nbuf, pattern, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11);
#else
		{
			va_list	va;
			va_start(va, pattern);
			(void)vsprintf(nbuf, pattern, va);
			va_end(va);
		}
#endif
		sendto_channel_butserv(chptr, &me, ":%s NOTICE %s :%s", ME, chptr->chname, nbuf);

#ifdef	USE_SERVICES
		switch (chan)
		    {
		case SCH_ERROR:
			check_services_butone(SERVICE_WANT_ERRORS, NULL, &me,
					      "&ERRORS :%s", nbuf);
			break;
		case SCH_NOTICE:
			check_services_butone(SERVICE_WANT_NOTICES, NULL, &me,
					      "&NOTICES :%s", nbuf);
			break;
		case SCH_LOCAL:
			check_services_butone(SERVICE_WANT_LOCAL, NULL, &me,
					      "&LOCAL :%s", nbuf);
			break;
		case SCH_NUM:
			check_services_butone(SERVICE_WANT_NUMERICS, NULL, &me,
					      "&NUMERICS :%s", nbuf);
			break;
		    }
#endif
	    }

	return;
}

/*
 * sendto_flog
 *	cptr		used for firsttime, auth, exitc, send/received M/K
 *	msg		replaces duration if duration is 0
 *	duration	only used if non 0
 *	username	can't get it from cptr
 *	hostname	i.e.
 */
void	sendto_flog(cptr, msg, duration, username, hostname)
aClient	*cptr;
char	*msg, *username, *hostname;
time_t	duration;
{
	char	linebuf[1024]; /* auth reply might be long.. */
	int	logfile;

#ifdef	USE_SERVICES
	if (duration)
	    {
		(void)sprintf(linebuf,
	      "%s (%3d:%02d:%02d): %s@%s [%s] %c %lu %luKb %lu %luKb\n",
			      myctime(cptr->firsttime),
			      (int) (duration / 3600),
			      (int) ((duration % 3600) / 60),
			      (int) (duration % 60),
			      username, hostname, cptr->auth,
			      cptr->exitc, cptr->sendM, cptr->sendK,
			      cptr->receiveM, cptr->receiveK);
		check_services_butone(SERVICE_WANT_USERLOG, NULL, &me,
				      "USERLOG :%s", linebuf);
	    }
	else
	    {
		(void)sprintf(linebuf,
			      "%s (%s): %s@%s [%s] %c %lu %luKb %lu %luKb\n",
			      myctime(cptr->firsttime), msg, username,
			      hostname, cptr->auth,
			      cptr->exitc, cptr->sendM, cptr->sendK,
			      cptr->receiveM, cptr->receiveK);
		check_services_butone(SERVICE_WANT_CONNLOG, NULL, &me,
				      "CONNLOG :%s", linebuf);
	    }
#endif
	/*
	 * This conditional makes the logfile active only after
	 * it's been created, thus logging can be turned off by
	 * removing the file.
	 *
	 * stop NFS hangs...most systems should be able to
	 * file in 3 seconds. -avalon (curtesy of wumpus)
	 */
	(void)alarm(3);
	if (
#ifdef	FNAME_USERLOG
	    (duration && 
	     (logfile = open(FNAME_USERLOG, O_WRONLY|O_APPEND)) != -1)
# ifdef	FNAME_CONNLOG
	    ||
# endif
#endif
#ifdef	FNAME_CONNLOG
	    (!duration && 
	     (logfile = open(FNAME_CONNLOG, O_WRONLY|O_APPEND)) != -1)
#else
# ifndef	FNAME_USERLOG
	    0
# endif
#endif
	   )
	    {
		(void)alarm(0);
#ifndef	USE_SERVICES
		if (duration)
			(void)sprintf(linebuf,
	      "%s (%3d:%02d:%02d): %s@%s [%s] %c %lu %luKb %lu %luKb\n",
				      myctime(cptr->firsttime),
				      (int) (duration / 3600),
				      (int) ((duration % 3600) / 60),
				      (int) (duration % 60),
				      username, hostname, cptr->auth,
				      cptr->exitc, cptr->sendM, cptr->sendK,
				      cptr->receiveM, cptr->receiveK);
		else
			(void)sprintf(linebuf,
			      "%s (%s): %s@%s [%s] %c %lu %luKb %lu %luKb\n",
				      myctime(cptr->firsttime), msg, username,
				      hostname, cptr->auth,
                                      cptr->exitc, cptr->sendM, cptr->sendK,
                                      cptr->receiveM, cptr->receiveK);
#endif
		(void)alarm(3);
		(void)write(logfile, linebuf, strlen(linebuf));
		(void)alarm(0);
		(void)close(logfile);
	    }
	(void)alarm(0);
}
#endif /* CLIENT_COMPILE */
