/************************************************************************
 *   IRC - Internet Relay Chat, server/s_user.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: s_user.c,v 1.9 2004/07/12 09:14:59 tr-ircd Exp $
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "tools.h"
#include "h.h"
#include "channel.h"
#include "class.h"
#include "fd.h"
#include "listener.h"
#include "msg.h"
#include "numeric.h"
#include "s_bsd.h"
#include "s_conf.h"
#include "throttle.h"
#include "supported.h"
#include "packet.h"
#include "hook.h"
#include "language.h"

#define IRCD_BUFSIZE 512

static int hookid_introduce_client = 0;
static int hookid_proxy_add = 0;

static int user_welcome(struct Client *source_p, struct ConfItem *aconf);
static void manage_faked_oper_hostname(struct Client *sptr, struct ConfItem *pwaconf);

static int introduce_client(struct Client *client_p, struct Client *source_p,
			    struct User *user, char *nick);

void init_user(void)
{
    hookid_introduce_client = hook_add_event("introduce client");
    hookid_proxy_add = hook_add_event("add to proxy");
    return;
}

/*
 * ** register_local_user
 * **      This function is called when both NICK and USER messages
 * **      have been accepted for the client, in whatever order. Only
 * **      after this, is the USER message propagated.
 * **
 * **      NICK's must be propagated at once when received, although
 * **      it would be better to delay them too until full info is
 * **      available. Doing it is not so simple though, would have
 * **      to implement the following:
 * **
 * **      (actually it has been implemented already for a while) -orabidoo
 * **
 * **      1) user telnets in and gives only "NICK foobar" and waits
 * **      2) another user far away logs in normally with the nick
 * **         "foobar" (quite legal, as this server didn't propagate
 * **         it).
 * **      3) now this server gets nick "foobar" from outside, but
 * **         has already the same defined locally. Current server
 * **         would just issue "KILL foobar" to clean out dups. But,
 * **         this is not fair. It should actually request another
 * **         nick from local user or kill him/her...
 */

