/*
 * 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: protocol.c 99 2009-01-25 01:25:23Z loos-br $";

#include <string.h>

#include "ns.h"
#include "sb.h"
#include "fmt.h"
#include "msg.h"
#include "sql.h"
#include "return.h"
#include "protocol.h"
#include "array_cmd.h"
#include "msn-proxy.h"

#include "msnp8.h"
#include "msnp12.h"
#include "msnp18.h"

int
msn_encode(string *in, string *out, int options) {
 unsigned char	byte;
 string		buf;

    /* check args */
    if (in == (string *)0 || out == (string *)0)
	return(RFAIL);

    /* set everything */
    str_reset(out);
    str_zero(&buf);
    buf.s   = in->s;
    buf.len = in->len;

    while(buf.len > 0) {
	if (*buf.s >= 127) {
	    /* utf8 encode */

	    /* first utf8 byte */
	    byte = 0xc0;
	    byte |= (*buf.s >> 6) & 0x03;
	    if (str_cat(out, &byte, 1) == 0) die_nomem();

	    /* second and last - yes this is a bogus utf8 encode - utf8 byte */
	    byte = 0x80;
	    byte |= *buf.s & 0x3f;
	    if (str_cat(out, &byte, 1) == 0) die_nomem();

	} else if ((options & PAYLOAD) == 0 && *buf.s <= 32) {
	    /* url encode */
	    if (str_cat(out, (unsigned char *)"%", 1) == 0) die_nomem();

	    /* first byte */
	    byte = *buf.s >> 4;
	    byte = (byte > 9) ? ('a' + byte - 10) : ('0' + byte);
	    if (str_cat(out, &byte, 1) == 0) die_nomem();

	    /* last byte */
	    byte = *buf.s & 0x0f;
	    byte = (byte > 9) ? ('a' + byte - 10) : ('0' + byte);
	    if (str_cat(out, &byte, 1) == 0) die_nomem();

	} else if ((options & PAYLOAD) == 0 && *buf.s == '%') {
	    /* escape '%' */
	    if (str_cat(out, (unsigned char *)"%%", 2) == 0) die_nomem();
	} else if ((options & PAYLOAD) == 0 &&
		   options & INFO && *buf.s == '\'') {
	    /* escape '\'' */
	    if (str_cat(out, (unsigned char *)"&apos;", 6) == 0) die_nomem();
	} else {
	    /* no encode needed */
	    if (str_cat(out, buf.s, 1) == 0) die_nomem();
	}
	buf.len--;
	buf.s++;
    }
    return(ROK);
}

int
msn_decode(string *in, string *out) {
 string		buf;
 unsigned char	c;

    /* check args */
    if (in == (string *)0 || out == (string *)0)
	return(RFAIL);

    /* set everything */
    str_reset(out);
    str_zero(&buf);
    buf.s   = in->s;
    buf.len = in->len;

    while(buf.len > 0) {

	/* UTF8 encoded char */
	if ((*buf.s & 0xe0) == 0xc0) {

	    /* check for next byte */
	    if (buf.len <= 1)
		goto error;

	    c = (*buf.s & 0x1f) << 6;

	    buf.len--;
	    buf.s++;

	    c |= (*buf.s & 0x3f);

	    if (str_cat(out, &c, 1) == 0)
		die_nomem();

	    buf.len--;
	    buf.s++;
	    continue;
	}

	/* URL-like encoded char */
	if (*buf.s == '%' && buf.len >= 3 &&
	    ((*(buf.s + 1) >= '0' && *(buf.s + 1) <= '9') ||
	     (*(buf.s + 1) >= 'a' && *(buf.s + 1) <= 'f') ||
	     (*(buf.s + 1) >= 'A' && *(buf.s + 1) <= 'F')) &&
	    ((*(buf.s + 2) >= '0' && *(buf.s + 2) <= '9') ||
	     (*(buf.s + 2) >= 'a' && *(buf.s + 2) <= 'f') ||
	     (*(buf.s + 2) >= 'A' && *(buf.s + 2) <= 'F'))) {

	    buf.len--;
	    buf.s++;

	    if (*buf.s >= '0' && *buf.s <= '9')
		c = (*buf.s - '0') << 4;
	    else if (*buf.s >= 'a' && *buf.s <= 'f')
		c = (*buf.s - 'a') << 4;
	    else if (*buf.s >= 'A' && *buf.s <= 'F')
		c = (*buf.s - 'A') << 4;

	    buf.len--;
	    buf.s++;

	    if (*buf.s >= '0' && *buf.s <= '9')
		c |= (*buf.s - '0');
	    else if (*buf.s >= 'a' && *buf.s <= 'f')
		c |= (*buf.s - 'a');
	    else if (*buf.s >= 'A' && *buf.s <= 'F')
		c |= (*buf.s - 'A');

	    if (str_cat(out, &c, 1) == 0)
		die_nomem();

	    buf.len--;
	    buf.s++;
	    continue;
	}

	if (*buf.s == '&' && buf.len >= 6) {
	    if (strncmp((char *)buf.s, "&apos;", 6) == 0) {
		if (str_cat(out, (unsigned char *)"'", 1) == 0)
		    die_nomem();
		buf.len -= 6;
		buf.s   += 6;
	    }
	}

	if (str_cat(out, buf.s, 1) == 0)
	    die_nomem();

	buf.len--;
	buf.s++;
    }
    return(ROK);

error:
    str_free(out);
    return(RFAIL);
}

