/************************************************************************
 *   IRC - Internet Relay Chat, modules/m_server.c
 *
 *   Copyright (C) 2000-2003 TR-IRCD Development
 *
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Co 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 2, 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.
 */
    /*
     * $Id: m_server.c,v 1.5 2003/06/14 13:55:51 tr-ircd Exp $ 
     */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include "s_conf.h"
#include "confitem.h"
#include "hook.h"
#include "h.h"

static char *token = TOK1_SERVER;

static int hookid_inform_remote_servers = 0;

static struct Message _msgtab[] = {
    {MSG_SERVER, 0, MAXPARA, M_SLOW, 0L,
     m_server, m_ignore, m_ignore, s_server, m_ignore}
};

#ifndef STATIC_MODULES

char *_version = "$Revision: 1.5 $";

void _modinit(void)
{
    mod_add_cmd(_msgtab);
    tok1_msgtab[(u_char) *token].msg = _msgtab;
    hookid_inform_remote_servers = hook_add_event("inform remote servers");
}

void _moddeinit(void)
{
    mod_del_cmd(_msgtab);
    tok1_msgtab[(u_char) *token].msg = NULL;
}
#else
void m_server_init(void)
{
    mod_add_cmd(_msgtab);
    tok1_msgtab[(u_char) *token].msg = _msgtab;
    hookid_inform_remote_servers = hook_add_event("inform remote servers");
}
#endif

/*
 * bogus_host
 *
 * inputs       - hostname
 * output       - 1 if a bogus hostname input, 0 if its valid
 * side effects - none
 */
static int bogus_host(char *host)
{
    int bogus_server = 0;
    char *s;
    int dots = 0;

    for (s = host; *s; s++) {
	if (!IsServChar(*s)) {
	    bogus_server = 1;
	    break;
	}
	if ('.' == *s)
	    ++dots;
    }

    if (!dots || bogus_server)
	return 1;

    return 0;
}

/*
 * parse_server_args
 *
 * inputs       - parv parameters
 *              - parc count
 *              - info string (to be filled in by this routine)
 *              - hop count (to be filled in by this routine)
 * output       - NULL if invalid params, server name otherwise
 * side effects - parv[1] is trimmed to HOSTLEN size if needed.
 */

static char *parse_server_args(char *parv[], int parc, char *info, 
			       char *sid, int *flags, int *hop)
{
    char *name;

    info[0] = '\0';
    sid[0] = '\0';

    if (parc < 2 || *parv[1] == '\0')
	return NULL;

    *hop = 0;
    *flags = 0;

    name = parv[1];

    if (parc == 6) {
	*hop = atoi(parv[2]);
	*flags |= strchr(parv[3], 'H') ? PFLAGS_DOHIDENAME : 0;
	*flags |= strchr(parv[3], 'U') ? PFLAGS_ULINE : 0;
	*flags |= strchr(parv[3], 'R') ? PFLAGS_ISHUB : 0;
	strlcpy_irc(sid, parv[4] + 1, 8);
	sid[8] = '\0';
	strlcpy_irc(info, parv[5], REALLEN);
	info[REALLEN] = '\0';
    } else if ((parc == 5) && (parv[3][0] != '!')) {
	*hop = atoi(parv[2]);
	*flags |= strchr(parv[3], 'H') ? PFLAGS_DOHIDENAME : 0;
	*flags |= strchr(parv[3], 'U') ? PFLAGS_ULINE : 0;
	*flags |= strchr(parv[3], 'R') ? PFLAGS_ISHUB : 0;
	strlcpy_irc(info, parv[4], REALLEN);
	info[REALLEN] = '\0';
    } else if ((parc == 5) && (parv[3][0] == '!')) {	/* Kludge for Halycon */
        *hop = atoi(parv[2]);
	strlcpy_irc(sid, parv[3] + 1, 8);
        sid[8] = '\0';
        strlcpy_irc(info, parv[4], REALLEN);
        info[REALLEN] = '\0';
    } else if (parc == 4) {
	*hop = atoi(parv[2]);
	*flags = 0;
	strlcpy_irc(info, parv[3], REALLEN);
	info[REALLEN] = '\0';
    } else if (parc == 3) {
	*hop = 1;
	*flags = 0;
	strlcpy_irc(info, parv[2], REALLEN);
	info[REALLEN] = '\0';
    } else if (parc == 2) {
	*hop = 1;
	*flags = 0;
	strlcpy_irc(info, "no description", REALLEN);
	info[REALLEN] = '\0';
    }

    if (strlen(name) > HOSTLEN)
	name[HOSTLEN] = '\0';

    return (name);
}

/*
 * server_exists()
 * 
 * inputs       - servername
 * output       - 1 if server exists, 0 if doesnt exist
 */
static struct Client *server_exists(char *servername)
{
    struct Client *target_p;
    dlink_node *ptr;

