diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | include/net.h | 10 | ||||
-rw-r--r-- | include/purple.h | 27 | ||||
-rw-r--r-- | include/settings.h | 15 | ||||
-rw-r--r-- | meson.build | 6 | ||||
-rw-r--r-- | src/daemon/main.c | 23 | ||||
-rw-r--r-- | src/daemon/meson.build | 2 | ||||
-rw-r--r-- | src/daemon/net.c | 19 | ||||
-rw-r--r-- | src/daemon/purple.c | 254 | ||||
-rw-r--r-- | src/daemon/purple_private.h | 56 | ||||
-rw-r--r-- | src/daemon/settings.c | 168 | ||||
-rw-r--r-- | src/daemon/settings_private.h | 37 |
12 files changed, 594 insertions, 25 deletions
@@ -2,3 +2,5 @@ *.bin a.out build +*.swp +compile_commands.json diff --git a/include/net.h b/include/net.h index 59071a5..e4ca0a6 100644 --- a/include/net.h +++ b/include/net.h @@ -22,7 +22,7 @@ #ifndef USURPATION_NET_H_INCLUDED #define USURPATION_NET_H_INCLUDED -#define dgsize 512 +#define MTU 1500 enum response { ERROR = -1, @@ -34,7 +34,7 @@ enum response { }; /** - * Initialises connection with daemon and returns a network descriptor. + * Initialises a listening socket and returns the associated network descriptor. */ int net_init(const unsigned short int port); @@ -45,9 +45,9 @@ int net_close(int nd); /** * Get last data received from connection associated with network descriptor. - * Function mallocates a buffer for data received from packet. Don't forget to - * free the buffer. + * If the pointer pointed by data is NULL, a buffer is allocated by the function + * and needs to be free()'d later. Otherwise, the supplied buffer is reused. */ -int net_getlastdata(int nd, char * const data); +int net_getlastdata(int nd, char ** const data); #endif /* USURPATION_NET_H_INCLUDED */ diff --git a/include/purple.h b/include/purple.h new file mode 100644 index 0000000..1fa8a91 --- /dev/null +++ b/include/purple.h @@ -0,0 +1,27 @@ +/* + * Usurpation – server daemon IM module, private header + * + * Copyright (C) 2019 Gediminas Jakutis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef USURPATION_PURPLE_H +#define USURPATION_PURPLE_H + +int purple_init(void); +void purple_close(void); + +#endif /* USURPATION_PURPLE_H */ diff --git a/include/settings.h b/include/settings.h index 72addd0..e2e9644 100644 --- a/include/settings.h +++ b/include/settings.h @@ -22,9 +22,22 @@ #ifndef USURPATION_SETTINGS_H #define USURPATION_SETTINGS_H +enum verbosity { + SILENT = 0, + ERR = 1, + WARN, + DEBUG, + ALL +}; + void settings_init(void); int setting_detach(void); -unsigned short int setting_port(void); int setting_verbose(void); +unsigned short int setting_port(void); +char *setting_progname(void); +char *setting_im_user(void); +char *setting_im_password(void); +char *setting_im_proto(void); +void settings_cleanup(void); #endif /* USURPATION_SETTINGS_H */ diff --git a/meson.build b/meson.build index 69bcf7c..4efb996 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ project('usurpation', 'c', license : 'LGPL2.1', - default_options : ['c_std=gnu11', 'buildtype=release'], + default_options : ['c_std=gnu11', 'buildtype=debug', 'warning_level=3'], meson_version : '>=0.49.0') -deps = [dependency('threads'), dependency('rin')] +deps = [dependency('threads'), dependency('rin'), dependency('glib-2.0', version : '>= 2.14'), dependency('purple')] progname = 'usurpd' resource_dir = join_paths(get_option('datadir'), progname) @@ -24,7 +24,7 @@ elif get_option('buildtype') == 'debugoptimized' verb = '3' endif -daemon = executable(progname, d_sources, version, include_directories : inc, install : true, dependencies : deps, extra_files : d_conf, c_args : '-DUSURP_VERBOSITY=' + verb) +daemon = executable(progname, d_sources, version, include_directories : inc, install : true, dependencies : deps, extra_files : d_conf, c_args : ['-DUSURP_VERBOSITY=' + verb, '-DSYSCONFDIR=' + get_option('sysconfdir')]) #install_data(extra, install_dir : resource_dir) install_data(d_conf, install_dir : get_option('sysconfdir')) diff --git a/src/daemon/main.c b/src/daemon/main.c index 302a9ce..94e59fd 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -21,9 +21,17 @@ #include <unistd.h> #include <time.h> +#include <stdlib.h> #include <stdio.h> #include "settings.h" #include "net.h" +#include "purple.h" + +static struct _state { + int nd; +} _progstate; + +void cleanup(void); /* the logic is a placeholder right now */ int main(int argc, char **argv) @@ -33,8 +41,12 @@ int main(int argc, char **argv) printf("Usurpation daemon version %s starting\n", version); + atexit(cleanup); settings_init(); - net_init(setting_port()); /* TODO: get port from settings. */ + _progstate.nd = net_init(setting_port()); + if (purple_init() && setting_verbose()) { + fprintf(stderr, "libpurple initialization failed\n"); + } /* by default and if running by as a system service, the init system * needs to keep control of the process and thus only detach if @@ -48,4 +60,13 @@ int main(int argc, char **argv) /* noop */ } + + return 0; +} + +void cleanup(void) +{ + purple_close(); + net_close(_progstate.nd); + settings_cleanup(); } diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 44fa299..aa0aa75 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -2,7 +2,9 @@ d_filenames = [ 'main.c', 'settings.c', 'net.c', + 'purple.c', 'settings_private.h', + 'purple_private.h', ] d_conf_filenames = [ diff --git a/src/daemon/net.c b/src/daemon/net.c index 3b4f022..9180344 100644 --- a/src/daemon/net.c +++ b/src/daemon/net.c @@ -94,9 +94,9 @@ int net_init(const unsigned short int port) state[i].lastreply.tv_sec -= 5; state[i].nd = i + 1; state[i].port = port; - state[i].data = malloc(dgsize); - memset(state[i].data, 0, dgsize); - state[i].bufsize = dgsize; + state[i].data = malloc(MTU); + memset(state[i].data, 0, MTU); + state[i].bufsize = MTU; state[i].status = NONEWDATA; pthread_mutex_init(&state[i].datamutex, NULL); @@ -136,16 +136,25 @@ int net_close(int nd) return ret; } -int net_getlastdata(int nd, char * const data) +int net_getlastdata(int nd, char ** const data) { int ret; if (nd > count || nd < 1 || state[--nd].available) { ret = ERROR; } else if (!(ret = state[nd].status)) { - memcpy(data, state[nd].data, dgsize); + if (!data) { + ret = ERROR; + goto out; + } else if (!(*data)) { + *data = malloc(MTU); + } + + memset(*data, 0, MTU); + memcpy(*data, state[nd].data, MTU); } +out: return ret; } diff --git a/src/daemon/purple.c b/src/daemon/purple.c new file mode 100644 index 0000000..882ea61 --- /dev/null +++ b/src/daemon/purple.c @@ -0,0 +1,254 @@ +/* + * Usurpation – server daemon main logic + * + * Copyright (C) 2019 Gediminas Jakutis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <libpurple/purple.h> +#include <glib.h> +#include <signal.h> +#include <errno.h> +#include <unistd.h> +#include "purple_private.h" +#include "settings.h" + +static PurpleConversationUiOps conv_uiops = +{ + NULL, /* create_conversation */ + NULL, /* destroy_conversation */ + NULL, /* write_chat */ + NULL, /* write_im */ + NULL, /* write_conv */ + NULL, /* chat_add_users */ + NULL, /* chat_rename_user */ + NULL, /* chat_remove_users */ + NULL, /* chat_update_user */ + NULL, /* present */ + NULL, /* has_focus */ + NULL, /* send_confirm */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static PurpleEventLoopUiOps eventloops = +{ + g_timeout_add, + g_source_remove, + glib_input_add, + g_source_remove, + NULL, + g_timeout_add_seconds, + + /* padding */ + NULL, + NULL, + NULL +}; + +static gboolean purple_glib_io(GIOChannel *source, GIOCondition cond, gpointer data) +{ + struct glib_io *bloc = data; + PurpleInputCondition pio_cond = 0; + + if (cond & (G_IO_IN | G_IO_HUP | G_IO_ERR)) { + pio_cond |= PURPLE_INPUT_READ; + } + + if (cond & (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + pio_cond |= PURPLE_INPUT_WRITE; + } + + bloc->func(bloc->data, g_io_channel_unix_get_fd(source), pio_cond); + + return 1; +} + +static guint glib_input_add(gint fd, PurpleInputCondition pio_cond, PurpleInputFunction func, gpointer data) +{ + struct glib_io *bloc; + GIOChannel *channel; + GIOCondition cond = 0; + + bloc = g_new0(struct glib_io, 1); + bloc->func = func; + bloc->data = data; + + if (pio_cond & PURPLE_INPUT_READ) { + cond |= G_IO_IN | G_IO_HUP | G_IO_ERR; + } + + if (pio_cond & PURPLE_INPUT_WRITE) { + cond |= G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + } + + channel = g_io_channel_unix_new(fd); + bloc->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond, purple_glib_io, bloc, g_free); + g_io_channel_unref(channel); + + return bloc->result; +} + +/* TODO: semi-stub */ +static void iface_init(void) +{ + purple_conversations_set_ui_ops(&conv_uiops); +} + +static void pthread_mutex_unlock_thunk(void *arg) +{ + pthread_mutex_t *mutex = arg; + + (void) pthread_mutex_unlock(mutex); +} + +static void purple_account_destroy_thunk(void *arg) +{ + PurpleAccount *account = arg; + + purple_account_destroy(account); +} + +static void purple_core_destroy(void *arg) +{ + (void) arg; + + purple_timeout_add(0, purple_core_quit_cb, NULL); +} + +static void g_main_loop_unref_thunk(void *arg) +{ + GMainLoop *loop = arg; + + g_main_loop_unref(loop); +} + +static void g_main_loop_quit_thunk(void *arg) +{ + GMainLoop *loop = arg; + + g_main_loop_quit(loop); +} + + +static void *purple_spawn(void *disregard) +{ + GMainLoop *loop; + char *progname = NULL; + char *user = NULL; + char *password = NULL; + char *proto = NULL; + + (void) disregard; + + pthread_cleanup_push(pthread_mutex_unlock_thunk, &state.mutex); + + progname = setting_progname(); + pthread_cleanup_push(free, progname); + + user = setting_im_user(); + pthread_cleanup_push(free, user); + + password = setting_im_password(); + pthread_cleanup_push(free, password); + + proto = setting_im_proto(); + pthread_cleanup_push(free, proto); + + loop = g_main_loop_new(NULL, FALSE); + pthread_cleanup_push(g_main_loop_unref_thunk, loop); + + /* avoid an unholy army of libpurple's DNS resolver process zombies */ + signal(SIGCHLD, SIG_IGN); + + /* do not actually save anything about the account through libpurple */ + purple_util_set_user_dir("/dev/full"); + + /* + * TODO: + * we do allow verbosity here yet + * use this once we do: + * purple_debug_set_enabled(setting_verbose()); + */ + purple_debug_set_enabled(0); + + purple_core_set_ui_ops(&core_uiops); + purple_eventloop_set_ui_ops(&eventloops); + purple_core_init(progname); + purple_prefs_load(); + + pthread_cleanup_push(purple_core_destroy, NULL); + + state.account = purple_account_new(user, proto); + purple_account_set_password(state.account, password); + purple_account_set_enabled(state.account, progname, TRUE); + + pthread_cleanup_push(purple_account_destroy_thunk, state.account); + + g_main_loop_run(loop); + pthread_cleanup_push(g_main_loop_quit_thunk, loop); + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + pthread_exit(NULL); +} + +/* + * The following functions are exported and thus, can be wildly called by other + * modules and thus, are guarded by a mutex to make kind of a singleton, as it + * is NOT safe to access libpurple from more than one context / thread while + * using the same configuration (read: confdir). + */ + +int purple_init(void) +{ + int ret = 0; + + /* aquire the """singleton""" mutex */ + if (pthread_mutex_trylock(&state.mutex)) { + /* TODO: use proper error numbers */ + ret = 1; + } else { + if ((ret = pthread_create(&state.purple, NULL, purple_spawn, NULL))) { + /* thread creation failed, release the mutex */ + pthread_mutex_unlock(&state.mutex); + } + } + + return ret; +} + +void purple_close(void) +{ + + if (pthread_mutex_trylock(&state.mutex) == EBUSY) { + pthread_cancel(state.purple); + pthread_join(state.purple, NULL); + } +} diff --git a/src/daemon/purple_private.h b/src/daemon/purple_private.h new file mode 100644 index 0000000..ff50139 --- /dev/null +++ b/src/daemon/purple_private.h @@ -0,0 +1,56 @@ +/* + * Usurpation – server daemon IM module, private header + * + * Copyright (C) 2019 Gediminas Jakutis + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; version 2.1 + * of the License. + * + * 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef USURPATION_PURPLE_PRIVATE_H +#define USURPATION_PURPLE_PRIVATE_H + +#include <pthread.h> + +static void iface_init(void); + +static struct state { + pthread_mutex_t mutex; + pthread_t purple; + PurpleAccount *account; +} state = {PTHREAD_MUTEX_INITIALIZER, 0, 0}; + +struct glib_io { + PurpleInputFunction func; + guint result; + void *data; +}; + +static PurpleCoreUiOps core_uiops = { + NULL, + NULL, + iface_init, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +static gboolean purple_glib_io(GIOChannel *source, GIOCondition cond, gpointer data); +static guint glib_input_add(gint fd, PurpleInputCondition pio_cond, PurpleInputFunction function, gpointer data); +static void *purple_spawn(void *disregard); +static void pthread_mutex_unlock_thunk(void *arg); + +#endif /* USURPATION_PURPLE_PRIVATE_H */ diff --git a/src/daemon/settings.c b/src/daemon/settings.c index bac1c3b..5314fef 100644 --- a/src/daemon/settings.c +++ b/src/daemon/settings.c @@ -19,6 +19,16 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +#define _GNU_SOURCE + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> #include "settings.h" #include "settings_private.h" @@ -30,7 +40,16 @@ void settings_init(void) { unset_flag(flag_daemonize); settings.port = 6996; - settings.verbosity = USURP_VERBOSITY; + settings.verboselevel = USURP_VERBOSITY; + settings.progname = strdup(program_invocation_short_name); + settings.im_user = strdup("user"); + settings.im_password = strdup("password"); + settings.im_proto = strdup("prpl-irc"); +#define macro2str(a) _macro2str(a) +#define _macro2str(a) #a + setting_readconf("/" macro2str(SYSCONFDIR) "/usurpation.conf"); +#undef macro2str +#undef _macro2str } int setting_detach(void) @@ -40,7 +59,7 @@ int setting_detach(void) int setting_verbose(void) { - return settings.verbosity; + return settings.verboselevel; } unsigned short int setting_port(void) @@ -48,8 +67,28 @@ unsigned short int setting_port(void) return settings.port; } +char *setting_progname(void) +{ + return strdup(settings.progname); +} + +char *setting_im_user(void) +{ + return strdup(settings.im_user); +} + +char *setting_im_password(void) +{ + return strdup(settings.im_password); +} + +char *setting_im_proto(void) +{ + return strdup(settings.im_proto); +} + /* could be a one-liner, but let's make the logic more obvious */ -int test_flag(unsigned int flag) +static int test_flag(unsigned int flag) { int ret; @@ -59,12 +98,131 @@ int test_flag(unsigned int flag) return ret; } -void set_flag(unsigned int flag) +static void set_flag(unsigned int flag) { settings.flags |= flag; } -void unset_flag(unsigned int flag) +static void unset_flag(unsigned int flag) { settings.flags &= ~flag; } + +/* TODO: semi-stub! */ +static int setting_readconf(const char * const path) +{ + struct stat filestat; + char *buf; + char *line; + char *entry; + char *value; + char *line_state; + char *token_state; + int fd; + + + if ((fd = open(path, O_RDONLY)) == -1) { + if (USURP_VERBOSITY >= 2) { + perror("error while opening the configuration file"); + return 1; + } + } + + fstat(fd, &filestat); + buf = malloc(filestat.st_size+1); + buf[filestat.st_size] = 0; + if (read(fd, buf, filestat.st_size) != filestat.st_size) { + if (USURP_VERBOSITY >= 2) { + perror("partial read of configuration file detected"); + free(buf); + return 1; + } + } + + line = strtok_r(buf, "\n", &line_state); + + while (line) { + if (line[0] != '#') { + entry = strtok_r(line, "=", &token_state); + value = strtok_r(NULL, "=", &token_state); + setting_handle_config_entry(entry, value); + } + + line = strtok_r(NULL, "\n", &line_state); + } + + free(buf); + + return 0; +} + +static int setting_handle_config_entry(const char * const entry, const char * const value) +{ + size_t i; + + for (i = 0; i < sizeof(ent_table)/sizeof(*ent_table); ++i) { + if (!(strcmp(entry, ent_table[i].name))) { + ent_table[i].set(value); + return 0; + } + } + + return 1; +} + +static void set_daemonize(const void * const arg) +{ + const char * const a = arg; + + atoi(a) ? set_flag(flag_daemonize) : unset_flag(flag_daemonize); +} + +static void set_port(const void * const arg) +{ + const char * const a = arg; + + settings.port = strtoul(a, NULL, 0); +} + +static void set_verbosity(const void * const arg) +{ + const char * const a = arg; + + settings.verboselevel = strtol(a, NULL, 0); +} + +static void set_progname(const void * const arg) +{ + const char * const a = arg; + free(settings.progname); + settings.progname = strdup(a); +} + +static void set_im_user(const void * const arg) +{ + const char * const a = arg; + free(settings.im_user); + settings.im_user = strdup(a); +} + +static void set_im_password(const void * const arg) +{ + const char * const a = arg; + free(settings.im_password); + settings.im_password = strdup(a); +} + +static void set_im_proto(const void * const arg) +{ + const char * const a = arg; + free(settings.im_proto); + settings.im_proto = strdup(a); +} + +void settings_cleanup(void) +{ + free(settings.progname); + free(settings.im_user); + free(settings.im_password); + free(settings.im_proto); +} diff --git a/src/daemon/settings_private.h b/src/daemon/settings_private.h index 7839cb9..69ed25a 100644 --- a/src/daemon/settings_private.h +++ b/src/daemon/settings_private.h @@ -23,7 +23,7 @@ #define USURPATION_SETTINGS_PRIVATE_H #ifndef USURP_VERBOSITY - #define USURP_VERBOSITY 1 + #define USURP_VERBOSITY ERR #endif static const unsigned int flag_daemonize = 1 << 0; /* 1st bit */ @@ -31,11 +31,38 @@ static const unsigned int flag_daemonize = 1 << 0; /* 1st bit */ static struct settings { unsigned int flags; unsigned short int port; - int verbosity; + enum verbosity verboselevel; + char *progname; + char *im_user; + char *im_password; + char *im_proto; } settings; -int test_flag(unsigned int flag); -void set_flag(unsigned int flag); -void unset_flag(unsigned int flag); +struct entry_description { + const char * const name; + void (*set)(const void * const); +}; + +static int test_flag(unsigned int flag); +static void set_flag(unsigned int flag); +static void unset_flag(unsigned int flag); +static int setting_readconf(const char * const path); +static int setting_handle_config_entry(const char * const entry, const char * const value); + +static void set_daemonize(const void * const arg); +static void set_port(const void * const arg); +static void set_verbosity(const void * const arg); +static void set_progname(const void * const arg); +static void set_im_user(const void * const arg); +static void set_im_password(const void * const arg); +static void set_im_proto(const void * const arg); + +struct entry_description ent_table[] = {{ "daemonize", set_daemonize }, + { "port", set_port }, + { "verbosity", set_verbosity }, + { "progname", set_progname }, + { "user", set_im_user}, + { "password", set_im_password }, + { "proto", set_im_proto }}; #endif /* USURPATION_SETTINGS_PRIVATE_H */ |