int register_local_user(struct Client *client_p, struct Client *source_p,
			char *nick, char *username)
{
    struct ConfItem *aconf;
    struct User *user = source_p->user;
    char *p;
    char passarr[PASSWDLEN];
    char tmpstr2[IRCD_BUFSIZE];
    char ipaddr[HOSTIPLEN];
    int status;
    int r;
    dlink_node *ptr;
    dlink_node *m;
    char newhostarr[HOSTLEN];
    char *q;

    assert(0 != source_p);
    assert(0 != source_p);
    assert(source_p->username != username);

    user->last = timeofday;

    /* pointed out by Mortiis, never be too careful */
    if (strlen(username) > USERLEN)
	username[USERLEN] = '\0';

    if (!valid_hostname(source_p->sockhost)) {
	send_me_notice(source_p, ":*** Notice -- You have an illegal character in your hostname");
	if (IsClosing(source_p))
	    return CLIENT_EXITED;
	strlcpy_irc(source_p->sockhost, source_p->hostip, HOSTIPLEN);
    }

    if (strchr(source_p->hostip, ':'))
	source_p->protoflags |= PFLAGS_IPV6HOST;

    strlcpy_irc(user->host, source_p->sockhost, HOSTLEN);

    q = user->host;
    strlcpy_irc(user->fakehost, calcmask(q, newhostarr), HOSTLEN);

    status = check_client(client_p, source_p, username);
    if (status < 0)
	return CLIENT_EXITED;

    ptr = source_p->confs.head;
    aconf = ptr->data;

    source_p->allow_read = MAX_FLOOD_PER_SEC_I;

    if (aconf == NULL) 
	return CLIENT_EXITED;

    if (!IsGotId(source_p)) {
	if (!ServerOpts.identd_use_tilde && ServerOpts.identd_complain) {
	    ircstp->is_ref++;
	    send_me_notice(source_p,
			   ":*** Notice -- You need to install identd to use this server");
	    exit_client(source_p, &me, "Install identd");
	    return CLIENT_EXITED;
	} else if (!ServerOpts.identd_use_tilde && !ServerOpts.identd_complain) {
	    strlcpy_irc(source_p->username, username, USERLEN);
	} else {
	    *(source_p->username) = '~';
	    strlcpy_irc(&source_p->username[1], username, USERLEN - 1);
	}
	source_p->username[USERLEN] = '\0';
    }

    /* password check */
    if (!BadPtr(aconf->passwd) && (p = source_p->passwd) &&
	strcmp(calcpass(p, passarr), aconf->passwd)) {
	ircstp->is_ref++;
	send_me_numeric(source_p, ERR_PASSWDMISMATCH);
	exit_client(source_p, &me, "Bad Password");
	return CLIENT_EXITED;
    }
    memset(source_p->passwd, 0, sizeof(source_p->passwd));

    /* Limit clients */
    /*
     * We want to be able to have servers and F-line clients
     * connect, so save room for "buffer" connections.
     * Smaller servers may want to decrease this, and it should
     * probably be just a percentage of the MAXCLIENTS...
     *   -Taner
     */
    /* Except "F:" clients */
    if ((((Count.local + 1) >= GeneralOpts.maxclients)
	 || ((Count.local + 1) >= (GeneralOpts.maxclients - 5))) && !IsConfExemptLimits(aconf)) {
	sendto_lev(REJ_LEV, "Too many clients, rejecting %s[%s].", nick, source_p->sockhost);

	ircstp->is_ref++;
	exit_client(source_p, &me, "Sorry, server is full - try later");
	return CLIENT_EXITED;
    }

    /* valid user name check */

    if (!valid_username(source_p->username)) {
	sendto_lev(REJ_LEV, "Invalid username: %s (%s@%s)",
		   nick, source_p->username, source_p->sockhost);
	ircstp->is_ref++;
	ircsprintf(tmpstr2, "Invalid username [%s]", source_p->username);
	exit_client(source_p, &me, tmpstr2);
	return CLIENT_EXITED;
    }

    if (check_drone_PB(source_p->username, source_p->info)) {
	sendto_lev(REJ_LEV, "Rejecting acebot-style drone: %^C [%s]", source_p, source_p->info);
	exit_client(source_p, &me, "You match the pattern of a known trojan, please check your system.");
	return CLIENT_EXITED;
    }

    SetClient(source_p);

    strlcpy_irc(user->username, source_p->username, USERLEN);

    /* end of valid user name check */

    m = dlinkFind(&unknown_list, source_p);
    if (m != NULL) {
        Count.unknown--;
        dlinkDelete(m, &unknown_list);
        dlinkAdd(source_p, m, &lclient_list);
    }

    inetntop(source_p->aftype, &IN_ADDR(source_p->ip), ipaddr, HOSTIPLEN);
    sendto_lev(CCONN_LEV, "Client connecting: %^C [%s] {%d} [%s]",
	       source_p, ipaddr, get_client_class(source_p), source_p->info);

    if ((++Count.local) > Count.max_loc) {
	Count.max_loc = Count.local;
	if (!(Count.max_loc % 10))
	    sendto_lev(DEBUG_LEV, "New Max Local Clients: %d", Count.max_loc);
    }

    source_p->pingval = get_client_ping(source_p);
    source_p->sendqlen = get_sendq(source_p);

    source_p->servptr = &me;
    add_client_to_llist(&(source_p->servptr->serv->users), source_p);

    source_p->servptr->serv->usercnt++;

    /* Increment our total user count here */
    if (++Count.total > Count.max_tot)
	Count.max_tot = Count.total;

    assign_localuser_identity(source_p);
    SetHasID(source_p);

    if (source_p->langstring[0]) {
	source_p->lang = lang_parse(source_p->langstring);
	if (source_p->lang)
	    sendto_one_person(source_p, NULL, TOK1_NOTICE, "AUTH :*** Setting language to %s",
				  source_p->langstring);
    }

    r = user_welcome(source_p, aconf);

    if (r & FLAGS_EXITED)
	return CLIENT_EXITED;

    return (introduce_client(client_p, source_p, user, nick));
}

/*
 * register_remote_user
 *
 * inputs
 * output
 * side effects - This function is called when a remote client
 *                is introduced by a server.
 */
int register_remote_user(struct Client *client_p, struct Client *source_p,
			 char *nick, char *username)
{
    struct User *user = source_p->user;
    struct Client *target_p;

    assert(0 != source_p);
    assert(source_p->username != username);

    user->last = timeofday;

    /* pointed out by Mortiis, never be too careful */
    if (strlen(username) > USERLEN)
	username[USERLEN] = '\0';

    strlcpy_irc(source_p->username, username, USERLEN);
    strlcpy_irc(user->username, username, USERLEN);

    SetClient(source_p);

    /* Increment our total user count here */
    if (++Count.total > Count.max_tot)
	Count.max_tot = Count.total;

    source_p->servptr = find_server(user->server); 

    if (source_p->servptr == NULL) {
	sendto_ops("Ghost killed: %s on invalid server %s", source_p->name, source_p->user->server);

	kill_client(client_p, source_p, "%s (Server doesn't exist)", me.name);

	source_p->flags |= FLAGS_KILLED;
	return exit_client(source_p, &me, "Ghost");
    }

    if ((target_p = source_p->servptr) && target_p->from != source_p->from) {
	sendto_lev(DEBUG_LEV, "Bad User [%s] :%s USER %s@%s %s, != %s[%s]",
		   client_p->name, nick, source_p->username,
		   source_p->sockhost, user->server, target_p->name, target_p->from->name);
	kill_client(client_p, source_p,
		    ":%s (%s != %s[%s] USER from wrong direction)", me.name,
		    user->server, target_p->from->name, target_p->from->sockhost);

	source_p->flags |= FLAGS_KILLED;
	return exit_client(source_p, &me, "USER server wrong direction");

    }
    /*
     * Super GhostDetect:
     * If we can't find the server the user is supposed to be on,
     * then simply blow the user away.        -Taner
     */
    if (!target_p) {
	kill_client(client_p, source_p, "%s GHOST (no server found)", me.name, user->server);
	sendto_ops("No server %s for user %s[%s@%s] from %s",
		   user->server, source_p->name, source_p->username,
		   source_p->sockhost, source_p->from->name);
	source_p->flags |= FLAGS_KILLED;
	return exit_client(source_p, &me, "Ghosted Client");
    }

    add_client_to_llist(&(source_p->servptr->serv->users), source_p);

    source_p->servptr->serv->usercnt++;

    return (introduce_client(client_p, source_p, user, nick));
}

