/*
 * 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: contacts.c 114 2009-03-17 01:08:32Z loos-br $";

#include <errno.h>
#include <string.h>

#include "io.h"
#include "fmt.h"
#include "sql.h"
#include "xml.h"
#include "return.h"
#include "contacts.h"
#include "protocol.h"
#include "array_cmd.h"
#include "msn-proxy.h"

int
contact_rb_cmp(struct contact_ *c1, struct contact_ *c2) {
    if (!c1 || !c1->c.s || !c1->c.len ||
	!c2 || !c2->c.s || !c2->c.len) {
	return(0);
    }
    return strcmp((char *)c1->c.s, (char *)c2->c.s);
}

RB_GENERATE(contacts, contact_, contact__, contact_rb_cmp)

void
contact_zero(struct contact_ *contact) {
    memset(contact, 0, sizeof(struct contact_));
}

void
contact_free(struct contact_ *contact) {
    str_free(&contact->status);
    str_free(&contact->group);
    str_free(&contact->info);
    str_free(&contact->uid);
    str_free(&contact->dn);
    str_free(&contact->c);
    str_free(&contact->o);
    if (contact->save) {
	contact_free(contact->save);
	free(contact->save);
	contact->save = NULL;
    }
    contact_zero(contact);
}

void
contact_print(struct contact_ *contact) {
 char		*update;
 int		i = 0;

    switch (contact->updated) {
	case UPDATE: update = "update"; break;
	case NONE: update = "none"; break;
	case NEW: update = "new"; break;
	default: update = "";
    }
    io_printf(1, "debug contact: update: [%S]\n", update);
    io_printf(1, "debug contact:      c: [%s]\n", &contact->c);
    io_printf(1, "debug contact:     dn: [%s]\n", &contact->dn);
    io_printf(1, "debug contact:   chat: [%S]\n",
	(contact->chat == YES) ? "YES" : "NO");

    io_printf(1, "debug contact:   deny: [");
    if (contact->deny & CONTACT_DENY) {
	io_printf(1, "CONTACT_DENY");
	++i;
    }
    if (contact->deny & CONTACT_BLOCKED) {
	if (i) io_printf(1, ", ");
	io_printf(1, "CONTACT_BLOCKED");
    }
    io_printf(1, "]\n");

    io_printf(1, "debug contact:   info: [%s]\n", &contact->info);
    io_printf(1, "debug contact:  flags: [%l]\n", contact->flags);
    io_printf(1, "debug contact: flags2: [%l]\n", contact->flags2);
    io_printf(1, "debug contact:  lists: [%d]\n", contact->lists);
    io_printf(1, "debug contact: status: [%s]\n", &contact->status);
    io_printf(1, "debug contact: object: [%s]\n", &contact->o);

    if (contact->save)
	contact_print(contact->save);
}

void
contacts_free(struct user_ *user) {
 struct contact_	*contact;
 struct contact_	*next;

    for (contact = RB_MIN(contacts, &user->contacts); contact != NULL;
	 contact = next) {
	next = RB_NEXT(contacts, &user->contacts, contact);
	RB_REMOVE(contacts, &user->contacts, contact);
	contact_free(contact);
	free(contact);
    }
}

struct contact_ *
contact_add(struct user_ *user, string *c) {
 struct contact_	*contact;
 struct contact_	*tmp;

    contact = (struct contact_ *)malloc(sizeof(struct contact_));
    if (contact == NULL) die_nomem();

    memset(contact, 0, sizeof (struct contact_));
    if (c->len > 0 && str_copy(&contact->c, c->s, c->len) == 0)
	die_nomem();
    contact->updated = NONE;

    tmp = RB_INSERT(contacts, &user->contacts, contact);
    if (tmp) {
	contact_free(contact);
	free(contact);
	contact = tmp;
    }

    return contact;
}

struct contact_ *
contact_update(struct user_ *user, string *c) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 unsigned int		len = 0;

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

    /* find contact in db if exist */
    memset(&find, 0, sizeof (struct contact_));
    c->p = (unsigned char *)strchr((char *)c->s, ':');
    if (c->p)
	len = ++c->p - c->s;

    if (len > 0 && len < c->len) {
	if (str_copy(&find.c, c->s + len, c->len - len) == 0) die_nomem();
    } else {
	if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    }
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    if (contact == NULL) {
	contact = (struct contact_ *)malloc(sizeof(struct contact_));
	if (contact == NULL) die_nomem();
	memset(contact, 0, sizeof (struct contact_));
	if (len > 0 && len < c->len) {
	    if (str_copy(&contact->c, c->s + len, c->len - len) == 0) die_nomem();
	} else {
	    if (str_copy(&contact->c, c->s, c->len) == 0) die_nomem();
	}
	RB_INSERT(contacts, &user->contacts, contact);
	contact->chat = NO;
	contact->updated = NEW;
	if (user->commands & CONTACT_DENY)
	    contact->deny |= CONTACT_DENY;
	return(contact);
    } else {
	save = (struct contact_ *)malloc(sizeof(struct contact_));
	if (save == NULL) die_nomem();
	memset(save, 0, sizeof (struct contact_));
	if (str_copy(&save->c, contact->c.s, contact->c.len) == 0) die_nomem();
	save->deny = contact->deny;
	save->chat = NO;
	save->updated = NEW;
	contact->updated = UPDATE;
	contact->save = save;
	return(save);
    }
}

