/************************************************************************
 *   IRC - Internet Relay Chat, ircd/s_serv.c (formerly ircd/s_msg.c)
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Computing Center
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers. 
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef lint
static  char rcsid[] = "@(#)$Id: s_serv.c,v 1.22 2004/03/31 23:46:13 skold Exp $";
#endif

#include "os.h"
#include "s_defines.h"
#define S_SERV_C
#include "s_externs.h"
#undef S_SERV_C

static	char	buf[BUFSIZE];

static	int	check_link __P((aClient *));

/*
** m_functions execute protocol messages on this server:
**
**	cptr	is always NON-NULL, pointing to a *LOCAL* client
**		structure (with an open socket connected!). This
**		identifies the physical socket where the message
**		originated (or which caused the m_function to be
**		executed--some m_functions may call others...).
**
**	sptr	is the source of the message, defined by the
**		prefix part of the message if present. If not
**		or prefix not found, then sptr==cptr.
**
**		(!IsServer(cptr)) => (cptr == sptr), because
**		prefixes are taken *only* from servers...
**
**		(IsServer(cptr))
**			(sptr == cptr) => the message didn't
**			have the prefix.
**
**			(sptr != cptr && IsServer(sptr) means
**			the prefix specified servername. (?)
**
**			(sptr != cptr && !IsServer(sptr) means
**			that message originated from a remote
**			user (not local).
**
**		combining
**
**		(!IsServer(sptr)) means that, sptr can safely
**		taken as defining the target structure of the
**		message in this server.
**
**	*Always* true (if 'parse' and others are working correct):
**
**	1)	sptr->from == cptr  (note: cptr->from == cptr)
**
**	2)	MyConnect(sptr) <=> sptr == cptr (e.g. sptr
**		*cannot* be a local connection, unless it's
**		actually cptr!). [MyConnect(x) should probably
**		be defined as (x == x->from) --msa ]
**
**	parc	number of variable parameter strings (if zero,
**		parv is allowed to be NULL)
**
**	parv	a NULL terminated list of parameter pointers,
**
**			parv[0], sender (prefix string), if not present
**				this points to an empty string.
**			parv[1]...parv[parc-1]
**				pointers to additional parameters
**			parv[parc] == NULL, *always*
**
**		note:	it is guaranteed that parv[0]..parv[parc-1] are all
**			non-NULL pointers.
*/

/*
** m_version
**	parv[0] = sender prefix
**	parv[1] = remote server
*/
int	m_version(cptr, sptr, parc, parv)
aClient *sptr, *cptr;
int	parc;
char	*parv[];
{
	if (hunt_server(cptr,sptr,":%s VERSION :%s",1,parc,parv)==HUNTED_ISME)
		sendto_one(sptr, rpl_str(RPL_VERSION, parv[0]),
			   version, debugmode, ME, serveropts);
	return 2;
}

/*
** m_squit
**	parv[0] = sender prefix
**	parv[1] = server name
**	parv[2] = comment
*/
int	m_squit(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	Reg	aConfItem *aconf;
	char	*server;
	Reg	aClient	*acptr;
	char	*comment = (parc > 2 && parv[2]) ? parv[2] : cptr->name;

	if (parc > 1)
	    {
		server = parv[1];
		/*
		** To accomodate host masking, a squit for a masked server
		** name is expanded if the incoming mask is the same as
		** the server name for that link to the name of link.
		*/
		while ((*server == '*') && IsServer(cptr))
		    {
			aconf = cptr->serv->nline;
			if (!aconf)
				break;
			if (!strcasecmp(server,
				   my_name_for_link(ME, aconf->port)))
				server = cptr->name;
			break; /* WARNING is normal here */
		    }
		/*
		** The following allows wild cards in SQUIT. Only usefull
		** when the command is issued by an oper.
		*/
		for (acptr = client; (acptr = next_client(acptr, server));
		     acptr = acptr->next)
			if (IsServer(acptr) || IsMe(acptr))
				break;
		if (acptr && IsMe(acptr))
		    {
			acptr = cptr;
			server = cptr->sockhost;
		    }
	    }
	else
	    {
		/*
		** This is actually protocol error. But, well, closing
		** the link is very proper answer to that...
		*/
		server = cptr->name;
		acptr = cptr;
	    }

	/*
	** SQUIT semantics is tricky, be careful...
	**
	** The old (irc2.2PL1 and earlier) code just cleans away the
	** server client from the links (because it is never true
	** "cptr == acptr".
	**
	** This logic here works the same way until "SQUIT host" hits
	** the server having the target "host" as local link. Then it
	** will do a real cleanup spewing SQUIT's and QUIT's to all
	** directions, also to the link from which the orinal SQUIT
	** came, generating one unnecessary "SQUIT host" back to that
	** link.
	**
	** One may think that this could be implemented like
	** "hunt_server" (e.g. just pass on "SQUIT" without doing
	** nothing until the server having the link as local is
	** reached). Unfortunately this wouldn't work in the real life,
	** because either target may be unreachable or may not comply
	** with the request. In either case it would leave target in
	** links--no command to clear it away. So, it's better just
	** clean out while going forward, just to be sure.
	**
	** ...of course, even better cleanout would be to QUIT/SQUIT
	** dependant users/servers already on the way out, but
	** currently there is not enough information about remote
	** clients to do this...   --msa
	*/
	if (!acptr)
	    {
		sendto_one(sptr, err_str(ERR_NOSUCHSERVER, parv[0]), server);
		return 1;
	    }
	if (MyConnect(sptr) && !MyConnect(acptr) && parc < 3)
	    {
                sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS,parv[0]), "SQUIT");
		return 0;
            }
	if (IsLocOp(sptr) && !MyConnect(acptr))
	    {
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES, parv[0]));
		return 1;
	    }
	if (!MyConnect(acptr) && (cptr != acptr->from))
	    {
		/*
		** The following is an awful kludge, but I don't see any other
		** way to change the pre 2.10.3 behaviour.  I'm probably going
		** to regret it.. -kalt
		*/
		if ((acptr->from->serv->version & SV_OLDSQUIT) == 0)
		    {
			/* better server: just propagate upstream */
			sendto_one(acptr->from, ":%s SQUIT %s :%s", parv[0],
				   acptr->name, comment);
			sendto_flag(SCH_SERVER,
				    "Forwarding SQUIT %s from %s (%s)",
				    acptr->name, parv[0], comment);
			sendto_flag(SCH_DEBUG,
				    "Forwarding SQUIT %s to %s from %s (%s)",
				    acptr->name, acptr->from->name, 
				    parv[0], comment);
			return 1;
		    }
		/*
		** ack, bad server encountered!
		** must send back to other good servers which were trying to
		** do the right thing, and fake the yet to come SQUIT which
		** will never be received from the bad servers.
		*/
		if (IsServer(cptr) && 
		    (cptr->serv->version & SV_OLDSQUIT) == 0)
		    {
			sendto_one(cptr, ":%s SQUIT %s :%s (Bounced for %s)",
				   ME, acptr->name, comment, parv[0]);
			sendto_flag(SCH_DEBUG, "Bouncing SQUIT %s back to %s",
				    acptr->name, acptr->from->name);
		    }
	    }
	/*
	**  Notify all opers, if my local link is remotely squitted
	*/
	if (MyConnect(acptr) && !IsAnOper(cptr))
	    {
		sendto_ops_butone(NULL, &me,
			":%s WALLOPS :Received SQUIT %s from %s (%s)",
			ME, server, parv[0], comment);
#if defined(USE_SYSLOG) && defined(SYSLOG_SQUIT)
		syslog(LOG_DEBUG,"SQUIT From %s : %s (%s)",
		       parv[0], server, comment);
#endif
	    }
	if (MyConnect(acptr))
	    {
		int timeconnected = timeofday - acptr->firsttime;
		sendto_flag(SCH_NOTICE, 
			    "Closing link to %s (%d, %2d:%02d:%02d)",
			    get_client_name(acptr, FALSE),
			    timeconnected / 86400,
			    (timeconnected % 86400) / 3600,
			    (timeconnected % 3600)/60, 
			    timeconnected % 60);
	    }
	sendto_flag(SCH_SERVER, "Received SQUIT %s from %s (%s)",
		    acptr->name, parv[0], comment);

	if (MyConnect(acptr) && 
	    IsServer(cptr) && (cptr->serv->version & SV_OLDSQUIT) == 0)
	    {
		sendto_one(cptr, ":%s SQUIT %s :%s", ME, acptr->name, comment);
		sendto_flag(SCH_DEBUG, "Issuing additionnal SQUIT %s for %s",
			    acptr->name, acptr->from->name);
	    }
	return exit_client(cptr, acptr, sptr, comment);
    }

/*
** check_version
**      The PASS command delivers additional information about incoming
**	connection. The data is temporarily stored to info/name/username
**	in m_pass() and processed here before the fields are natively used.
** Return: < 1: exit/error, > 0: no error
*/
int	check_version(cptr)
aClient	*cptr;
{
	char *id, *misc = NULL, *link = NULL;

	Debug((DEBUG_INFO,"check_version: %s", cptr->info));

	if (cptr->info == DefInfo)
	    {
		cptr->hopcount = SV_OLD;
		return 1; /* no version checked (e.g. older than 2.9) */
	    }
	if (id = index(cptr->info, ' '))
	    {
		*id++ = '\0';
		if (link = index(id, ' '))
			*link++ = '\0';
		if (misc = index(id, '|'))
			*misc++ = '\0';
		else
		    {
			misc = id;
			id = "";
		    }
	    }
	else
		id = "";

	if (!strncmp(cptr->info, "021", 3))
	{
		cptr->hopcount = SV_29|SV_NJOIN|SV_NMODE|SV_NCHAN; /* SV_2_10*/

#ifdef RUSNET_IRCD
		cptr->hopcount |= SV_FORCE;	/* RusNet 1.4 */

		if (link && strchr(link, PROTO_CAPS_K))
			cptr->hopcount |= SV_KLINE;

		if (link && strchr(link, PROTO_CAPS_R))
			cptr->hopcount |= SV_RMODE;
#endif
	}
	else if (!strncmp(cptr->info, "0209", 4))
		cptr->hopcount = SV_29|SV_OLDSQUIT;	/* 2.9+ protocol */
	else
		cptr->hopcount = SV_OLD; /* uhuh */

	if (!strcmp("IRC", id) && !strncmp(cptr->info, "02100", 5) &&
	    atoi(cptr->info+5) < 20600)
		/* before 2.10.3a6 ( 2.10.3a5 is just broken ) */
		cptr->hopcount |= SV_OLDSQUIT;

	/* Check version number/mask from conf */
	sprintf(buf, "%s/%s", id, cptr->info);
	if (find_two_masks(cptr->name, buf, CONF_VER))
	    {
		sendto_flag(SCH_ERROR, "Bad version %s %s from %s", id,
			    cptr->info, get_client_name(cptr, TRUE));
		return exit_client(cptr, cptr, &me, "Bad version");
	    }

	if (misc)
	    {
		sprintf(buf, "%s/%s", id, misc);
		/* Check version flags from conf */
		if (find_conf_flags(cptr->name, buf, CONF_VER))
		    {
			sendto_flag(SCH_ERROR, "Bad flags %s (%s) from %s",
				    misc, id, get_client_name(cptr, TRUE));
			return exit_client(cptr, cptr, &me, "Bad flags");
		    }
	    }

	/* right now, I can't code anything good for this */
	/* Stop whining, and do it! ;) */
	if (link && strchr(link, 'Z'))	/* Compression requested */
                cptr->flags |= FLAGS_ZIPRQ;
	/*
	 * If server was started with -p strict, be careful about the
	 * other server mode.
	 */
	if (link && strncmp(cptr->info, "020", 3) &&
	    (bootopt & BOOT_STRICTPROT) && !strchr(link, 'P'))
		return exit_client(cptr, cptr, &me, "Unsafe mode");

	return 2;
}

