/* ARISA - User account manipulation functions
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"
#include "user.h"

priv_text_t priv_text[] = {
	{PRIV_PACK_ADD,		"PACK_ADD"},
	{PRIV_PACK_DEL,		"PACK_DEL"},
	{PRIV_PACK_MOD,		"PACK_MOD"},
	{PRIV_SEND_ADD,		"SEND_ADD"},
	{PRIV_SEND_DEL,		"SEND_DEL"},
	{PRIV_SEND_MOD,		"SEND_MOD"},
	{PRIV_PACKLIST_SEND,	"PACKLIST_SEND"},
	{PRIV_PACKLIST_SCAN,	"PACKLIST_SCAN"},
	{PRIV_PACKLIST_MOD,	"PACKLIST_MOD"},
	{PRIV_ALL_PACKLISTS,	"ALL_PACKLISTS"},
	{PRIV_PACKLISTS,	"PACKLISTS"},
	{PRIV_QUEUES,		"QUEUES"},
	{PRIV_POOLS,		"POOLS"},
	{PRIV_INTERFACES,	"INTERFACES"},
	{PRIV_FILES,		"FILES"},
	{PRIV_FILE_MOVE,	"FILE_MOVE"},
	{PRIV_FILE_DEL,		"FILE_DEL"},
	{PRIV_DIR_ADD,		"DIR_ADD"},
	{PRIV_DIR_DEL,		"DIR_DEL"},
	{PRIV_SELF_MOD,		"SELF_MOD"},
	{PRIV_USERS,		"USERS"},
	{PRIV_UPLOAD,		"UPLOAD"},
	{PRIV_DOWNLOAD,		"DOWNLOAD"},
	{PRIV_LOG,		"LOG"},
	{PRIV_PARTY_LINE,	"PARTY_LINE"},
	{PRIV_MESSAGE,		"MESSAGE"},
	{PRIV_NETWORKS,		"NETWORKS"},
	{PRIV_SETTINGS,		"SETTINGS"},
	{PRIV_SHUTDOWN,		"SHUTDOWN"},
	{PRIV_SOCKET,		"SOCKET"},
	{PRIV_MAX,		NULL}
};

user_t *alloc_user(const char *username) {
	user_t *u = xalloc(sizeof(user_t));
	LOCK_INIT(&u->lock);
	
	u->deleted 	= 0;
	if(username != NULL)
		u->username = xstrdup(username);
	else
		u->username = NULL;
	
	u->password	= NULL;
	
	u->hosts 	= NULL;
	u->no_hosts	= 0;
	u->dirs		= NULL;
	u->no_dirs	= 0;
	
	u->packlists	= NULL;
	u->no_packlists	= 0;

	u->no_permitted_logons = 1;
	u->privs	= NULL;
	u->uprivs	= PRIVS_EMPTY;
	if(u->username != NULL) {
		user_set_privs(u,PRIVS_EMPTY);
	}

	u->last_logon 	= 0;

	return u;
}

void free_user(user_t *u) {
	LOCK_FREE(&(u->lock));
	
	if(u->username != NULL)
		xfree(u->username);
	if(u->password != NULL)
		xfree(u->password);
	if(u->hosts != NULL)
		xfree(u->hosts);
	if(u->dirs != NULL)
		xfree(u->dirs);
	if(u->packlists != NULL)
		xfree(u->packlists);
	if(u->privs != NULL)
		xfree(u->privs);
	xfree(u);
}

int user_add(user_t *u) {
	int i,ret = -1;
	LOCK(global);
	LOCK(u);
	for(i = 0; i < global->no_users; ++i) {
		LOCK(global->users[i]);
		if(strcmp(global->users[i]->username,u->username) == 0
			&& global->users[i]->deleted == 0) {
			LOGP(L_ERR,
				"Error, attempted to add duplicate user %s.",
					global->users[i]->username);
			break;
		}
		UNLOCK(global->users[i]);
	}
	if(i == global->no_users) {
		LOGP(L_INF,"Added User %s",u->username);
		PTRARR_ADD(&global->users,&global->no_users,u);
		validity_insert(u);
		ret = 0;
	}
	UNLOCK(u);
	UNLOCK(global);
	if(ret == 0)
		se_notify_user_add(u);
	return ret;
}

user_t *user_find(const char *name) {
	user_t *u = NULL;
	int i;
	
	LOCK(global);
	for(i = 0; i < global->no_users && u == NULL; ++i) {
		LOCK(global->users[i]);
		if(global->users[i]->deleted == 0 &&
			strcmp(global->users[i]->username,name) == 0) {
			u = global->users[i];
			validity_insert(u);
		}
		UNLOCK(global->users[i]);
	}
	UNLOCK(global);

	return u;
}

int user_del(user_t *u) {
	interface_t *intf;
	int i,j;
	
	LOCK(global);
	LOCK(u);
	u->deleted = xtime();
	validity_delete(u);
	UNLOCK(u);
	
	for(i = 0; i < global->no_interfaces; ++i) {
		intf = global->interfaces[i];
		LOCK(intf);
		if(intf->type == INTERFACE_CHAT) {
			for(j = 0; j < intf->chat.no_chats; ++j) {
				LOCK(intf->chat.chats[j]);
				if(intf->chat.chats[j]->type == CHAT_ADMIN
					&& intf->chat.chats[j]->admin.user == u) {
					intf->chat.chats[j]->state = STATE_DELETED;
					intf->chat.chats[j]->admin.user = NULL;
				}
				UNLOCK(intf->chat.chats[j]);
			}
		} /* else if(intf->type == INTERFACE_RECV) {
			for(j = 0; j < intf->recv.no_uploads; ++j) {
				LOCK(intf->recv.uploads[j]);
				if(intf->recv.uploads[j]->user == u) {
					intf->recv.uploads[j]->state = STATE_DELETED;
					intf->recv.uploads[j]->user = NULL;
				}
				UNLOCK(intf->recv.uploads[j]);
			}
		} */
		UNLOCK(intf);
	}
	for(i = 0; i < global->no_admins; ++i) {
		LOCK(global->admins[i]);
		if(global->admins[i]->admin.user == u)
			global->admins[i]->state = STATE_DELETED;
		UNLOCK(global->admins[i]);
	}
	UNLOCK(global);

	return 0;
}

