/*
 * Copyright (c) 2004-2009, Luiz Otavio O Souza <loos.br@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

static const char rcsid[] = "$Id: user.c 112 2009-03-15 17:30:28Z loos-br $";

#include <string.h>

#include "ns.h"
#include "sb.h"
#include "sql.h"
#include "user.h"
#include "net-io.h"
#include "return.h"
#include "protocol.h"
#include "array_cmd.h"
#include "msn-proxy.h"
#include "contacts.h"

struct users_	users;			/* users rb list data */
unsigned int	user_id  = 0;
unsigned int	user_max = 0;
unsigned int	user_inuse = 0;

int
user_rb_cmp(struct user_ *u1, struct user_ *u2) {
    return (u1->id - u2->id);
}

RB_GENERATE(users_, user_, user__, user_rb_cmp)

void
user_rb_init(void) {
    memset(&users, 0, sizeof(struct users_));
}

int
msnp12_update_dn(struct user_ *user, command *cmd, int args) {

    /* SYN friendly name */
    if (cmd->args_len == 2 && check_arg(cmd->args[0], "MFN") == ROK) {
	if (msn_decode(cmd->args[1], &user->dn) == RFAIL)
	    return(RFAIL);
    } else if (cmd->args_len == 3 && check_arg(cmd->args[1], "MFN") == ROK) {
	if (msn_decode(cmd->args[2], &user->dn) == RFAIL)
	    return(RFAIL);
    } else
	/* PRP PHN and others */
	return(ROK);

    if (sql_update_user_dn(user) == RFAIL)
	return(RFAIL);

    return(ROK);
}

int
msn_update_status(struct user_ *user, command *cmd, int args) {
 string			*status = cmd->args[1];
 string			*o	= cmd->args[3];
 log_			*log	= &config.log;

    if (cmd->args_len < 2 || status == NULL || status->len == 0)
	return(RFAIL);

    /* save user status */
    if (str_copy(&user->status, status->s, status->len) == 0)
	die_nomem();

    //user->flags = arg[2];

    /* update sql */
    if (sql_set_status(user) == RFAIL) {
	log->debug("debug: cannot save status\n");
	return(RFAIL);
    }

    /* update contact information */
    if (sql_contact_save_all(user) == RFAIL) {
	log->debug("debug: cannot save contacts\n");
	return(RFAIL);
    }

    /* check img acl */
    if ( (user->commands & USEIMG) && cmd->args_len == 4 && o) {
	switch (user->version) {
	case MSNP15:
	case MSNP16:
	case MSNP17:
	case MSNP18:
	    if (str_copys(o, (unsigned char *)"0") == 0) exit (51);
	    break;
	default:
	    str_free(o);
	    free(o);
	    cmd->args[3] = NULL;
	    cmd->args_len--;
	}
    }

    return(ROK);
}

int
msn_url(struct user_ *user, command *cmd, int args) {
    return(ROK);
}

int
msn_ubx(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 string			*c;
 string			buf;
 string			tmp;
 int			state;

    /* check for contact */
    if (cmd->args_len < 1 || (c = get_arg(cmd, 0)) == NULL)
	return(RFAIL);

    contact = contact_update(user, c);
    if (contact == NULL)
	return(RFAIL);

    state = END;
    str_zero(&buf);
    str_zero(&tmp);
    buf.len = cmd->payload.len;
    buf.s   = cmd->payload.s;
    while (buf.len > 0) {

	/* check for tags */
	if (*buf.s == '<') {
	    if (buf.len >= 5 && strncasecmp("<PSM>", (char *)buf.s, 5) == 0) {
		buf.len -= 5;
		buf.s   += 5;
		state = ROK;
		continue;
	    }
	    if (buf.len >= 6 && strncasecmp("</PSM>", (char *)buf.s, 6) == 0) {
		buf.len -= 6;
		buf.s   += 6;
		state = END;
		continue;
	    }
	}

	/* copy data */
	if (state == ROK && str_cat(&tmp, buf.s, 1) == 0)
	    return(RFAIL);

	buf.len -= 1;
	buf.s   += 1;
    }

    if (tmp.len > 0) {
	/* decode info */
	if (msn_decode(&tmp, &contact->info) == RFAIL) {
	    str_free(&tmp);
	    return(RFAIL);
	}
    }
    str_free(&tmp);

    if (sql_contact_save(user, contact) == RFAIL)
	return(RFAIL);

    return(ROK);
}