/*
** m_server
**	parv[0] = sender prefix
**	parv[1] = servername
**	parv[2] = serverinfo/hopcount
**	parv[3] = token/serverinfo (2.9)
**      parv[4] = serverinfo
*/
int	m_server(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	char	*ch;
	Reg	int	i;
	char	info[REALLEN+1], *inpath, *host, *stok;
	aClient *acptr, *bcptr;
	aConfItem *aconf;
	int	hop = 0, token = 0;

	if (sptr->user) /* in case NICK hasn't been received yet */
            {
                sendto_one(sptr, err_str(ERR_ALREADYREGISTRED, parv[0]));
                return 1;
            }
	info[0] = info[REALLEN] = '\0';	/* strncpy() doesn't guarantee NULL */
	inpath = get_client_name(cptr, FALSE);
	if (parc < 2 || *parv[1] == '\0')
	    {
			sendto_one(cptr,"ERROR :No servername");
			return 1;
	    }
	host = parv[1];
	if (parc > 3 && (hop = atoi(parv[2])))
	    {
		if (parc > 4 && (token = atoi(parv[3])))
			(void)strncpy(info, parv[4], REALLEN);
		else
			(void)strncpy(info, parv[3], REALLEN);
	    }
	else if (parc > 2)
	    {
		(void)strncpy(info, parv[2], REALLEN);
		i = strlen(info);
		if (parc > 3 && ((i+2) < REALLEN))
		    {
				(void)strncat(info, " ", REALLEN - i - 1);
				(void)strncat(info, parv[3], REALLEN - i - 2);
		    }
	    }
	/*
	** Check for "FRENCH " infection ;-) (actually this should
	** be replaced with routine to check the hostname syntax in
	** general). [ This check is still needed, even after the parse
	** is fixed, because someone can send "SERVER :foo bar " ].
	** Also, changed to check other "difficult" characters, now
	** that parse lets all through... --msa
	*/
	if (strlen(host) > (size_t) HOSTLEN)
		host[HOSTLEN] = '\0';
	for (ch = host; *ch; ch++)
		if (*ch <= ' ' || *ch > '~')
			break;
	if (*ch || !index(host, '.'))
	    {
		sendto_one(sptr,"ERROR :Bogus server name (%s)", host);
		sendto_flag(SCH_ERROR, "Bogus server name (%s) from %s", host,
			    get_client_name(cptr, TRUE));
		return 2;
	    }
	
	/* *WHEN* can it be that "cptr != sptr" ????? --msa */
	/* When SERVER command (like now) has prefix. -avalon */

	if (IsRegistered(cptr) && ((acptr = find_name(host, NULL))
				   || (acptr = find_mask(host, NULL))))
	    {
		/*
		** This link is trying feed me a server that I already have
		** access through another path -- multiple paths not accepted
		** currently, kill this link immeatedly!!
		**
		** Rather than KILL the link which introduced it, KILL the
		** youngest of the two links. -avalon
		*/
		bcptr = (cptr->firsttime > acptr->from->firsttime) ? cptr :
			acptr->from;
		sendto_one(bcptr, "ERROR :Server %s already exists", host);
		/* in both cases the bcptr (the youngest is killed) */
		if (bcptr == cptr)
		    {
			sendto_flag(SCH_ERROR,
			    "Link %s cancelled, server %s already exists",
				    get_client_name(bcptr, TRUE), host);
			return exit_client(bcptr, bcptr, &me, "Server Exists");
		    }
		else
		    {
			/*
			** in this case, we are not dropping the link from
			** which we got the SERVER message.  Thus we canNOT
			** `return' yet! -krys
			*/
			strcpy(buf, get_client_name(bcptr, TRUE));
			sendto_flag(SCH_ERROR,
			    "Link %s cancelled, server %s reintroduced by %s",
				    buf, host, get_client_name(cptr, TRUE));
			(void) exit_client(bcptr, bcptr, &me, "Server Exists");
		    }
	    }
	if ((acptr = find_person(host, NULL)) && (acptr != cptr))
	    {
		/*
		** Server trying to use the same name as a person. Would
		** cause a fair bit of confusion. Enough to make it hellish
		** for a while and servers to send stuff to the wrong place.
		*/
		sendto_one(cptr,"ERROR :Nickname %s already exists!", host);
		sendto_flag(SCH_ERROR,
			    "Link %s cancelled: Server/nick collision on %s",
			    inpath, host);
		sendto_serv_butone(NULL, /* all servers */
				   ":%s KILL %s :%s (%s <- %s)",
				   ME, acptr->name, ME,
				   acptr->from->name, host);
		acptr->flags |= FLAGS_KILLED;
		(void)exit_client(NULL, acptr, &me, "Nick collision");
		return exit_client(cptr, cptr, &me, "Nick as Server");
	    }

	if (IsServer(cptr))
	    {
		/* A server can only be introduced by another server. */
		if (!IsServer(sptr))
		    {
			sendto_flag(SCH_LOCAL,
			    "Squitting %s brought by %s (introduced by %s)",
				    host, get_client_name(cptr, FALSE),
				    sptr->name);
			sendto_one(cptr,
				   ":%s SQUIT %s :(Introduced by %s from %s)",
				   me.name, host, sptr->name,
				   get_client_name(cptr, FALSE));
	  		return 1;
		    }
		/*
		** Server is informing about a new server behind
		** this link. Create REMOTE server structure,
		** add it to list and propagate word to my other
		** server links...
		*/
		if (parc == 1 || info[0] == '\0')
		    {
	  		sendto_one(cptr,
				   "ERROR :No server info specified for %s",
				   host);
			sendto_flag(SCH_ERROR, "No server info for %s from %s",
				    host, get_client_name(cptr, TRUE));
	  		return 1;
		    }

		/*
		** See if the newly found server is behind a guaranteed
		** leaf (L-line). If so, close the link.
		*/
		if ((aconf = find_conf_host(cptr->confs, host, CONF_LEAF)) &&
		    (!aconf->port || (hop > aconf->port)))
		    {
	      		sendto_flag(SCH_ERROR,
				    "Leaf-only link %s->%s - Closing",
				    get_client_name(cptr, TRUE),
				    aconf->host ? aconf->host : "*");
	      		sendto_one(cptr, "ERROR :Leaf-only link, sorry.");
      			return exit_client(cptr, cptr, &me, "Leaf Only");
		    }
		/*
		**
		*/
		if (!(aconf = find_conf_host(cptr->confs, host, CONF_HUB)) ||
		    (aconf->port && (hop > aconf->port)) )
		    {
			sendto_flag(SCH_ERROR,
				    "Non-Hub link %s introduced %s(%s).",
				    get_client_name(cptr, TRUE), host,
				   aconf ? (aconf->host ? aconf->host : "*") :
				   "!");
			return exit_client(cptr, cptr, &me,
					   "Too many servers");
		    }
		/*
		** See if the newly found server has a Q line for it in
		** our conf. If it does, lose the link that brought it
		** into our network. Format:
		**
		** Q:<unused>:<reason>:<servername>
		**
		** Example:  Q:*:for the hell of it:eris.Berkeley.EDU
		*/
		if ((aconf = find_conf_name(host, CONF_QUARANTINED_SERVER)))
		    {
			sendto_ops_butone(NULL, &me,
				":%s WALLOPS * :%s brought in %s, %s %s",
				ME, get_client_name(cptr, TRUE),
				host, "closing link because",
				BadPtr(aconf->passwd) ? "reason unspecified" :
				aconf->passwd);

			sendto_one(cptr,
				   "ERROR :%s is not welcome: %s. %s",
				   host, BadPtr(aconf->passwd) ?
				   "reason unspecified" : aconf->passwd,
				   "Go away and get a life");

			return exit_client(cptr, cptr, &me, "Q-Lined Server");
		    }

		acptr = make_client(cptr);
		(void)make_server(acptr);
		acptr->hopcount = hop;
		strncpyzt(acptr->name, host, sizeof(acptr->name));
#ifdef RUSNET_IRCD
		acptr->serv->crc = gen_crc(host);
#endif
		if (acptr->info != DefInfo)
			MyFree(acptr->info);
		acptr->info = mystrdup(info);
		acptr->serv->up = sptr->name;
		acptr->serv->stok = token;
		acptr->serv->snum = find_server_num(acptr->name);
		SetServer(acptr);
		istat.is_serv++;
#ifdef EXTRA_STATISTICS
		/*
		**  Keep track of the highest number of connected
		**  servers (hostmasks) to this network.
		**/
		if (istat.is_serv > istat.is_m_serv)
			istat.is_m_serv = istat.is_serv;
#endif
		add_client_to_list(acptr);
		(void)add_to_client_hash_table(acptr->name, acptr);
		(void)add_to_server_hash_table(acptr->serv, cptr);
		/*
		** Old sendto_serv_but_one() call removed because we now
		** need to send different names to different servers
		** (domain name matching)
		*/
		for (i = fdas.highest; i >= 0; i--)
		    {
			if (!(bcptr = local[fdas.fd[i]]) || !IsServer(bcptr) ||
			    bcptr == cptr || IsMe(bcptr))
				continue;
			if (!(aconf = bcptr->serv->nline))
			    {
				sendto_flag(SCH_NOTICE,
					    "Lost N-line for %s on %s:Closing",
					    get_client_name(cptr, TRUE), host);
				return exit_client(cptr, cptr, &me,
						   "Lost N line");
			    }
			if (match(my_name_for_link(ME, aconf->port),
				    acptr->name) == 0)
				continue;
			stok = acptr->serv->tok;
			sendto_one(bcptr, ":%s SERVER %s %d %s :%s", parv[0],
				   acptr->name, hop+1, stok, acptr->info);
		    }
#ifdef	USE_SERVICES
		check_services_butone(SERVICE_WANT_SERVER, acptr->name, acptr,
				      ":%s SERVER %s %d %s :%s", parv[0],
				      acptr->name, hop+1, acptr->serv->tok,
				      acptr->info);
#endif
		sendto_flag(SCH_SERVER, "Received SERVER %s from %s (%d %s)",
			    acptr->name, parv[0], hop+1, acptr->info);
		return 0;
	    }

	if ((!IsUnknown(cptr) && !IsHandshake(cptr)) ||
	    (cptr->flags & FLAGS_UNKCMD))
		return 1;
	/*
	** A local link that is still in undefined state wants
	** to be a SERVER. Check if this is allowed and change
	** status accordingly...
	*/
	strncpyzt(cptr->name, host, sizeof(cptr->name));
	/* cptr->name has to exist before check_version(), and cptr->info
	 * may not be filled before check_version(). */
	if ((hop = check_version(cptr)) < 1)
		return hop;	/* from exit_client() */
	if (cptr->info != DefInfo)
		MyFree(cptr->info);
	cptr->info = mystrdup(info[0] ? info : ME);

	switch (check_server_init(cptr))
	{
	case 0 :
		return m_server_estab(cptr);
	case 1 :
		sendto_flag(SCH_NOTICE, "Checking access for %s",
			    get_client_name(cptr,TRUE));
		return 1;
	default :
		ircstp->is_ref++;
		sendto_flag(SCH_NOTICE, "Unauthorized server from %s.",
			    get_client_host(cptr));
		return exit_client(cptr, cptr, &me, "No C/N conf lines");
	}
}

