/*
 * user.cc
 *
 * Part of ezbounce
 *
 * (C) 2001-2002 Murat Deligonul
 *
 * 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 of the License, 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.
 *
 *
 */

#include <stdlib.h>
#include "user.h"
#include "logfile.h"
#include "linkedlist.h"
#include "debug.h"

userdef::userdef(const char * user)
{
    name = my_strdup(user);
    rulesets = new list<ruleset>;
    conns = new list<conn>;
    vhosts = 0;
    obsolete = 0;
    DEBUG("userdef::userdef(): [%p] (%s)\n", this, name);
}

userdef::~userdef()
{
    DEBUG("userdef::~userdef(): [%p] (%s)\n", this, name);
    destroy_list(rulesets, 0);
    destroy_list(this->vhosts, 1);  /* do delete[] */

    delete rulesets;
    delete conns;
    delete[] name;
    delete vhosts;
}

/*
 * "Register" a user. return:
 *  0 -- bad password
 * -1 -- not allowed
 *  1 -- added */
int userdef::add(conn * c, list<ruleset> * plist, const char * password,
        const char * hostname, unsigned short port)
{
    if (strcasecmp(password, cfg.get(OPT_PASSWORD, NULL)))
        return 0;
    if (!ruleset::find_matching(hostname, port, rulesets, plist))
        return -1;
    conns->add(c);
    return 1;
}

userdef * userdef::find(list<userdef> * ls, const char * str)
{
    list_iterator<userdef> i(ls);
    while (i.has_next())
    {
    	userdef * u = i.next();
        if (!u->obsolete && !strcasecmp(u->name, str))
            return u;
    }
    return 0;
}

int userdef::remove(conn * c)
{
    DEBUG("userdef::remove for %s (%p)\n", name, c);
    conns->remove(c);
    return 1;
}

/*
 * The efficiency of this function is atrocious.
 * But it works :-)
 */

list<userdef> * userdef::sync_lists(list<userdef> * oldlist, list<userdef> * newlist)
{
    list<userdef> * list3 = new list<userdef>;

    /*
     * For each user in the old list:
     *      - Check if he has a match in the new list
     *          - if he does, syncronize options
     *              add original to list3, remove new one from new list, and delete
     *          - if he doesn't,
     *              he is obsolete.. if has no users, delete and remove from list
     *              if he he has users, mark obsolete and add to list3
     *
     *      Add anyone left in newlist to list3
     *
     */
    list_iterator<userdef> i(oldlist);
    while (i.has_next())
    {
		userdef * u = i.next();
        userdef * u2 = userdef::find(newlist, u->name);
        /* Found a match */
        if (u2)
        {
            /* Copy vhosts */
            destroy_list(u->vhosts, 1);
            u->vhosts = u2->vhosts;
            u2->vhosts = 0;

            u->obsolete = 0;

            /* Copy options */
            u->cfg.copy(&u2->cfg);

            /* the rulesets :-P */
            if (!u->conns->size())
            {
                DEBUG("         USER %s: no conns found, copying new rulesets to old\n", u->name);
                destroy_list(u->rulesets, 0);
                u->rulesets = u2->rulesets;
                u2->rulesets = 0;
            } else {
                /* Since we have clients ... */
                DEBUG("         USER %s: This user has clients, syncing ruleset lists\n", u->name);
                u->rulesets = ruleset::sync_lists(u->rulesets, u2->rulesets);
                u2->rulesets = 0;   /* deleted by above call */
                /* Update clients' config */
                list_iterator<conn> i(u->conns);
                while (i.has_next())
                {
                	conn * c = i.next();
                    if (c->get_config())
                        c->get_config()->copy_config_only(&u2->cfg);
                }
            }
            delete u2;
            newlist->remove(u2);
            list3->add(u);
            DEBUG("         \\---->Deleted [new] %p, added [original] %p to list3\n", u2, u);
        }
        else {
            /* No match -- whats the usage count on this ? */
            if (!u->conns->size())
            {
                DEBUG("         USER %s: NO MATCH deleting this user\n", u->name);
                /* ok cool, you don't go anywhere */
                delete u;
                continue;
            }
            else {
                /* We have users .. mark this one as obsolete */
                u->obsolete = 1;
                DEBUG("         USER %s: marking as obsolete\n", u->name);
                list3->add(u);
            }
        }
    }

    /* Add any left over in newlist to list3 */
    i.attach(newlist);
    while (i.has_next())
    {
	    userdef * u = i.next();
	    DEBUG("--Adding new user %s (%p)\n", u->name, u);
        list3->add(u);
    }

    delete oldlist;     /* don't delete each of the members, because */
    delete newlist;     /* they are referenced in list3 */
    return list3;
}



