/* 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 <config.h>
#endif
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/mman.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1