int	m_server_estab(cptr)
Reg	aClient	*cptr;
{
	Reg	aClient	*acptr;
	Reg	aConfItem	*aconf, *bconf;
	char	mlname[HOSTLEN+1], *inpath, *host, *s, *encr, *stok;
	int	split, i;

	host = cptr->name;
	inpath = get_client_name(cptr,TRUE); /* "refresh" inpath with host */
	split = strcasecmp(cptr->name, cptr->sockhost);

	if (!(aconf = find_conf(cptr->confs, host, CONF_NOCONNECT_SERVER)))
	    {
		ircstp->is_ref++;
		sendto_one(cptr,
			   "ERROR :Access denied. No N line for server %s",
			   inpath);
		sendto_flag(SCH_ERROR,
			    "Access denied. No N line for server %s", inpath);
		return exit_client(cptr, cptr, &me, "No N line for server");
	    }
	if (!(bconf = find_conf(cptr->confs, host, CONF_CONNECT_SERVER|
				CONF_ZCONNECT_SERVER)))
	    {
		ircstp->is_ref++;
		sendto_one(cptr, "ERROR :Only N (no C) field for server %s",
			   inpath);
		sendto_flag(SCH_ERROR,
			    "Only N (no C) field for server %s",inpath);
		return exit_client(cptr, cptr, &me, "No C line for server");
	    }

	if (cptr->hopcount == SV_OLD) /* lame test, should be == 0 */
	    {
		sendto_one(cptr, "ERROR :Server version is too old.");
		sendto_flag(SCH_ERROR, "Old version for %s", inpath);
		return exit_client(cptr, cptr, &me, "Old version");
	    }

#ifdef CRYPT_LINK_PASSWORD
	/* pass whole aconf->passwd as salt, let crypt() deal with it */

	if (*cptr->passwd)
	    {
		extern  char *crypt();

		encr = crypt(cptr->passwd, aconf->passwd);
		if (encr == NULL)
		    {
			ircstp->is_ref++;
			sendto_one(cptr, "ERROR :No Access (crypt failed) %s",
			  	inpath);
			sendto_flag(SCH_ERROR,
			    	"Access denied (crypt failed) %s", inpath);
			return exit_client(cptr, cptr, &me, "Bad Password");
		    }
	    }
	else
		encr = "";
#else
	encr = cptr->passwd;
#endif  /* CRYPT_LINK_PASSWORD */
	if (*aconf->passwd && !StrEq(aconf->passwd, encr))
	    {
		ircstp->is_ref++;
		sendto_one(cptr, "ERROR :No Access (passwd mismatch) %s",
			   inpath);
		sendto_flag(SCH_ERROR,
			    "Access denied (passwd mismatch) %s", inpath);
		return exit_client(cptr, cptr, &me, "Bad Password");
	    }
	bzero(cptr->passwd, sizeof(cptr->passwd));

#ifndef	HUB
	for (i = 0; i <= highest_fd; i++)
		if (local[i] && IsServer(local[i]))
		    {
			ircstp->is_ref++;
			sendto_flag(SCH_ERROR, "I'm a leaf, cannot link %s",
				    get_client_name(cptr, TRUE));
			return exit_client(cptr, cptr, &me, "I'm a leaf");
		    }
#endif
	(void) strcpy(mlname, my_name_for_link(ME, aconf->port));
	if (IsUnknown(cptr))
	    {
		if (bconf->passwd[0])
			sendto_one(cptr, "PASS %s %s IRC|%s %s"
#ifdef	ZIP_LINKS
			"%s"
#endif
#ifdef RUSNET_IRCD
			"%c%c"
#endif
			, bconf->passwd, pass_version, serveropts,
#ifdef	ZIP_LINKS
			   (bconf->status == CONF_ZCONNECT_SERVER) ? "Z" : "",
#endif
				   (bootopt & BOOT_STRICTPROT) ? "P" : ""
#ifdef RUSNET_IRCD				   
				   , PROTO_CAPS_K, PROTO_CAPS_R
#endif
				   );
		/*
		** Pass my info to the new server
		*/
		sendto_one(cptr, "SERVER %s 1 :%s", mlname, me.info);

		/*
		** If we get a connection which has been authorized to be
		** an already existing connection, remove the already
		** existing connection if it has a sendq else remove the
		** new and duplicate server. -avalon
		** Remove existing link only if it has been linked for longer
		** and has sendq higher than a threshold. -Vesa
		*/
		if ((acptr = find_name(host, NULL))
		    || (acptr = find_mask(host, NULL)))
		    {
			if (MyConnect(acptr) &&
			    DBufLength(&acptr->sendQ) > CHREPLLEN &&
			    timeofday - acptr->firsttime > TIMESEC)
				(void) exit_client(acptr, acptr, &me,
						   "New Server");
			else
				return exit_client(cptr, cptr, &me,
						   "Server Exists");
		    }
	    }
	else
	    {
		s = (char *)index(aconf->host, '@');
		*s = '\0'; /* should never be NULL */
		Debug((DEBUG_INFO, "Check Usernames [%s]vs[%s]",
			aconf->host, cptr->username));
		if (match(aconf->host, cptr->username))
		    {
			*s = '@';
			ircstp->is_ref++;
			sendto_flag(SCH_ERROR,
				    "Username mismatch [%s]v[%s] : %s",
				    aconf->host, cptr->username,
				    get_client_name(cptr, TRUE));
			sendto_one(cptr, "ERROR :No Username Match");
			return exit_client(cptr, cptr, &me, "Bad User");
		    }
		*s = '@';
	    }

#ifdef	ZIP_LINKS
	if ((cptr->flags & FLAGS_ZIPRQ) &&
	    (bconf->status == CONF_ZCONNECT_SERVER))	    
	    {
		if (zip_init(cptr) == -1)
		    {
			zip_free(cptr);
			sendto_flag(SCH_ERROR,
			    "Unable to setup compressed link for %s",
				    get_client_name(cptr, TRUE));
			return exit_client(cptr, cptr, &me,
					   "zip_init() failed");
		    }
		cptr->flags |= FLAGS_ZIP|FLAGS_ZIPSTART;
	    }
#endif

	det_confs_butmask(cptr, CONF_LEAF|CONF_HUB|CONF_NOCONNECT_SERVER);
	/*
	** *WARNING*
	** 	In the following code in place of plain server's
	**	name we send what is returned by get_client_name
	**	which may add the "sockhost" after the name. It's
	**	*very* *important* that there is a SPACE between
	**	the name and sockhost (if present). The receiving
	**	server will start the information field from this
	**	first blank and thus puts the sockhost into info.
	**	...a bit tricky, but you have been warned, besides
	**	code is more neat this way...  --msa
	*/
	SetServer(cptr);
	istat.is_unknown--;
	istat.is_serv++;
	istat.is_myserv++;
#ifdef EXTRA_STATISTICS
	/*
	**  Keep track of the highest number of connected
	**  servers to this network as well as the servers
	**  connected to me
	**/
	if (istat.is_serv > istat.is_m_serv)
		istat.is_m_serv = istat.is_serv;
	if (istat.is_myserv > istat.is_m_myserv)
		istat.is_m_myserv = istat.is_myserv;
#endif
	nextping = timeofday;
	sendto_flag(SCH_NOTICE, "Link with %s established. (%X%s)", inpath,
		    cptr->hopcount, (cptr->flags & FLAGS_ZIP) ? "z" : "");
	(void)add_to_client_hash_table(cptr->name, cptr);
	/* doesnt duplicate cptr->serv if allocted this struct already */
	(void)make_server(cptr);
	cptr->serv->up = me.name;
	cptr->serv->nline = aconf;
	cptr->serv->version = cptr->hopcount;   /* temporary location */
	cptr->hopcount = 1;			/* local server connection */
	cptr->serv->snum = find_server_num(cptr->name);
	cptr->serv->stok = 1;
#ifdef RUSNET_IRCD
	cptr->serv->crc = gen_crc(cptr->name);
/*
#ifdef USE_SERVICES
	if (strcasecmp(cptr->name, SERVICES_SERV) == 0)
		invincible = cptr->serv->crc;
#endif
*/
#endif
	cptr->flags |= FLAGS_CBURST;
	(void) add_to_server_hash_table(cptr->serv, cptr);
	Debug((DEBUG_NOTICE, "Server link established with %s V%X %d",
		cptr->name, cptr->serv->version, cptr->serv->stok));
	add_fd(cptr->fd, &fdas);
	/*
	** Remove all NDELAY locks depends on this server. -kmale
	*/
	release_locks_byserver(cptr->name,(long)(DELAYCHASETIMELIMIT));
#ifdef	USE_SERVICES
	check_services_butone(SERVICE_WANT_SERVER, cptr->name, cptr,
			      ":%s SERVER %s %d %s :%s", ME, cptr->name,
			      cptr->hopcount+1, cptr->serv->tok, cptr->info);
#endif
	sendto_flag(SCH_SERVER, "Sending SERVER %s (%d %s)", cptr->name,
		    1, cptr->info);
	/*
	** Old sendto_serv_but_one() call removed because we now
	** need to send different names to different servers
	** (domain name matching) Send new server to other servers.
	*/
	for (i = fdas.highest; i >= 0; i--)
	    {
		if (!(acptr = local[fdas.fd[i]]) || !IsServer(acptr) ||
		    acptr == cptr || IsMe(acptr))		    
			continue;
		if ((aconf = acptr->serv->nline) &&
		    !match(my_name_for_link(ME, aconf->port), cptr->name))
			continue;
		stok = cptr->serv->tok;
		if (split)
			sendto_one(acptr,":%s SERVER %s 2 %s :[%s] %s",
				   ME, cptr->name, stok,
				   cptr->sockhost, cptr->info);
		else
			sendto_one(acptr,":%s SERVER %s 2 %s :%s",
				   ME, cptr->name, stok, cptr->info);
	    }
	/*
	** Pass on my client information to the new server
	**
	** First, pass only servers (idea is that if the link gets
	** cancelled beacause the server was already there,
	** there are no NICK's to be cancelled...). Of course,
	** if cancellation occurs, all this info is sent anyway,
	** and I guess the link dies when a read is attempted...? --msa
	** 
	** Note: Link cancellation to occur at this point means
	** that at least two servers from my fragment are building
	** up connection this other fragment at the same time, it's
	** a race condition, not the normal way of operation...
	**
	** ALSO NOTE: using the get_client_name for server names--
	**	see previous *WARNING*!!! (Also, original inpath
	**	is destroyed...)
	*/
	aconf = cptr->serv->nline;
	for (acptr = &me; acptr; acptr = acptr->prev)
	    {
		/* acptr->from == acptr for acptr == cptr */
		if ((acptr->from == cptr) || !IsServer(acptr))
			continue;
		if (*mlname == '*' && match(mlname, acptr->name) == 0)
			continue;
		split = (MyConnect(acptr) &&
			 strcasecmp(acptr->name, acptr->sockhost));
		stok = acptr->serv->tok;
		if (split)
			sendto_one(cptr, ":%s SERVER %s %d %s :[%s] %s",
				   acptr->serv->up,
				   acptr->name, acptr->hopcount+1, stok,
				   acptr->sockhost, acptr->info);
		else
			sendto_one(cptr, ":%s SERVER %s %d %s :%s",
				   acptr->serv->up, acptr->name,
				   acptr->hopcount+1, stok, acptr->info);
	    }

	for (acptr = &me; acptr; acptr = acptr->prev)
	    {
		/* acptr->from == acptr for acptr == cptr */
		if (acptr->from == cptr)
			continue;
		if (IsPerson(acptr))
		    {
			/*
			** IsPerson(x) is true only when IsClient(x) is true.
			** These are only true when *BOTH* NICK and USER have
			** been received. -avalon
			*/
			if (*mlname == '*' &&
			    match(mlname, acptr->user->server) == 0)
				stok = me.serv->tok;
			else
				stok = acptr->user->servp->tok;
			send_umode(NULL, acptr, 0, SEND_UMODES, buf);
			sendto_one(cptr,"NICK %s %d %s %s %s %s :%s",
				   acptr->name, acptr->hopcount + 1,
				   acptr->user->username,
#ifdef RUSNET_IRCD
				   acptr->sockhost,
#else
				   acptr->user->host,
#endif
				   stok,
				   (*buf) ? buf : "+", acptr->info);
			if ((cptr->serv->version & SV_NJOIN) == 0)
				send_user_joins(cptr, acptr);
		    }
		else if (IsService(acptr) &&
			 match(acptr->service->dist, cptr->name) == 0)
		    {
			if (*mlname == '*' &&
			    match(mlname, acptr->service->server) == 0)
				stok = me.serv->tok;
			else
				stok = acptr->service->servp->tok;
			sendto_one(cptr, "SERVICE %s %s %s %d %d :%s",
				   acptr->name, stok, acptr->service->dist,
				   acptr->service->type, acptr->hopcount + 1,
				   acptr->info);
		    }
		/* the previous if does NOT catch all services.. ! */
	    }

	flush_connections(cptr->fd);

	/*
	** Last, pass all channels modes
	** only sending modes for LIVE channels.
	*/
	    {
		Reg	aChannel *chptr;
		for (chptr = channel; chptr; chptr = chptr->nextch)
			if (chptr->users)
			    {
				if (cptr->serv->version & SV_NJOIN)
					send_channel_members(cptr, chptr);
				send_channel_modes(cptr, chptr);
			    }
	    }

	cptr->flags &= ~FLAGS_CBURST;
#ifdef	ZIP_LINKS
 	/*
 	** some stats about the connect burst,
 	** they are slightly incorrect because of cptr->zip->outbuf.
 	*/
 	if ((cptr->flags & FLAGS_ZIP) && cptr->zip->out->total_in)
	  sendto_flag(SCH_NOTICE,
		      "Connect burst to %s: %lu, compressed: %lu (%3.1f%%)",
		      get_client_name(cptr, TRUE),
		      cptr->zip->out->total_in,cptr->zip->out->total_out,
 	    (float) 100*cptr->zip->out->total_out/cptr->zip->out->total_in);
#endif
	return 0;
}