/*
 *	Saving user preferences -- syntax is:
 *
 *  prefs <username> <option - 8>
 *
 *  option 1: flags
 * 	option 2: log options
 *  option 3: default vhost
 *  option 7: fake ident
 *  option 8: auto detach password
 *  option 9: autoserver
 *
 *	don't know what else to put in there, perhaps to make it extensible for the
 *  future..
*/

int user_options::load(const char * s)
{
    char s_dummy[256];
    char s_autoserver[256], s_autopass[128], s_fake_ident[128];
    int dummy;
    int r ;

    r = sscanf(s, "%s %s %d %d %u %d %d %d %s %s %s\n",
        s_dummy,
        s_dummy,
        &prefs,
        &log_options,
        &iface.s_addr,
        &dummy,
        &dummy,
        &dummy,
        s_fake_ident,
        s_autopass,
        s_autoserver);

    if (r < 11)
        DEBUG("User file corrupted??\n");;

    delete[] autoserver;
    delete[] autopass;
    delete[] fake_ident;
    autoserver = autopass = fake_ident = 0;

    if (strcmp(s_autoserver, "[none]") != 0 && *s_autoserver)
        autoserver = my_strdup(s_autoserver);
    if (strcmp(s_autopass, "[none]") != 0 && *s_autopass)
        autopass = my_strdup(s_autopass);
    if (strcmp(s_fake_ident, "[none]") != 0 && *s_fake_ident)
        fake_ident = my_strdup(s_fake_ident);


    DEBUG("    ---> load() for %s ...\n", s_dummy);
    DEBUG("         prefs: %d\n", prefs);
    DEBUG("         log options: %d\n" , log_options);
    DEBUG("         autopass: %s\n" , autopass);
    DEBUG("         autoserver: %s\n" , autoserver);
    DEBUG("         fake-ident: %s\n", fake_ident);


    return 1;
}

int user_options::save(const char * name, int f)
{
    fdprintf(f, "prefs %s %d %d %u %d %d %d %s %s %s\n",
            name,
            prefs,
            log_options,
            iface.s_addr,
            0,                      /* reserved */
            0,                      /* reserved */
            0,                      /* reserved */
            fake_ident ? fake_ident : "[none]",
            autopass ? autopass : "[none]",
            autoserver ? autoserver : "[none]");
    DEBUG("    ---> save() for %s ...\n", name);
    DEBUG("         prefs: %d\n", prefs);
    DEBUG("         log options: %d\n" , log_options);
    DEBUG("         autopass: %s\n" , autopass);
    DEBUG("         autoserver: %s\n" , autoserver);
    DEBUG("         fake-ident: %s\n" , fake_ident);

    return 1;
}

/* copy constructor type of thing */
user_options::user_options(user_options * u2)
{
    autoserver = autopass = password = fake_ident = 0;
    copy(u2);
}

/* default constructor
 * default user settings reside here */
user_options::user_options()
{
    autoserver = autopass = password = fake_ident = 0;
    max_logsize = 0;
    max_idle = 0;
    prefs = 0;
    log_options = logfile::LOG_ALL | logfile::LOG_SEPERATE;
    flags = OPT_ENABLE_DETACH_COMMAND | OPT_LOG_OPTIONS;
    memset(&iface, 0, sizeof(iface));
}


int user_options::copy(user_options * u2)
{
    DEBUG("user_options::copy()\n");
    delete[] this->password;
    delete[] this->autoserver;
    delete[] this->fake_ident;
    delete[] this->autopass;

    this->password = my_strdup(u2->password);
    this->autoserver = my_strdup(u2->autoserver);
    this->fake_ident = my_strdup(u2->fake_ident);
    this->autopass   = my_strdup(u2->autopass);

    max_logsize = u2->max_logsize;
    max_idle = u2->max_idle;
    log_options = u2->log_options;
    flags = u2->flags;
    prefs = u2->prefs;
    iface = u2->iface;
    return 1;
}


/* set integer option */
int user_options::set(int field, int value)
{
    switch (field)
    {
    case OPT_MAX_LOGFILE_SIZE:
        max_logsize = value;
        break;
    case OPT_MAX_IDLE_TIME:
        max_idle = value;
        break;
    case OPT_LOG_OPTIONS:
    case OPT_DEFAULT_LOG_OPTIONS:
        log_options = value;
        break;
    case PREF_VHOST:
        iface.s_addr = (unsigned long) value;
        break;
    default:
        DEBUG("user_options::set_option(int, int) --> bad option!\n");
        abort();
    }

    return 1;
}

