/* PureAdmin
 * Copyright (C) 2003 Isak Savo
 *
 *  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.
 */

/*
 * Misc functions used to handle system accounts
 *
 * Copyright (C) 2006 Isak Savo
 */
#include <gtk/gtk.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include "globals.h"
#include "cfg.h"
#include "helper.h"
#include "system_accounts.h"

/*******************************************/
/*        Private Functions                */
/*******************************************/

typedef enum {
	USERS,
	GROUPS
} AccountType;

static gint cmp_system_accounts (SystemAccount *a, SystemAccount *b)
{
	if (a->id < b->id)
		return -1;
	else if (a->id > b->id)
		return 1;
	
	return 0;
}

static GList *internal_get_system_ids(AccountType what)
{
	FILE *fl;
	GList *retval = NULL;
	gchar buf[LINE_MAX], **arr;
	gchar *filename;
	if (what == USERS)
		filename = "/etc/passwd";
	else
		filename = "/etc/group";
	
	if ((fl = fopen (filename, "r")) == NULL)
	{
		pur_log_wrn ("Unable to open file %s: %s", filename, g_strerror(errno));
		return NULL;
	}
	while (fgets (buf, LINE_MAX, fl))
	{
		arr = g_strsplit (buf, ":", -1);
		if (arr_count (arr) < 2)
		{
			g_strfreev (arr);
			continue;
		}
		SystemAccount *account = g_new0 (SystemAccount, 1);
		if (arr[0][0] && arr[2][0] &&
		    atoi (arr[2]) > 0)
		{
			account->id = atoi(arr[2]);
			account->name = g_strdup (arr[0]);
			retval = g_list_insert_sorted (retval, account, (GCompareFunc) cmp_system_accounts);
		}
		g_strfreev (arr);
	}
	fclose (fl);
	return g_list_first (retval);
}

static GList *internal_get_system_ids_str(AccountType what)
{
	GList *list = internal_get_system_ids(what);
	GList *node = list;
	
	while (node)
	{
		SystemAccount *a = (SystemAccount*) node->data;
		node->data = g_strdup_printf("%s (%d)", a->name, a->id);
		system_account_free(a);
		node = g_list_next(node);
	}
	return list;
}

static gboolean internal_get_account_id_exists(AccountType what, guint id)
{
	GList *ids = internal_get_system_ids(what);
	GList *node = ids;
	gboolean found = FALSE;
	
	while (node)
	{
		SystemAccount *a = (SystemAccount*) node->data;
		if (a->id == id) {
			found = TRUE;
			/* We keep iterating the entire loop since we want to free all node data */
		}
		system_account_free(a);
		node = g_list_next(node);
	}
	g_list_free(ids);
	
	return found;
}

static guint internal_find_available_account_id(AccountType what, guint min_id)
{
	GList *list = internal_get_system_ids(what);
	GList *node = list;
	guint last_id = 0, rv = 0;
	while (node)
	{
		SystemAccount *a = (SystemAccount *) node->data;
		node = g_list_next(node);
		if (a->id <= min_id) 
			continue;
		if (a->id > last_id + 1 && last_id > min_id)
		{
			rv = last_id + 1;
			break;
		}
		last_id = a->id;
	}
	system_account_list_free(list);
	if (rv == 0)
		rv = last_id + 1;
	
	return rv;
}

/*******************************************/
/*            Public Functions             */
/*******************************************/

GList *sys_get_user_ids (void)
{
	return internal_get_system_ids(USERS);
}

GList *sys_get_group_ids (void)
{
	return internal_get_system_ids(GROUPS);
}

GList *sys_get_user_ids_str (void)
{
	return internal_get_system_ids_str(USERS);	
}

GList *sys_get_group_ids_str (void)
{
	return internal_get_system_ids_str(GROUPS);
}

gboolean sys_get_uid_exists(guint uid)
{
	return internal_get_account_id_exists(USERS, uid);
}

gboolean sys_get_gid_exists(guint gid)
{
	return internal_get_account_id_exists(GROUPS, gid);
}

guint sys_get_available_user_id (guint min_id)
{
	return internal_find_available_account_id(USERS, min_id);
}