int
msn_get_ver(struct user_ *user, command *cmd, int args) {
 string			*arg;

    if (cmd->args_len < 2)
	return(RFAIL);

    arg = cmd->args[1];
    if (!arg || arg->len == 0)
	return(RFAIL);

    if (strncmp("CVR0", (char *)arg->s, arg->len) == 0 ||
	strncmp("MSNP8", (char *)arg->s, arg->len) == 0 ||
	strncmp("MSNP9", (char *)arg->s, arg->len) == 0) {
	user->version = MSNP8;
	user->cmds    = MSNP8_CMDS;
	user->sbcmds  = MSNP8_SBCMDS;
    } else if (strncmp("MSNP10", (char *)arg->s, arg->len) == 0 ||
	       strncmp("MSNP11", (char *)arg->s, arg->len) == 0 ||
	       strncmp("MSNP12", (char *)arg->s, arg->len) == 0) {
	user->version = MSNP12;
	user->cmds    = MSNP12_CMDS;
	user->sbcmds  = MSNP12_SBCMDS;
    } else if (strncmp("MSNP13", (char *)arg->s, arg->len) == 0 ||
	       strncmp("MSNP14", (char *)arg->s, arg->len) == 0) {
	user->version = MSNP13;
	user->cmds    = MSNP18_CMDS;
	user->sbcmds  = MSNP18_SBCMDS;
    } else if (strncmp("MSNP15", (char *)arg->s, arg->len) == 0 ||
	       strncmp("MSNP16", (char *)arg->s, arg->len) == 0) {
	user->version = MSNP15;
	user->cmds    = MSNP18_CMDS;
	user->sbcmds  = MSNP18_SBCMDS;
    } else if (strncmp("MSNP17", (char *)arg->s, arg->len) == 0 ||
	       strncmp("MSNP18", (char *)arg->s, arg->len) == 0) {
	user->version = MSNP18;
	user->cmds    = MSNP18_CMDS;
	user->sbcmds  = MSNP18_SBCMDS;
    } else
	return(RFAIL);

    return(ROK);
}

string *
get_arg(command *cmd, int narg) {

    if (cmd == NULL || cmd->args_len < narg || cmd->args[narg] == NULL)
	return(NULL);
    return(cmd->args[narg]);
}

int
check_arg(string *arg, char *cmp) {
    /* check if arg exist */
    if (!arg || !arg->s || arg->len == 0)
	return(RFAIL);

    /* check for string to compare */
    if (!cmp)
	return(RFAIL);

    /* compare */
    if (strcmp((char *)arg->s, cmp) == 0)
	return(ROK);

    return(RFAIL);
}

