/* 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 helperfunctions used throughout the program 
 *
 * -- NOTE:
 * crypt() was taken and slightly modified from pureftpd: Copyright to the author(s)
 * --
 */
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#define _XOPEN_SOURCE
#include <gtk/gtk.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#include <stdarg.h>
#include "helper.h"
#include "usr_manager.h"
#include "globals.h"
#include "cfg.h"

G_GNUC_PURE gint arr_count (gchar **arr)
{
	gint i = 0;

	g_return_val_if_fail (arr, 0);

	while (arr[i] != NULL)
		i++;

	return i;
}

G_GNUC_PURE gboolean misc_str_is_only_whitespace (const gchar *s1)
{
	guint i;

	for (i = 0; i < strlen (s1); i++)
		if (!isspace (s1[i]))
			return FALSE;
   
	return TRUE;
}

/* create_array: Creates a null terminated array of strings containing all arguments passed
 *		 to this function. The argument list MUST be null-terminated.
 *
 * Arguments:
 *		argc_out: If non-null, the number of items in the array will be stored here.
 *		...: A null-terminated list of strings to add to the array.
 *
 *	Example: gchar **myarr = create_array (NULL, "item1", "item2", "item3", NULL);
 */
gchar **create_array (gint *argc_out, ...)
{
	va_list ap;
	GPtrArray *arr;
	gchar *cur;

	arr = g_ptr_array_sized_new (20);
	va_start (ap, argc_out);
	while ((cur = va_arg(ap, gchar*)))
	{
		g_ptr_array_add (arr, g_strdup (cur));
	}
	g_ptr_array_add (arr, NULL);
	va_end (ap);
	if (argc_out)
		*argc_out = arr->len;

	return (gchar **) g_ptr_array_free (arr, FALSE);
}


/* Spawns a command and return its output (only from stdout) as a newly allocated
 * string. */
gchar *misc_spawn_command (const gchar *cmdline)
{
	gchar *rv = NULL;
	gboolean success = g_spawn_command_line_sync (cmdline, &rv, NULL, NULL, NULL);
	if (!success)
		return NULL;

	return rv;
}

/* Gets the contents of /proc/<PID>/file as a newly allocated string. */
gchar *misc_get_process_info (const gchar *pid, const gchar *file)
{
	gchar *path = g_build_filename ("/proc", pid, file, NULL);
	gchar *rv = NULL;
	g_file_get_contents (path, &rv, NULL, NULL);

	return rv;
}

/* string_to_utf8: This function will do its best trying to convert the
 *		   input string to UTF-8. It will convert as many bytes as
 *		   possible, appending partial conversion with the
 *		   string 'Invalid Unicode'
 *		   If 'str' is null, the empty string ("") is returned!
 *
 * Returns: the (partially) converted string. This must always be free'd
 */
gchar *string_to_utf8 (const gchar *str)
{
	gchar *retval, *tmp;
	GError *err = NULL;
	gsize bytes_read, bytes_written;
	gboolean ends_with_newline = FALSE;
	gint valid_utf8bytes = 0;
	
#define INVALID_UNICODE_STRING g_strconcat (_("Invalid Unicode"), (ends_with_newline ? "\n" : ""), NULL)

	g_return_val_if_fail (str, g_strdup(""));
	
	if (str[strlen (str) - 1] == '\n')
		ends_with_newline = TRUE;
	if (g_utf8_validate (str, -1, (const gchar**) &tmp))
		return g_strdup (str);

	valid_utf8bytes = gpointer_diff(tmp,str) - 1;
	
	retval = g_locale_to_utf8 (str, -1, &bytes_read, &bytes_written, &err);
	if (err)
	{
		g_free (retval);

		g_error_free (err);
		err = NULL;
		if (bytes_read > 0)
			valid_utf8bytes = bytes_read;
		
		tmp = g_locale_to_utf8 (str, valid_utf8bytes, NULL, NULL, &err);
		if (G_UNLIKELY(err)) {
			/* This should not even happen */
			pur_log_wrn ("Couldn't even convert %d first bytes to UTF-8: %s", valid_utf8bytes, err->message);
			g_error_free (err);
			return INVALID_UNICODE_STRING;
		}
		/* Failed character encoding conversion, return partial string */
		retval = g_strdup_printf ("%s (%s)%c", tmp, _("Invalid Unicode"), ends_with_newline ? '\n' : '\0');
		g_free (tmp);
	}

	return retval;
}