    for (ptr = global_serv_list.head; ptr; ptr = ptr->next) {
	target_p = ptr->data;

	if (!match(target_p->name, servername) || !match(servername, target_p->name))
	    return target_p;
    }

    return NULL;
}

/*
 * * m_server       
 * parv[0] = sender prefix 
 * parv[1] = servername 
 * parv[2] = serverinfo
 */
int m_server(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
    char info[REALLEN + 1];
    char *host;
    char sid[8];
    struct Client *target_p;
    int hop;
    int flags = 0;

    if ((host = parse_server_args(parv, parc, info, sid, &flags, &hop)) == NULL) {
	sendto_one_server(cptr, NULL, TOK1_ERROR, ":No servername");
	return 0;
    }

    if (!DoesTS(cptr)) {
	sendto_gnotice("Link %s dropped, non-TS server", get_client_name(cptr, MASK_IP));
	return exit_client(cptr, cptr, "Non-TS server");
    }

    if (bogus_host(host)) {
	return exit_client(cptr, cptr, "Bogus server name");
    }

    /* Now we just have to call check_server and everything should be
     * check for us... -A1kmm. */
    switch (check_server(host, cptr)) {
	case -2:
	    sendto_gnotice("Unauthorized server connection attempt from %s: No entry for "
		       "servername %s", get_client_name(cptr, HIDE_IP), host);

	    return exit_client(cptr, cptr, "Invalid servername.");
	    break;

	case NOT_AUTHORIZED:
	    sendto_gnotice("Unauthorized server connection attempt from %s: Bad password "
		       "for server %s", get_client_name(cptr, HIDE_IP), host);

	    return exit_client(cptr, cptr, "Invalid password.");
	    break;

	case -3:
	    sendto_gnotice("Unauthorized server connection attempt from %s: Invalid host "
		       "for server %s", get_client_name(cptr, HIDE_IP), host);

	    return exit_client(cptr, cptr, "Invalid host.");
	    break;
	case INVALID_CONNECTION:
	    sendto_gnotice("Unauthorized server connection attempt from %s to non-server port",
			get_client_name(cptr, HIDE_IP));
	    return exit_client(cptr, cptr, "No server port");
	    break;
    }
    if ((target_p = server_exists(host))) {
	/*
	 * This link is trying feed me a server that I already have
	 * access through another path -- multiple paths not accepted
	 * currently, kill this link immediately!!
	 *
	 * Rather than KILL the link which introduced it, KILL the
	 * youngest of the two links. -avalon
	 *
	 * Definitely don't do that here. This is from an unregistered
	 * connect - A1kmm.
	 */
	sendto_gnotice("Attempt to re-introduce server %s from %s", host,
		   get_client_name(cptr, HIDE_IP));

	sendto_one_server(cptr, NULL, TOK1_ERROR, ":Server already exists.");
	return exit_client(cptr, cptr, "Server Exists");
    }

    /*
     * if we are connecting (Handshake), we already have the name from the
     * C:line in client_p->name
     */

    strlcpy_irc(cptr->name, host, HOSTLEN);
    strlcpy_irc(cptr->info, info, REALLEN);
    cptr->hopcount = hop;
    cptr->protoflags |= flags;

    return server_estab(cptr);
}

/*
 * * m_server
 * parv[0] = sender prefix
 * parv[1] = servername
 * parv[2] = hopcount 
 * parv[3] = hidechar
 * parv[4] = serverid
 * parv[5] = info
 */

