/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: userdb.c,v 1.14 2002/08/25 14:24:21 leodav Exp $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#else
#include <io.h>
#endif
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <limits.h>
#include "opennap.h"
#include "debug.h"

HASH   *User_Db = 0;

int
get_level (const char *s)
{
    if (!strncasecmp ("lee", s, 3))
	return LEVEL_LEECH;
    if (!strncasecmp ("use", s, 3))
	return LEVEL_USER;
    if (!strncasecmp ("mod", s, 3))
	return LEVEL_MODERATOR;
    if (!strncasecmp ("eli", s, 3))
	return LEVEL_ELITE;
    if (!strncasecmp ("adm", s, 3))
	return LEVEL_ADMIN;
    return -1;
}

void
userdb_free (USERDB * p)
{
    if (p)
    {
	if (p->nick)
	    FREE (p->nick);
#if EMAIL
	if (p->email)
	    FREE (p->email);
#endif
	if (p->password)
	    FREE (p->password);
	FREE (p);
    }
}

int
userdb_init (void)
{
    int     fd;
    int     ac, regen = 0, level;
    char   *av[7], path[_POSIX_PATH_MAX];
    USERDB *u;

    snprintf (path, sizeof (path), "%s/" FILENAME_USERS, Config_Dir);
    log_message_level (LOG_LEVEL_SERVER, "userdb_init: path: \"%s\"", path);
    if ((fd = open (path, O_RDONLY))==-1)
    {
	    logerr ("userdb_init", path);
	    return -1;
    }
    User_Db = hash_init (257, (hash_destroy) userdb_free);
    if (fake_fgets (Buf, sizeof (Buf), fd))
    {
	if (strncmp (":version 1", Buf, 10))
	{
	    regen = 1;
	    lseek(fd,0,SEEK_SET);
	}
    }
    while (fake_fgets (Buf, sizeof (Buf), fd))
    {
	ac = split_line (av, FIELDS (av), Buf);
	if (ac >= 6)
	{
	    if (invalid_nick (av[0]))
	    {
		log_message_level (LOG_LEVEL_ERROR, "userdb_init: %s: invalid nickname", av[0]);
		continue;
	    }
	    u = CALLOC (1, sizeof (USERDB));
	    if (u)
	    {
		u->nick = STRDUP (av[0]);
		if (regen)
		    u->password = generate_pass (av[1]);
		else
		    u->password = STRDUP (av[1]);
#if EMAIL
		u->email = STRDUP (av[2]);
#endif
	    }
	    if (!u || !u->nick || !u->password
#if EMAIL
		|| !u->email
#endif
		)
	    {
		OUTOFMEMORY ("userdb_init");
		if (u)
		    userdb_free (u);
		close (fd);
		return -1;
	    }
	    level = get_level (av[3]);
	    if (level < 0 || level > LEVEL_ELITE)
	    {
		log_message_level (LOG_LEVEL_ERROR, "userdb_init: invalid level %s for user %s", av[3],
		     u->nick);
		level = LEVEL_USER;
	    }
	    u->level = level;
	    u->created = atol (av[4]);
	    u->lastSeen = atol (av[5]);
	    if (ac > 6)
		u->flags = atoi (av[6]);	/* u_short, atoi() is fine */
	    hash_add (User_Db, u->nick, u);
	}
	else
	{
	    log_message_level (LOG_LEVEL_ERROR, "userdb_init: bad user db entry");
	    print_args (ac, av);
	}
    }
    close (fd);
    log_message_level (LOG_LEVEL_SERVER, "userdb_init: %d registered users", User_Db->dbsize);
    /* reformat to version 1 specification */
    if (regen)
	userdb_dump ();
    return 0;
}