struct contact_ *
contact_find(struct user_ *user, string *c) {
 struct contact_	*contact;
 struct contact_	f;

    memset(&f, 0, sizeof (struct contact_));
    if (c->len > 0 && str_copy(&f.c, c->s, c->len) == 0) die_nomem();

    contact = RB_FIND(contacts, &user->contacts, &f);
    str_free(&f.c);

    return contact;
}

int
msnp13_save_contact(struct user_ *user, command *cmd, int args) {
 struct xml_tag_opt_	*tag_opt;
 struct xml_tags	xml_tag_head;
 struct xml_tag_	*tag_domain;
 struct xml_tag_	*tag_name;
 struct xml_tag_	*tag;
 struct contact_	*contact;
 unsigned long		lists;
 string			domain;
 string			name;
 string			c;

    memset(&xml_tag_head, 0, sizeof(xml_tag_head));
    if (xml_parse(&xml_tag_head, &cmd->payload) == RFAIL)
	return(RFAIL);

    tag = SLIST_FIRST(&xml_tag_head);
    if (tag->name.len == 0 || strcmp((char *)tag->name.s, "ml") != 0)
	return(RFAIL);

    SLIST_FOREACH(tag_domain, &tag->xml_tag_head, xml_tag__) {
	if (tag_domain->name.len == 0 ||
	    strcmp((char *)tag_domain->name.s, "d") != 0) {
	    continue;
	}

	str_zero(&domain);
	SLIST_FOREACH(tag_opt, &tag_domain->xml_tag_opt_head, xml_tag_opt__) {
	    if (tag_opt->name.len > 0 &&
		strcmp((char *)tag_opt->name.s, "n") == 0) {
		domain.len = tag_opt->value.len;
		domain.s   = tag_opt->value.s;
		break;
	   }
	}

	SLIST_FOREACH(tag_name, &tag_domain->xml_tag_head, xml_tag__) {

	    if (tag_name->name.len == 0 ||
		strcmp((char *)tag_name->name.s, "c") != 0) {
		return(RFAIL);
	    }

	    lists = 0;
	    str_zero(&name);
	    SLIST_FOREACH(tag_opt, &tag_name->xml_tag_opt_head, xml_tag_opt__) {
		if (tag_opt->name.len > 0 &&
		    strcmp((char *)tag_opt->name.s, "n") == 0) {
		    name.len = tag_opt->value.len;
		    name.s   = tag_opt->value.s;
		} else if (tag_opt->name.len > 0 &&
		    strcmp((char *)tag_opt->name.s, "l") == 0) {
		    lists  = atol((char *)tag_opt->value.s);
		} else if (tag_opt->name.len > 0 &&
		    strcmp((char *)tag_opt->name.s, "t") == 0) {
		}
	    }

	    /* get contact e-mail */
	    str_zero(&c);
	    c.len = fmt_printf(NULL, "%s@%s", &name, &domain);
	    if (str_ready(&c, c.len + 1) == 0) exit(51);
	    c.len = fmt_printf(c.s, "%s@%s", &name, &domain);
	    str_free(&name);

	    if (c.len == 0) { str_free(&c); continue; }
	    contact = contact_update(user, &c);
	    str_free(&c);

	    if (contact == NULL) continue;

	    /* decode lists */
	    contact->lists = lists | RL;

	    if (str_copy(&contact->dn, contact->c.s, contact->c.len) == 0)
		die_nomem();
	    if (str_copys(&contact->status, (unsigned char *)"OFF") == 0)
		die_nomem();
	    if (str_copys(&contact->group, (unsigned char *)"0") == 0)
		die_nomem();
	    if (str_copys(&contact->uid, (unsigned char *)"0") == 0)
		die_nomem();
	}
    }
    xml_free(&xml_tag_head);

    user->contact_delete = 1;
    return(ROK);
}

