/* 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. */ /* * Functions for communication with PureFTPd server * Some parts is taken from the pure-ftpwho utility distributed with PureFTPd, * copyright is in these cases held by their respective author(s) * * Copyright (C) 2003 Isak Savo */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "srv_comm.h" #include "helper.h" #include "cfg.h" #include "globals.h" GStaticMutex alist_mutex = G_STATIC_MUTEX_INIT; GList *cur_activities = NULL; /* Generates a unique id from the provided hash string. In most cases, * this will be the same as returned from g_str_hash, but this function * also guarantees that the id is unique in the provided list of activities. * * Theoreticly this could lead to a eternal loop, but it shouldn't * in practice! (Unless there are a couple of billion entries in the list) */ static guint _list_generate_unique_id (const gchar *hashstr, GList *list) { GList *node; gboolean already_exists; guint current; /* This should in most case generate a number that is unique */ current = g_str_hash (hashstr); do { already_exists = FALSE; node = g_list_first (list); while (node && already_exists == FALSE) { guint id = ((PActivity *) node->data)->id; if (id > current) /* The list is sorted ascending by ID, so this shortcut is valid */ return current; if (id == current) already_exists = TRUE; node = g_list_next (node); } if (G_UNLIKELY (already_exists)) current += g_random_int(); } while (already_exists); return current; } /* Parses a line separated by pipes ('|'), and returns an array of strings containing all the fields */ static gchar **ftpwho_parse_line (gchar *line) { gchar **retval; gchar *field; gboolean done = FALSE; gint array_count, i, j, line_length; if (!line || !strstr (line, "|")) return g_strsplit ("", "|", -1); if (G_LIKELY (strstr (line, "\\|") == NULL)) return g_strsplit (line, "|", -1); /* We need to handle an escaped pipe symbol in the username */ retval = g_new0 (gchar *, 13); i = 0; line_length = strlen (line); field = g_new0 (gchar, line_length); for (array_count = 0; array_count < 12 && i < line_length; array_count++) { done = FALSE; j = 0; while (!done && i < line_length) { if (line[i] == '\\' && line[i + 1] == '|') { field[j++] = '|'; i++; } else if (line[i] == '|') done = TRUE; else field[j++] = line[i]; i++; } retval[array_count] = g_strdup (field); memset (field, '\0', j); } g_free (field); return retval; } /* This function is used to find out/set whether the srv_com function * has fetched *any* data from the ftp server. This is used to display an * initial "please wait, fetching info..." message to the user * * Arguments: * set - If TRUE, the init_status will be set to TRUE, otherwise it will be left untouched. * * Returns: * The init status (TRUE if activities has been fetched at least once, otherwise FALSE) */ gboolean has_initialized (gboolean set) { static GStaticMutex mutex = G_STATIC_MUTEX_INIT; static gboolean init_status = FALSE; gboolean rv = FALSE; g_static_mutex_lock (&mutex); if (set) init_status = TRUE; rv = init_status; g_static_mutex_unlock (&mutex); return rv; } static gint cmp_activities_by_id (const PActivity *a1, const PActivity *a2) { if (G_UNLIKELY (a1 == NULL || a2 == NULL)) return 0; if (a1->id < a2->id) return 1; if (a1->id == a2->id) return 0; return -1; } static gint lst_find_user (gconstpointer listdata, gconstpointer searchdata) { gchar *haystack = (listdata ? ((POnlineUser *) listdata)->username : NULL); gchar *needle = (searchdata ? ((PActivity *) searchdata)->username : NULL); gboolean namematch, hostmatch; namematch = hostmatch = FALSE; if (G_UNLIKELY (!haystack || !needle || *haystack == 0 || *needle == 0)) return 0; if (g_str_equal (haystack, needle)) namematch = TRUE; haystack = ((POnlineUser *) listdata)->remote_addr; needle = ((PActivity *) searchdata)->remote_addr; if (g_str_equal (needle, haystack)) hostmatch = TRUE; if (hostmatch && namematch) return 0; /* Absolute match */ else if (namematch) /* These are actually not relevant, but return <0 or > 0 anyway */ return -1; else return 1; } /* Main worker function of this file. This function is responsible for fetching data from pure-ftpwho, parse it * and then store it in a GList of activities. Note: This function is running in a separate thread than the main * program, and extra care must be taken when accessing shared data structures: * * GList *cur_activities: Protected by static mutex alist_mutex */ static void update_activities (void) { gchar *c_stdout, *c_stderr; gchar **lines, **entries; gchar *cmd; GList *tmp_alist = NULL; gint exit_status, lineno, count; gboolean ret; /* FIXME: (Maybe) make the log handler thread safe so that we can call pur_log_*() from this function */ c_stdout = c_stderr = NULL; cmd = g_strdup_printf ("%s -s %s", cfg.cmd_ftpwho, cfg.resolve_hostnames ? "" : "-H"); ret = g_spawn_command_line_sync (cmd, &c_stdout, NULL, &exit_status, NULL); if (!ret) { //pur_log_wrn ("Unable to execute child: [%s]", cmd); g_free (cmd); return; } g_free (cmd); /* Parse output */ c_stdout = g_strchomp (c_stdout); lines = g_strsplit (c_stdout, "\n", 0); g_free (c_stdout); lineno = 0; while (lines[lineno]) { PActivity *activity; entries = ftpwho_parse_line (lines[lineno]); count = arr_count (entries); //pur_log_dbg ("ftpwho_parse_line: %d entries returned", count); if (count < 11) { //pur_log_wrn ("Line [%s]: only %d entries found!", lines[lineno], count); lineno++; g_strfreev (entries); continue; } /* Add new found entries to global list */ activity = g_new0 (PActivity, 1); activity->pid = atoi (entries[0]); activity->username = g_strdup (entries[1]); activity->online_since = atoi (entries[2]); if (strncmp (entries[3], "IDLE", 4) == 0) activity->state = FTPWHO_STATE_IDLE; else if (strncmp (entries[3], " DL ", 4) == 0) activity->state = FTPWHO_STATE_DOWNLOAD; else if (strncmp (entries[3], " UL ", 4) == 0) activity->state = FTPWHO_STATE_UPLOAD; activity->filename = g_strdup (entries[4]); activity->remote_addr = g_strdup (entries[5]); activity->download_current_size = atoi (entries[8]); activity->download_total_size = atoi (entries[9]); activity->percentage = atoi (entries[10]); activity->speed = atoi (entries[11]); gchar *hash_string = g_strconcat (activity->username, activity->remote_addr, activity->filename, entries[3], NULL); activity->id = _list_generate_unique_id (hash_string, tmp_alist); tmp_alist = g_list_insert_sorted (tmp_alist, activity, (GCompareFunc) cmp_activities_by_id); //tmp_alist = g_list_append (tmp_alist, activity); g_strfreev (entries); lineno++; } g_strfreev (lines); /* Clear the real (old) list in replace it with the newly fetched list */ g_static_mutex_lock (&alist_mutex); g_list_foreach (cur_activities, (GFunc) pactivity_free, NULL); g_list_free (cur_activities); cur_activities = tmp_alist; g_static_mutex_unlock (&alist_mutex); has_initialized (TRUE); } /** GLOBAL FUNCTIONS **/ void srv_terminate (void) { GList *node = g_list_first (cur_activities); if (!node) return; do { g_free (node->data); } while ((node = node->next)); g_list_free (cur_activities); cur_activities = NULL; return; } PActivity *pactivity_dup (PActivity *a) { PActivity *rv; rv = (PActivity *) g_memdup (a, sizeof (PActivity)); rv->username = g_strdup (a->username); rv->remote_addr = g_strdup (a->remote_addr); rv->filename = g_strdup (a->filename); return rv; } void pactivity_free (PActivity *a) { if (!a) return; g_free (a->username); g_free (a->remote_addr); g_free (a->filename); g_free (a); } void free_activity_list (GList *l) { GList *node = g_list_first (l); while (node) { pactivity_free ((PActivity *) node->data); node = g_list_next(node); } g_list_free (l); } void pouser_free (POnlineUser *u) { if (!u) return; g_free (u->username); g_free (u->remote_addr); g_free (u); } void free_pouser_list (GList *l) { GList *node = g_list_first (l); while (node) { pouser_free ((POnlineUser *) node->data); node = g_list_next(node); } g_list_free (l); } /* Returns the current activities on the server. The returned value is a GList of PActivity and should * be free'd when no longer used. See also: free_activity_list */ GList *srv_get_activities (void) { GList *retval = NULL, *node; PActivity *n_data; g_static_mutex_lock (&alist_mutex); node = cur_activities; while (node) { n_data = pactivity_dup ((PActivity *) node->data); retval = g_list_append (retval, n_data); node = g_list_next (node); } g_static_mutex_unlock (&alist_mutex); return retval; } gboolean srv_has_activities (void) { gboolean rv = FALSE; g_static_mutex_lock (&alist_mutex); if (cur_activities) rv = TRUE; g_static_mutex_unlock (&alist_mutex); return rv; } gboolean srv_try_get_activities (GError **err) { gchar *output, *err_output, *cmd; gint ret; gint exit_status; g_return_val_if_fail (err == NULL || *err == NULL, FALSE); if (!cfg.cmd_ftpwho) { g_set_error (err, PUREADMIN_SRV_ERROR, PA_SRV_ERROR_COMMAND_NOT_FOUND, _("Command 'pure-ftpwho' not found. Perhaps PureFTPd isn't installed")); return FALSE; } cmd = g_strdup_printf ("%s -s -H", cfg.cmd_ftpwho); ret = g_spawn_command_line_sync (cmd, &output, &err_output, &exit_status, NULL); pur_log_dbg ("Running command: [%s]", cmd); if (!ret) { g_set_error (err, PUREADMIN_SRV_ERROR, PA_SRV_ERROR_COMMAND_NOT_FOUND, _("Command 'pure-ftpwho' not found. Perhaps PureFTPd isn't installed")); g_free (cmd); g_free (output); return FALSE; } g_free (cmd); exit_status = (signed char) WEXITSTATUS(exit_status); if (exit_status) { if (strstr (output, "root")) g_set_error (err, PUREADMIN_SRV_ERROR, PA_SRV_ERROR_PERMISSION_DENIED, _("Permission denied")); else { if (exit_status == -1) g_set_error (err, PUREADMIN_SRV_ERROR, PA_SRV_ERROR_FAILED, _("Resource temporarily unavailable.")); else g_set_error (err, PUREADMIN_SRV_ERROR, PA_SRV_ERROR_FAILED, _("Pure-ftpwho exited abnormally: %d. Output was: '%s' and '%s' "), exit_status, output, err_output); } g_free (output); g_free (err_output); return FALSE; } g_free (output); return TRUE; } G_GNUC_NORETURN gpointer srv_activity_thread (gpointer data) { while (1) { update_activities (); g_usleep (3000000); } } /* Returns the PActivity that has the provided id number. * The returned value should be free'd using pactivity_free when no longer used */ PActivity *srv_get_activity_with_id (guint id) { GList *node = NULL; PActivity *act = NULL, *rv; gboolean found = FALSE; g_static_mutex_lock (&alist_mutex); node = g_list_first (cur_activities); while (node && !found) { act = (PActivity *) node->data; if (act->id == id) found = TRUE; node = g_list_next (node); } rv = pactivity_dup (act); g_static_mutex_unlock (&alist_mutex); if (found) return rv; else return NULL; } GList *srv_get_pids_for_user_host (const gchar *user, const gchar *host) { GList *node = NULL; GList *ret = NULL; PActivity *act; if (!user || !host || *user == '\0' || *host == '\0') return NULL; g_static_mutex_lock (&alist_mutex); node = g_list_first (cur_activities); while (node) { act = (PActivity *) node->data; if (g_str_equal (act->username, user) && g_str_equal (act->remote_addr, host)) { ret = g_list_append (ret, GINT_TO_POINTER (act->pid)); } node = g_list_next (node); } g_static_mutex_unlock (&alist_mutex); return ret; } GList *srv_get_connected_users (GList *activities) { GList *retval = NULL, *node; POnlineUser *user = NULL; g_static_mutex_lock (&alist_mutex); activities = g_list_first (activities); while (activities) { if ((node = g_list_find_custom (retval, activities->data, (GCompareFunc) lst_find_user))) ((POnlineUser *) node->data)->num_connections++; else { user = g_new0 (POnlineUser, 1); user->username = g_strdup (((PActivity *) activities->data)->username); user->remote_addr = g_strdup (((PActivity *) activities->data)->remote_addr); user->num_connections = 1; retval = g_list_append (retval, user); } activities = g_list_next (activities); } g_static_mutex_unlock (&alist_mutex); return g_list_first (retval); }