diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/daemon/Daemon.cpp | 176 | ||||
-rw-r--r-- | src/daemon/Makefile.am | 20 | ||||
-rw-r--r-- | src/daemon/dumpsocket.cpp | 582 | ||||
-rw-r--r-- | src/daemon/dumpsocket.h | 81 |
4 files changed, 371 insertions, 488 deletions
diff --git a/src/daemon/Daemon.cpp b/src/daemon/Daemon.cpp index a0875279..1237e7c4 100644 --- a/src/daemon/Daemon.cpp +++ b/src/daemon/Daemon.cpp @@ -19,6 +19,7 @@ #if HAVE_LOCALE_H # include <locale.h> #endif +#include <sys/un.h> #include <syslog.h> #include <pthread.h> #include <resolv.h> /* res_init */ @@ -32,12 +33,20 @@ #include "abrt_exception.h" #include "CrashWatcher.h" #include "Daemon.h" -#include "dumpsocket.h" #include "rpm.h" using namespace std; +#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" +#define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" + +#define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" +#define SOCKET_PERMISSION 0666 +/* Maximum number of simultaneously opened client connections. */ +#define MAX_CLIENT_COUNT 10 + + /* Daemon initializes, then sits in glib main loop, waiting for events. * Events can be: * - inotify: something new appeared under /var/spool/abrt @@ -77,12 +86,129 @@ using namespace std; * If set_client_name(NULL) was done, they are not sent. */ +CCommLayerServer* g_pCommLayer; -#define VAR_RUN_LOCK_FILE VAR_RUN"/abrt/abrtd.lock" -#define VAR_RUN_PIDFILE VAR_RUN"/abrtd.pid" +static bool daemonize = true; +static volatile sig_atomic_t s_sig_caught; +static int s_signal_pipe[2]; +static int s_signal_pipe_write = -1; +static int s_upload_watch = -1; +static unsigned s_timeout; +static bool s_exiting; + +static GIOChannel *socket_channel = NULL; +static guint socket_channel_cb_id = 0; +static int socket_client_count = 0; + + +/* Helpers */ + +static guint add_watch_or_die(GIOChannel *channel, unsigned condition, GIOFunc func) +{ + errno = 0; + guint r = g_io_add_watch(channel, (GIOCondition)condition, func, NULL); + if (!r) + perror_msg_and_die("g_io_add_watch failed"); + return r; +} + + +/* Socket handling */ + +/* Callback called by glib main loop when a client connects to ABRT's socket. */ +static gboolean server_socket_cb(GIOChannel *source, GIOCondition condition, gpointer ptr_unused) +{ + /* Check the limit for number of simultaneously attached clients. */ + if (socket_client_count >= MAX_CLIENT_COUNT) + { + error_msg("Too many clients, refusing connections to '%s'", SOCKET_FILE); + /* To avoid infinite loop caused by the descriptor in "ready" state, + * the callback must be disabled. + * It is added back in client_free(). */ + g_source_remove(socket_channel_cb_id); + socket_channel_cb_id = 0; + return TRUE; + } + + int socket = accept(g_io_channel_unix_get_fd(source), NULL, NULL); + if (socket == -1) + { + perror_msg("accept"); + return TRUE; + } + + log("New client connected"); + pid_t pid = fork(); + if (pid < 0) + { + close(socket); + perror_msg("fork"); + return TRUE; + } + if (pid == 0) /* child */ + { + xmove_fd(socket, 0); + xdup2(0, 1); + + char *argv[3]; /* abrt-server [-s] NULL */ + char **pp = argv; + *pp++ = (char*)"abrt-server"; + if (logmode & LOGMODE_SYSLOG) + *pp++ = (char*)"-s"; + *pp = NULL; + + execvp(argv[0], argv); + perror_msg_and_die("Can't execute '%s'", argv[0]); + } + /* parent */ + socket_client_count++; + close(socket); + return TRUE; +} + +/* Initializes the dump socket, usually in /var/run directory + * (the path depends on compile-time configuration). + */ +static void dumpsocket_init() +{ + unlink(SOCKET_FILE); /* not caring about the result */ + + int socketfd = xsocket(AF_UNIX, SOCK_STREAM, 0); + close_on_exec_on(socketfd); + + struct sockaddr_un local; + memset(&local, 0, sizeof(local)); + local.sun_family = AF_UNIX; + strcpy(local.sun_path, SOCKET_FILE); + xbind(socketfd, (struct sockaddr*)&local, sizeof(local)); + xlisten(socketfd, MAX_CLIENT_COUNT); + + if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0) + perror_msg_and_die("chmod '%s'", SOCKET_FILE); + + socket_channel = g_io_channel_unix_new(socketfd); + g_io_channel_set_close_on_unref(socket_channel, TRUE); + socket_channel_cb_id = add_watch_or_die(socket_channel, G_IO_IN | G_IO_PRI, server_socket_cb); +} + +/* Releases all resources used by dumpsocket. */ +static void dumpsocket_shutdown() +{ + /* Set everything to pre-initialization state. */ + if (socket_channel) + { + /* Undo add_watch_or_die */ + g_source_remove(socket_channel_cb_id); + /* Undo g_io_channel_unix_new */ + g_io_channel_unref(socket_channel); + socket_channel = NULL; + } +} + + +/* Cron handling */ -//FIXME: add some struct to be able to join all threads! typedef struct cron_callback_data_t { std::string m_sPluginName; @@ -99,17 +225,6 @@ typedef struct cron_callback_data_t {} } cron_callback_data_t; - -static volatile sig_atomic_t s_sig_caught; -static int s_signal_pipe[2]; -static int s_signal_pipe_write = -1; -static int s_upload_watch = -1; -static unsigned s_timeout; -static bool s_exiting; - -CCommLayerServer* g_pCommLayer; - - static void cron_delete_callback_data_cb(gpointer data) { cron_callback_data_t* cronDeleteCallbackData = static_cast<cron_callback_data_t*>(data); @@ -392,7 +507,7 @@ static int Lock() /* we leak opened lfd intentionally */ } -static void handle_fatal_signal(int signo) +static void handle_signal(int signo) { // Enable for debugging only, malloc/printf are unsafe in signal handlers //VERB3 log("Got signal %d", signo); @@ -415,7 +530,18 @@ static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpoint { /* we did receive a signal */ VERB3 log("Got signal %d through signal pipe", signo); - s_exiting = 1; + if (signo != SIGCHLD) + s_exiting = 1; + else + { + if (socket_client_count) + socket_client_count--; + if (!socket_channel_cb_id) + { + log("Accepting connections on '%s'", SOCKET_FILE); + socket_channel_cb_id = add_watch_or_die(socket_channel, G_IO_IN | G_IO_PRI, server_socket_cb); + } + } return TRUE; } return FALSE; @@ -739,7 +865,6 @@ static void sanitize_dump_dir_rights() int main(int argc, char** argv) { - bool daemonize = true; int opt; int parent_pid = getpid(); @@ -753,6 +878,10 @@ int main(int argc, char** argv) if (getuid() != 0) error_msg_and_die("ABRT daemon must be run as root"); + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + while ((opt = getopt(argc, argv, "dsvt:")) != -1) { unsigned long ul; @@ -787,15 +916,18 @@ int main(int argc, char** argv) } } + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = "abrtd"; /* for log(), error_msg() and such */ xpipe(s_signal_pipe); close_on_exec_on(s_signal_pipe[0]); close_on_exec_on(s_signal_pipe[1]); - signal(SIGTERM, handle_fatal_signal); - signal(SIGINT, handle_fatal_signal); + signal(SIGTERM, handle_signal); + signal(SIGINT, handle_signal); + signal(SIGCHLD, handle_signal); if (s_timeout) - signal(SIGALRM, handle_fatal_signal); + signal(SIGALRM, handle_signal); /* Daemonize unless -d */ if (daemonize) @@ -995,7 +1127,7 @@ int main(int argc, char** argv) g_main_loop_unref(pMainloop); /* Exiting */ - if (s_sig_caught && s_sig_caught != SIGALRM) + if (s_sig_caught && s_sig_caught != SIGALRM && s_sig_caught != SIGCHLD) { error_msg_and_die("Got signal %d, exiting", s_sig_caught); signal(s_sig_caught, SIG_DFL); diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index cd69cd36..38535d1c 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -1,6 +1,6 @@ bin_SCRIPTS = abrt-debuginfo-install abrt-handle-upload -sbin_PROGRAMS = abrtd +sbin_PROGRAMS = abrtd abrt-server abrtd_SOURCES = \ PluginManager.h PluginManager.cpp \ @@ -10,7 +10,6 @@ abrtd_SOURCES = \ CommLayerServer.h CommLayerServer.cpp \ CommLayerServerDBus.h CommLayerServerDBus.cpp \ Settings.h Settings.cpp \ - dumpsocket.h dumpsocket.cpp \ Daemon.h Daemon.cpp abrtd_CPPFLAGS = \ -I$(srcdir)/../../inc \ @@ -36,6 +35,23 @@ abrtd_LDADD = \ $(RPM_LIBS) \ $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) +abrt_server_SOURCES = \ + abrt-server.c +abrt_server_CPPFLAGS = \ + -I$(srcdir)/../../inc \ + -I$(srcdir)/../../lib/utils \ + -DBIN_DIR=\"$(bindir)\" \ + -DVAR_RUN=\"$(VAR_RUN)\" \ + -DCONF_DIR=\"$(CONF_DIR)\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -D_GNU_SOURCE \ + -Wall -Werror +abrt_server_LDADD = \ + ../../lib/utils/libABRTUtils.la + dbusabrtconfdir = ${sysconfdir}/dbus-1/system.d/ dist_dbusabrtconf_DATA = dbus-abrt.conf diff --git a/src/daemon/dumpsocket.cpp b/src/daemon/dumpsocket.cpp index 8fd14292..44955351 100644 --- a/src/daemon/dumpsocket.cpp +++ b/src/daemon/dumpsocket.cpp @@ -15,184 +15,127 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include <glib.h> -#include <sys/un.h> #include "abrtlib.h" -#include "dumpsocket.h" -#include "crash_types.h" -//#include "abrt_exception.h" +#include "dump_dir.h" +#include "crash_types.h" /* FILENAME_foo */ #include "hooklib.h" -#define SOCKET_FILE VAR_RUN"/abrt/abrt.socket" -#define SOCKET_PERMISSION 0666 /* Maximal length of backtrace. */ #define MAX_BACKTRACE_SIZE (1024*1024) - /* Amount of data received from one client for a message before reporting error. */ #define MAX_MESSAGE_SIZE (4*MAX_BACKTRACE_SIZE) +/* Maximal number of characters read from socket at once. */ +#define INPUT_BUFFER_SIZE (8*1024) +/* We exit after this many seconds */ +#define TIMEOUT 10 -/* Maximum number of simultaneously opened client connections. */ -#define MAX_CLIENT_COUNT 10 -/* Interval between checks of client halt, in seconds. */ -#define CLIENT_CHECK_INTERVAL 10 +/* +Unix socket in ABRT daemon for creating new dump directories. + +Why to use socket for creating dump dirs? Security. When a Python +script throws unexpected exception, ABRT handler catches it, running +as a part of that broken Python application. The application is running +with certain SELinux privileges, for example it can not execute other +programs, or to create files in /var/cache or anything else required +to properly fill a dump directory. Adding these privileges to every +application would weaken the security. +The most suitable solution is for the Python application +to open a socket where ABRT daemon is listening, write all relevant +data to that socket, and close it. ABRT daemon handles the rest. + +** Protocol + +Initializing new dump: +open /var/run/abrt.socket + +Providing dump data (hook writes to the socket): +-> "PID=" + number 0 - PID_MAX (/proc/sys/kernel/pid_max) + \0 +-> "EXECUTABLE=" + string (maximum length ~MAX_PATH) + \0 +-> "BACKTRACE=" + string (maximum length 1 MB) + \0 +-> "ANALYZER=" + string (maximum length 100 bytes) + \0 +-> "BASENAME=" + string (maximum length 100 bytes, no slashes) + \0 +-> "REASON=" + string (maximum length 512 bytes) + \0 + +Finalizing dump creation: +-> "DONE" + \0 +*/ -/* Interval with no data received from client, after which the client is - considered halted, in seconds. */ -#define CLIENT_HALT_INTERVAL 10 -/* Maximal number of characters read from socket at once. */ -#define INPUT_BUFFER_SIZE 1024 +/* Buffer for incomplete incoming messages. */ +static char *messagebuf_data = NULL; +static unsigned messagebuf_len = 0; -static GIOChannel *channel = NULL; -static guint channel_cb_id = 0; -static int client_count = 0; +static unsigned total_bytes_read = 0; -/* Information about single socket session. */ -struct client -{ - /* Client user id */ - uid_t uid; - /* Buffer for incomplete incoming messages. */ - GByteArray *messagebuf; - /* Executable. */ - char *executable; - /* Process ID. */ - int pid; - char *backtrace; - /* Python, Ruby etc. */ - char *analyzer; - /* Directory base name: python (or pyhook), ruby etc. */ - char *basename; - /* Crash reason. - * Python example: "CCMainWindow.py:1:<module>:ZeroDivisionError: - * integer division or modulo by zero" - */ - char *reason; - /* Last time some data were received over the socket - * from the client. - */ - time_t lastwrite; - /* Timer checking client halt. */ - guint timer_id; - /* Client socket callback id. */ - guint socket_id; - /* Client socket channel */ - GIOChannel *channel; -}; - -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data); - -/* Releases all memory that belongs to a client session. */ -static void client_free(struct client *client) -{ - /* Delete the uncompleted message if there is some. */ - g_byte_array_free(client->messagebuf, TRUE); - free(client->executable); - free(client->backtrace); - free(client->analyzer); - free(client->basename); - free(client->reason); - g_source_remove(client->timer_id); - g_source_remove(client->socket_id); - g_io_channel_unref(client->channel); - free(client); - --client_count; - if (!channel_cb_id) - { - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - (GIOFunc)server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); - } -} +static uid_t client_uid = (uid_t)-1L; -/* Callback called by glib main loop at regular intervals when - some client is connected. */ -static gboolean client_check_cb(gpointer data) -{ - struct client *client = (struct client*)data; - if (time(NULL) - client->lastwrite > CLIENT_HALT_INTERVAL) - { - log("dumpsocket: client socket timeout reached, closing connection"); - client_free(client); - return FALSE; - } - return TRUE; -} +static int pid; +static char *executable; +static char *backtrace; +/* "python", "ruby" etc. */ +static char *analyzer; +/* Directory base name: "pyhook", "ruby" etc. */ +static char *dir_basename; +/* Crash reason. + * Python example: + * "CCMainWindow.py:1:<module>:ZeroDivisionError: integer division or modulo by zero" + */ +static char *reason; -/* Caller is responsible to free() the returned value. */ -static char *giocondition_to_string(GIOCondition condition) -{ - struct strbuf *strbuf = strbuf_new(); - if (condition & G_IO_HUP) - strbuf_append_str(strbuf, "G_IO_HUP | "); - if (condition & G_IO_ERR) - strbuf_append_str(strbuf, "G_IO_ERR | "); - if (condition & G_IO_NVAL) - strbuf_append_str(strbuf, "G_IO_NVAL | "); - if (condition & G_IO_IN) - strbuf_append_str(strbuf, "G_IO_IN | "); - if (condition & G_IO_OUT) - strbuf_append_str(strbuf, "G_IO_OUT | "); - if (condition & G_IO_PRI) - strbuf_append_str(strbuf, "G_IO_PRI | "); - if (strbuf->len == 0) - strbuf_append_str(strbuf, "none"); - else - { - /* remove the last " | " */ - strbuf->len -= 3; - strbuf->buf[strbuf->len] = '\0'; - } - char *result = strbuf->buf; - strbuf_free_nobuf(strbuf); - return result; -} /* Create a new debug dump from client session. * Caller must ensure that all fields in struct client * are properly filled. */ -static void create_debug_dump(struct client *client) +static void create_debug_dump() { /* Create temp directory with the debug dump. This directory is renamed to final directory name after all files have been stored into it. */ char *path = xasprintf(DEBUG_DUMPS_DIR"/%s-%ld-%u.new", - client->basename, + dir_basename, (long)time(NULL), - client->pid); + pid); /* No need to check the path length, as all variables used are limited, and dd_create() fails if the path is too long. */ - dump_dir_t *dd = dd_init();; - if (!dd_create(dd, path, client->uid)) + dump_dir_t *dd = dd_init(); + if (!dd_create(dd, path, client_uid)) { dd_delete(dd); dd_close(dd); - error_msg_and_die("dumpsocket: Error while creating crash dump %s", path); + error_msg_and_die("Error creating crash dump %s", path); } - dd_save_text(dd, FILENAME_ANALYZER, client->analyzer); - dd_save_text(dd, FILENAME_EXECUTABLE, client->executable); - dd_save_text(dd, FILENAME_BACKTRACE, client->backtrace); - dd_save_text(dd, FILENAME_REASON, client->reason); + dd_save_text(dd, FILENAME_ANALYZER, analyzer); + dd_save_text(dd, FILENAME_EXECUTABLE, executable); + dd_save_text(dd, FILENAME_BACKTRACE, backtrace); + dd_save_text(dd, FILENAME_REASON, reason); /* Obtain and save the command line. */ - char *cmdline = get_cmdline(client->pid); // never NULL + char *cmdline = get_cmdline(pid); // never NULL dd_save_text(dd, FILENAME_CMDLINE, cmdline); free(cmdline); /* Store id of the user whose application crashed. */ char uid_str[sizeof(long) * 3 + 2]; - sprintf(uid_str, "%lu", (long)client->uid); + sprintf(uid_str, "%lu", (long)client_uid); dd_save_text(dd, CD_UID, uid_str); dd_close(dd); @@ -204,11 +147,9 @@ static void create_debug_dump(struct client *client) strcpy(path, newpath); free(newpath); - log("dumpsocket: Saved %s crash dump of pid %u to %s", - client->analyzer, client->pid, path); + log("Saved %s crash dump of pid %u to %s", analyzer, pid, path); - /* Handle free space checking. */ -//FIXME! needs to use globals, like the rest of daemon does! + /* Trim old crash dumps if necessary */ unsigned maxCrashReportsSize = 0; parse_conf(NULL, &maxCrashReportsSize, NULL, NULL); if (maxCrashReportsSize > 0) @@ -252,25 +193,23 @@ static char *try_to_get_string(const char *message, return NULL; const char *contents = message + strlen(tag); - if ((printable && !printable_str(contents)) || - (!allow_slashes && strchr(contents, '/'))) - { - error_msg("dumpsocket: Received %s contains invalid characters -> skipping", tag); + if ((printable && !printable_str(contents)) + || (!allow_slashes && strchr(contents, '/')) + ) { + error_msg("Received %s contains invalid characters, skipping", tag); return NULL; } if (strlen(contents) > max_len) { - char *max_len_str = g_format_size_for_display(max_len); - error_msg("dumpsocket: Received %s too long -> trimming to %s", tag, max_len_str); - g_free(max_len_str); + error_msg("Received %s too long, trimming to %lu", tag, (long)max_len); } return xstrndup(contents, max_len); } /* Handles a message received from client over socket. */ -static void process_message(struct client *client, const char *message) +static void process_message(const char *message) { /* @param tag * The message identifier. Message starting with it @@ -287,17 +226,20 @@ static void process_message(struct client *client, const char *message) * Whether to allow slashes to be a part of input. */ #define HANDLE_INCOMING_STRING(tag, field, max_len, printable, allow_slashes) \ - char *field = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ - if (field) \ +{ \ + char *s = try_to_get_string(message, tag, max_len, printable, allow_slashes); \ + if (s) \ { \ - free(client->field); \ - client->field = field; \ + free(field); \ + field = s; \ + VERB3 log("Saved %s%s", tag, s); \ return; \ - } + } \ +} HANDLE_INCOMING_STRING("EXECUTABLE=", executable, PATH_MAX, true, true); HANDLE_INCOMING_STRING("BACKTRACE=", backtrace, MAX_BACKTRACE_SIZE, false, true); - HANDLE_INCOMING_STRING("BASENAME=", basename, 100, true, false); + HANDLE_INCOMING_STRING("BASENAME=", dir_basename, 100, true, false); HANDLE_INCOMING_STRING("ANALYZER=", analyzer, 100, true, true); HANDLE_INCOMING_STRING("REASON=", reason, 512, false, true); @@ -306,261 +248,135 @@ static void process_message(struct client *client, const char *message) /* PID is not handled as a string, we convert it to pid_t. */ if (starts_with(message, "PID=")) { - /* xatou() cannot be used here, because it would - * kill whole daemon by non-numeric string. - */ - char *endptr; - errno = 0; - const char *nptr = message + strlen("PID="); - unsigned long number = strtoul(nptr, &endptr, 10); - /* pid == 0 is error, the lowest PID is 1. */ - if (errno || nptr == endptr || *endptr != '\0' || number > UINT_MAX || number == 0) - { - error_msg("dumpsocket: invalid PID received -> ignoring"); - return; - } - client->pid = number; + pid = xatou(message + strlen("PID=")); + if (pid < 1) + /* pid == 0 is error, the lowest PID is 1. */ + error_msg_and_die("Malformed or out-of-range number: '%s'", message + strlen("PID=")); + VERB3 log("Saved PID %u", pid); return; } /* Creates debug dump if all fields were already provided. */ if (starts_with(message, "DONE")) { - if (client->pid == 0 || - client->backtrace == NULL || - client->executable == NULL || - client->analyzer == NULL || - client->basename == NULL || - client->reason == NULL) - { - error_msg("dumpsocket: DONE received, but some data are missing -> ignoring"); - return; + if (!pid || !backtrace || !executable + || !analyzer || !dir_basename || !reason + ) { + error_msg_and_die("Got DONE, but some data are missing. Aborting"); } - create_debug_dump(client); - return; + /* Write out the crash dump. Don't let alarm to interrupt here */ + alarm(0); + create_debug_dump(); + + /* Reset alarm and the counter which detects oversized dumps */ + alarm(TIMEOUT); + total_bytes_read = 0; } } -/* Callback called by glib main loop when ABRT receives data that have - * been written to the socket by some client. - */ -static gboolean client_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - struct client *client = (struct client*)data; - - /* Detailed logging, useful for debugging. */ - if (g_verbose >= 3) - { - char *cond = giocondition_to_string(condition); - log("dumpsocket: client condition %s", cond); - free(cond); - } +static void dummy_handler(int sig_unused) {} - /* Handle incoming data. */ - if (condition & (G_IO_IN | G_IO_PRI)) +int main(int argc, char **argv) +{ + char *env_verbose = getenv("ABRT_VERBOSE"); + if (env_verbose) + g_verbose = atoi(env_verbose); + + enum { + OPT_s = (1 << 0), + }; + int optflags = 0; + int opt; + while ((opt = getopt(argc, argv, "u:vs")) != -1) { - guint loop = client->messagebuf->len; - gsize len; - gchar buf[INPUT_BUFFER_SIZE]; - GError *err = NULL; - /* Read data in chunks of size INPUT_BUFFER_SIZE. This allows to limit the number of - bytes received (to prevent memory exhaustion). */ - do { - GIOStatus result = g_io_channel_read_chars(source, buf, INPUT_BUFFER_SIZE, &len, &err); - if (result == G_IO_STATUS_ERROR) - { - g_assert(err); - error_msg("dumpsocket: Error while reading data from client socket: %s", err->message); - g_error_free(err); - client_free(client); - return FALSE; - } - - if (g_verbose >= 3) - log("dumpsocket: received %zu bytes of data", len); - - /* Append the incoming data to the message buffer. */ - g_byte_array_append(client->messagebuf, (guint8*)buf, len); - - if (client->messagebuf->len > MAX_MESSAGE_SIZE) - { - error_msg("dumpsocket: Message too long."); - client_free(client); - return FALSE; - } - } while (len > 0); - - /* Check, if we received a complete message now. */ - for (; loop < client->messagebuf->len; ++loop) + switch (opt) { - if (client->messagebuf->data[loop] != '\0') - continue; - - VERB3 log("dumpsocket: Processing message: %s", - client->messagebuf->data); - - /* Process the message. */ - process_message(client, (char*)client->messagebuf->data); - /* Remove the message including the ending \0 */ - g_byte_array_remove_range(client->messagebuf, 0, loop + 1); - loop = 0; + case 'u': + client_uid = xatoi_u(optarg); + break; + case 'v': + g_verbose++; + break; + case 's': + optflags |= OPT_s; + break; + default: + error_msg_and_die( + "Usage: abrt-server [-v] [-u UID]\n" + "\n(So far only) creates crash dumps" + "\nOptions:" + "\n\t-v\tVerbose" + "\n\t-s\tLog to syslog" + "\n\t-u UID\tUse UID as client uid" + ); } - - /* Update the last write access time */ - client->lastwrite = time(NULL); } - /* Handle socket disconnection. - It is important to do it after handling G_IO_IN, because sometimes - G_IO_HUP comes together with G_IO_IN. It means that some data arrived - and then the socket has been closed. - */ - if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + putenv(xasprintf("ABRT_VERBOSE=%u", g_verbose)); + msg_prefix = xasprintf("abrt-server[%u]", getpid()); + if (optflags & OPT_s) { - log("dumpsocket: Socket client disconnected"); - client_free(client); - return FALSE; + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; } - return TRUE; -} - -/* If the status indicates failure, report it. */ -static void check_status(GIOStatus status, GError *err, const char *operation) -{ - if (status == G_IO_STATUS_NORMAL) - return; - - if (err) - { - error_msg("dumpsocket: Error while %s: %s", operation, err->message); - g_error_free(err); - } - else - error_msg("dumpsocket: Error while %s", operation); -} - -/* Initializes a new client session data structure. */ -static struct client *client_new(int socket) -{ - struct client *client = (struct client*)xzalloc(sizeof(struct client)); - - /* Get credentials for the socket client. */ - struct ucred cr; - socklen_t crlen = sizeof(struct ucred); - if (0 != getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) - perror_msg_and_die("dumpsocket: Failed to get client uid"); - if (crlen != sizeof(struct ucred)) - error_msg_and_die("dumpsocket: Failed to get client uid (crlen)"); - client->uid = cr.uid; - - client->messagebuf = g_byte_array_new(); - client->lastwrite = time(NULL); - - close_on_exec_on(socket); - - /* Create client IO channel. */ - client->channel = g_io_channel_unix_new(socket); - g_io_channel_set_close_on_unref(client->channel, TRUE); - - /* Set nonblocking access. */ - GError *err = NULL; - GIOStatus status = g_io_channel_set_flags(client->channel, G_IO_FLAG_NONBLOCK, &err); - check_status(status, err, "setting NONBLOCK flag"); - - /* Disable channel encoding to protect binary data. */ - err = NULL; - status = g_io_channel_set_encoding(client->channel, NULL, &err); - check_status(status, err, "setting encoding"); - - /* Start timer to check the client problems. */ - client->timer_id = g_timeout_add_seconds(CLIENT_CHECK_INTERVAL, client_check_cb, client); - if (!client->timer_id) - error_msg_and_die("dumpsocket: Can't add client timer"); - - /* Register client callback. */ - client->socket_id = g_io_add_watch(client->channel, - (GIOCondition)(G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL), - client_socket_cb, - client); - if (!client->socket_id) - error_msg_and_die("dumpsocket: Can't add client socket watch"); - - ++client_count; - return client; -} - -/* Callback called by glib main loop when a client newly opens ABRT's socket. */ -static gboolean server_socket_cb(GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - /* Check the limit for number of simultaneously attached clients. */ - if (client_count >= MAX_CLIENT_COUNT) + /* Set up timeout handling */ + /* Part 1 - need this to make SIGALRM interrupt syscalls + * (as opposed to restarting them): I want read syscall to be interrupted + */ + struct sigaction sa; + /* sa.sa_flags.SA_RESTART bit is clear: make signal interrupt syscalls */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = dummy_handler; /* pity, SIG_DFL won't do */ + sigaction(SIGALRM, &sa, NULL); + /* Part 2 - set the timeout per se */ + alarm(TIMEOUT); + + if (client_uid == (uid_t)-1L) { - error_msg("dumpsocket: Too many clients, refusing connection."); - /* To avoid infinite loop caused by the descriptor in "ready" state, - the callback must be disabled. - It is added back in client_free(). */ - g_source_remove(channel_cb_id); - channel_cb_id = 0; - return TRUE; + /* Get uid of the connected client */ + struct ucred cr; + socklen_t crlen = sizeof(cr); + if (0 != getsockopt(STDIN_FILENO, SOL_SOCKET, SO_PEERCRED, &cr, &crlen)) + perror_msg_and_die("getsockopt(SO_PEERCRED)"); + if (crlen != sizeof(cr)) + error_msg_and_die("%s: bad crlen %d", "getsockopt(SO_PEERCRED)", (int)crlen); + client_uid = cr.uid; } - int socket = accept(g_io_channel_unix_get_fd(source), NULL, NULL); - if (socket == -1) + /* Loop until EOF/error/timeout */ + while (1) { - perror_msg("dumpsocket: Server can not accept client"); - return TRUE; - } - - log("dumpsocket: New client connected"); - client_new(socket); - return TRUE; -} + messagebuf_data = xrealloc(messagebuf_data, messagebuf_len + INPUT_BUFFER_SIZE); + int rd = read(STDIN_FILENO, messagebuf_data + messagebuf_len, INPUT_BUFFER_SIZE); + if (rd < 0) + { + if (errno == EINTR) /* SIGALRM? */ + error_msg_and_die("Timed out"); + perror_msg_and_die("read"); + } + if (rd == 0) + break; -/* Initializes the dump socket, usually in /var/run directory - * (the path depends on compile-time configuration). - */ -void dumpsocket_init() -{ - struct sockaddr_un local; - unlink(SOCKET_FILE); /* not caring about the result */ - int socketfd = xsocket(AF_UNIX, SOCK_STREAM, 0); - close_on_exec_on(socketfd); - memset(&local, 0, sizeof(local)); - local.sun_family = AF_UNIX; - strcpy(local.sun_path, SOCKET_FILE); - xbind(socketfd, (struct sockaddr*)&local, sizeof(local)); - xlisten(socketfd, MAX_CLIENT_COUNT); - - if (chmod(SOCKET_FILE, SOCKET_PERMISSION) != 0) - perror_msg_and_die("dumpsocket: failed to chmod socket file"); - - channel = g_io_channel_unix_new(socketfd); - g_io_channel_set_close_on_unref(channel, TRUE); - channel_cb_id = g_io_add_watch(channel, - (GIOCondition)(G_IO_IN | G_IO_PRI), - server_socket_cb, - NULL); - if (!channel_cb_id) - perror_msg_and_die("dumpsocket: Can't add socket watch"); -} + VERB3 log("Received %u bytes of data", rd); + messagebuf_len += rd; + total_bytes_read += rd; + if (total_bytes_read > MAX_MESSAGE_SIZE) + error_msg_and_die("Message is too long, aborting"); -/* Releases all resources used by dumpsocket. */ -void dumpsocket_shutdown() -{ - /* Set everything to pre-initialization state. */ - if (channel) - { - /* This one is for g_io_add_watch. */ - if (channel_cb_id) - g_source_remove(channel_cb_id); - /* This one is for g_io_channel_unix_new. */ - g_io_channel_unref(channel); - channel = NULL; + while (1) + { + unsigned len = strnlen(messagebuf_data, messagebuf_len); + if (len >= messagebuf_len) + break; + /* messagebuf has at least one NUL - process the line */ + process_message(messagebuf_data); + messagebuf_len -= (len + 1); + memmove(messagebuf_data, messagebuf_data + len + 1, messagebuf_len); + } } + + VERB1 log("EOF detected, exiting"); + return 0; } diff --git a/src/daemon/dumpsocket.h b/src/daemon/dumpsocket.h deleted file mode 100644 index 88fe2446..00000000 --- a/src/daemon/dumpsocket.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright (C) 2010 ABRT team - - 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., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#ifndef ABRT_DUMPSOCKET_H -#define ABRT_DUMPSOCKET_H - -/* -Unix socket in ABRT daemon for creating new dump directories. - -Why to use socket for creating dump dirs? Security. When a Python -script throws unexpected exception, ABRT handler catches it, running -as a part of that broken Python application. The application is running -with certain SELinux privileges, for example it can not execute other -programs, or to create files in /var/cache or anything else required -to properly fill a dump directory. Adding these privileges to every -application would weaken the security. -The most suitable solution is for the Python application -to open a socket where ABRT daemon is listening, write all relevant -data to that socket, and close it. ABRT daemon handles the rest. - -** Protocol - -Initializing new dump: -open /var/run/abrt.socket - -Providing dump data (hook writes to the socket): --> "PID=" - number 0 - PID_MAX (/proc/sys/kernel/pid_max) - \0 --> "EXECUTABLE=" - string (maximum length ~MAX_PATH) - \0 --> "BACKTRACE=" - string (maximum length 1 MB) - \0 --> "ANALYZER=" - string (maximum length 100 bytes) - \0 --> "BASENAME=" - string (maximum length 100 bytes, no slashes) - \0 --> "REASON=" - string (maximum length 512 bytes) - \0 - -Finalizing dump creation: --> "DONE" - \0 -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initializes the dump socket, usually in /var/run directory - * (the path depends on compile-time configuration). - */ -extern void dumpsocket_init(); - -/* Releases all resources used by dumpsocket. */ -extern void dumpsocket_shutdown(); - -#ifdef __cplusplus -} -#endif - -#endif |