int	m_reconnect(cptr, sptr, parc, parv)
aClient	*cptr, *sptr;
int	parc;
char	*parv[];
{
	aConfItem *aconf;
	aClient	*acptr = NULL;
	char	*name;
	int	i;

	if (IsRegistered(sptr))
		return exit_client(cptr, sptr, &me, "Already registered");

	if (parc < 3)
		return 1;

	name = parv[1];

	for (i = highest_fd; i >= 0; i--)
	     {
		if (!(acptr = local[i]) || !IsHeld(acptr) ||
		    bcmp((char *)&acptr->ip, (char *)&cptr->ip,
			 sizeof(acptr->ip)) || strcasecmp(acptr->name, name))
			continue;
		if (!(aconf = find_conf_name(name, CONF_CONNECT_SERVER|
					     CONF_ZCONNECT_SERVER)) ||
		    atoi(parv[2]) != acptr->receiveM)
			break;
		attach_confs(acptr, name, CONF_SERVER_MASK);
		acptr->flags &= ~FLAGS_HELD;
		acptr->fd = cptr->fd;
		cptr->fd = -2;
		SetUnknown(acptr);
		if (check_server(acptr, NULL, NULL, NULL, TRUE) < 0)
			break;
		sendto_flag(SCH_NOTICE, "%s has reconnected", 
			    get_client_name(acptr, TRUE));
		return exit_client(cptr, sptr, &me, "Reconnected");
	    }
	sendto_flag(SCH_NOTICE, "Reconnect from %s failed", 
		    get_client_name(cptr, TRUE));
	if (acptr)
		(void) exit_client(cptr, acptr, &me, "Reconnect failed");
	return exit_client(cptr, sptr, &me, "Reconnect failed");
}

/*
** m_info
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_info(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	char **text = infotext;

	if (IsServer(cptr) && check_link(cptr))
	    {
		sendto_one(sptr, rpl_str(RPL_TRYAGAIN, parv[0]),
			   "INFO");
		return 5;
	    }
	if (hunt_server(cptr,sptr,":%s INFO :%s",1,parc,parv) == HUNTED_ISME)
	    {
		while (*text)
			sendto_one(sptr, rpl_str(RPL_INFO, parv[0]), *text++);

		sendto_one(sptr, rpl_str(RPL_INFO, parv[0]), "");
		sendto_one(sptr,
			   ":%s %d %s :Birth Date: %s, compile # %s",
			   ME, RPL_INFO, parv[0], creation, generation);
		sendto_one(sptr, ":%s %d %s :On-line since %s",
			   ME, RPL_INFO, parv[0],
			   myctime(me.firsttime));
		sendto_one(sptr, rpl_str(RPL_ENDOFINFO, parv[0]));
		return 5;
	    }
	else
		return 10;
}

/*
** m_links
**	parv[0] = sender prefix
**	parv[1] = servername mask
** or
**	parv[0] = sender prefix
**	parv[1] = server to query 
**      parv[2] = servername mask
*/
int	m_links(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	aServer	*asptr;
	char	*mask;
	aClient	*acptr;

	if (parc > 2)
	    {
		if (IsServer(cptr) && check_link(cptr) && !IsOper(sptr))
		    {
			sendto_one(sptr, rpl_str(RPL_TRYAGAIN, parv[0]),
				   "LINKS");
			return 5;
		    }
		if (hunt_server(cptr, sptr, ":%s LINKS %s :%s", 1, parc, parv)
				!= HUNTED_ISME)
			return 5;
		mask = parv[2];
	    }
	else
		mask = parc < 2 ? NULL : parv[1];

	for (asptr = svrtop, (void)collapse(mask); asptr; asptr = asptr->nexts) 
	    {
		acptr = asptr->bcptr;
		if (!BadPtr(mask) && match(mask, acptr->name))
			continue;
		sendto_one(sptr, rpl_str(RPL_LINKS, parv[0]),
			   acptr->name, acptr->serv->up,
			   acptr->hopcount, (acptr->info[0] ? acptr->info :
			   "(Unknown Location)"));
	    }

	sendto_one(sptr, rpl_str(RPL_ENDOFLINKS, parv[0]),
		   BadPtr(mask) ? "*" : mask);
	return 2;
}

/*
** m_summon should be redefined to ":prefix SUMMON host user" so
** that "hunt_server"-function could be used for this too!!! --msa
** As of 2.7.1e, this was the case. -avalon
**
**	parv[0] = sender prefix
**	parv[1] = user
**	parv[2] = server
**	parv[3] = channel (optional)
*/
int	m_summon(cptr, sptr, parc, parv)
aClient *sptr, *cptr;
int	parc;
char	*parv[];
{
	char	*host, *user, *chname;
#ifdef	ENABLE_SUMMON
	char	hostbuf[17], namebuf[10], linebuf[10];
#  ifdef LEAST_IDLE
	char	linetmp[10], ttyname[15]; /* Ack */
	struct	stat stb;
	time_t	ltime = (time_t)0;
#  endif
	int	fd, flag = 0;
#endif

	if (parc < 2 || *parv[1] == '\0')
	    {
		sendto_one(sptr, err_str(ERR_NORECIPIENT, parv[0]), "SUMMON");
		return 1;
	    }
	user = parv[1];
	host = (parc < 3 || BadPtr(parv[2])) ? ME : parv[2];
	chname = (parc > 3) ? parv[3] : "*";
	/*
	** Summoning someone on remote server, find out which link to
	** use and pass the message there...
	*/
	parv[1] = user;
	parv[2] = host;
	parv[3] = chname;
	parv[4] = NULL;
	if (hunt_server(cptr, sptr, ":%s SUMMON %s %s %s", 2, parc, parv) ==
	    HUNTED_ISME)
	    {
#ifdef ENABLE_SUMMON
		if ((fd = utmp_open()) == -1)
		    {
			sendto_one(sptr, err_str(ERR_FILEERROR, parv[0]),
				   "open", UTMP);
			return 1;
		    }
#  ifndef LEAST_IDLE
		while ((flag = utmp_read(fd, namebuf, linebuf, hostbuf,
					 sizeof(hostbuf))) == 0) 
			if (StrEq(namebuf,user))
				break;
#  else
		/* use least-idle tty, not the first
		 * one we find in utmp. 10/9/90 Spike@world.std.com
		 * (loosely based on Jim Frost jimf@saber.com code)
		 */
		
		while ((flag = utmp_read(fd, namebuf, linetmp, hostbuf,
					 sizeof(hostbuf))) == 0)
		    {
			if (StrEq(namebuf,user))
			    {
				SPRINTF(ttyname,"/dev/%s",linetmp);
				if (stat(ttyname,&stb) == -1)
				    {
					sendto_one(sptr,
						   err_str(ERR_FILEERROR,
						   sptr->name),
						   "stat", ttyname);
					return 1;
				    }
				if (!ltime)
				    {
					ltime= stb.st_mtime;
					(void)strcpy(linebuf,linetmp);
				    }
				else if (stb.st_mtime > ltime) /* less idle */
				    {
					ltime= stb.st_mtime;
					(void)strcpy(linebuf,linetmp);
				    }
			    }
		    }
#  endif
		(void)utmp_close(fd);
#  ifdef LEAST_IDLE
		if (ltime == 0)
#  else
		if (flag == -1)
#  endif
			sendto_one(sptr, err_str(ERR_NOLOGIN, parv[0]), user);
		else
			summon(sptr, user, linebuf, chname);
#else
		sendto_one(sptr, err_str(ERR_SUMMONDISABLED, parv[0]));
#endif /* ENABLE_SUMMON */
	    }
	else
		return 3;
	return 2;
}