void user_purge(const time_t now) {
	user_t **old = NULL;
	int i,j,deleted,no_old = 0;
	
	//D("User Purge Begins");
	
	LOCK(global);
	for(i = 0, deleted = 0; i < global->no_users; ++i) {
		LOCK(global->users[i]);
		if(IS_PURGE_TIME(global->users[i]->deleted,now))
			deleted++;
		UNLOCK(global->users[i]);
	}
	if(deleted != 0) {
		old = global->users;
		no_old = global->no_users;
		PTRARR_ALLOC(&(global->users),&(global->no_users),
				no_old - deleted);
		for(i = 0, j = 0; i < no_old; ++i) {
			LOCK(old[i]);
			if(!IS_PURGE_TIME(old[i]->deleted,now)) {
				global->users[j++] = old[i];
				UNLOCK(old[i]);
				old[i] = NULL;
			} else
				UNLOCK(old[i]);
		}
	}
	UNLOCK(global);
	if(old != NULL) {
		for(i = 0; i < no_old; ++i) {
			if(old[i] != NULL)
				free_user(old[i]);
		}
		xfree(old);
	}

	//D("User Purge Ends");
}

int user_set_password(user_t *u, const char *password) {
	char buffer[128];
	char pwdhash[36];
	
	LOCK(u);
	snprintf(buffer,sizeof(buffer),"%s%s",u->username,password);
	strtomd5(pwdhash,sizeof(pwdhash),buffer);
	
	memset(buffer,0,sizeof(buffer));
	if(u->password != NULL)
		xfree(u->password);
	
	u->password = xstrdup(pwdhash);
	UNLOCK(u);

	return 0;
}

int user_check_password(user_t *u, const char *password) {
	char buffer[128];
	char pwdhash[36];
	int ret = 0;
	
	LOCK(u);
	if(u->password != NULL) {
		snprintf(buffer,sizeof(buffer),"%s%s",u->username,password);
		strtomd5(pwdhash,sizeof(pwdhash),buffer);
		memset(buffer,0,sizeof(buffer));
	
		if(strcmp(u->password,pwdhash) == 0)
			ret = 1;
	}
	UNLOCK(u);
	
	return ret;
}

int user_valid(user_t *u) {
	int i;

	if(u == NULL)
		return 0;
	
	if(validity_test(u))
		return 1;
	
	LOCK(global);
	for(i = 0; i < global->no_users; ++i) {
		if(global->users[i] == u) {
			LOCK(u);
			i = u->deleted;
			if(u->deleted == 0)
				validity_insert(u);
			UNLOCK(u);
			UNLOCK(global);
			if(i == 0) // u->deleted == 0
				return 1;
			else
				return 0;
		}
	}
	UNLOCK(global);
	
	return 0;
}

int user_rename(user_t *u, const char *name) {
	int i, ret = 0;
	
	if(u == NULL || name == NULL)
		return -1;

	LOCK(global);
	for(i = 0; i < global->no_users && ret == 0; ++i) {
		LOCK(global->users[i]);
		if(global->users[i]->deleted == 0 &&
				strcmp(global->users[i]->username,name) == 0)
			ret = -1;
		UNLOCK(global->users[i]);
	}
	if(ret == 0) {
		LOCK(u);
		if(u->username != NULL)
			xfree(u->username);
		u->username = xstrdup(name);
		UNLOCK(u);
	}
	UNLOCK(global);

	return ret;
}

/** Settings Data **/
static setting_t user_data[] = {
	{"permitted-logons",	ST_INT,	OFFSETOF(user_t,no_permitted_logons),
		0,"0",NULL,NULL},
	{NULL}
};

int user_apply_setting(user_t *u, const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret;
	LOCK(u);
	ret = apply_setting(user_data,u,v);
	UNLOCK(u);
	xfree(v);
	return ret;
}

int user_read_settings(user_t *u, pqueue_t *out) {
	int ret;
	LOCK(u);
	ret = read_settings(user_data,u,out);
	UNLOCK(u);
	return ret;
}
