/* 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.
*/
/*
* Purepw (password and db) functions
*
* Copyright (C) 2003 Isak Savo
*/
/* All functions take strings in UTF-8 format and handles encoding conversion before
* writing to disk.
* All strings returned are valid UTF-8
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#include "cfg.h"
#include "globals.h"
#include "helper.h"
#include "usr_manager.h"
#include "purepw.h"
/* Rebuilds the puredb by calling "pure-pw mkdb" on the password file */
static
gboolean rebuild_puredb (GError **err)
{
gchar *fullpath, *cmd;
gboolean ret;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if ((fullpath = g_find_program_in_path (cfg.cmd_purepw)) == NULL)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_COMMAND_NOT_FOUND,
"The command %s could not be found",
cfg.cmd_purepw);
g_free (fullpath);
return FALSE;
}
cmd = g_strdup_printf ("%s mkdb %s -f %s", fullpath, cfg.pdbfile, cfg.pwfile);
ret = g_spawn_command_line_async (cmd, err);
g_free (fullpath);
g_free (cmd);
return ret;
}
/* pw_add_line: Adds or modifies a password entry in the plain-text password-file used by pure-pw.
* If 'user' already exists, do an update; otherwise add a new line.
* Args:
* line: The already formatted line to add.
* user: The username in vuser locale
* Returns:
* FALSE - Couldn't add line. See err for details!
* TRUE - Everything updated successfully.
*/
static
gboolean pw_add_line (const gchar *line, const gchar *user, GError **err)
{
FILE *fl, *fl_tmp;
gint fd_tmp;
gchar *tmp_fname, buf[LINE_MAX], *dir;
gboolean added = FALSE, pwfile_exists = TRUE;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
dir = g_path_get_dirname (cfg.pwfile);
tmp_fname = g_strdup_printf ("%s/pureadmin_XXXXXX", dir);
if ((fl = fopen (cfg.pwfile, "r")) == NULL)
{
pur_log_wrn ("Unable to open password file: %s", strerror(errno));
if (g_file_test (cfg.pwfile, G_FILE_TEST_EXISTS))
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Couldn't create temporary password file %s",
tmp_fname);
g_free(tmp_fname);
return FALSE;
}
else
/* The file doesn't exist, but a new line could be added anyway */
pwfile_exists = FALSE;
}
fd_tmp = g_mkstemp (tmp_fname);
if (fd_tmp == -1)
{
/* Not likely to happen, but lets check for it anyway! */
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Couldn't create temporary file [%s]",
tmp_fname);
g_free(tmp_fname);
return FALSE;
}
if (((fl_tmp = fdopen (fd_tmp, "w")) == NULL))
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Couldn't open temporary file: %s", strerror (errno));
close (fd_tmp);
g_free(tmp_fname);
return FALSE;
}
while (pwfile_exists && fgets (buf, LINE_MAX, fl))
{
if (strncmp (buf, user, strlen (user)) == 0)
{
fputs (line, fl_tmp);
fputc ('\n', fl_tmp);
added = TRUE;
}
else
fputs (buf, fl_tmp);
}
if (!added)
{
fputs (line, fl_tmp);
fputc ('\n', fl_tmp);
}
fclose (fl_tmp);
if (pwfile_exists)
{
fclose (fl);
if (remove (cfg.pwfile) != 0)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to remove password-file");
g_free(tmp_fname);
return FALSE;
}
}
if (rename (tmp_fname, cfg.pwfile) != 0)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to rename [%s] to [%s]: %s",
tmp_fname, cfg.pwfile, strerror (errno));
g_free(tmp_fname);
return FALSE;
}
g_free(tmp_fname);
return TRUE;
}
static
gboolean pw_remove_line (const gchar *user, gchar **line, GError **err)
{
FILE *fl, *fl_tmp;
gint fd_tmp;
guint len;
gchar *tmp_fname, buf[LINE_MAX];
gboolean removed = FALSE;
gchar *dir, *sep_pos, *buf_start;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if ((fl = fopen (cfg.pwfile, "r")) == NULL)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_FILE_NOT_FOUND,
"Unable to open password file");
return FALSE;
}
dir = g_path_get_dirname (cfg.pwfile);
tmp_fname = g_strdup_printf ("%s/pureadmin_XXXXXX", dir);
g_free (dir);
fd_tmp = g_mkstemp (tmp_fname);
if ((fl_tmp = fdopen (fd_tmp, "w")) == NULL)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to open temporary password-file");
g_free (tmp_fname);
return FALSE;
}
buf_start = buf;
while (fgets (buf, LINE_MAX, fl))
{
if ((sep_pos = strchr (buf, PW_LINE_SEP_CH)) == NULL)
/* No ':' found in string */
continue;
len = GPOINTER_TO_INT(sep_pos) - GPOINTER_TO_INT(buf_start);
if (strlen (user) == len && strncmp (user, buf, len) == 0)
{
removed = TRUE;
*line = strdup (buf);
}
else
fputs (buf, fl_tmp);
}
fclose (fl_tmp);
fclose (fl);
if (removed == FALSE)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_USER_NOT_FOUND,
"The user %s does not exist", user);
remove (tmp_fname);
g_free (tmp_fname);
return FALSE;
}
if (remove (cfg.pwfile) != 0)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to remove password-file [%s]: %s ",
cfg.pwfile, strerror (errno));
g_free (tmp_fname);
return FALSE;
}
if (rename (tmp_fname, cfg.pwfile) != 0)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to rename [%s] to [%s]: %s",
tmp_fname, cfg.pwfile, strerror (errno));
g_free (tmp_fname);
return FALSE;
}
g_free (tmp_fname);
return TRUE;
}
/* pw_parse_line: Parses a line read from a pure-ftpd password file.
* Args: line the line to parse.
* pwinfo: preallocated struct where the info will be stored. (UTF-8)
* user: if not NULL, then return failure if users doesn't match.
*
* Returns: TRUE upon success, otherwise FALSE
*/
static
gboolean pw_parse_line(gchar *line, PWInfo * const pwinfo, const gchar *user)
{
gchar **arr;
memset (pwinfo, 0, sizeof (PWInfo));
arr = g_strsplit (line, PW_LINE_SEP, 0);
if (arr_count(arr) < 17)
return FALSE;
pwinfo->login = string_from_vuser_locale (arr[0]);
if (user && !g_str_equal (user, arr[0]))
{
g_strfreev (arr);
return FALSE;
}
pwinfo->pwd = g_strdup (arr[1]);
pwinfo->uid = (uid_t) strtoul(arr[2], NULL, 10);
pwinfo->gid = (gid_t) strtoul(arr[3], NULL, 10);
if (pwinfo->uid <= (uid_t) 0 || pwinfo->gid <= (gid_t) 0) {
g_strfreev (arr);
return FALSE;
}
pwinfo->gecos = string_to_utf8 (arr[4]);
if (*(arr[5]) != '/')
{
g_strfreev (arr);
return FALSE;
}
pwinfo->home = g_filename_to_utf8 (arr[5], -1, NULL, NULL, NULL);
if (*(arr[6])) {
pwinfo->bw_ul = strtoul(arr[6], NULL, 10) / 1024UL;
}
if (*(arr[7])) {
pwinfo->bw_dl = strtoul(arr[7], NULL, 10) / 1024UL;
}
if (*(arr[8])) {
pwinfo->ul_ratio = (unsigned int) strtoul(arr[8], NULL, 10);
}
if (*(arr[9])) {
pwinfo->dl_ratio = (unsigned int) strtoul(arr[9], NULL, 10);
}
if (*(arr[10])) {
pwinfo->per_user_max = (guint) strtoul(arr[10], NULL, 10);;
}
if (*(arr[11])) {
pwinfo->quota_files = strtoul(arr[11], NULL, 10);
}
if (*(arr[12])) {
pwinfo->quota_size = strtoull(arr[12], NULL, 10) / (1024ULL * 1024ULL);
}
pwinfo->allow_local_ip = g_strdup (arr[13]);
pwinfo->deny_local_ip = g_strdup (arr[14]);
pwinfo->allow_client_ip = g_strdup (arr[15]);
pwinfo->deny_client_ip = g_strdup (arr[16]);
if (sscanf(arr[17], "%u-%u", &pwinfo->time_begin, &pwinfo->time_end) == 2 &&
pwinfo->time_begin < 2360 && (pwinfo->time_begin % 100) < 60 &&
pwinfo->time_end < 2360 && (pwinfo->time_end % 100) < 60)
{
/* nothing */
}
else
{
pwinfo->time_begin = 0;
pwinfo->time_end = 0;
}
g_strfreev (arr);
return TRUE;
}
/*** Global functions ***/
/* pw_add_user: Adds a user to the database.
*
* Arguments: user The username to add (UTF8)
* passwd The password (UTF8)
*
* Returns: TRUE if the user was added
* FALSE upon failure. Check 'err' for details
*/
gboolean pw_add_user (const gchar *user, const gchar *passwd, GError **err)
{
GString *line;
gint ret;
gchar *conv;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
conv = string_to_vuser_locale (user);
line = g_string_new (conv);
g_free (conv);
g_string_append_printf (line, ":%s:", passwd);
g_string_append_printf (line, "%d:%d::", cfg.default_uid, cfg.default_gid);
g_string_append_printf (line, "%s/./", cfg.default_home); /* Make chroot by default */
g_string_append (line, "::::::::::::");
ret = pw_add_line (line->str, user, err);
g_string_free (line, TRUE);
if (ret == FALSE)
return FALSE;
return rebuild_puredb (err);
}
/* FIXME: This can cause user lost in rare cases.. */
/* pw_remove_user: Removes a user from the database.
*
* Arguments: user The username to remove (UTF-8)
* err The return location for errors, NULL to ignore
*
* Returns: TRUE if the user was removed completely
* FALSE if something went wrong, see 'err' for details
*/
gboolean pw_remove_user (const gchar *user, GError **err)
{
gchar *line = NULL;
GError *our_err = NULL;
gchar *conv;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if (!user || *user == 0)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_FAILED,
"Invalid username");
return FALSE;
}
conv = string_to_vuser_locale (user);
if (!pw_remove_line (conv, &line, err)) {
g_free (conv);
return FALSE;
}
g_free (conv);
if (!rebuild_puredb (&our_err))
{
g_propagate_error (err, our_err);
g_error_free (our_err);
our_err = NULL;
if (!pw_add_line (line, conv, &our_err))
{
/* We're screwed! */
g_error_free (*err);
*err = NULL;
pur_log_err ("Couldn't re-add removed line[%s] to password file: %s", line, our_err->message);
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_FAILED,
"User %s is lost :-(", user);
return FALSE;
}
return FALSE;
}
g_free (line);
return TRUE;
}
/* pw_user_exists: Checks if a user exists in the password file
*
* Arguments: user The username to check for
* err The location where errors should be stored
* Returns: TRUE if the user was found
* FALSE if the user wasn't found or an error occurred (check *err)
*/
gboolean pw_user_exists (const gchar *user, GError **err)
{
gboolean found = FALSE;
gchar *sep_pos, *buf, **arr, *conv;
gint i;
guint len;
gsize bytes;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if (!user || strlen (user) < 1)
{
/* FIXME: This is a programming error and shouldn't use GError... */
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_FAILED,
"Invalid username");
return FALSE;
}
if (!g_file_get_contents (cfg.pwfile, &buf, &bytes, err))
return FALSE;
conv = string_to_vuser_locale (user);
arr = g_strsplit (buf, "\n", -1);
g_free (buf);
i = -1;
while (arr[++i])
{
buf = arr[i];
if ((sep_pos = strchr (arr[i], PW_LINE_SEP_CH)) == NULL)
continue;
len = gpointer_diff (sep_pos,buf);
if (strlen (conv) == len && strncmp (conv, buf, len) == 0)
{
found = TRUE;
break;
}
}
g_strfreev (arr);
g_free (conv);
return found;
}
/* pw_get_available_users: Get a list of available users in the puredb
*
* Returns: a list of available users, all strings valid UTF-8
*/
GList *pw_get_available_users (GError **err)
{
GList *retval = NULL;
PAvailableUser *user;
FILE *pw;
gchar buf[200], **arr;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if ((pw = fopen (cfg.pwfile, "r")) == NULL)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Couldn't open %s: %s",
cfg.pwfile, g_strerror (errno));
return NULL;
}
while (fgets (buf, 200, pw))
{
g_strchomp (buf);
arr = g_strsplit (buf, PW_LINE_SEP, -1);
if (arr_count (arr) < 4)
continue;
user = g_new0 (PAvailableUser, 1);
user->login = string_from_vuser_locale (arr[0]);
user->realname = string_to_utf8 (arr[4]);
retval = g_list_prepend (retval, user);
g_strfreev (arr);
}
fclose (pw);
return g_list_first (retval);
}
/* pw_get_user_information: Fetches information for the specified user.
*
* Arguments: user
* Returns: A struct w/ all info or NULL upon failure and err is set appropiately
*/
PWInfo *pw_get_user_information (gchar *user, GError **err)
{
PWInfo *info;
FILE *pw_fl;
gchar *buf, *conv;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
if ((pw_fl = fopen (cfg.pwfile, "r")) == NULL)
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_PERMISSION_DENIED,
"Unable to open password file %s: %s",
cfg.pwfile, g_strerror (errno));
return NULL;
}
buf = g_new0 (gchar, LINE_MAX);
info = g_new0 (PWInfo, 1);
conv = string_to_vuser_locale (user);
while (fgets (buf, LINE_MAX, pw_fl))
{
g_strstrip (buf);
if (*buf == 0)
continue;
if (pw_parse_line (buf, info, conv)) {
g_free (conv);
return info;
}
}
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_USER_NOT_FOUND,
"User %s not found", user);
g_free (info);
g_free (conv);
return NULL;
}
/* pw_set_user_information: Sets the information for the user in i->login.
*
* Arguments: i The information, including username. In UTF-8 format
* err Return location for error, or NULL to ignore errors.
*
* Returns: TRUE if user information was updated.
* FALSE if something went wrong. Check err for details
*/
gboolean pw_set_user_information (PWInfo * const i, GError **err)
{
GString *line;
gchar *tmp, *user_vlocale;
g_return_val_if_fail (err == NULL || *err == NULL, FALSE);
user_vlocale = string_to_vuser_locale (i->login);
line = g_string_new (user_vlocale);
g_string_append_printf (line, ":%s:", i->pwd);
/* UID and GID */
g_string_append_printf (line, "%d:%d:", i->uid, i->gid);
/* Real name */
g_string_append_printf (line, "%s:", i->gecos ? i->gecos : "");
/* Home */
if (MISC_VALID_STRING (i->home))
{
tmp = g_filename_from_utf8 (i->home, -1, NULL, NULL, NULL);
g_string_append_printf (line, "%s:", tmp);
g_free (tmp);
}
else
{
g_set_error (err,
PUREADMIN_USERMANAGER_ERROR,
PA_USR_ERROR_INVALID_FIELD,
"Home directory field invalid [%s]", i->home);
g_string_free (line, TRUE);
g_free (user_vlocale);
return FALSE;
}
/* Bandwidth- and size-restrictions */
if (i->bw_ul)
g_string_append_printf (line, "%li:", i->bw_ul * 1024UL);
else
g_string_append (line, ":");
if (i->bw_dl)
g_string_append_printf (line, "%li:", i->bw_dl * 1024UL);
else
g_string_append (line, ":");
if (i->ul_ratio)
g_string_append_printf (line, "%d:", i->ul_ratio);
else
g_string_append (line, ":");
if (i->dl_ratio)
g_string_append_printf (line, "%d:", i->dl_ratio);
else
g_string_append (line, ":");
if (i->per_user_max)
g_string_append_printf (line, "%d:", i->per_user_max);
else
g_string_append (line, ":");
if (i->quota_files)
g_string_append_printf (line, "%d:", i->quota_files);
else
g_string_append (line, ":");
if (i->quota_size)
g_string_append_printf (line, "%lld:", i->quota_size * 1024ULL * 1024ULL);
else
g_string_append (line, ":");
/* IP-restrictions */
if (MISC_VALID_STRING (i->allow_local_ip))
g_string_append_printf (line, "%s:", i->allow_local_ip);
else
g_string_append (line, ":");
if (MISC_VALID_STRING (i->deny_local_ip))
g_string_append_printf (line, "%s:", i->deny_local_ip);
else
g_string_append (line, ":");
if (MISC_VALID_STRING (i->allow_client_ip))
g_string_append_printf (line, "%s:", i->allow_client_ip);
else
g_string_append (line, ":");
if (MISC_VALID_STRING (i->deny_client_ip))
g_string_append_printf (line, "%s:", i->deny_client_ip);
else
g_string_append (line, ":");
if (i->time_begin == 0 && i->time_end == 0) {
/* Nada */
}
else if (i->time_begin < 2360 && (i->time_begin % 100) < 60 &&
i->time_end < 2360 && (i->time_end % 100) < 60)
g_string_append_printf (line, "%04d-%04d", i->time_begin, i->time_end);
if (!pw_add_line (line->str, user_vlocale, err))
{
g_string_free (line, TRUE);
g_free (user_vlocale);
return FALSE;
}
g_string_free (line, TRUE);
g_free (user_vlocale);
return rebuild_puredb (err);
}
syntax highlighted by Code2HTML, v. 0.9.1