/*
** m_stats
**	parv[0] = sender prefix
**	parv[1] = statistics selector (defaults to Message frequency)
**	parv[2] = server name (current server defaulted, if omitted)
**
**	Currently supported are:
**		M = Message frequency (the old stat behaviour)
**		L = Local Link statistics
**		C = Report C and N configuration lines
*/
/*
** m_stats/stats_conf
**    Report N/C-configuration lines from this server. This could
**    report other configuration lines too, but converting the
**    status back to "char" is a bit akward--not worth the code
**    it needs...
**
**    Note:   The info is reported in the order the server uses
**	      it--not reversed as in ircd.conf!
*/

#ifdef RUSNET_IRCD
#define	REP_ARRAY_SIZE	19
#else
#define	REP_ARRAY_SIZE	17
#endif

static int report_array[REP_ARRAY_SIZE][3] = {
		{ CONF_ZCONNECT_SERVER,	  RPL_STATSCLINE, 'c'},
		{ CONF_CONNECT_SERVER,	  RPL_STATSCLINE, 'C'},
		{ CONF_NOCONNECT_SERVER,  RPL_STATSNLINE, 'N'},
		{ CONF_CLIENT,		  RPL_STATSILINE, 'I'},
		{ CONF_RCLIENT,		  RPL_STATSILINE, 'i'},
		{ CONF_OTHERKILL,	  RPL_STATSKLINE, 'k'},
		{ CONF_KILL,		  RPL_STATSKLINE, 'K'},
		{ CONF_QUARANTINED_SERVER,RPL_STATSQLINE, 'Q'},
		{ CONF_LEAF,		  RPL_STATSLLINE, 'L'},
		{ CONF_OPERATOR,	  RPL_STATSOLINE, 'O'},
		{ CONF_HUB,		  RPL_STATSHLINE, 'H'},
		{ CONF_LOCOP,		  RPL_STATSOLINE, 'o'},
		{ CONF_SERVICE,		  RPL_STATSSLINE, 'S'},
		{ CONF_VER,		  RPL_STATSVLINE, 'V'},
		{ CONF_BOUNCE,		  RPL_STATSBLINE, 'B'},
		{ CONF_DENY,		  RPL_STATSDLINE, 'D'},
#ifdef RUSNET_IRCD
		{ CONF_EXEMPT,		  RPL_STATSKLINE, 'E'},
		{ CONF_INTERFACE,	  RPL_STATSFLINE, 'F'},
#endif
		{ 0, 0, 0}
	};

static	void	report_configured_links(sptr, to, mask)
aClient *sptr;
char	*to;
int	mask;
{
	static	char	null[] = "<NULL>";
	aConfItem *tmp;
	int	*p, port;
	char	c, *host, *pass, *name;

#ifdef RUSNET_IRCD
	aClient *acptr;
	acptr = find_client(to, NULL);
#endif		
	for (tmp = (mask & (CONF_KILL|CONF_OTHERKILL)) ? kconf : conf;
	     tmp; tmp = tmp->next)
		if (tmp->status & mask)
		    {
			for (p = &report_array[0][0]; *p; p += 3)
				if (*p == tmp->status)
					break;
			if (!*p)
				continue;
			c = (char)*(p+2);
			host = BadPtr(tmp->host) ? null : tmp->host;
			pass = BadPtr(tmp->passwd) ? NULL : tmp->passwd;
			name = BadPtr(tmp->name) ? null : tmp->name;
			port = (int)tmp->port;
			/*
			 * On K/V/B/F lines the passwd contents can be
			 * displayed on STATS reply. 	-Vesa
			 */
			if (tmp->status == CONF_KILL
			    || tmp->status == CONF_OTHERKILL
			    || tmp->status == CONF_VER
#ifdef RUSNET_IRCD
			    || tmp->status == CONF_INTERFACE
#endif
			    || tmp->status == CONF_BOUNCE)
				sendto_one(sptr, rpl_str(p[1], to), c, host,
					   (pass) ? pass : null,
					   name, port, get_conf_class(tmp));
#ifdef RUSNET_IRCD
			else if (tmp->status == CONF_CONNECT_SERVER ||
				tmp->status == CONF_ZCONNECT_SERVER ||
				tmp->status == CONF_CLIENT ||
				tmp->status == CONF_RCLIENT)
				sendto_one(sptr, rpl_str(p[1], to), c, host,
					   (pass || !(IsOper(sptr) ||
					    IsServer(sptr))) ? "x" : null,
					   name, port, get_conf_class(tmp),
							tmp->localpref);

			else if (tmp->status == CONF_OPERATOR ||
				tmp->status == CONF_LOCOP)
				sendto_one(sptr, rpl_str(p[1], to), c, 
					    (acptr && IsAnOper(acptr)) ? host : "-",
					   (pass) ? "x" : null,
					   name, port, get_conf_class(tmp));
#endif
			else
				sendto_one(sptr, rpl_str(p[1], to), c, host,
					    (pass) ? "x" : null,
					   name, port, get_conf_class(tmp));
		    }
#ifdef RUSNET_IRCD
	if (mask & CONF_EXEMPT)
	    for (tmp = econf; tmp; tmp = tmp->next)
		if (tmp->status & mask)
		    {
			for (p = &report_array[0][0]; *p; p += 3)
				if (*p == tmp->status)
					break;
			if (!*p)
				continue;
			c = (char)*(p+2);
			host = BadPtr(tmp->host) ? null : tmp->host;
			pass = BadPtr(tmp->passwd) ? NULL : tmp->passwd;
			name = BadPtr(tmp->name) ? null : tmp->name;
			port = (int)tmp->port;
			sendto_one(sptr, rpl_str(p[1], to), c, host,
					   (pass) ? pass : null,
					   name, port, get_conf_class(tmp));
		    }
#endif
	return;
}

static	void	report_ping(sptr, to)
aClient	*sptr;
char	*to;
{
	aConfItem *tmp;
	aCPing	*cp;

	for (tmp = conf; tmp; tmp = tmp->next)
		if ((cp = tmp->ping) && cp->lseq)
		    {
			if (strcasecmp(tmp->name, tmp->host))
				SPRINTF(buf,"%s[%s]",tmp->name, tmp->host);
			else
				(void)strcpy(buf, tmp->name);
			sendto_one(sptr, rpl_str(RPL_STATSPING, to),
				   buf, cp->lseq, cp->lrecvd,
				   cp->ping / (cp->recvd ? cp->recvd : 1),
				   tmp->pref);
			sendto_flag(SCH_DEBUG, "%s: %d", buf, cp->seq);
		    }
	return;
}

int	m_stats(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	static	char	Lformat[]  = ":%s %d %s %s %u %u %u %u %u :%u";
	struct	Message	*mptr;
	aClient	*acptr;
	char	stat = parc > 1 ? parv[1][0] : '\0';
	Reg	int	i;
	int	wilds, doall;
	char	*name, *cm;
#ifdef STATS_RESTRICTED
	if (!IsAnOper(sptr) && !(stat == 'c' || stat == 'C' ||
				stat == 'H' || stat == 'h' ||
				stat == 'o' || stat == 'O' ||
				stat == 'U' || stat == 'u'))
	{
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES, parv[0]));
		return 2;
	}