/* string_to vuser_locale: Converts a string into the locale used by the virtual users
 *			   
 */
gchar *string_to_vuser_locale (const char *str)
{
	gchar *retval;
	const gchar *charset;
	GError *err = NULL;

	if (cfg.use_system_encoding)
		g_get_charset (&charset);
	else
		charset = cfg.uname_encoding;
	retval = g_convert (str, -1,
			    charset,
			    "UTF-8",
			    NULL, NULL, &err);
	if (err)
	{
		pur_log_err ("Unable to username string from UTF-8 to %s: %s", charset, err->message);
		g_error_free (err);
	}
	/* We return NULL here if conversion failed! */
	return retval;
}

/* Converts a string encoding in virtual users locale to UTF-8 */
gchar *string_from_vuser_locale (const char *str)
{
	gchar *retval;
	const gchar *charset;
	GError *err = NULL;
	if (cfg.use_system_encoding)
		g_get_charset (&charset);
	else
		charset = cfg.uname_encoding;
	retval = g_convert (str, -1,
			    "UTF-8",
			    charset,
			    NULL, NULL, &err);
	if (err)
	{
		pur_log_err ("Unable to username string from %s to UTF-8: %s", charset, err->message);
		g_error_free (err);
	}
	/* We return NULL here if conversion failed! */
	return retval;
}

/* Searches 'haystack' for a case insensitive match of 'needle'
 * This function most likely fails on UTF-8 encoded strings */
gboolean misc_str_isearch (const gchar *haystack, const gchar *needle)
{
	gchar *n, *h;
	gboolean rv;
	
	g_return_val_if_fail (haystack != NULL, FALSE);
	g_return_val_if_fail (needle != NULL, FALSE);
	n = g_ascii_strdown (needle, -1);
	h = g_ascii_strdown (haystack, -1);

	if (strstr (h, n))
		rv = TRUE;
	else
		rv = FALSE;
	g_free (n);
	g_free (h);

	return rv;
}

/* Elipsize a string at length 'len'.
 * Example: pur_elipsize ("eat my shorts", 6) => "eat my..." */
gchar *pur_elipzise (const gchar *orig_s, guint len)
{
	gchar *rv;
	if (strlen (orig_s) < len + 3)
		return g_strdup (orig_s);

	rv = (gchar *) g_strndup (orig_s, len + 3 + 1);

	rv[len] = rv[len + 1] = rv[len + 2] = '.';
	rv[len + 3] = '\0';

	return rv;
}

gchar *filename_without_extension (const gchar *filename)
{
	if (filename == NULL || *filename == '\0')
		return g_strdup (filename);
	const gchar *ext = strrchr (filename, '.');
	if (ext == NULL)
		return g_strdup (filename);
	
	return g_strndup (filename, gpointer_diff (ext, filename));
}

gboolean misc_stop_server (GError **err)
{
	GError *i_err = NULL;
	gchar *cmd, *out_stdout, *out_stderr;
	gint ret, exitstatus;

	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
	
	cmd = g_strdup_printf ("%s stop", cfg.cmd_startstop);
	pur_log_nfo ("Stopping server: %s", cmd);
	ret = g_spawn_command_line_sync (cmd, &out_stdout, &out_stderr,
					 &exitstatus, &i_err);
	g_free (cmd);
	g_free (out_stdout);
	g_free (out_stderr);
	if (!ret) {
		exitstatus = WEXITSTATUS(exitstatus);
		if (exitstatus != 0)
			pur_log_wrn ("Stop server failed, child exited abnormally: %d", exitstatus);
		g_propagate_error (err, i_err);
		return FALSE;
	}
	
	return TRUE;
}

gboolean misc_start_server (GError **err)
{
	GError *i_err = NULL;
	gchar *cmd, *out_stdout, *out_stderr;
	gint ret, exitstatus;

	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
	
	cmd = g_strdup_printf ("%s start", cfg.cmd_startstop);
	pur_log_nfo ("Starting server: %s", cmd);
	ret = g_spawn_command_line_sync (cmd, &out_stdout, &out_stderr,
					 &exitstatus, &i_err);
	g_free (cmd);
	/* FIXME: Do something clever with the output */
	g_free (out_stdout);
	g_free (out_stderr);
	if (!ret) {
		exitstatus = WEXITSTATUS(exitstatus);
		if (exitstatus != 0)
			pur_log_wrn ("Start server failed, child exited abnormally: %d", exitstatus);
		g_propagate_error (err, i_err);
		return FALSE;
	}
   
	return TRUE;
}