int s_server(aClient *client_p, aClient *source_p, int parc, char *parv[])
{
    char info[REALLEN + 1];
    char *name;
    struct Client *target_p;
    struct hook_data thisdata;
    int hop;			/* for hopcount information */
    char sid[8];		/* for server id */
    int flags = 0;			/* for hidechar information */

    if ((name = parse_server_args(parv, parc, info, sid, &flags, &hop)) == NULL) {
	sendto_one_server(client_p, NULL, TOK1_ERROR, ":No servername");
	return 0;
    }

    if ((target_p = server_exists(name))) {
	/*
	 * This link is trying feed me a server that I already have
	 * access through another path -- multiple paths not accepted
	 * currently, kill this link immediately!!
	 *
	 * Rather than KILL the link which introduced it, KILL the
	 * youngest of the two links. -avalon
	 *
	 * I think that we should exit the link itself, not the introducer,
	 * and we should always exit the most recently received(i.e. the
	 * one we are receiving this SERVER for. -A1kmm
	 *
	 * You *cant* do this, if you link somewhere, it bursts you a server
	 * that already exists, then sends you a client burst, you squit the
	 * server, but you keep getting the burst of clients on a server that
	 * doesnt exist, although ircd can handle it, its not a realistic
	 * solution.. --fl_ 
	 */
	/* It is behind a host-masked server. Completely ignore the
	 * server message(don't propagate or we will delink from whoever
	 * we propagate to). -A1kmm */
	if (irc_strcmp(target_p->name, name) && target_p->from == client_p)
	    return 0;

	if (client_p->firsttime > target_p->from->firsttime) {
	    sendto_one_server(client_p, NULL, TOK1_ERROR, ":Server %s already exists", name);

	    sendto_gnotice("Link %s cancelled, server %s already exists",
		       get_client_name(client_p, SHOW_IP), name);

	    return exit_client(client_p, &me, "Server Exists");
	} else {
	    sendto_one_server(target_p->from, NULL, TOK1_ERROR, ":Server %s already exists", name);
	    sendto_gnotice("Link %s cancelled, server %s reintroduced by %s",
		       get_client_name(target_p->from, SHOW_IP), name, client_p->name);

	    return exit_client(target_p->from, &me, "Server Exists");
	}
    }

    if (sid[0] && IsIDCapable(client_p)) {
	if (!valid_base64_server_id(sid)) {
	    sendto_one_server(client_p, NULL, TOK1_ERROR, ":Invalid identity %s", sid);
	    return 0;
	}
    }

    if (sid[0] && (target_p = find_server_by_base64_id(sid, NULL))) {
	/*
	 * we have duplicate identities on the network.
	 * this is most likely the result of some misconfiguration.
	 * throw it out.
	 */

	sendto_one_server(client_p, NULL, TOK1_ERROR, ":Duplicate identity!");

	sendto_gnotice("Link %s cancelled, identity %s already held by %s",
		   get_client_name(client_p, HIDEME), sid, target_p->name);
	return exit_client(client_p, &me, "Duplicate identity");
    }

    /* 
     * User nicks never have '.' in them and server names
     * must always have '.' in them.
     */
    if (strchr(name, '.') == NULL) {
	/*
	 * 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_server(client_p, NULL, TOK1_ERROR, ":Nickname %s already exists!", name);
	sendto_gnotice("Link %s cancelled: Server/nick collision on %s",
		   /* inpath */ get_client_name(client_p, HIDE_IP),
		   name);
	return exit_client(client_p, client_p, "Nick as Server");
    }

    /*
     * 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_server(client_p, NULL, TOK1_ERROR, ":No server info specified for %s", name);
	return 0;
    }

    /*
     * See if the newly found server is behind a guaranteed
     * leaf. If so, close the link.
     * Ok, check client_p can hub the new server, and make sure it's not a LL 
     */

    if (!IsHub(client_p)) {
	sendto_gnotice("Non-Hub link %s introduced %s.", get_client_name(client_p, HIDE_IP), name);

	/* If it is new, we are probably misconfigured, so split the
	 * non-hub server introducing this. Otherwise, split the new
	 * server. -A1kmm. */
	if ((timeofday - source_p->firsttime) < 20) {
	    return exit_client(source_p, &me, "No H-line.");
	} else {
	    sendto_one_server(source_p, &me, TOK1_SQUIT, "%s :Sorry, no H-line.", name);
	    return 0;
	}
    }

    /*
     * Since it is possible, that the remote server uses old protocol,
     * we might not know if they are U:Lined since the SERVER line
     * contains less parameters. On the other hand, in such cases, we
     * can check, if there is a corresponding U:Line or H:Line given
     * with the load directive in files { } section of the new ircd.conf
     * Then we can append the needed flags to the new structure.  
     * -TimeMr14C
     */

    target_p = make_client(client_p);
    make_server(target_p);
    target_p->hopcount = hop;
    target_p->protoflags |= flags;
    strlcpy_irc(target_p->name, name, HOSTLEN);
    strlcpy_irc(target_p->info, info, REALLEN);

    target_p->servptr = source_p;

    SetServer(target_p);

    Count.server++;
    GeneralOpts.split = 0;

    if (IsULine(source_p)) {
	target_p->protoflags |= PFLAGS_ULINE;
	sendto_gnotice("%s introducing U:lined server %s", client_p->name, target_p->name);
    }
    add_client_to_list(target_p);
    add_server_to_list(target_p);
    add_to_client_hash_table(target_p->name, target_p);
    add_client_to_llist(&(target_p->servptr->serv->servers), target_p);

    target_p->servptr->serv->servercnt++;

    if (sid)
	add_base64_server(target_p, sid);

    /*
     * Old sendto_serv_but_one() call removed because we now
     * need to send different names to different servers
     * (domain name matching)
     */

    thisdata.client_p = client_p;
    thisdata.source_p = source_p;
    thisdata.aclient_p = target_p;
    thisdata.name = name;
    thisdata.check = hop;

    hook_call_event(hookid_inform_remote_servers, &thisdata);

    sendto_gnotice("Server %s being introduced by %s", target_p->name, source_p->name);
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1