int
msnp12_save_contact(struct user_ *user, command *cmd, int args) {
 struct contact_	*save;
 struct contact_	c;
 unsigned long		tmp;
 string			buf;
 log_			*log = &config.log;
 char			*ep;
 int			i;

    /* zero everything */
    str_zero(&buf);

    /* check min len of cmds */
    if (cmd->args_len < 1)
	return(RFAIL);

    /* get contact email */
    buf.s   = cmd->args[0]->s;
    buf.len = cmd->args[0]->len;

    /* check for min buf len */
    if (buf.len < 2)
	return(RFAIL);

    /* skip "N=" */
    if (strncasecmp((char *)buf.s, "N=", 2) == 0) {
        buf.s   += 2;
        buf.len -= 2;
    }

    /* save contact email */
    contact_zero(&c);
    c.c.len = buf.len;
    c.c.s   = buf.s;

    for (i = 1; i < cmd->args_len; i++) {

	str_zero(&buf);
	buf.s   = cmd->args[i]->s;
	buf.len = cmd->args[i]->len;

	if (buf.len >= 2 && strncasecmp((char *)buf.s, "F=", 2) == 0) {
	    /* "F=" == contact display name */
	    buf.len -= 2;
	    buf.s   += 2;
	    if (msn_decode(&buf, &c.dn) == RFAIL) {
		contact_free(&c);
		return(RFAIL);
	    }
	} else if (buf.len >= 2 && strncasecmp((char *)buf.s, "C=", 2) == 0) {
	    /* "C=" == contact uid */
	    buf.len -= 2;
	    buf.s   += 2;
	    if (msn_decode(&buf, &c.uid) == RFAIL) {
		contact_free(&c);
		return(RFAIL);
	    }
	} else if (buf.len >= 1 &&
	    strtoul((char *)buf.s, (char **)0, 10) > 0) {
	    break;
	}
    }

    errno = 0;
    if (i < cmd->args_len) {
	c.lists = strtoul((char *)cmd->args[i++]->s, &ep, 10);
	if (errno != 0 || (ep && *ep != 0)) {
	    log->debug("debug: fail to decode lists value\n");
	    contact_free(&c);
	    return(RFAIL);
	}
    }

    /* skip some unknown flag */
    if (i < cmd->args_len) {
	errno = 0;
	tmp = strtoul((char *)cmd->args[i]->s, &ep, 10);
	if (errno == 0 && *cmd->args[i]->s != 0 && ep && *ep == 0) i++;
    }

    /* save contact group */
    if (i < cmd->args_len) {
	if (msn_decode(cmd->args[i], &c.group) == RFAIL) {
	    contact_free(&c);
	    return(RFAIL);
	}
    }

    save = contact_update(user, &c.c);
    if (save == NULL) {
	contact_free(&c);
	return(ROK);
    }

    if (c.dn.len > 0 && str_copy(&save->dn, c.dn.s, c.dn.len) == 0)
	die_nomem();
    if (c.uid.len > 0 && str_copy(&save->uid, c.uid.s, c.uid.len) == 0)
	die_nomem();
    if (c.group.len > 0 && str_copy(&save->group, c.group.s, c.group.len) == 0)
	die_nomem();

    if (c.lists > 0)
	save->lists = c.lists;

    if (user->contact_delete == 0)
	user->contact_delete = 1;
    return(ROK);
}