int
client_msn_pre_usr(struct user_ *user, command *cmd, int args) {
 string		*email;
 int		version = ROK;

    /* TWN I - initial authentication command */
    if (cmd->args_len == 4 &&
		(check_arg(cmd->args[1], "TWN") == ROK ||
		check_arg(cmd->args[1], "SSO") == ROK) &&
		check_arg(cmd->args[2], "I") == ROK ) {

	/* set state - initial authentication */
	user->state = USR_TWN_I;

	/* save email */
	email = cmd->args[3];
	if (email->len == 0)
	    return(RFAIL);

	if (user->email.len > 0 &&
	    strcmp((char *)user->email.s, (char *)email->s) == 0)
	    return(ROK);

	if (str_copy(&user->email, email->s, email->len) == 0)
	    die_nomem();

	/* update database */
	if (sql_users_update(user) == RFAIL)
	    return(RFAIL);

	/* load contacts */
	if (sql_contact_load_all(user) == RFAIL)
	    return(RFAIL);

	/* read acls from database */
	switch (sql_read_user_acl(user)) {

	    case ROK:
		switch (user->version) {
		    case CVR0:
		    case MSNP8:
		    case MSNP9:
			if ((user->commands & MSNP8) == 0) break;
			version = RFAIL;

		    case MSNP10:
		    case MSNP11:
		    case MSNP12:
			if (version == ROK && (user->commands & MSNP12) == 0) break;
			version = RFAIL;

		    case MSNP13:
		    case MSNP14:
			if (version == ROK && (user->commands & MSNP13) == 0) break;
			version = RFAIL;

		    case MSNP15:
		    case MSNP16:
			if (version == ROK && (user->commands & MSNP15) == 0) break;
			version = RFAIL;

		    case MSNP17:
		    case MSNP18:
			if (version == ROK && (user->commands & MSNP18) == 0) break;
			version = RFAIL;

		    default:
			sql_version_denied(&user->email);
			return(RFAIL);
		}
		return(ROK);

	    case NO:
		/* connection denied - log */
		if (user->commands & CONNECT)
		    sql_connection_denied(&user->email);

		/* fallthrough */
	    case RFAIL:
		/* fallthrough */
	    default:
		return(RFAIL);
	}

	/* notreached */
	return(RFAIL);

    /* TWN S - sending password */
    } else if (cmd->args_len == 4 &&
		check_arg(cmd->args[1], "TWN") == ROK &&
		check_arg(cmd->args[2], "S") == ROK ) {

	return(ROK);

    /* SSO S - sending password */
    } else if (cmd->args_len >= 5 &&
		check_arg(cmd->args[1], "SSO") == ROK &&
		check_arg(cmd->args[2], "S") == ROK ) {

	return(ROK);
    }
    return(RFAIL);
}

int
server_msnp12_pre_usr(struct user_ *user, command *cmd, int args) {
 string		*dn;

    /* USR OK - authentication sucessfull */
    /* XXX - xarg */
    if (cmd->args_len == 5 &&
                check_arg(cmd->args[1], "OK") == ROK &&
                check_arg(cmd->args[4], "0") == ROK ) {

	/* save user state */
	user->state = CONNECTED;

	/* count one more happy (and connected) user */
	if (++user_inuse > user_max)
	    user_max = user_inuse;

	/* update display name */
	dn = cmd->args[2];
	if (dn->len == 0)
	    return(RFAIL);

        /* decode display name */
	if (msn_decode(dn, &user->dn) == RFAIL)
	    return(RFAIL);

        return(ROK);

    /* TWN S - hash for authentication */
    } else if (cmd->args_len == 4 &&
                check_arg(cmd->args[1], "TWN") == ROK &&
                check_arg(cmd->args[2], "S") == ROK ) {

	user->state = USR_TWN_S;
        return(ROK);
    } else if (cmd->args_len == 5 &&
                check_arg(cmd->args[1], "SSO") == ROK &&
                check_arg(cmd->args[2], "S") == ROK ) {

        user->state = USR_TWN_S;
        return(ROK);
    }
    return(RFAIL);
}

int
server_msnp8_pre_usr(struct user_ *user, command *cmd, int args) {
 string		*dn;

    /* USR OK - authentication sucessfull */
    if (cmd->args_len == 6 &&
                check_arg(cmd->args[1], "OK") == ROK &&
                check_arg(cmd->args[4], "1") == ROK &&
                check_arg(cmd->args[5], "0") == ROK ) {

	/* save user state */
        user->state = CONNECTED;

	/* count one more happy (and connected) user */
	if (++user_inuse > user_max)
	    user_max = user_inuse;

	/* update display name */
	dn = cmd->args[3];
	if (dn->len == 0)
	    return(RFAIL);

	/* decode display name */
	if (msn_decode(dn, &user->dn) == RFAIL)
	    return(RFAIL);

	return(sql_update_user_dn(user));

    /* TWN S - hash for authentication */
    } else if (cmd->args_len == 4 &&
                check_arg(cmd->args[1], "TWN") == ROK &&
                check_arg(cmd->args[2], "S") == ROK ) {

	user->state = USR_TWN_S;
        return(ROK);
    }
    return(RFAIL);
}

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

    if (cmd->args_len < 2)
	return(RFAIL);

    if (check_arg(cmd->args[1], "NS") == ROK) {

	/* XFR NS - request notification server reconnect */
	switch (user->version) {
	case MSNP13:
	case MSNP15:
	case MSNP18:

	    return msnp13_xfr_ns_proxy(user, cmd, args);
	}
	return xfr_ns_proxy(user, cmd, args);

    } else if (check_arg(cmd->args[1], "SB") == ROK) {

	/* XFR SB - request switch board connection */
	return xfr_sb_proxy(user, cmd, args);
    }
    return(RFAIL);
}