#endif
	if (IsServer(cptr) &&
	    (stat != 'd' && stat != 'p' && stat != 'q' && stat != 's' &&
	     stat != 'u' && stat != 'v') &&
	    !((stat == 'o' || stat == 'c') && IsOper(sptr)))
	    {
		if (check_link(cptr))
		    {
			sendto_one(sptr, rpl_str(RPL_TRYAGAIN, parv[0]),
				   "STATS");
			return 5;
		    }
	    }
	if (parc == 3)
	    {
		if (hunt_server(cptr, sptr, ":%s STATS %s %s",
				2, parc, parv) != HUNTED_ISME)
			return 5;
	    }
	else if (parc >= 3)
	    {
		if (hunt_server(cptr, sptr, ":%s STATS %s %s %s",
				2, parc, parv) != HUNTED_ISME)
			return 5;
	    }

	name = (parc > 2) ? parv[2] : ME;
	cm = (parc > 3) ? parv[3]: name;
	doall = !match(name, ME) && !match(cm, ME);
	wilds = index(cm, '*') || index(cm, '?');

	switch (stat)
	{
	case 'L' : case 'l' :
		/*
		 * send info about connections which match, or all if the
		 * mask matches ME.  Only restrictions are on those who
		 * are invisible not being visible to 'foreigners' who use
		 * a wild card based search to list it.
		 */
		if (doall || wilds)
		    {
			for (i = 0; i <= highest_fd; i++)
			    {
				if (!(acptr = local[i]))
					continue;
				if (IsPerson(acptr) && !(MyConnect(sptr) 
				    && IsAnOper(sptr)) && acptr != sptr)
					continue;
				if (wilds && match(cm, acptr->name))
					continue;
				sendto_one(cptr, Lformat, ME,
					RPL_STATSLINKINFO, parv[0],
					get_client_name(acptr, isupper(stat)),
					(int)DBufLength(&acptr->sendQ),
					(int)acptr->sendM, (int)acptr->sendK,
					(int)acptr->receiveM, 
					(int)acptr->receiveK,
					timeofday - acptr->firsttime);
			    }
		    }
		else
		    {
			if ((acptr = find_client(cm, NULL)))
				sendto_one(cptr, Lformat, ME,
					RPL_STATSLINKINFO, parv[0],
					get_client_name(acptr, isupper(stat)),
					(int)DBufLength(&acptr->sendQ),
					(int)acptr->sendM, (int)acptr->sendK,
					(int)acptr->receiveM,
					(int)acptr->receiveK,
					timeofday - acptr->firsttime);
			
		    }
		break;
#if defined(USE_IAUTH)
	case 'a' : case 'A' : /* iauth configuration */
		report_iauth_conf(sptr, parv[0]);
		break;
#endif
	case 'B' : case 'b' : /* B conf lines */
		report_configured_links(cptr, parv[0], CONF_BOUNCE);
		break;
	case 'c' : case 'C' : /* C and N conf lines */
		report_configured_links(cptr, parv[0], CONF_CONNECT_SERVER|
					CONF_ZCONNECT_SERVER|
					CONF_NOCONNECT_SERVER);
		break;
	case 'd' : case 'D' : /* defines */
		send_defines(cptr, parv[0]);
		break;
#ifdef RUSNET_IRCD
	case 'F' : case 'f' : /* F conf lines */
		report_configured_links(cptr, parv[0], CONF_INTERFACE);
		break;
#endif
	case 'H' : case 'h' : /* H, L and D conf lines */
		report_configured_links(cptr, parv[0],
					CONF_HUB|CONF_LEAF|CONF_DENY);
		break;
	case 'I' : case 'i' : /* I (and i) conf lines */
		report_configured_links(cptr, parv[0],
					CONF_CLIENT|CONF_RCLIENT);
		break;
	case 'K' : case 'k' : /* K lines */
		report_configured_links(cptr, parv[0],
					(CONF_KILL|CONF_OTHERKILL
#ifdef RUSNET_IRCD
							|CONF_EXEMPT
#endif
									));
		break;
	case 'M' : case 'm' : /* commands use/stats */
		for (mptr = msgtab; mptr->cmd; mptr++)
			if (mptr->count)
				sendto_one(cptr, rpl_str(RPL_STATSCOMMANDS,
					   parv[0]), mptr->cmd,
					   mptr->count, mptr->bytes,
					   mptr->rcount);
		break;
	case 'o' : case 'O' : /* O (and o) lines */
		report_configured_links(cptr, parv[0], CONF_OPS);
		break;
	case 'p' : case 'P' : /* ircd ping stats */
		report_ping(sptr, parv[0]);
		break;
	case 'Q' : case 'q' : /* Q lines */
		report_configured_links(cptr,parv[0],CONF_QUARANTINED_SERVER);
		break;
	case 'R' : case 'r' : /* usage */
		send_usage(cptr, parv[0]);
		break;
	case 'S' : case 's' : /* S lines */
		report_configured_links(cptr, parv[0], CONF_SERVICE);
		break;
	case 'T' : case 't' : /* various statistics */
		tstats(cptr, parv[0]);
		break;
	case 'U' : case 'u' : /* uptime */
	    {
		register time_t now;

		now = timeofday - me.since;
		sendto_one(sptr, rpl_str(RPL_STATSUPTIME, parv[0]),
			   now/86400, (now/3600)%24, (now/60)%60, now%60);
		break;
	    }
	case 'V' : case 'v' : /* V conf lines */
		report_configured_links(cptr, parv[0], CONF_VER);
		break;
	case 'X' : case 'x' : /* lists */
#ifdef	DEBUGMODE
		send_listinfo(cptr, parv[0]);
#endif
		break;
	case 'Y' : case 'y' : /* Y lines */
		report_classes(cptr, parv[0]);
		break;
	case 'Z' : 	      /* memory use (OPER only) */
		if (MyOper(sptr))
			count_memory(cptr, parv[0], 1);
		else
			sendto_one(sptr, err_str(ERR_NOPRIVILEGES, parv[0]));
		break;
	case 'z' :	      /* memory use */
		count_memory(cptr, parv[0], 0);
		break;
	default :
		stat = '*';
		break;
	}
	sendto_one(cptr, rpl_str(RPL_ENDOFSTATS, parv[0]), stat);
	return 2;
    }

/*
** m_users
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_users(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
#ifdef ENABLE_USERS
	char	namebuf[10],linebuf[10],hostbuf[17];
	int	fd, flag = 0;
#endif

	if (hunt_server(cptr,sptr,":%s USERS :%s",1,parc,parv) == HUNTED_ISME)
	    {
#ifdef ENABLE_USERS
		if ((fd = utmp_open()) == -1)
		    {
			sendto_one(sptr, err_str(ERR_FILEERROR, parv[0]),
				   "open", UTMP);
			return 1;
		    }

		sendto_one(sptr, rpl_str(RPL_USERSSTART, parv[0]));
		while (utmp_read(fd, namebuf, linebuf,
				 hostbuf, sizeof(hostbuf)) == 0)
		    {
			flag = 1;
			sendto_one(sptr, rpl_str(RPL_USERS, parv[0]),
				   namebuf, linebuf, hostbuf);
		    }
		if (flag == 0) 
			sendto_one(sptr, rpl_str(RPL_NOUSERS, parv[0]));

		sendto_one(sptr, rpl_str(RPL_ENDOFUSERS, parv[0]));
		(void)utmp_close(fd);
#else
		sendto_one(sptr, err_str(ERR_USERSDISABLED, parv[0]));
#endif
	    }
	else
		return 3;
	return 2;
}

/*
** Note: At least at protocol level ERROR has only one parameter,
** although this is called internally from other functions
** --msa
**
**	parv[0] = sender prefix
**	parv[*] = parameters
*/
int	m_error(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	Reg	char	*para;

	para = (parc > 1 && *parv[1] != '\0') ? parv[1] : "<>";

	Debug((DEBUG_ERROR,"Received ERROR message from %s: %s",
	      sptr->name, para));
	/*
	** Ignore error messages generated by normal user clients
	** (because ill-behaving user clients would flood opers
	** screen otherwise). Pass ERROR's from other sources to
	** the local operator...
	*/
	if (IsPerson(cptr) || IsUnknown(cptr) || IsService(cptr))
		return 2;
	if (cptr == sptr)
		sendto_flag(SCH_ERROR, "from %s -- %s",
			   get_client_name(cptr, FALSE), para);
	else
		sendto_flag(SCH_ERROR, "from %s via %s -- %s",
			   sptr->name, get_client_name(cptr,FALSE), para);
	return 2;
    }

/*
** m_help
**	parv[0] = sender prefix
*/
int	m_help(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	int i;

	for (i = 0; msgtab[i].cmd; i++)
	  sendto_one(sptr,":%s NOTICE %s :%s",
		     ME, parv[0], msgtab[i].cmd);
	return 2;
    }

/*
 * parv[0] = sender
 * parv[1] = host/server mask.
 * parv[2] = server to query
 */
int	 m_lusers(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	int	s_count = 0,	/* server */
		c_count = 0,	/* client (visible) */
		u_count = 0,	/* unknown */
		i_count = 0,	/* invisible client */
		o_count = 0,	/* oparator */
		v_count = 0;	/* service */
	int	m_client = 0,	/* my clients */
		m_server = 0,	/* my server links */
		m_service = 0;	/* my services */
	aClient *acptr;

	if (parc > 2)
		if (hunt_server(cptr, sptr, ":%s LUSERS %s :%s", 2, parc, parv)
		    != HUNTED_ISME)
			return 3;

	if (parc == 1 || !MyConnect(sptr))
	    {
		sendto_one(sptr, rpl_str(RPL_LUSERCLIENT, parv[0]),
			   istat.is_user[0] + istat.is_user[1],
			   istat.is_service, istat.is_serv);
		if (istat.is_oper)
			sendto_one(sptr, rpl_str(RPL_LUSEROP, parv[0]),
				   istat.is_oper);
		if (istat.is_unknown > 0)
			sendto_one(sptr, rpl_str(RPL_LUSERUNKNOWN, parv[0]),
				   istat.is_unknown);
		if (istat.is_chan)
			sendto_one(sptr, rpl_str(RPL_LUSERCHANNELS, parv[0]),
				   istat.is_chan);
		sendto_one(sptr, rpl_str(RPL_LUSERME, parv[0]),
			   istat.is_myclnt, istat.is_myservice,
			   istat.is_myserv);
#ifdef EXTRA_STATISTICS
		sendto_one(sptr, rpl_str(RPL_LOCALUSERS, parv[0]),
				istat.is_myclnt, istat.is_m_myclnt);
		sendto_one(sptr, rpl_str(RPL_GLOBALUSERS, parv[0]),
				istat.is_user[1] + istat.is_user[0],
							istat.is_m_users);
#endif
		return 2;
	    }
	(void)collapse(parv[1]);
	for (acptr = client; acptr; acptr = acptr->next)
	    {
		if (!IsServer(acptr) && acptr->user)
		    {
			if (match(parv[1], acptr->user->server))
				continue;
		    }
		else
      			if (match(parv[1], acptr->name))
				continue;

		switch (acptr->status)
		{
		case STAT_SERVER:
			if (MyConnect(acptr))
				m_server++;
			/* flow thru */
		case STAT_ME:
			s_count++;
			break;
		case STAT_SERVICE:
			if (MyConnect(acptr))
				m_service++;
			v_count++;
			break;
		case STAT_CLIENT:
			if (IsOper(acptr))
				o_count++;
#ifdef	SHOW_INVISIBLE_LUSERS
			if (MyConnect(acptr))
		  		m_client++;
			if (!IsInvisible(acptr))
				c_count++;
			else
				i_count++;
#else
			if (MyConnect(acptr))
			    {
				if (IsInvisible(acptr))
				    {
					if (IsAnOper(sptr))
						m_client++;
				    }
				else
					m_client++;
			    }
	 		if (!IsInvisible(acptr))
				c_count++;
			else
				i_count++;
#endif
			break;
		default:
			u_count++;
			break;
	 	}
	     }
#ifndef	SHOW_INVISIBLE_LUSERS
	if (IsAnOper(sptr) && i_count)
#endif
	sendto_one(sptr, rpl_str(RPL_LUSERCLIENT, parv[0]),
		   c_count + i_count, v_count, s_count);
#ifndef	SHOW_INVISIBLE_LUSERS
	else
		sendto_one(sptr, rpl_str(RPL_LUSERCLIENT, parv[0]),
			   c_count, v_count, s_count);
#endif
	if (o_count)
		sendto_one(sptr, rpl_str(RPL_LUSEROP, parv[0]), o_count);
	if (u_count > 0)
		sendto_one(sptr, rpl_str(RPL_LUSERUNKNOWN, parv[0]), u_count);
	if ((c_count = count_channels(sptr))>0)
		sendto_one(sptr, rpl_str(RPL_LUSERCHANNELS, parv[0]),
			   count_channels(sptr));
	sendto_one(sptr, rpl_str(RPL_LUSERME, parv[0]), m_client, m_service,
		   m_server);
#if 0	/* it sends local values which is probably not that you wanted  -erra */
/* #ifdef EXTRA_STATISTICS */
	sendto_one(sptr, rpl_str(RPL_LOCALUSERS, parv[0]),
			istat.is_myclnt, istat.is_m_myclnt);
	sendto_one(sptr, rpl_str(RPL_GLOBALUSERS, parv[0]),
                         c_count + i_count, istat.is_m_users);
#endif
	return 2;
    }

  
/***********************************************************************
 * m_connect() - Added by Jto 11 Feb 1989
 ***********************************************************************/