struct user_ *
user_free(struct user_ *user) {
    str_free(&user->dn);
    str_free(&user->addr);
    str_free(&user->email);
    str_free(&user->status);
    free(user);
    return(NULL);
}

struct user_ *
user_alloc(struct sockaddr *sa, socklen_t sa_len) {
 struct user_	*user = NULL;
 log_		*log = &config.log;

    /* alloc new user */
    user = (struct user_ *)malloc(sizeof(struct user_));
    if (user == NULL) die_nomem();

    /* zero everything */
    memset(user, 0, sizeof(struct user_));

    /* get a unique id for user */
    user->id = ++user_id;

    user->state	= PRE_AUTH;
    RB_INIT(&user->contacts);
    LIST_INIT(&user->sbs);

    /* get the user ip address */
    if (resolve_client(&user->addr, sa, sa_len) == RFAIL) {
        log->debug("debug: fail to get ns client address\n");
	user = user_free(user);
        return(NULL);
    }

    if (RB_INSERT(users_, &users, user)) {
	log->debug("debug: fail to insert new user on tree (already there)\n");
	user = user_free(user);
	return(NULL);
    }

    log->debug("debug: connection from [%s]\n", &user->addr);
    return(user);
}

void
user_disconnect(struct user_ *user) {
 client_	*proxy = user->ns.xfr_proxy;

    if (user->state == USR_TWN_S)
        sql_password_error(&user->email);

    /* free old proxy xfr address */
    if (proxy != NULL) {

	/* free xfr_proxy */
	proxy_free(proxy);
	user->ns.xfr_proxy = NULL;
	free(proxy);
    }

    /* close all SB connections */
    sb_disconnect_all(user);

    /* close connection to ns client */
    user->ns.client = client_disconnect(user->ns.client);

    /* close connection to ns server */
    user->ns.server = server_disconnect(user->ns.server);

    /* free contacts */
    contacts_free(user);

    /* remove from sql */
    if (user->email.len > 0)
	sql_user_disconnect(&user->email);

    /* decrease user counter */
    if (user->state == CONNECTED)
	--user_inuse;

    /* remove user from tree and memory */
    RB_REMOVE(users_, &users, user);
    user = user_free(user);
}

/* remove users from tree and memory */
void free_users_tree(void) {

	struct user_ *user;
	struct user_ *next;
	struct sb_ *sb;
	struct sb_user_	 *sb_user;

	 for (user = RB_MIN(users_, &users); user != NULL; user = next) {
		next = RB_NEXT(users_, &users, user);

		contacts_free(user);
		str_free(&user->addr);
		str_free(&user->dn);
		str_free(&user->email);
		str_free(&user->status);
		server_flush_commands(user->ns.server);
		str_free(&user->ns.server->host);
		str_free(&user->ns.server->port);
		client_flush_commands(user->ns.client);

		while (!LIST_EMPTY(&user->sbs)) {
			sb = LIST_FIRST(&user->sbs);
			sb_free_xfr(sb);
			str_free(&sb->start);
			server_flush_commands(sb->server);
			str_free(&sb->server->host);
			str_free(&sb->server->port);
			client_flush_commands(sb->client);
			free(sb->server);
			free(sb->client);

		    while (!SLIST_EMPTY(&sb->sb_users)) {
		    	sb_user = SLIST_FIRST(&sb->sb_users);
		    	sb_user = sb_user_free(sb, sb_user);
		    }
		    LIST_REMOVE(sb, sb__);
		    free(sb);
		}

		free(user->ns.server);
		free(user->ns.client);
		RB_REMOVE(users_, &users, user);
		free(user);
    }

}