int
xfr_post_proxy(struct user_ *user, command *cmd, int args) {
    if (cmd->args_len != 5)
	return(RFAIL);

    if (check_arg(cmd->args[1], "NS") == ROK ) {

	/* XFR NS - request notification server reconnect */
	return xfr_ns_post_proxy(user, cmd, args);

    } else if (check_arg(cmd->args[1], "SB") == ROK ) {

	return(ROK);
    }
    return(RFAIL);
}

int
msn_warning_msg_client(struct sb_ *sb) {
 command		*cmd;
 string			*arg;
 string			msgfont;
 string			msgcolor;
 string			warndn;
 string			warnmsg;
 string			warnemail;

    /* reset strings */
    str_zero(&msgfont);
    str_zero(&msgcolor);
    str_zero(&warndn);
    str_zero(&warnmsg);
    str_zero(&warnemail);

    /* read warning message */
    if (sql_read_warning(&warndn, &warnmsg, &warnemail, &msgfont, &msgcolor) == RFAIL)
	return(RFAIL);

    /* fake command */
    cmd = command_alloc();
    if (str_copy(&cmd->cmd, (unsigned char *)"MSG", 3) == 0) die_nomem();

    arg = command_add_arg(cmd);
    if (warnemail.len > 0 && str_copy(arg, warnemail.s, warnemail.len) == 0)
	die_nomem();
    str_free(&warnemail);

    arg = command_add_arg(cmd);
    if (warndn.len > 0 && str_copy(arg, warndn.s, warndn.len) == 0)
	die_nomem();
    str_free(&warndn);

    /* format msg payload */
    cmd->payload.len = fmt_printf(NULL,
		"MIME-Version: 1.0\r\n"
		"Content-Type: text/plain; charset=UTF-8\r\n"
		"X-MMS-IM-Format: FN=%s; EF=; CO=%s; CS=0; PF=0\r\n\r\n%s",
		&msgfont, &msgcolor, &warnmsg);
    if (str_ready(&cmd->payload, cmd->payload.len + 1) == 0) die_nomem();
    cmd->payload.len = fmt_printf(cmd->payload.s,
		"MIME-Version: 1.0\r\n"
		"Content-Type: text/plain; charset=UTF-8\r\n"
		"X-MMS-IM-Format: FN=%s; EF=; CO=%s; CS=0; PF=0\r\n\r\n%s",
		&msgfont, &msgcolor, &warnmsg);
    str_free(&warnmsg);
    str_free(&msgfont);
    str_free(&msgcolor);

    /* add payload size */
    arg = command_add_arg(cmd);
    arg->len = fmt_printf(NULL, "%d", cmd->payload.len);
    if (str_ready(arg, arg->len + 1) == 0) die_nomem();
    arg->len = fmt_printf(arg->s, "%d", cmd->payload.len);

    /* add command to queue */
    commands_add_command(&sb->server->commands, cmd);
    client_sched_write(sb->client);

    return(ROK);
}

int
send_fln(struct user_ *user, struct contact_ *contact) {
 command		*cmd;
 string			*arg;

    if (contact == NULL || contact->c.len == 0)
	return(RFAIL);

    if (strcmp((char *)contact->status.s, "OFF") == 0)
	return(ROK);

    /* fake command */
    cmd = command_alloc();
    cmd->ignore = 1;
    if (str_copy(&cmd->cmd, (unsigned char *)"FLN", 3) == 0) die_nomem();

    arg = command_add_arg(cmd);
    if (user->version == MSNP18) {
	if (contact->c.len > 0 &&
	    (str_copys(arg, (unsigned char *)"1:") == 0 ||
	    str_cat(arg, contact->c.s, contact->c.len) == 0)) {

	    die_nomem();
	}
    } else {
	if (contact->c.len > 0 &&
	    str_copy(arg, contact->c.s, contact->c.len) == 0) {

	    die_nomem();
	}
    }

    switch (user->version) {
    case MSNP12:
    case MSNP13:
    case MSNP15:
    case MSNP18:
	arg = command_add_arg(cmd);
	if (str_copys(arg, (unsigned char *)"1") == 0) die_nomem();

	arg = command_add_arg(cmd);
	if (str_copys(arg, (unsigned char *)"0") == 0) die_nomem();
	break;
    }

    /* add command to queue */
    commands_add_command(&user->ns.server->commands, cmd);
    client_sched_write(user->ns.client);

    return(ROK);
}