/*
 * introduce_client
 *
 * inputs       -
 * output       -
 * side effects - This common function introduces a client to the rest
 *                of the net, either from a local client connect or
 *                from a remote connect.
 */
static int introduce_client(struct Client *cptr, struct Client *sptr, struct User *user, char *nick)
{
    struct hook_data thisdata;
    thisdata.client_p = cptr;
    thisdata.source_p = sptr;
    thisdata.user = user;
    thisdata.name = nick;

    if ((NOW - Count.day) > 86400) {
	Count.today = 0;
	Count.day = NOW;
    }
    if ((NOW - Count.week) > 604800) {
	Count.weekly = 0;
	Count.week = NOW;
    }
    if ((NOW - Count.month) > 2592000) {
	Count.monthly = 0;
	Count.month = NOW;
    }
    if ((NOW - Count.year) > 31536000) {
	Count.yearly = 0;
	Count.year = NOW;
    }
    Count.today++;
    Count.weekly++;
    Count.monthly++;
    Count.yearly++;
    
    return hook_call_event(hookid_introduce_client, &thisdata);
}

/*
 * do_local_user
 *
 * inputs       -
 * output       -
 * side effects -
 */
int do_local_user(char *nick, aClient *cptr, aClient *sptr, char *username, char *host,
		  char *mask, char *server, unsigned long serviceid, char *realname)
{
    struct User *user;

    user = make_user(sptr);

    if (!IsUnknown(sptr)) {
	send_me_numeric(sptr, ERR_ALREADYREGISTRED);
	return 0;
    }
    sptr->umode |= UMODE_i;	/* invisible IS default. always */
    if(ServerOpts.default_fakehost_mode)	
    	sptr->umode |= UMODE_x;
    if (ServerOpts.use_registerfilter)
	sptr->umode |= UMODE_R;

    strlcpy_irc(user->host, host, sizeof(user->host));
    DupString(user->server, me.name);

    server[strlen(server) - 1] = '\0';

    if ((ServerInfo.aliasname[0] != '\0') && (server[0] != '\0')) {
	if (!irc_strcmp(server + 1, ServerInfo.aliasname))
	    sptr->protoflags |= PFLAGS_ALIASED;
    }

    Count.invisi++;

    if (mask) 
	strlcpy_irc(user->fakehost, mask, sizeof(user->fakehost));

    strlcpy_irc(sptr->info, realname, REALLEN);

    sptr->user->servicestamp = serviceid;
    sptr->oflag = 0;

    if (sptr->name[0]) {
	strlcpy_irc(user->username, username, USERLEN);
	/* NICK already received, now I have USER... */
	return register_local_user(cptr, sptr, sptr->name, username);
    } else if (!IsGotId(sptr)) {
	strlcpy_irc(user->username, username, USERLEN);
	strlcpy_irc(sptr->username, username, USERLEN);
    }
    return 0;
}

/*
 * do_remote_user
 *
 * inputs       -
 * output       -
 * side effects -
 */

int do_remote_user(char *nick, aClient *client_p, aClient *source_p, char *username, char *host,
		   char *mask, char *server, unsigned long serviceid, char *realname)
{
    struct User *user;

    assert(0 != source_p);
    assert(source_p->username != username);

    user = make_user(source_p);

    strlcpy_irc(source_p->sockhost, host, HOSTLEN);
    strlcpy_irc(user->host, host, HOSTLEN);
    strlcpy_irc(user->fakehost, mask, HOSTLEN);
    strlcpy_irc(source_p->info, realname, REALLEN);
    DupString(user->server, server);

    source_p->user->servicestamp = serviceid;

    return register_remote_user(client_p, source_p, source_p->name, username);
}

