/* 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 #endif #include #include #include #include #include #include #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); }