int
send_nln(struct user_ *user, struct contact_ *contact) {
 command		*cmd;
 string			*arg;
 string			dn;

    if (contact == NULL || contact->c.len == 0)
	return(RFAIL);

    if (strcmp((char *)contact->status.s, "OFF") == 0)
	return(ROK);

    /* fake command */
    cmd = command_alloc();
    cmd->ignore = 1;
    if (str_copy(&cmd->cmd, (unsigned char *)"NLN", 3) == 0) die_nomem();

    /* status */
    arg = command_add_arg(cmd);
    if (contact->status.len > 0 && 
	str_copy(arg, contact->status.s, contact->status.len) == 0)
	die_nomem();

    /* contact email */
    arg = command_add_arg(cmd);
    if (user->version == MSNP18) {
	if (contact->c.len > 0 && 
	    (str_copys(arg, (unsigned char *)"1:") == 0 ||
	    str_cat(arg, contact->c.s, contact->c.len) == 0)) {

	    die_nomem();
	}
    } else {
	if (contact->c.len > 0 && 
	    str_copy(arg, contact->c.s, contact->c.len) == 0) {

	    die_nomem();
	}
    }

    /* only for some versions */
    if (user->version == MSNP13 || user->version == MSNP15) {

	arg = command_add_arg(cmd);
	if (str_copys(arg, (unsigned char *)"1") == 0) die_nomem();
    }

    /* display name */
    arg = command_add_arg(cmd);
    (void)str_copys(arg, (unsigned char *)"");
    if (contact->dn.len > 0) {
	str_zero(&dn);
	(void)msn_encode(&contact->dn, &dn, 0);
	if (dn.len > 0 && str_copy(arg, dn.s, dn.len) == 0)
	    die_nomem();
    }

    /* flags */
    arg = command_add_arg(cmd);
    if (user->version == MSNP18) {
	arg->len = fmt_printf(NULL, "%l:%l", contact->flags, contact->flags2);
	if (str_ready(arg, arg->len + 1) == 0) die_nomem();
	arg->len = fmt_printf(arg->s, "%l:%l", contact->flags, contact->flags2);
    } else {
	arg->len = fmt_printf(NULL, "%l", contact->flags);
	if (str_ready(arg, arg->len + 1) == 0) die_nomem();
	arg->len = fmt_printf(arg->s, "%l", contact->flags);
    }

    /* obj */
    arg = command_add_arg(cmd);
    if (contact->o.len > 0) {
	if (str_copy(arg, contact->o.s, contact->o.len) == 0)
	    die_nomem();
    } else {
	if (str_copys(arg, (unsigned char *)"0") == 0)
	    die_nomem();
    }

    /* add command to queue */
    commands_add_command(&user->ns.server->commands, cmd);
    client_sched_write(user->ns.client);

    if (user->version == MSNP18) {

	/* fake command */
	cmd = command_alloc();
	cmd->ignore = 1;
	if (str_copys(&cmd->cmd, (unsigned char *)"UBX") == 0) die_nomem();

	/* user */
	arg = command_add_arg(cmd);
	if (contact->c.len > 0 &&
	    (str_copys(arg, (unsigned char *)"1:") == 0 ||
	    str_cat(arg, contact->c.s, contact->c.len) == 0)) {

	    die_nomem();
	}

	arg = command_add_arg(cmd);
	if (str_copys(arg, (unsigned char *)"0") == 0) die_nomem();

	/* add command to queue */
	commands_add_command(&user->ns.server->commands, cmd);
	client_sched_write(user->ns.client);
    }

    return(ROK);
}

int
msnp18_adl_sync(struct user_ *user, command *cmd, int args) {
 client_		*client = user->ns.client;
 server_		*server = user->ns.server;

    has_trid(cmd);
    if (cmd->trid >= client->commands.wait) {
	client->commands.state &= ~SENDDELAY;
	server_sched_write(server);
    }

    return(ROK);
}