gint misc_close_connection (guint pid)
{
	gint ret;

	/* FIXME: There is no safe way to sanity check this.. but this prevents us from
	 * at least killing init and the kernel services :-) */
	if (pid < 100)
		return 0;
	
#ifndef SIGTERM
	pur_log_wrn ("SIGTERM is not defined, defining it to 15");
#define SIGTERM 15
#endif /* SIGTERM */
	
	ret = kill (pid, SIGTERM);
	if (ret == -1)
	{
		perror ("kill");
		pur_log_wrn ("Could not close kill process %d\n", pid);
		return 0;
	}
	return 1;
}

gchar *misc_find_file_in_dirs (const gchar *filename, const gchar *dirs)
{
	gchar *tmp, **dirs_arr, **start;

	start = dirs_arr = g_strsplit (dirs, " ", 0);
	while (*dirs_arr)
	{
		tmp = g_build_filename (*dirs_arr, filename, NULL);
		if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR))
			return tmp;
		g_free (tmp);
		dirs_arr++;
	}
	g_strfreev (start);

	return NULL;
}

gchar *misc_find_prog_in_dirs (const gchar *filename, const gchar *dirs)
{
	gchar *tmp, **dirs_arr, **start;
	
	start = dirs_arr = g_strsplit (dirs, " ", 0);
	while (*dirs_arr)
	{
		tmp = g_build_filename (*dirs_arr, filename, NULL);
		if (g_file_test (tmp, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE))
			return tmp;
		g_free (tmp);
		dirs_arr++;
	}
	g_strfreev (start);
	
	return NULL;
}

gchar *misc_get_line_beginning_with (const gchar *filename, const gchar *needle)
{
	FILE *fl;
	gchar buf[200], *rv;
	gint len;
	
	g_return_val_if_fail (filename != NULL, NULL);
	g_return_val_if_fail (needle != NULL, NULL);

	if ((fl = fopen (filename, "r")) == NULL)
	{
		pur_log_dbg ("helper.c: Unable to open %s for reading", filename);
		return NULL;
	}
	rv = NULL;
	len = strlen (needle);
	while (!feof (fl))
	{
		fgets (buf, 200, fl);
		if (strncmp (buf, needle, len) == 0) {
			rv = g_strdup (buf);
			break;
		}
	}
	fclose (fl);
	
	return rv;
}

/* Tries to locate the users web browser by the following heuristics:
 *  1) Check $BROWSER environment variable
 *  2) Check gconf for GNOME default
 *  3) Check if any of the hardcoded defaults exists: konqueror, firefox, mozilla
 *
 * FIXME: Should be enhanced to get KDEs default, but I don't know how. Also, browser
 *	  source should be dependent on which DE the user is running => check for KDE/GNOME!
 */
gchar *misc_find_webbrowser (void)
{
	gchar *browser = NULL;
	gchar *output = NULL;

	/* First choise: the $BROWSER environment variable */
	if (g_getenv("BROWSER")) {
		pur_log_dbg ("Web browser found by $BROWSER envvar: %s", g_getenv ("BROWSER"));
		return g_strdup (g_getenv("BROWSER"));
	}
	
	/* Check gconf if available - this is used by GNOME */
	if (g_find_program_in_path("gconftool-2") &&
	    g_spawn_command_line_sync ("gconftool-2 -g "
				       "/desktop/gnome/url-handlers/http/enabled "
				       "/desktop/gnome/url-handlers/http/command", &output, NULL, NULL, NULL))
	{
		gchar **values = g_strsplit(g_strchomp(output), "\n", 2);
		if (arr_count(values) == 2) {
			pur_log_dbg ("Web browser found by gconftool-2: [%s], [%s])", values[0], values[1]);
			if (strcmp (values[0], "true") == 0)
				browser = g_strdup (values[1]);
		}
		g_strfreev (values);
	}
	g_free (stdout);
	
	/* Had I known how to check for default browser in KDE, it would
	 * be done here. But I don't know this, so lets try some hard coded ones instead */
	
	if (!browser) {
		if (g_find_program_in_path ("konqueror"))
			browser = g_strdup ("konqueror");
		else if (g_find_program_in_path ("firefox"))
			browser = g_strdup ("firefox");
		else if (g_find_program_in_path ("mozilla"))
			browser = g_strdup ("mozilla");
		pur_log_dbg ("Web browser found by hard-coded defaults: %s", browser);

	}
	if (!browser) 
		return NULL;
	
	if (!strstr (browser, "%s")) {
		char *tmp = g_strconcat (browser, " %s", NULL);
		g_free (browser);
		browser = tmp;
	}

	return browser;
}