int
msnp8_save_contact(struct user_ *user, command *cmd, int args) {
 struct contact_	*save;
 struct contact_	c;
 char			*ep;

    /* check min len of cmds */
    if (cmd->args_len < 3)
        return(RFAIL);

    /* need to rewrite user syn command ? */
    if (user->commands & SAVE_CONTACTS)
	return(ROK);

    /* save contact email */
    contact_zero(&c);
    c.c.len = cmd->args[0]->len;
    c.c.s   = cmd->args[0]->s;

    /* save contact display name */
    if (msn_decode(cmd->args[1], &c.dn) == RFAIL) {
	contact_free(&c);
	return(RFAIL);
    }

    /* save contact group */
    if (cmd->args_len == 4) {
	if (msn_decode(cmd->args[3], &c.group) == RFAIL) {
	    contact_free(&c);
	    return(RFAIL);
	}
    }

    /* save contact lists */
    errno = 0;
    c.lists = strtoul((char *)cmd->args[2]->s, &ep, 10);
    if (errno != 0 || (ep && *ep != 0)) {
	io_printf(1, "ep (%S}\n", ep);
	io_printf(1, "ep (%d}\n", *ep);
	contact_free(&c);
	return(RFAIL);
    }

    save = contact_update(user, &c.c);
    if (save == NULL) {
	contact_free(&c);
	return(ROK);
    }

    if (c.dn.len > 0 && str_copy(&save->dn, c.dn.s, c.dn.len) == 0)
	die_nomem();
    if (c.group.len > 0 && str_copy(&save->group, c.group.s, c.group.len) == 0)
	die_nomem();

    if (c.lists > 0)
	save->lists = c.lists;

    if (user->contact_delete == 0)
	user->contact_delete = 1;
    return(ROK);
}

int
msn_save_contactgroup(string *email, string *group, string *gid) {
 string			tmp;

    /* zero group */
    str_zero(&tmp);

    /* decode group name */
    if (msn_decode(group, &tmp) == RFAIL)
        return(RFAIL);

    /* save group on database */
    if (sql_save_contactgroup(email, &tmp, gid) != ROK) {
        str_free(&tmp);
        return(RFAIL);
    }

    str_free(&tmp);
    return(ROK);
}

int
msnp12_save_contactgroup(struct user_ *user, command *cmd, int args) {
    if (user->commands & SAVE_CONTACTS) return(ROK);
    return(msn_save_contactgroup(&user->email, cmd->args[0], cmd->args[1]));
}

int
msnp8_save_contactgroup(struct user_ *user, command *cmd, int args) {
    if (user->commands & SAVE_CONTACTS) return(ROK);
    return(msn_save_contactgroup(&user->email, cmd->args[1], cmd->args[0]));
}

int
msnp13_contact_initial_state(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 string			*c;		/* contact */
 string			*s;		/* status */
 string			*d;		/* display name */
 string			*o;		/* object data */

    if (cmd->args_len < 5)
	return(ROK);

    if ((s = get_arg(cmd, 1)) == NULL ||
	(c = get_arg(cmd, 2)) == NULL ||
	(d = get_arg(cmd, 4)) == NULL) {

	return(ROK);
    }

    memset(&find, 0, sizeof (struct contact_));
    if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    save = contact_update(user, c);
    if (save == NULL)
	return(ROK);

    if (contact == NULL)
	contact = save;

    if (d->len > 0 && msn_decode(d, &save->dn) == RFAIL) {
	contact_free(save);
	free(save);
	contact->save = NULL;
	return(ROK);
    }
    if (c->len > 0 && str_copy(&save->c, c->s, c->len) == 0)
	die_nomem();
    o = get_arg(cmd, 6);
    if (o && o->len > 0 && str_copy(&save->o, o->s, o->len) == 0)
	die_nomem();
    if (s->len > 0 && str_copy(&save->status, s->s, s->len) == 0)
	die_nomem();
    save->flags = (get_arg(cmd, 5) == NULL) ? 0 : atoll((char *)cmd->args[5]->s);

    /* check for blocked contacts */
    if (save->deny & CONTACT_DENY)
	save->deny |= CONTACT_BLOCKED;

    /* SEEIMG */
    if (o && (user->commands & SEEIMG)) {
	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[6] = NULL;
	    cmd->args_len--;
	}
    }

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

    /* check for blocked contacts */
    if (contact->deny & CONTACT_BLOCKED)
	return(RETURN);

    return(ROK);
}