static void
dump_userdb (USERDB * db, int fd)
{
    char outbuf[1024];

    if (global.current_time - db->lastSeen >= Nick_Expire)
    {
	if (db->level < LEVEL_MODERATOR && !db->flags)
	{
	    strcpy (Buf, ctime (&db->lastSeen));
	    Buf[strlen (Buf) - 1] = 0;
	    log_message_level ( LOG_LEVEL_DEBUG, "dump_userdb: %s has expired (last seen %s)", db->nick,
		 Buf);
	    hash_remove (User_Db, db->nick);
	    return;
	}
	/* warn, but dont nuke expired accounts for privileged users */
	/*
	if (db->flags) {
	   flag = User_Flags[db->flags];
	} else {
	   flag = "None";
	}
	log_message_level ( LOG_LEVEL_SERVER, "dump_userdb: %s has expired (ignored: level=%s flag=%s)",
	     db->nick, Levels[db->level], flag);
	*/
	log_message_level ( LOG_LEVEL_SERVER, "dump_userdb: %s has expired (ignored: level=%s)",
	     db->nick, Levels[db->level]);
    }

#ifdef EMAIL
    snprintf(outbuf, sizeof (outbuf), "%s %s %s %s %d %u %hu", db->nick, db->password, db->email, Levels[db->level], (int) db->created, (int) db->lastSeen, db->flags);
#else
    snprintf(outbuf, sizeof (outbuf), "%s %s unknown %s %d %u %hu", db->nick, db->password,  Levels[db->level], (int) db->created, (int) db->lastSeen, db->flags);
#endif
#ifdef WIN32
    strcat(outbuf,"\r\n");
#else
    strcat(outbuf,"\n");
#endif
fake_fputs(outbuf,fd);
}

int
userdb_dump (void)
{
    int     fd;
    char    path[_POSIX_PATH_MAX], tmppath[_POSIX_PATH_MAX];
    struct  stat stat;

    log_message_level (LOG_LEVEL_SERVER, "userdb_dump: dumping user database");
    snprintf (tmppath, sizeof (tmppath), "%s/users.tmp", Config_Dir);
    if ((fd = open (tmppath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR ))==-1)
    {
  	logerr ("userdb_dump", tmppath);
  	return -1;
    }
#ifdef WIN32
    fake_fputs (":version 1\r\n", fd);
#else
    fake_fputs (":version 1\n", fd);
#endif
    hash_foreach (User_Db, (hash_callback_t) dump_userdb, (void *) fd);
	if (fstat(fd,&stat)==-1) {
		log_message_level (LOG_LEVEL_SERVER,  "userdb_dump: fstat failed!");
		return 0;
		}
	if (stat.st_size==0) {
		log_message_level (LOG_LEVEL_SERVER,  "userdb_dump: fstat on users.tmp returned 0 file size!");
		return 0;
		}

    if (close (fd))
    {
	logerr ("userdb_dump", "close");
	return -1;
    }
    snprintf (path, sizeof (path), "%s/" FILENAME_USERS, Config_Dir);
    if (unlink (path))
	logerr ("userdb_dump", "unlink");	/* not fatal, may not exist */
    if (rename (tmppath, path))
    {
	logerr ("userdb_dump", "rename");
	return -1;
    }
    log_message_level (LOG_LEVEL_SERVER,  "userdb_dump: wrote %d entries", User_Db->dbsize);
    return 0;
}

/* create a default USERDB record from the existing user */
USERDB *
create_db (USER * user)
{
    USERDB *db = CALLOC (1, sizeof (USERDB));

    if (db)
    {
	db->nick = STRDUP (user->nick);
	db->password = generate_pass (user->pass);
#if EMAIL
	snprintf (Buf, sizeof (Buf), "anon@%s", Server_Name);
	db->email = STRDUP (Buf);
#endif
	db->level = user->level;
	db->created = global.current_time;
	db->lastSeen = global.current_time;
    }
    if (!db || !db->nick || !db->password
#if EMAIL
	|| !db->email
#endif
	)
    {
	OUTOFMEMORY ("create_db");
	userdb_free (db);
	return 0;
    }
    if (hash_add (User_Db, db->nick, db))
    {
	userdb_free (db);
	return 0;
    }
    return db;
}