/*
** m_connect
**	parv[0] = sender prefix
**	parv[1] = servername
**	parv[2] = port number
**	parv[3] = remote server
*/
int	m_connect(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	int	port, tmpport, retval;
	aConfItem *aconf;
	aClient *acptr;

	if (parc > 3 && IsLocOp(sptr))
	    {
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES, parv[0]));
		return 1;
	    }

	if (hunt_server(cptr,sptr,":%s CONNECT %s %s :%s",
		       3,parc,parv) != HUNTED_ISME)
		return 1;

	if (parc < 3 || *parv[1] == '\0')
	    {
		sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]),
			   "CONNECT");
		return 0;
	    }

	if ((acptr = find_name(parv[1], NULL))
	    || (acptr = find_mask(parv[1], NULL)))
	    {
		sendto_one(sptr, ":%s NOTICE %s :Connect: Server %s %s %s.",
			   ME, parv[0], parv[1], "already exists from",
			   acptr->from->name);
		return 0;
	    }

	for (aconf = conf; aconf; aconf = aconf->next)
		if ((aconf->status == CONF_CONNECT_SERVER ||
		     aconf->status == CONF_ZCONNECT_SERVER) &&
		    match(parv[1], aconf->name) == 0)
		  break;
	/* Checked first servernames, then try hostnames. */
	if (!aconf)
		for (aconf = conf; aconf; aconf = aconf->next)
			if ((aconf->status == CONF_CONNECT_SERVER ||
			     aconf->status == CONF_ZCONNECT_SERVER) &&
			    (match(parv[1], aconf->host) == 0 ||
			     match(parv[1], index(aconf->host, '@')+1) == 0))
		  		break;

	if (!aconf)
	    {
	      sendto_one(sptr,
			 "NOTICE %s :Connect: Host %s not listed in ircd.conf",
			 parv[0], parv[1]);
	      return 0;
	    }
	/*
	** Get port number from user, if given. If not specified,
	** use the default form configuration structure. If missing
	** from there, then use the precompiled default.
	*/
	tmpport = port = aconf->port;
	if ((port = atoi(parv[2])) <= 0)
	    {
		sendto_one(sptr, "NOTICE %s :Connect: Illegal port number",
			   parv[0]);
		return 0;
	    }
	else if (port <= 0)
	    {
		sendto_one(sptr, ":%s NOTICE %s :Connect: missing port number",
			   ME, parv[0]);
		return 0;
	    }
	/*
	** Notify all operators about remote connect requests
	*/
	if (!IsAnOper(cptr))
	    {
		sendto_ops_butone(NULL, &me,
				  ":%s WALLOPS :Remote CONNECT %s %s from %s",
				   ME, parv[1], parv[2] ? parv[2] : "",
				   get_client_name(sptr,FALSE));
#if defined(USE_SYSLOG) && defined(SYSLOG_CONNECT)
		syslog(LOG_DEBUG, "CONNECT From %s : %s %d", parv[0],
		       parv[1], parv[2] ? parv[2] : "");
#endif
	    }
	aconf->port = port;
	switch (retval = connect_server(aconf, sptr, NULL))
	{
	case 0:
		sendto_one(sptr, ":%s NOTICE %s :*** Connecting to %s[%s].",
			   ME, parv[0], aconf->host, aconf->name);
		sendto_flag(SCH_NOTICE, "Connecting to %s[%s] by %s",
			    aconf->host, aconf->name,
			    get_client_name(sptr, FALSE));
		break;
	case -1:
		sendto_one(sptr, ":%s NOTICE %s :*** Couldn't connect to %s.",
			   ME, parv[0], aconf->host);
		sendto_flag(SCH_NOTICE, "Couldn't connect to %s by %s",
			    aconf->host, get_client_name(sptr, FALSE));
		break;
	case -2:
		sendto_one(sptr, ":%s NOTICE %s :*** Host %s is unknown.",
			   ME, parv[0], aconf->host);
		sendto_flag(SCH_NOTICE, "Connect by %s to unknown host %s",
			    get_client_name(sptr, FALSE), aconf->host);
		break;
	default:
		sendto_one(sptr,
			   ":%s NOTICE %s :*** Connection to %s failed: %s",
			   ME, parv[0], aconf->host, strerror(retval));
		sendto_flag(SCH_NOTICE, "Connection to %s by %s failed: %s",
			    aconf->host, get_client_name(sptr, FALSE),
			    strerror(retval));
	}
	aconf->port = tmpport;
	return 0;
    }

/*
** m_wallops (write to *all* opers currently online)
**	parv[0] = sender prefix
**	parv[1] = message text
*/
int	m_wallops(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	char	*message, *pv[4];

	message = parc > 1 ? parv[1] : NULL;

	if (BadPtr(message))
	    {
		sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]),
			   "WALLOPS");
		return 1;
	    }

	if (!IsServer(sptr))
	    {
		pv[0] = parv[0];
		pv[1] = "+wallops";
		pv[2] = message;
		pv[3] = NULL;
		return m_private(cptr, sptr, 3, pv);
	    }
	sendto_ops_butone(IsServer(cptr) ? cptr : NULL, sptr,
			":%s WALLOPS :%s", parv[0], message);
#ifdef	USE_SERVICES
	check_services_butone(SERVICE_WANT_WALLOP, NULL, sptr,
			      ":%s WALLOP :%s", parv[0], message);
#endif
	return 2;
    }

/*
** m_time
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_time(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	if (hunt_server(cptr,sptr,":%s TIME :%s",1,parc,parv) == HUNTED_ISME)
		sendto_one(sptr, rpl_str(RPL_TIME, parv[0]), ME, date((long)0));
	return 2;
}


/*
** m_admin
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_admin(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
    {
	aConfItem *aconf;

	if (IsRegistered(cptr) &&	/* only local query for unregistered */
	    hunt_server(cptr,sptr,":%s ADMIN :%s",1,parc,parv) != HUNTED_ISME)
		return 3;
	if ((aconf = find_admin()) && aconf->host && aconf->passwd
	    && aconf->name)
	    {
		sendto_one(sptr, rpl_str(RPL_ADMINME, parv[0]), ME);
		sendto_one(sptr, rpl_str(RPL_ADMINLOC1, parv[0]), aconf->host);
		sendto_one(sptr, rpl_str(RPL_ADMINLOC2, parv[0]),
			   aconf->passwd);
		sendto_one(sptr, rpl_str(RPL_ADMINEMAIL, parv[0]),
			   aconf->name);
	    }
	else
		sendto_one(sptr, err_str(ERR_NOADMININFO, parv[0]), ME);
	return 2;
    }

#if defined(OPER_REHASH) || defined(LOCOP_REHASH)
/*
** m_rehash
**
*/
int	m_rehash(cptr, sptr, parc, parv)
aClient	*cptr, *sptr;
int	parc;
char	*parv[];
{
	sendto_one(sptr, rpl_str(RPL_REHASHING, parv[0]),
		   mybasename(configfile));
	sendto_flag(SCH_NOTICE,
		    "%s is rehashing Server config file", parv[0]);
#ifdef USE_SYSLOG
	syslog(LOG_INFO, "REHASH From %s\n", get_client_name(sptr, FALSE));
#endif
	return rehash(cptr, sptr, (parc > 1) ? ((*parv[1] == 'q')?2:0) : 0);
}
#endif

#if defined(OPER_RESTART) || defined(LOCOP_RESTART)
/*
** m_restart
**
*/
int	m_restart(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	aClient	*acptr;
	Reg	int	i;
	char	killer[HOSTLEN * 2 + USERLEN + 5];
#ifdef RUSNET_IRCD
	strcpy(killer, get_client_xname(sptr, FALSE));
	SPRINTF(buf, "RESTART by %s", get_client_xname(sptr, FALSE));
#else
	strcpy(killer, get_client_name(sptr, FALSE));
	SPRINTF(buf, "RESTART by %s", get_client_name(sptr, FALSE));
#endif
	for (i = 0; i <= highest_fd; i++)
	    {
		if (!(acptr = local[i]))
			continue;
		if (IsClient(acptr) || IsService(acptr))
		    {
			sendto_one(acptr,
				   ":%s NOTICE %s :Server Restarting. %s",
				   ME, acptr->name, killer);
			acptr->exitc = EXITC_DIE;
			if (IsClient(acptr))
				exit_client(acptr, acptr, &me,
					    "Server Restarting");
			/* services are kept for logging purposes */
		    }
		else if (IsServer(acptr))
			sendto_one(acptr, ":%s ERROR :Restarted by %s",
				   ME, killer);
	    }
	flush_connections(me.fd);

	server_reboot(buf);
	/*NOT REACHED*/
	return 0;
}
#endif