/*
 * set string option ... */
int user_options::set(int field, const char * value)
{
    switch (field)
    {
    case OPT_PASSWORD:
        delete[] password;
        password = my_strdup(value);
        break;
    case OPT_AUTOSERVER:
        delete[] autoserver;
        autoserver = my_strdup(value);
        break;
    case PREF_AUTO_PASS:
        delete[] autopass;
        autopass = my_strdup(value);
        break;
    case PREF_FAKE_IDENT:
        delete[] fake_ident;
        fake_ident = my_strdup(value);
        break;
    case PREF_VHOST:
    case OPT_DEFAULT_VHOST:
        /* String given .. resolve, and try to fill in */
        struct in_addr in;
        if (fill_in_addr(value, &in) < 1)
            return 0;
        set(PREF_VHOST, in.s_addr);
        break;

    default:
        DEBUG("user_options::set_option() --> bad option!\n");
        abort();
    }
    return 1;
}


int user_options::clear(int field)
{
    switch (field)
    {
    case OPT_MAX_LOGFILE_SIZE:
        max_logsize = 0;
        break;

    case OPT_MAX_IDLE_TIME:
        max_idle = 0;
        break;

    case OPT_LOG_OPTIONS:
        log_options = 0;
        break;

    case PREF_VHOST:
        iface.s_addr = 0;
        break;

    case OPT_PASSWORD:
        delete[] password;
        password = 0;
        break;

    case OPT_AUTOSERVER:
        delete[] autoserver;
        autoserver = 0;
        break;

    case PREF_AUTO_PASS:
        delete[] autopass;
        autopass = 0;
        break;

    case PREF_FAKE_IDENT:
        delete[] fake_ident;
        fake_ident = 0;
        break;

    default:
        DEBUG("WTF?");
        abort();
    }
    return 1;
}


int user_options::get(int field)  const
{
    switch (field)
    {
    case OPT_MAX_LOGFILE_SIZE:
        return max_logsize;
    case OPT_MAX_IDLE_TIME:
        return max_idle;
    case OPT_LOG_OPTIONS:
    case OPT_DEFAULT_LOG_OPTIONS:
        return log_options;
    case PREF_VHOST:
        return (int) iface.s_addr;
    default:
        DEBUG("user_options::get(int) ---> bad option!\n");
        abort();
    }
    return 1;
}

char * user_options::get(int field, char * ) const
{
    switch (field)
    {
    case OPT_PASSWORD:
        return password;
    case OPT_AUTOSERVER:
        return autoserver;
    case PREF_AUTO_PASS:
        return autopass;
    case PREF_FAKE_IDENT:
        return fake_ident;
    default:
        DEBUG("user_options::get() --> bad option!\n");
        abort();
    }
    return 0;
}

/*
 * decide whether or not we can do v and we want to do it
 */
int user_options::decide(int v) const
{
    if ((flags & v) || (flags & OPT_USER_IS_ADMIN))
        if (prefs & v)
            return 1;
        else
            return 0;
    return 0;
}

user_options::~user_options(void)
{
    delete[] fake_ident;
    delete[] autopass;
    delete[] password;
    delete[] autoserver;
}

int user_options::lookup_cmd(const char * cmd)
{
    struct user_pref_struct {
        const char * str;
        int flag;
    };
    const static struct user_pref_struct user_pref_table[] = {
        { "fake-ident",         PREF_FAKE_IDENT},
        { "log",                PREF_LOG},
        { "auto-detach",        PREF_AUTO_DETACH},
        { "vhost",              PREF_VHOST},
        { "proxy-dcc-in",       PREF_DCC_IN},
        { "proxy-dcc-out",      PREF_DCC_OUT},
        { "auto-detach-pass",   PREF_AUTO_PASS},
        { "auto-server",        OPT_AUTOSERVER},
    };

    for (unsigned i = 0; i < sizeof(user_pref_table) / sizeof(user_pref_struct); i++)
        if (!strcasecmp(cmd, user_pref_table[i].str))
            return user_pref_table[i].flag;
    return 0;
}

int user_options::copy_config_only(const user_options * u2)
{
    flags = u2->flags;
    max_logsize = u2->max_logsize;
    max_idle = u2->max_idle;
    return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1