summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/daemon/Daemon.cpp176
-rw-r--r--src/daemon/Makefile.am20
-rw-r--r--src/daemon/dumpsocket.cpp582
-rw-r--r--src/daemon/dumpsocket.h81
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