gboolean misc_create_passwd_file (const gchar *filename)
{
	FILE *f;
   
	if ((f = fopen (filename, "w")) == NULL)
	{
		pur_log_wrn ("Could not create password file: %s", strerror (errno));
		return FALSE;
	}
	fclose (f);
	return TRUE;
}

/* Creates a directory and all its components. Also sets the owner and group of the directory */
/* If owner and/or group shouldn't be set, specify -1 as uid/gid. */
gboolean misc_create_directory (const gchar *directory, gint uid, gint gid, GError **err)
{
	gchar **parts;
	gboolean rv = TRUE;
	g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
	if (!g_path_is_absolute(directory))
	{
		g_set_error(err, PUREADMIN_MAIN_ERROR,
				PA_MAIN_ERROR_FAILED,
				"'%s' is not a valid directory", directory);
		return FALSE;
	}
	parts = g_strsplit(directory, G_DIR_SEPARATOR_S, -1);
	GString *s = g_string_new("");
	for (gint i = 1; parts[i] != NULL; i++)
	{
		g_string_append_c(s, '/');
		g_string_append(s, parts[i]);
		if (g_file_test (s->str, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
			continue;
		if (mkdir(s->str, 0755) != 0)
		{
			g_set_error(err, PUREADMIN_MAIN_ERROR,
				PA_MAIN_ERROR_FAILED,
				"Couldn't create directory: %s", g_strerror(errno));
			rv = FALSE;
			break;
		}
	}
	g_strfreev(parts);
	g_string_free(s, TRUE);
	if (rv == TRUE)
		chown(directory, uid, gid);
	return rv;
}

/* Crypt functions taken pure-pw distributed with pureftpd source package:
 * Copyright (c) 2001, 2002, 2003 Frank Denis <j@pureftpd.org> with the
 * help of all Pure-FTPd contributors.
 *
 * Modified slightly to use GLibs random numbers
 */
gchar *misc_crypt_passwd (const gchar * const pwd)
{
   
	static const char crcars[64] =
		"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
	register const char *crypted;
    
	if ((crypted = (const char *)      /* Blowfish */
	     crypt("test", "$2a$07$1234567890123456789012")) != NULL &&  
	    strcmp(crypted, "$2a$07$123456789012345678901uKO4"
		   "/IReKqBzRzT6YaajGvw20UBdHW7m") == 0) {
		char salt[] = "$2a$07$0000000000000000000000";        
		int c = 28;
        
		do {            
			c--;
			salt[c] = crcars[g_random_int() & 63];
		} while (c > 7);
        
		return (char *) crypt(pwd, salt);        
	} else if ((crypted = (const char *)    /* MD5 */
		    crypt("test", "$1$12345678$")) != NULL &&
		   strcmp(crypted, "$1$12345678$oEitTZYQtRHfNGmsFvTBA/") == 0) {
		char salt[] = "$1$00000000";
		int c = 10;
        
		do {            
			c--;
			salt[c] = crcars[g_random_int() & 63];
		} while (c > 3);
        
		return (char *) crypt(pwd, salt);
	} else if ((crypted = (const char *)    /* Extended DES */
		    crypt("test", "_.../1234")) != NULL &&
		   strcmp(crypted, "_.../1234PAPUVmqGzpU") == 0) {
		char salt[] = "_.../0000";
		int c = 8;
        
		do {
			c--;
			salt[c] = crcars[g_random_int() & 63];
		} while (c > 5);
        
		return (char *) crypt(pwd, salt);
	}
	/* Simple DES */
	{
		char salt[] = "00";
        
		salt[0] = crcars[g_random_int() & 63];
		salt[1] = crcars[g_random_int() & 63];
        
		return (char *) crypt(pwd, salt);
	} 
}


syntax highlighted by Code2HTML, v. 0.9.1