int
msn_contact_initial_state(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 string			*c;		/* contact */
 string			*s;		/* status */
 string			*d;		/* display name */
 string			*o;		/* object data */

    if (cmd->args_len < 4)
	return(ROK);

    if ((s = get_arg(cmd, 1)) == NULL ||
	(c = get_arg(cmd, 2)) == NULL ||
	(d = get_arg(cmd, 3)) == NULL) {

	return(ROK);
    }

    memset(&find, 0, sizeof (struct contact_));
    if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    save = contact_update(user, c);
    if (save == NULL)
	return(ROK);

    if (contact == NULL)
	contact = save;

    if (d->len > 0 && msn_decode(d, &save->dn) == RFAIL) {
	contact_free(save);
	free(save);
	contact->save = NULL;
	return(ROK);
    }
    if (c->len > 0 && str_copy(&save->c, c->s, c->len) == 0)
	die_nomem();
    o = get_arg(cmd, 6);
    if (o && o->len > 0 && str_copy(&save->o, o->s, o->len) == 0)
	die_nomem();
    if (s->len > 0 && str_copy(&save->status, s->s, s->len) == 0)
	die_nomem();
    save->flags = (get_arg(cmd, 4) == NULL) ? 0 : atoll((char *)cmd->args[4]->s);

    /* check for blocked contacts */
    if (save->deny & CONTACT_DENY)
	save->deny |= CONTACT_BLOCKED;

    /* SEEIMG */
    if (o && (user->commands & SEEIMG)) {
	str_free(o);
	free(o);
	cmd->args[5] = NULL;
	cmd->args_len--;
    }

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

    /* check for blocked contacts */
    if (contact->deny & CONTACT_BLOCKED)
	return(RETURN);

    return(ROK);
}

int
msn_contact_logoff(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 unsigned int		len;
 string			*c;

    if (cmd->args_len < 1)
	return(ROK);

    c = get_arg(cmd, 0);
    if (c == NULL || c->len == 0)
	return(ROK);

    /* find contact in db if exist */
    memset(&find, 0, sizeof (struct contact_));
    if (user->version == MSNP18 && c->len > 0) {
	c->p = (unsigned char *)strchr((char *)c->s, ':');
	if (c->p) {
	    len = c->p - c->s;
	    if (c->len > ++len)
		if (str_copy(&find.c, c->s + len, c->len - len) == 0) die_nomem();
	} else
	    goto error;
    } else {
	if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    }
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    if (contact == NULL)
	return(ROK);

    if (cmd->ignore)
	return(ROK);

    save = contact_update(user, &contact->c);
    if (save == NULL)
	return(ROK);

    if (str_copys(&save->status, (unsigned char *)"OFF") == 0) die_nomem();
    save->deny &= ~CONTACT_BLOCKED;

    (void)sql_contact_save(user, save);

    if (contact->deny & CONTACT_DENY)
	return(RETURN);

error:
    return(ROK);
}