static void manage_faked_oper_hostname(aClient *sptr, aConfItem *pwaconf)
{
    if (!ServerOpts.staffhide)
	return;

    if (!IsConfDoSpoofIp(pwaconf))
	return;

    if (IsDead(sptr))	
	return;

	/* It is possible to enter this function, even though the
	 * client has been marked as dead. -TimeMr14C 17.05.2003
	 */

    if (IsConfSpoofNotice(pwaconf))
	sendto_ops("%^C has masked their hostname.", sptr);

    send_me_notice(sptr, ":*** Your hostname has been masked.");

    if (IsDead(sptr))
	return;

	/* The above line ensures us that we dont touch the
	 * user structrue (sptr->user) if a write error has happened
	 * whilst the send_me_notice -TimeMr14C 17.05.2003
	 */

    sptr->protoflags |= PFLAGS_OPERFAKEHOST;

    sptr->user->real_oper_host = MyMalloc(strlen(sptr->user->host) + 1);
    sptr->user->real_oper_ip = MyMalloc(strlen(sptr->hostip) + 1);

    strcpy(sptr->user->real_oper_host, sptr->user->host);
    strcpy(sptr->user->real_oper_ip, sptr->hostip);

    if (IsConfCanChooseFakehost(pwaconf)) {
	if (sptr->fakehost_override[0]) {
	    strlcpy_irc(sptr->sockhost, sptr->fakehost_override, HOSTLEN);
	    strlcpy_irc(sptr->user->host, sptr->fakehost_override, HOSTLEN);
	} else if (pwaconf->localhost[0]) {
	    strlcpy_irc(sptr->sockhost, pwaconf->localhost, HOSTLEN);
	    strlcpy_irc(sptr->user->host, pwaconf->localhost, HOSTLEN);
	}
    } else if (pwaconf != NULL) {
	if (pwaconf->localhost[0]) {
	    strlcpy_irc(sptr->sockhost, pwaconf->localhost, HOSTLEN);
	    strlcpy_irc(sptr->user->host, pwaconf->localhost, HOSTLEN);
	}
    }

    throttle_remove(sptr->hostip);

    sptr->flags |= FLAGS_GOTID;
    IN4_ADDR(sptr->ip) = 0;
    strcpy(sptr->hostip, "0.0.0.0");

    return;
}

/* 
 * user_welcome
 *
 * inputs       - client pointer to client to welcome
 * output       - NONE
 * side effects -
 */
static int user_welcome(struct Client *sptr, struct ConfItem *aconf)
{
    aMotd *smotd;
    aMotdItem *smotdline;

    send_me_numeric(sptr, RPL_WELCOME, ServerInfo.networkname, sptr->name,
		    sptr->user->username, sptr->user->host);
    /* We require the above line for DCC to work, otherwise,   
     * we could use %C instead -TimeMr14C */
    send_me_numeric(sptr, RPL_YOURHOST, get_client_name(&me, TRUE), version);
    send_me_numeric(sptr, RPL_CREATED, creation);
    send_me_numeric(sptr, RPL_MYINFO, me.name, version, 
		    GeneralOpts.umodelist, GeneralOpts.allchanmodes, GeneralOpts.paramchanmodes);
    send_supported(sptr);

    manage_faked_oper_hostname(sptr, aconf);

    if (IsDead(sptr))
	return sptr->flags;

	/* The above call helps us in not being required to deal with additional things
	 * about the structure itself (reading host, while there isn't one, since freed.
	 * -TimeMr14C 17.05.2003
	 */

    /* This call here may call the dummy function with no content, or the one
     * after the replacement by the proxymon module.
     * -TimeMr14C */

    if (GeneralOpts.enable_proxymonitor && !IsConfExemptKline(aconf) && !IsOperFakehost(sptr)) {
	struct hook_data thisdata;
	thisdata.source_p = sptr;
	hook_call_event(hookid_proxy_add, &thisdata);
    }

    send_lusers(sptr, sptr, 1, NULL);

    send_me_notice(sptr, ":*** Notice -- motd was last changed at %s", (&(GeneralOpts.motd))->lastchange);
    if (ServerOpts.short_motd) {
	send_me_notice(sptr, ":*** Notice -- Please read the motd if you haven't read it");
	send_me_numeric(sptr, RPL_MOTDSTART, me.name);
	if ((smotd = &(GeneralOpts.shortmotd)) == NULL) {
	    send_me_numeric(sptr, RPL_MOTD, "*** This is the short motd ***");
	} else {
	    smotdline = smotd->content;
	    while (smotdline) {
		send_me_numeric(sptr, RPL_MOTD, smotdline->line);
		smotdline = smotdline->next;
	    }
	}
	send_me_numeric(sptr, RPL_ENDOFMOTD);
    } else {
	send_message_file(&(GeneralOpts.motd), sptr, sptr, 1, NULL);
    }
    return sptr->flags;
}



syntax highlighted by Code2HTML, v. 0.9.1