/*
** m_trace
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_trace(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	int	i;
	Reg	aClient	*acptr;
	aClass	*cltmp;
	char	*tname;
	int	doall, link_s[MAXCONNECTIONS], link_u[MAXCONNECTIONS];
	int	wilds, dow;

	if (parc > 1)
		tname = parv[1];
	else
		tname = ME;

	switch (hunt_server(cptr, sptr, ":%s TRACE :%s", 1, parc, parv))
	{
	case HUNTED_PASS: /* note: gets here only if parv[1] exists */
	    {
		aClient	*ac2ptr;

		ac2ptr = next_client(client, parv[1]);
		sendto_one(sptr, rpl_str(RPL_TRACELINK, parv[0]),
			   version, debugmode, tname, ac2ptr->from->name,
			   ac2ptr->from->serv->version,
			   (ac2ptr->from->flags & FLAGS_ZIP) ? "z" : "",
			   timeofday - ac2ptr->from->firsttime,
			   (int)DBufLength(&ac2ptr->from->sendQ),
			   (int)DBufLength(&sptr->from->sendQ));
		return 5;
	    }
	case HUNTED_ISME:
		break;
	default:
		return 1;
	}

	doall = (parv[1] && (parc > 1)) ? !match(tname, ME): TRUE;
	wilds = !parv[1] || index(tname, '*') || index(tname, '?');
	dow = wilds || doall;

	if (doall) {
		for (i = 0; i < MAXCONNECTIONS; i++)
			link_s[i] = 0, link_u[i] = 0;
		for (acptr = client; acptr; acptr = acptr->next)
#ifdef	SHOW_INVISIBLE_LUSERS
			if (IsPerson(acptr))
				link_u[acptr->from->fd]++;
#else
			if (IsPerson(acptr) &&
			    (!IsInvisible(acptr) || IsOper(sptr)))
				link_u[acptr->from->fd]++;
#endif
			else if (IsServer(acptr))
				link_s[acptr->from->fd]++;
	}

	/* report all direct connections */
	
	for (i = 0; i <= highest_fd; i++)
	    {
		char	*name;
		int	class;

		if (!(acptr = local[i])) /* Local Connection? */
			continue;
		if (IsPerson(acptr) && IsInvisible(acptr) && dow &&
		    !(MyConnect(sptr) && IsAnOper(sptr)) &&
		    !IsAnOper(acptr) && (acptr != sptr))
			continue;
		if (!doall && wilds && match(tname, acptr->name))
			continue;
		if (!dow && strcasecmp(tname, acptr->name))
			continue;
#ifdef RUSNET_IRCD
		name = get_client_xname(acptr, IsAnOper(sptr));
#else
		name = get_client_name(acptr,FALSE);
#endif
		class = get_client_class(acptr);

		switch(acptr->status)
		{
		case STAT_CONNECTING:
			sendto_one(sptr, rpl_str(RPL_TRACECONNECTING,
				   parv[0]), class, name);
			break;
		case STAT_HANDSHAKE:
			sendto_one(sptr, rpl_str(RPL_TRACEHANDSHAKE, parv[0]),
				   class, name);
			break;
		case STAT_ME:
			break;
		case STAT_UNKNOWN:
			if (IsAnOper(sptr) || (MyPerson(sptr) && SendWallops(sptr)))
				sendto_one(sptr,
					   rpl_str(RPL_TRACEUNKNOWN, parv[0]),
					   class, name);
			break;
		case STAT_CLIENT:
			/* Only opers see users if there is a wildcard
			 * but anyone can see all the opers.
			 */

			if (IsAnOper(acptr))
				sendto_one(sptr,
					   rpl_str(RPL_TRACEOPERATOR, parv[0]),
					   class, name);
			else if (!dow || (MyConnect(sptr) && IsAnOper(sptr)))
				sendto_one(sptr,
					   rpl_str(RPL_TRACEUSER, parv[0]),
					   class, name);
			break;
		case STAT_SERVER:
			if (acptr->serv->user)
				sendto_one(sptr, rpl_str(RPL_TRACESERVER,
					   parv[0]), class, link_s[i],
					   link_u[i], name, acptr->serv->by,
					   acptr->serv->user->username,
					   acptr->serv->user->host,
					   acptr->serv->version,
					   (acptr->flags & FLAGS_ZIP) ?"z":"");
			else
				sendto_one(sptr, rpl_str(RPL_TRACESERVER,
					   parv[0]), class, link_s[i],
					   link_u[i], name,
					   *(acptr->serv->by) ?
					   acptr->serv->by : "*", "*", ME,
					   acptr->serv->version,
					   (acptr->flags & FLAGS_ZIP) ?"z":"");
			break;
		case STAT_RECONNECT:
			sendto_one(sptr, rpl_str(RPL_TRACERECONNECT, parv[0]),
				   class, name);
			break;
		case STAT_SERVICE:
			sendto_one(sptr, rpl_str(RPL_TRACESERVICE, parv[0]),
				   class, name, acptr->service->type,
				   acptr->service->wants);
			break;
		case STAT_LOG:
			sendto_one(sptr, rpl_str(RPL_TRACELOG, parv[0]),
				   ME, acptr->port);
			break;
		default: /* ...we actually shouldn't come here... --msa */
			sendto_one(sptr, rpl_str(RPL_TRACENEWTYPE, parv[0]),
				   name);
			break;
		}
	    }

	/*
	 * Add these lines to summarize the above which can get rather long
	 * and messy when done remotely - Avalon
	 */
	if (IsPerson(sptr) && SendWallops(sptr))
	    for (cltmp = FirstClass(); doall && cltmp; cltmp = NextClass(cltmp))
		if (Links(cltmp) > 0)
			sendto_one(sptr, rpl_str(RPL_TRACECLASS, parv[0]),
				   Class(cltmp), Links(cltmp));
	sendto_one(sptr, rpl_str(RPL_TRACEEND, parv[0]), tname, version,
		   debugmode);
	return 2;
    }

/*
** m_motd
**	parv[0] = sender prefix
**	parv[1] = servername
*/
int	m_motd(cptr, sptr, parc, parv)
aClient *cptr, *sptr;
int	parc;
char	*parv[];
{
#ifdef	CACHED_MOTD
	register aMotd *temp;
	struct tm *tm;
#else
	int	fd;
	char	line[512], *head, *tail;
	Reg	char	 *tmp;
	struct	stat	sb;
	struct	tm	*tm;
#endif

	if (check_link(cptr))
	    {
		sendto_one(sptr, rpl_str(RPL_TRYAGAIN, parv[0]), "MOTD");
		return 5;
	    }
	if (hunt_server(cptr, sptr, ":%s MOTD :%s", 1,parc,parv)!=HUNTED_ISME)
		return 5;
#ifdef CACHED_MOTD
	tm = &motd_tm;
	if (motd == NULL)
	    {
		sendto_one(sptr, err_str(ERR_NOMOTD, parv[0]));
		return 1;
	    }
	sendto_one(sptr, rpl_str(RPL_MOTDSTART, parv[0]), ME);
	sendto_one(sptr, ":%s %d %s :- %d/%d/%d %d:%02d", ME, RPL_MOTD,
		   parv[0], tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year,
		   tm->tm_hour, tm->tm_min);
	temp = motd;
	for(temp=motd;temp != NULL;temp = temp->next)
		sendto_one(sptr, rpl_str(RPL_MOTD, parv[0]), temp->line);
	sendto_one(sptr, rpl_str(RPL_ENDOFMOTD, parv[0]));
	return 2;
#else
	/*
	 * stop NFS hangs...most systems should be able to open a file in
	 * 3 seconds. -avalon (curtesy of wumpus)
	 */
	(void)alarm(3);
	fd = open(IRCDMOTD_PATH, O_RDONLY);
	(void)alarm(0);
	if (fd == -1)
	    {
		sendto_one(sptr, err_str(ERR_NOMOTD, parv[0]));
		return 1;
	    }
	(void)fstat(fd, &sb);
	sendto_one(sptr, rpl_str(RPL_MOTDSTART, parv[0]), ME);
	tm = localtime(&sb.st_mtime);
	sendto_one(sptr, ":%s %d %s :- %d/%d/%d %d:%02d", ME, RPL_MOTD,
		   parv[0], tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year,
		   tm->tm_hour, tm->tm_min);
	(void)dgets(-1, line, 0, &head, &tail); /* initialize line */
	while (dgets(fd, line, sizeof(line)-1, &head, &tail) > 0)
	    {
		if ((tmp = (char *)index(line,'\n')))
			*tmp = '\0';
		if ((tmp = (char *)index(line,'\r')))
			*tmp = '\0';
		sendto_one(sptr, rpl_str(RPL_MOTD, parv[0]), line);
	    }
	sendto_one(sptr, rpl_str(RPL_ENDOFMOTD, parv[0]));
	(void)close(fd);
	return 2;
#endif	/* CACHED_MOTD */
}

/*
** m_close - added by Darren Reed Jul 13 1992.
*/
int	m_close(cptr, sptr, parc, parv)
aClient	*cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	aClient	*acptr;
	Reg	int	i;
	int	closed = 0;

	for (i = highest_fd; i; i--)
	    {
		if (!(acptr = local[i]))
			continue;
		if (!IsUnknown(acptr) && !IsConnecting(acptr) &&
		    !IsHandshake(acptr))
			continue;
		sendto_one(sptr, rpl_str(RPL_CLOSING, parv[0]),
			   get_client_name(acptr, TRUE), acptr->status);
		(void)exit_client(acptr, acptr, &me, "Oper Closing");
		closed++;
	    }
	sendto_one(sptr, rpl_str(RPL_CLOSEEND, parv[0]), closed);
	return 1;
}

#if defined(OPER_DIE) || defined(LOCOP_DIE)
int	m_die(cptr, sptr, parc, parv)
aClient	*cptr, *sptr;
int	parc;
char	*parv[];
{
	Reg	aClient	*acptr;
	Reg	int	i;
	char	killer[HOSTLEN * 2 + USERLEN + 5];

	strcpy(killer, get_client_name(sptr, TRUE));
	for (i = 0; i <= highest_fd; i++)
	    {
		if (!(acptr = local[i]))
			continue;
		if (IsClient(acptr) || IsService(acptr))
		    {
			sendto_one(acptr,
				   ":%s NOTICE %s :Server Terminating. %s",
				   ME, acptr->name, killer);
			acptr->exitc = EXITC_DIE;
			if (IsClient(acptr))
				exit_client(acptr, acptr, &me, "Server died");
			/* services are kept for logging purposes */
		    }
		else if (IsServer(acptr))
			sendto_one(acptr, ":%s ERROR :Terminated by %s",
				   ME, killer);
	    }
	flush_connections(me.fd);
	(void)s_die(0);
	return 0;
}
#endif

/*
** storing server names in User structures is a real waste,
** the following functions change it to only store a pointer.
** A better way might be to store in Server structure and use servp. -krys
*/

static char	**server_name = NULL;
static int	server_max = 0, server_num = 0;

/*
** find_server_string
**
** Given an index, this will return a pointer to the corresponding
** (already allocated) string
*/
char *
find_server_string(snum)
int snum;
{
	if (snum < server_num && snum >= 0)
		return server_name[snum];
	/* request for a bogus snum value, something is wrong */
	sendto_flag(SCH_ERROR, "invalid index for server_name[] : %d (%d,%d)",
		    snum, server_num, server_max);
	return NULL;
}

/*
** find_server_num
**
** Given a server name, this will return the index of the corresponding
** string. This index can be used with find_server_name_from_num().
** If the string doesn't exist already, it will be allocated.
*/
int
find_server_num(sname)
char *sname;
{
	Reg int i = 0;

	while (i < server_num)
	    {
		if (!strcasecmp(server_name[i], sname))
			break;
		i++;
	    }
	if (i < server_num)
		return i;
	if (i == server_max)
	  {
	    /* server_name[] array is full, let's make it bigger! */
	    if (server_name)
		    server_name = (char **) MyRealloc((char *)server_name,
					      sizeof(char *)*(server_max+=50));
	    else
		    server_name = (char **) MyMalloc(sizeof(char *)*(server_max=50));
	  }
	server_name[server_num] = mystrdup(sname);
	return server_num++;
}

/*
** check_link (added 97/12 to prevent abuse)
**	routine which tries to find out how healthy a link is.
**	useful to know if more strain may be imposed on the link or not.
**
**	returns 0 if link load is light, -1 otherwise.
*/
static int
check_link(cptr)
aClient	*cptr;
{
    if (!IsServer(cptr))
	    return 0;
    if (!(bootopt & BOOT_PROT))
	    return 0;

    ircstp->is_ckl++;
    if ((int)DBufLength(&cptr->sendQ) > 65536) /* SendQ is already (too) high*/
	{
	    cptr->serv->lastload = timeofday;
	    ircstp->is_cklQ++;
	    return -1;
	}
    if (timeofday - cptr->firsttime < 60) /* link is too young */
	{
	    ircstp->is_ckly++;
	    return -1;
	}
    if (timeofday - cptr->serv->lastload > 30)
	    /* last request more than 30 seconds ago => OK */
	{
	    cptr->serv->lastload = timeofday;
	    ircstp->is_cklok++;
	    return 0;
	}
    if (timeofday - cptr->serv->lastload > 15
	&& (int)DBufLength(&cptr->sendQ) < CHREPLLEN)
	    /* last request between 15 and 30 seconds ago, but little SendQ */
	{
	    cptr->serv->lastload = timeofday;
	    ircstp->is_cklq++;
	    return 0;
	}
    ircstp->is_cklno++;
    return -1;
}


syntax highlighted by Code2HTML, v. 0.9.1