int
msnp18_contact_change(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 unsigned int		len = 0;
 string			*c;		/* contato */
 string			*d;		/* texto nome */
 string			*o;		/* object */
 string			*s;		/* status */
 char			*ep;
 char			*f;


    if (cmd->args_len < 5)
	return(ROK);

    if ((s = get_arg(cmd, 0)) == NULL ||
	(c = get_arg(cmd, 1)) == NULL ||
	(d = get_arg(cmd, 2)) == NULL ||
	(o = get_arg(cmd, 4)) == NULL) {

	return(ROK);
    }

    memset(&find, 0, sizeof (struct contact_));
    c->p = (unsigned char *)strchr((char *)c->s, ':');
    if (c->p)
	len = ++c->p - c->s;
    if (len > 0 && len < c->len) {
	if (str_copy(&find.c, c->s + len, c->len - len) == 0) die_nomem();
    } else {
	if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    }
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    if (contact == NULL || cmd->ignore)
	return(ROK);

    save = contact_update(user, &contact->c);
    if (save == NULL)
	return(ROK);

    if (d->len > 0 && msn_decode(d, &save->dn) == RFAIL) {
	contact_free(save);
	free(save);
	contact->save = NULL;
	return(ROK);
    }
    if (contact->c.len > 0 && str_copy(&save->c, contact->c.s, contact->c.len) == 0)
	die_nomem();
    if (o->len > 0 && str_copy(&save->o, o->s, o->len) == 0)
	die_nomem();
    if (s->len > 0 && str_copy(&save->status, s->s, s->len) == 0)
	die_nomem();

    f = (get_arg(cmd, 3) == NULL) ? "" : (char *)cmd->args[3]->s;
    save->flags = strtoul(f, &ep, 10);
    if (ep && *ep == ':')
	save->flags2 = atol(++ep);

    /* check for blocked contacts */
    if (save->deny & CONTACT_DENY)
	save->deny |= CONTACT_BLOCKED;

    /* SEEIMG */
    if (o && (user->commands & SEEIMG)) {
	if (str_copys(o, (unsigned char *)"0") == 0) exit (51);
    }

    if (sql_contact_save(user, save) == RFAIL)
	return(ROK);

    /* check for blocked contacts */
    if (contact->deny & CONTACT_BLOCKED)
	return(RETURN);

    return(ROK);
}

int
msnp13_contact_change(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 string			*c;
 string			*d;
 string			*o;
 string			*s;

    if (cmd->args_len < 4)
	return(ROK);

    if ((s = get_arg(cmd, 0)) == NULL ||
	(c = get_arg(cmd, 1)) == NULL ||
	(d = get_arg(cmd, 3)) == NULL) {

	return(ROK);
    }

    memset(&find, 0, sizeof (struct contact_));
    if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    if (contact == NULL || cmd->ignore)
	return(ROK);

    save = contact_update(user, c);
    if (save == NULL)
	return(ROK);

    if (d->len > 0 && msn_decode(d, &save->dn) == RFAIL) {
	contact_free(save);
	free(save);
	contact->save = NULL;
	return(ROK);
    }
    if (c->len > 0 && str_copy(&save->c, c->s, c->len) == 0)
	die_nomem();
    o = get_arg(cmd, 5);
    if (o && o->len > 0 && str_copy(&save->o, o->s, o->len) == 0)
	die_nomem();
    if (s->len > 0 && str_copy(&save->status, s->s, s->len) == 0)
	die_nomem();
    save->flags = (get_arg(cmd, 4) == NULL) ? 0 : atoll((char *)cmd->args[4]->s);

    /* check for blocked contacts */
    if (save->deny & CONTACT_DENY)
	save->deny |= CONTACT_BLOCKED;

    /* SEEIMG */
    if (o && (user->commands & SEEIMG)) {
	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[5] = NULL;
	    cmd->args_len--;
	}
    }

    if (sql_contact_save(user, save) == RFAIL)
	return(ROK);

    /* check for blocked contacts */
    if (contact->deny & CONTACT_BLOCKED)
	return(RETURN);

    return(ROK);
}