guint sys_get_available_group_id (guint min_id)
{
	return internal_find_available_account_id(GROUPS, min_id);
}

gboolean sys_create_user (const gchar *username, guint userid, guint groupid, GError **err)
{
	gint child_retval;
	gboolean ret;
	GError *spawn_err = NULL;
	
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
	
	if (!cfg.cmd_useradd) {
		g_set_error (err, PUREADMIN_MAIN_ERROR,
			     PA_MAIN_ERROR_COMMAND_NOT_FOUND,
			     "Command 'useradd' isn't available");
		return FALSE;
	}
	
	if (!MISC_VALID_STRING(username))
	{
		g_set_error(err, PUREADMIN_MAIN_ERROR,
				PA_MAIN_ERROR_FAILED,
				"Invalid username");
		return FALSE;
	}
	gchar *cmd = g_strdup_printf ("%s -u %d -g %d -d /dev/null -c 'PureFTP User' -s /etc %s", cfg.cmd_useradd, userid, groupid, username);
	pur_log_dbg ("Running command [%s]", cmd);
	ret = g_spawn_command_line_sync (cmd, NULL, NULL, &child_retval, &spawn_err);
	g_free (cmd);    
	child_retval = (signed char) WEXITSTATUS(child_retval);
	if (child_retval){
		g_set_error (err, PUREADMIN_MAIN_ERROR,
				 PA_MAIN_ERROR_FAILED,
				 "Child '%s' returned abnormally: %d", cfg.cmd_useradd, child_retval);
		return FALSE;
	}
	if (!ret) {
		g_set_error (err, PUREADMIN_MAIN_ERROR,
				 PA_MAIN_ERROR_FAILED,
				 "Couldn't spawn child process '%s': %s", cfg.cmd_useradd, spawn_err->message);
		g_error_free(spawn_err);
		return FALSE;
	}

	return TRUE;
}

/* Creates a new group with the specified name and id. If 'groupid' is 0, the groupadd command will pick one
 * instead. */
gboolean sys_create_group (const gchar *groupname, guint groupid, GError **err)
{
	gboolean ret;
	gint child_retval;
	GError *spawn_err = NULL;
	
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
	
	if (!cfg.cmd_groupadd) {
		g_set_error (err, PUREADMIN_MAIN_ERROR,
			     PA_MAIN_ERROR_COMMAND_NOT_FOUND,
			     "Command 'groupadd' isn't available");
		return FALSE;
	}
	
	if (!MISC_VALID_STRING(groupname))
	{
		g_set_error(err, PUREADMIN_MAIN_ERROR,
				PA_MAIN_ERROR_FAILED,
				"One or more arguments was incorrect");
		return FALSE;
	}
	
	gchar *cmd;
	if (groupid > 0)
		cmd	= g_strdup_printf ("%s -g %d %s", cfg.cmd_groupadd, groupid, groupname);
	else
		cmd = g_strdup_printf ("%s %s", cfg.cmd_groupadd, groupname);
	pur_log_dbg ("Running command [%s]", cmd);
	ret = g_spawn_command_line_sync (cmd, NULL, NULL, &child_retval, &spawn_err);
	g_free (cmd);    
	child_retval = (signed char) WEXITSTATUS(child_retval);
	if (child_retval){
		g_set_error (err, PUREADMIN_MAIN_ERROR,
				 PA_MAIN_ERROR_FAILED,
				 "Child '%s' returned abnormally: %d", cfg.cmd_groupadd, child_retval);
		return FALSE;
	}
	if (!ret) {
		g_set_error (err, PUREADMIN_MAIN_ERROR,
				 PA_MAIN_ERROR_FAILED,
				 "Couldn't spawn child process '%s': %s", cfg.cmd_groupadd, spawn_err->message);
		g_error_free(spawn_err);
		return FALSE;
	}

	return TRUE;
}

/* Frees one SystemAccount object */
void system_account_free(SystemAccount *account)
{
	if (account == NULL)
		return;
	g_free (account->name);
	g_free (account);
}

/* Frees a GList containing SystemAccount objects */
void system_account_list_free (GList *list)
{
	g_list_foreach(list, (GFunc) system_account_free, NULL);
	g_list_free (list);
}


syntax highlighted by Code2HTML, v. 0.9.1