int
msnp8_contact_change(struct user_ *user, command *cmd, int args) {
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	find;
 string			*c;
 string			*d;
 string			*o;
 string			*s;

    if (cmd->args_len < 4)
        return(ROK);

    if ((s = get_arg(cmd, 0)) == NULL ||
	(c = get_arg(cmd, 1)) == NULL ||
	(d = get_arg(cmd, 2)) == NULL) {

	return(ROK);
    }

    memset(&find, 0, sizeof (struct contact_));
    if (str_copy(&find.c, c->s, c->len) == 0) die_nomem();
    contact = RB_FIND(contacts, &user->contacts, &find);
    contact_free(&find);

    if (contact == NULL || cmd->ignore)
	return(ROK);

    save = contact_update(user, c);
    if (save == NULL)
	return(ROK);

    if (d->len > 0 && msn_decode(d, &save->dn) == RFAIL) {
	contact_free(save);
	free(save);
	contact->save = NULL;
	return(ROK);
    }
    if (c->len > 0 && str_copy(&save->c, c->s, c->len) == 0)
	die_nomem();
    o = get_arg(cmd, 4);
    if (o && o->len > 0 && str_copy(&save->o, o->s, o->len) == 0)
	die_nomem();
    if (s->len > 0 && str_copy(&save->status, s->s, s->len) == 0)
	die_nomem();
    save->flags = (get_arg(cmd, 3) == NULL) ? 0 : atoll((char *)cmd->args[3]->s);

    /* check for blocked contacts */
    if (save->deny & CONTACT_DENY)
	save->deny |= CONTACT_BLOCKED;

    /* SEEIMG */
    if (o && (user->commands & SEEIMG)) {
	str_free(o);
	free(o);
	cmd->args[4] = NULL;
	cmd->args_len--;
    }

    if (sql_contact_save(user, save) == RFAIL)
	return(ROK);

    /* check for blocked contacts */
    if (contact->deny & CONTACT_BLOCKED)
	return(RETURN);

    return(ROK);
}

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

    switch (user->version) {
    case MSNP18:
	return msnp18_contact_change(user, cmd, args);
    case MSNP15:
    case MSNP13:
    case MSNP12:
	return msnp13_contact_change(user, cmd, args);
    default:
	return msnp8_contact_change(user, cmd, args);
    }
}

int
msnp12_client_syn_rewrite(struct user_ *user, command *cmd, int args) {
 string			*arg1;
 string			*arg2;

    if (cmd->args_len != 3)
	return(RFAIL);

    /* free arg */
    arg1 = cmd->args[1];
    arg2 = cmd->args[2];
    str_free(arg1);
    str_free(arg2);
    free(arg1);
    free(arg2);

    /* alloc new arg */
    arg1 = str_alloc();
    if (arg1 == (string *)0)
	die_nomem();

    arg2 = str_alloc();
    if (arg2 == (string *)0)
	die_nomem();

    /* rewrite arg */
    if (str_copys(arg1, (unsigned char *)"0") == 0 ||
	str_copys(arg2, (unsigned char *)"0") == 0)
	die_nomem();

    /* set pointer arg */
    cmd->args[1] = arg1;
    cmd->args[2] = arg2;

    return(ROK);
}

int
msnp8_client_syn_rewrite(struct user_ *user, command *cmd, int args) {
 string			*arg1;
 char			*ep;

    if (cmd->args_len != 2)
	return(RFAIL);

    if (user->commands & SAVE_CONTACTS)
	return(ROK);

    /* windows messenger bug */
    if (user->version == MSNP8) {

	/* save list version */
	errno = 0;
	user->msnp8_syn_bug = strtoul((char *)cmd->args[1]->s, &ep, 10);
	if (errno != 0 || (ep && *ep != 0))
	    return(RFAIL);

	user->msnp8_syn_bug++;
    }

    /* free arg */
    arg1 = cmd->args[1];
    str_free(arg1);
    free(arg1);

    /* alloc new arg */
    arg1 = str_alloc();
    if (arg1 == (string *)0)
	die_nomem();

    /* rewrite arg */
    if (str_copys(arg1, (unsigned char *)"0") == 0)
	die_nomem();

    /* set pointer arg */
    cmd->args[1] = arg1;

    return(ROK);
}

int
msnp8_server_syn_rewrite(struct user_ *user, command *cmd, int args) {
 string			*arg1;

    if (user->version != MSNP8)
	return(ROK);

    /* free arg */
    arg1 = cmd->args[1];
    str_free(arg1);
    free(arg1);

    /* alloc new arg */
    arg1 = str_alloc();
    if (arg1 == (string *)0)
	die_nomem();

    arg1->len = fmt_printf(NULL, "%d", user->msnp8_syn_bug);
    if (str_ready(arg1, arg1->len + 1) == 0)
	die_nomem();
    arg1->len = fmt_printf(arg1->s, "%d", user->msnp8_syn_bug);

    /* set pointer arg */
    cmd->args[1] = arg1;

    return(ROK);
}

int
contact_update_status(struct user_ *user, struct contact_ *c) {
 struct contact_	*contact;

    contact = RB_FIND(contacts, &user->contacts, c);
    if (contact == NULL ||
	contact->updated != UPDATE ||
	contact->save == NULL) {
	return(RFAIL);
    }

    if ((contact->deny & CONTACT_DENY) == 0 &&
	(contact->save->deny & CONTACT_DENY)) {

	if (send_fln(user, contact) != ROK)
	    return(RFAIL);
    }

    if ((contact->deny & CONTACT_DENY) &&
	(contact->save->deny & CONTACT_DENY) == 0) {

	if (send_nln(user, contact) != ROK)
	    return(RFAIL);
    }

    if (contact->deny != contact->save->deny) {

	contact->deny = contact->save->deny;
    }

    if (contact->deny & CONTACT_REMOVED)
	contact->deny &= ~CONTACT_REMOVED;

    /* free saving data */
    contact_free(contact->save);
    free(contact->save);
    contact->save = NULL;

    contact->updated = NONE;
    contact->save = NULL;
    return(ROK);
}

int
loaduser(struct ctl_ *ctl, struct xml_tags *xml_tag_head) {
 struct xml_tag_	*tmp;
 struct xml_tag_	*u;
 struct xml_tag_	*c;
 struct contact_	*contact;
 struct contact_	*save;
 struct contact_	cfind;
 struct user_		*user;
 struct user_		*ufind;
 log_			*log = &config.log;

    /* check for contact */
    c = u = NULL;
    SLIST_FOREACH(tmp, xml_tag_head, xml_tag__) {

        if (tmp->name.len == 0)
            continue;

        if (strcmp((char *)tmp->name.s, "user") == 0) {
            u = tmp;
            continue;
        } else if (strcmp((char *)tmp->name.s, "contact") == 0) {
            c = tmp;
            continue;
        }
    }

    if (u == NULL || c == NULL || u->data.len == 0 || c->data.len == 0) {
        log->debug("debug: loaduser: no ctl data\n");
        return(RFAIL);
    }

    user = NULL;
    RB_FOREACH(ufind, users_, &users) {
        if (strcmp((char*)ufind->email.s, (char *)u->data.s) == 0) {
            user = ufind;
            break;
        }
    }

    if (user == NULL) {
        log->debug("debug: loaduser: no ctl user\n");
        return(RFAIL);
    }

    /* find contact in db if exist */
    memset(&cfind, 0, sizeof(struct contact_));
    if (str_copy(&cfind.c, c->data.s, c->data.len) == 0) die_nomem();
    contact = RB_FIND(contacts, &user->contacts, &cfind);
    contact_free(&cfind);

    if (contact == NULL) {
	log->debug("debug: loaduser: no ctl contact\n");
	return(RFAIL);
    }

    save = contact_update(user, &contact->c);
    if (save == NULL) {
	log->debug("debug: loaduser: fail to create save contact\n");
	return(RFAIL);
    }

    if (sql_contact_load(user, save) == RFAIL) {
	log->debug("debug: loaduser: fail to load user from sql\n");
	return(RFAIL);
    }

    if (contact_update_status(user, save) == RFAIL) {
	log->debug("debug: loaduser: fail to save user data from sql\n");
	return(RFAIL);
    }

    if (contact->deny & CONTACT_DENY &&
	strcmp((char *)contact->status.s, "OFF") != 0) {

	save = contact_update(user, &contact->c);
	if (save == NULL) {
	    log->debug("debug: loaduser: fail to create save contact\n");
	    return(RFAIL);
	}
	save->deny |= CONTACT_BLOCKED;
	if (sql_contact_save(user, save) == RFAIL)
	    return(RFAIL);
    }

    if ((contact->deny & CONTACT_DENY) == 0 &&
	contact->deny & CONTACT_BLOCKED) {

	save = contact_update(user, &contact->c);
	if (save == NULL) {
	    log->debug("debug: loaduser: fail to create save contact\n");
	    return(RFAIL);
	}
	save->deny &= ~CONTACT_BLOCKED;
	if (sql_contact_save(user, save) == RFAIL)
	    return(RFAIL);
    }

    return(ROK);
}
