diff --git a/README b/README index 040ebfe..b52d32b 100644 --- a/README +++ b/README @@ -59,10 +59,6 @@ name of the application. Then the booster process waits for a connection from the invoker with the information about which application should be launched. -Before launching application boosters check that the calling process is allowed -to invoke applications. This requires CAP_SYS_PTRACE. All boosters must have -that set to function. - Contributors ============================== diff --git a/rpm/mapplauncherd.spec b/rpm/mapplauncherd.spec index 477e1b1..0765f9d 100644 --- a/rpm/mapplauncherd.spec +++ b/rpm/mapplauncherd.spec @@ -8,13 +8,13 @@ Source0: %{name}-%{version}.tar.bz2 Source1: booster-cgroup-mount.service Requires: systemd-user-session-targets Requires(post): /sbin/ldconfig -Requires(post): /usr/sbin/setcap Requires(postun): /sbin/ldconfig Requires(pre): sailfish-setup BuildRequires: pkgconfig(libshadowutils) BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(libcap) +BuildRequires: pkgconfig(glib-2.0) BuildRequires: cmake Provides: meegotouch-applauncherd > 3.0.3 Obsoletes: meegotouch-applauncherd <= 3.0.3 @@ -75,7 +75,6 @@ install -D -m 0755 scripts/booster-cgroup-mount %{buildroot}/usr/lib/startup/boo %post /sbin/ldconfig -/usr/sbin/setcap cap_sys_ptrace+pe %{_libexecdir}/mapplauncherd/booster-generic || : %postun -p /sbin/ldconfig @@ -87,7 +86,8 @@ install -D -m 0755 scripts/booster-cgroup-mount %{buildroot}/usr/lib/startup/boo %{_bindir}/single-instance %{_libdir}/libapplauncherd.so* %attr(2755, root, privileged) %{_libexecdir}/mapplauncherd/booster-generic -%{_userunitdir}//booster-generic.service +%{_userunitdir}/booster-generic.service +%{_userunitdir}/booster-generic@.service %{_userunitdir}/user-session.target.wants/booster-generic.service %files devel diff --git a/src/booster-generic/CMakeLists.txt b/src/booster-generic/CMakeLists.txt index c26b867..bda181a 100644 --- a/src/booster-generic/CMakeLists.txt +++ b/src/booster-generic/CMakeLists.txt @@ -21,4 +21,5 @@ install(TARGETS booster-generic DESTINATION ${CMAKE_INSTALL_FULL_LIBEXECDIR}/map if(INSTALL_SYSTEMD_UNITS) install(FILES booster-generic.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/) + install(FILES booster-generic@.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/user/) endif() diff --git a/src/booster-generic/booster-generic@.service b/src/booster-generic/booster-generic@.service new file mode 100644 index 0000000..161bd7d --- /dev/null +++ b/src/booster-generic/booster-generic@.service @@ -0,0 +1,11 @@ +[Unit] +Description=Generic application launch booster (sandboxed) +Requires=dbus.socket booster-silica-session.path lipstick.service +After=dbus.service booster-silica-session.path lipstick.service + +[Service] +Type=simple +ExecStart=/usr/bin/invoker --type=silica-session -- /usr/bin/sailjail --profile=%i -- /usr/libexec/mapplauncherd/booster-generic --application=%i +Restart=always +RestartSec=1 +OOMScoreAdjust=-250 diff --git a/src/common/report.c b/src/common/report.c index bf6b11a..ea7f4c4 100644 --- a/src/common/report.c +++ b/src/common/report.c @@ -17,16 +17,39 @@ ** ****************************************************************************/ +#define _GNU_SOURCE + #include #include -#include -#include +#include #include #include +#include +#include #include "report.h" static enum report_output output = report_guess; +static enum report_type level = report_warning; + +static const char *progname(void) +{ + static const char *name = NULL; + if (!name) { + char buff[PATH_MAX]; + char path[PATH_MAX]; + snprintf(path, sizeof path, "/proc/%d/exe", (int)getpid()); + int n = readlink(path, buff, sizeof buff); + if (n > 0 && n < (int)sizeof buff) { + buff[n] = 0; + /* Note: this is intentionally never released */ + name = strdup(basename(buff)); + } + if (!name) + name = "unknown"; + } + return name; +} static char *strip(char *str) { @@ -49,64 +72,94 @@ static char *strip(char *str) return str; } -void report_set_output(enum report_output new_output) +extern enum report_type report_get_type(void) { - if (output == new_output) - return; + return level; +} - if (output == report_syslog) - closelog(); +static enum report_type normalize_type(enum report_type type) +{ + if (type < report_minimum) + return report_minimum; + if (type > report_maximum) + return report_maximum; + return type; +} - if (new_output == report_syslog) - openlog(PROG_NAME_INVOKER, LOG_PID, LOG_DAEMON); +extern void report_set_type(enum report_type type) +{ + level = normalize_type(type); +} - output = new_output; +enum report_output report_get_output(void) +{ + if (output == report_guess) + report_set_output(isatty(STDIN_FILENO) ? report_console : report_syslog); + return output; } -static void vreport(enum report_type type, const char *msg, va_list arg) +void report_set_output(enum report_output new_output) { - char str[400]; - char *str_type = ""; - int log_type; + if (output != new_output) { + if (output == report_syslog) + closelog(); + + output = new_output; + + if (output == report_syslog) + openlog(PROG_NAME_INVOKER, LOG_PID, LOG_DAEMON); + } +} + +void vreport(enum report_type type, const char *msg, va_list arg) +{ + /* Any errors during logging must not change errno */ + int saved = errno; + + if ((type = normalize_type(type)) > level) + goto EXIT; - switch (type) - { + char *str_type = ""; + switch (type) { case report_debug: - log_type = LOG_DEBUG; + str_type = "debug: "; break; - default: case report_info: - log_type = LOG_INFO; + str_type = "info: "; + break; + case report_notice: + str_type = "notice: "; break; case report_warning: str_type = "warning: "; - log_type = LOG_WARNING; break; case report_error: str_type = "error: "; - log_type = LOG_ERR; break; case report_fatal: str_type = "died: "; - log_type = LOG_ERR; + break; + default: break; } + char str[400]; vsnprintf(str, sizeof(str), msg, arg); - if (output == report_guess) { - if (isatty(STDIN_FILENO)) - output = report_console; - else - output = report_syslog; - } - - if (output == report_console) { - fprintf(stderr, "%s: %s%s\n", PROG_NAME_INVOKER, str_type, strip(str)); + switch (report_get_output()) { + case report_console: + fprintf(stderr, "%s: %s%s\n", progname(), str_type, strip(str)); fflush(stderr); - } else if (output == report_syslog) { - syslog(log_type, "%s%s", str_type, str); + break; + case report_syslog: + syslog(type, "%s%s", str_type, str); + break; + default: + break; } + +EXIT: + errno = saved; } void report(enum report_type type, const char *msg, ...) diff --git a/src/common/report.h b/src/common/report.h index a6f904f..6490824 100644 --- a/src/common/report.h +++ b/src/common/report.h @@ -22,6 +22,13 @@ #ifndef REPORT_H #define REPORT_H +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + #ifdef __GNUC__ #define ATTR_NORET __attribute__((noreturn)) #else @@ -32,31 +39,39 @@ enum report_output { report_guess, report_console, report_syslog, - report_none + report_none, }; enum report_type { - report_debug, - report_info, - report_warning, - report_error, - report_fatal + report_fatal = LOG_CRIT, + report_error = LOG_ERR, + report_warning = LOG_WARNING, + report_notice = LOG_NOTICE, + report_info = LOG_INFO, + report_debug = LOG_DEBUG, + report_default = report_warning, + report_minimum = report_fatal, + report_maximum = report_debug, }; +extern enum report_output report_get_output(void); extern void report_set_output(enum report_output new_output); +extern enum report_type report_get_type(void); +extern void report_set_type(enum report_type type); extern void report(enum report_type type, const char *msg, ...) __attribute__((format(printf, 2, 3))); +extern void vreport(enum report_type type, const char *msg, va_list arg); +extern void ATTR_NORET die(int status, const char *msg, ...); -#ifndef DEBUG_LOGGING_DISABLED -#define debug(msg, ...) report(report_debug, msg, ##__VA_ARGS__) -#else -#define debug(...) do {} while (0) -#endif - -#define info(msg, ...) report(report_info, msg, ##__VA_ARGS__) -#define warning(msg, ...) report(report_warning, msg, ##__VA_ARGS__) #define error(msg, ...) report(report_error, msg, ##__VA_ARGS__) +#define warning(msg, ...) report(report_warning, msg, ##__VA_ARGS__) +#define notice(msg, ...) report(report_notice, msg, ##__VA_ARGS__) +#define info(msg, ...) report(report_info, msg, ##__VA_ARGS__) +#define debug(msg, ...) report(report_debug, msg, ##__VA_ARGS__) -extern void ATTR_NORET die(int status, const char *msg, ...); +#define HERE warning("%s:%d: %s() ...", __FILE__, __LINE__, __func__); +#ifdef __cplusplus +}; #endif +#endif // REPORT_H diff --git a/src/common/sailjail.c b/src/common/sailjail.c new file mode 100644 index 0000000..cad0674 --- /dev/null +++ b/src/common/sailjail.c @@ -0,0 +1,559 @@ +#include "sailjail.h" +#include "report.h" + +#include +#include + +#include + +/* Standard desktop properties */ +#define DESKTOP_SECTION "Desktop Entry" +#define DESKTOP_KEY_NAME "Name" +#define DESKTOP_KEY_TYPE "Type" +#define DESKTOP_KEY_ICON "Icon" +#define DESKTOP_KEY_EXEC "Exec" +#define DESKTOP_KEY_NO_DISPLAY "NoDisplay" + +/* Maemo desktop properties */ +#define MAEMO_SECTION "Desktop Entry" +#define MAEMO_KEY_SERVICE "X-Maemo-Service" +#define MAEMO_KEY_OBJECT "X-Maemo-Object-Path" +#define MAEMO_KEY_METHOD "X-Maemo-Method" + +/* Sailjail desktop properties */ +#define SAILJAIL_SECTION_PRIMARY "X-Sailjail" +#define SAILJAIL_SECTION_SECONDARY "Sailjail" +#define SAILJAIL_KEY_ORGANIZATION_NAME "OrganizationName" +#define SAILJAIL_KEY_APPLICATION_NAME "ApplicationName" +#define SAILJAIL_KEY_PERMISSIONS "Permissions" +#define NEMO_KEY_APPLICATION_TYPE "X-Nemo-Application-Type" +#define NEMO_KEY_SINGLE_INSTANCE "X-Nemo-Single-Instance" +#define MAEMO_KEY_FIXED_ARGS "X-Maemo-Fixed-Args" +#define OSSO_KEY_SERVICE "X-Osso-Service" + +/* Sailjaild D-Bus service */ +#define PERMISSIONMGR_SERVICE "org.sailfishos.sailjaild1" +#define PERMISSIONMGR_INTERFACE "org.sailfishos.sailjaild1" +#define PERMISSIONMGR_OBJECT "/org/sailfishos/sailjaild1" +#define PERMISSIONMGR_METHOD_PROMPT "PromptLaunchPermissions" +#define PERMISSIONMGR_METHOD_QUERY "QueryLaunchPermissions" +#define PERMISSIONMGR_METHOD_GET_APPLICATIONS "GetApplications" +#define PERMISSIONMGR_METHOD_GET_APPINFO "GetAppInfo" +#define PERMISSIONMGR_METHOD_GET_LICENSE "GetLicenseAgreed" +#define PERMISSIONMGR_METHOD_SET_LICENSE "SetLicenseAgreed" +#define PERMISSIONMGR_METHOD_GET_LAUNCHABLE "GetLaunchAllowed" +#define PERMISSIONMGR_METHOD_SET_LAUNCHABLE "SetLaunchAllowed" +#define PERMISSIONMGR_METHOD_GET_GRANTED "GetGrantedPermissions" +#define PERMISSIONMGR_METHOD_SET_GRANTED "SetGrantedPermissions" +#define PERMISSIONMGR_SIGNAL_APP_ADDED "ApplicationAdded" +#define PERMISSIONMGR_SIGNAL_APP_CHANGED "ApplicationChanged" +#define PERMISSIONMGR_SIGNAL_APP_REMOVED "ApplicationRemoved" + +static DBusConnection * +sailjail_connect_bus(void) +{ + DBusError err = DBUS_ERROR_INIT; + DBusConnection *con = 0; + + if (!(con = dbus_bus_get_private(DBUS_BUS_SYSTEM, &err))) { + error("system bus connect failed: %s: %s", + err.name, err.message); + } else { + warning("PRIVATE CONNECTION %p CONNECTED", con); + dbus_connection_set_exit_on_disconnect(con, false); + } + + dbus_error_free(&err); + return con; +} + +static void +sailjail_disconnect_bus(DBusConnection *con) +{ + if (con) { + warning("PRIVATE CONNECTION %p DISCONNECTED", con); + dbus_connection_close(con); + dbus_connection_unref(con); + } +} + +static GVariant * +appinfo_variant(GHashTable *info, const char *key) +{ + return g_hash_table_lookup(info, key); +} + +static const char * +appinfo_string(GHashTable *info, const char *key) +{ + const char *value = NULL; + GVariant *variant = appinfo_variant(info, key); + if (variant) { + const GVariantType *type = g_variant_get_type(variant); + if (g_variant_type_equal(type, G_VARIANT_TYPE_STRING)) + value = g_variant_get_string(variant, NULL); + } + return value; +} + +static const char ** +appinfo_strv(GHashTable *info, const char *key) +{ + const char **value = NULL; + GVariant *variant = appinfo_variant(info, key); + if (variant) { + const GVariantType *type = g_variant_get_type(variant); + if (g_variant_type_equal(type, G_VARIANT_TYPE("as"))) + value = g_variant_get_strv(variant, NULL); + } + return value; +} + +static bool +iter_at(DBusMessageIter *iter, int type) +{ + int have = dbus_message_iter_get_arg_type(iter); + if (have != type && type != 0) + warning("Expected: %c got: %c", type ?: '?', have ?: '?'); + return have == type; +} + +static GHashTable * +sailjail_application_info(DBusConnection *con, const char *desktop) +{ + GHashTable *info = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref); + DBusError err = DBUS_ERROR_INIT; + DBusMessage *req = NULL; + DBusMessage *rsp = NULL; + + if (!(req = dbus_message_new_method_call(PERMISSIONMGR_SERVICE, + PERMISSIONMGR_OBJECT, + PERMISSIONMGR_INTERFACE, + PERMISSIONMGR_METHOD_GET_APPINFO))) { + error("failed to create dbus method call"); + goto EXIT; + } + + if (!dbus_message_append_args(req, DBUS_TYPE_STRING, &desktop, DBUS_TYPE_INVALID)) { + error("failed to add method call args"); + goto EXIT; + } + + if (!(rsp = dbus_connection_send_with_reply_and_block(con, req, DBUS_TIMEOUT_INFINITE, &err))) { + error("method call failed: %s: %s", err.name, err.message); + goto EXIT; + } + + if (dbus_set_error_from_message(&err, rsp)) { + error("error reply received: %s: %s", err.name, err.message); + goto EXIT; + } + + DBusMessageIter bodyIter; + if (!dbus_message_iter_init(rsp, &bodyIter)) { + error("empty reply received"); + goto EXIT; + } + + if (!iter_at(&bodyIter, DBUS_TYPE_ARRAY)) { + error("reply is not an array"); + goto EXIT; + } + + DBusMessageIter ArrayIter; + dbus_message_iter_recurse(&bodyIter, &ArrayIter); + while (!iter_at(&ArrayIter, DBUS_TYPE_INVALID)) { + if (!iter_at(&ArrayIter, DBUS_TYPE_DICT_ENTRY)) { + error("reply is not an array of dict entries"); + goto EXIT; + } + DBusMessageIter dictIter; + dbus_message_iter_recurse(&ArrayIter, &dictIter); + dbus_message_iter_next(&ArrayIter); + if (!iter_at(&dictIter, DBUS_TYPE_STRING)) { + error("key is not a string"); + goto EXIT; + } + const char *key = NULL; + dbus_message_iter_get_basic(&dictIter, &key); + dbus_message_iter_next(&dictIter); + if (!iter_at(&dictIter, DBUS_TYPE_VARIANT)) { + error("values is not a variant"); + goto EXIT; + } + DBusMessageIter variantIter; + dbus_message_iter_recurse(&dictIter, &variantIter); + dbus_message_iter_next(&dictIter); + DBusBasicValue value; + switch (dbus_message_iter_get_arg_type(&variantIter)) { + case DBUS_TYPE_INT32: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = int32:%d", key, value.i32); + g_hash_table_insert(info, g_strdup(key), g_variant_new_int32(value.i32)); + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = uint32:%d", key, value.u32); + g_hash_table_insert(info, g_strdup(key), g_variant_new_uint32(value.u32)); + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = bool:%d", key, value.bool_val); + g_hash_table_insert(info, g_strdup(key), g_variant_new_boolean(value.bool_val)); + break; + case DBUS_TYPE_STRING: + dbus_message_iter_get_basic(&variantIter, &value); + warning("%s = string:'%s'", key, value.str); + g_hash_table_insert(info, g_strdup(key), g_variant_new_string(value.str)); + break; + case DBUS_TYPE_ARRAY: + if (dbus_message_iter_get_element_type(&variantIter) != DBUS_TYPE_STRING) { + error("only arrays of strings are supported"); + } else { + int n = dbus_message_iter_get_element_count(&variantIter); + DBusMessageIter valueIter; + dbus_message_iter_recurse(&variantIter, &valueIter); + char **v = g_malloc0_n(n + 1, sizeof *v); + int i = 0; + while (i < n) { + if (!iter_at(&valueIter, DBUS_TYPE_STRING)) + break; + dbus_message_iter_get_basic(&valueIter, &value); + warning("%s[%d] = string:'%s'", key, i, value.str); + + dbus_message_iter_next(&valueIter); + v[i++] = g_strdup(value.str); + } + v[i] = NULL; + g_hash_table_insert(info, g_strdup(key), g_variant_new_strv((const gchar *const *)v, i)); + g_strfreev(v); + } + break; + default: + warning("reply contains unhandled variant types"); + break; + } + } + +EXIT: + if (info && g_hash_table_size(info) < 1) { + /* There should always be at least app id key. If we get + * empty hash table, either parser is not working or there + * are other kinds of problems. + */ + error("no information about application '%s'", desktop); + g_hash_table_destroy(info), info = NULL; + } + dbus_error_free(&err); + if (rsp) + dbus_message_unref(rsp); + if (req) + dbus_message_unref(req); + warning("info received = %s", info ? "true" : "false"); + return info; +} + +static char ** +sailjail_prompt_permissions(DBusConnection *con, const char *desktop) +{ + char **granted = NULL; + DBusError err = DBUS_ERROR_INIT; + DBusMessage *req = NULL; + DBusMessage *rsp = NULL; + int len = 0; + + if (!(req = dbus_message_new_method_call(PERMISSIONMGR_SERVICE, + PERMISSIONMGR_OBJECT, + PERMISSIONMGR_INTERFACE, + PERMISSIONMGR_METHOD_PROMPT))) { + error("failed to create dbus method call"); + goto EXIT; + } + + if (!dbus_message_append_args(req, + DBUS_TYPE_STRING, &desktop, + DBUS_TYPE_INVALID)) { + error("failed to add method call args"); + goto EXIT; + } + + if (!(rsp = dbus_connection_send_with_reply_and_block(con, req, DBUS_TIMEOUT_INFINITE, &err))) { + error("method call failed: %s: %s", err.name, err.message); + goto EXIT; + } + + if (dbus_set_error_from_message(&err, rsp)) { + error("error reply received: %s: %s", err.name, err.message); + goto EXIT; + } + + if (!dbus_message_get_args(rsp, &err, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &granted, &len, + DBUS_TYPE_INVALID)) { + error("parsing reply failed: %s: %s", err.name, err.message); + dbus_free_string_array(granted), granted = NULL; + goto EXIT; + } + +EXIT: + dbus_error_free(&err); + if (rsp) + dbus_message_unref(rsp); + if (req) + dbus_message_unref(req); + + warning("launch permitted = %s", granted ? "true" : "false"); + return granted; +} + +static int +sailjailclient_get_field_code(const char *arg) +{ + // Non-null string starting with a '%' followed by exactly one character + return arg && arg[0] == '%' && arg[1] && !arg[2] ? arg[1] : 0; +} + +static bool +sailjailclient_is_option(const char *arg) +{ + // Non-null string starting with a hyphen + return arg && arg[0] == '-'; +} + +static bool +sailjailclient_ignore_arg(const char *arg) +{ + return !g_strcmp0(arg, "-prestart"); +} + +static bool +sailjailclient_match_argv(const char **tpl_argv, const char **app_argv) +{ + bool matching = false; + + /* Rule out template starting with a field code */ + if (sailjailclient_get_field_code(*tpl_argv)) { + error("Exec line starts with field code"); + goto EXIT; + } + + /* Match each arg in template */ + for (;;) { + const char *want = *tpl_argv++; + + /* Allow some slack e.g. regarding "-prestart" options */ + while (*app_argv && g_strcmp0(*app_argv, want) && + sailjailclient_ignore_arg(*app_argv)) { + warning("ignoring argument: %s", *app_argv); + ++app_argv; + } + + if (!want) { + /* Template args exhausted */ + if (*app_argv) { + /* Excess application args */ + error("argv has unwanted '%s'", *app_argv); + goto EXIT; + } + break; + } + + int field_code = sailjailclient_get_field_code(want); + + if (!field_code) { + /* Exact match needed */ + if (g_strcmp0(*app_argv, want)) { + /* Application args has something else */ + error("argv is missing '%s'", want); + goto EXIT; + } + ++app_argv; + continue; + } + + /* Field code explanations from "Desktop Entry Specification" + * + * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + */ + int code_args = 0; + switch (field_code) { + case 'f': /* A single file name (or none) */ + case 'u': /* A single URL (or none) */ + code_args = -1; + break; + case 'c': /* The translated name of the application */ + case 'k': /* The location of the desktop file */ + code_args = 1; + break; + case 'F': /* A list of files */ + case 'U': /* A list of URLs */ + code_args = INT_MIN; + break; + case 'i': + /* The Icon key of the desktop entry expanded as two + * arguments, first --icon and then the value of the + * Icon key. Should not expand to any arguments if + * the Icon key is empty or missing. + */ + if (!g_strcmp0(*app_argv, "--icon")) + ++app_argv, code_args = 1; + break; + case 'd': + case 'D': + case 'n': + case 'N': + case 'v': + case 'm': + /* Deprecated */ + error("Exec line has deprecated field code '%s'", want); + goto EXIT; + default: + /* Unknown */ + error("Exec line has unknown field code '%s'", want); + goto EXIT; + } + + if (code_args < 0) { + /* Variable number of args */ + if (sailjailclient_get_field_code(*tpl_argv)) { + error("Can't validate '%s %s' combination", want, *tpl_argv); + goto EXIT; + } + for (; code_args < 0; ++code_args) { + if (!*app_argv || !g_strcmp0(*app_argv, *tpl_argv)) + break; + if (sailjailclient_is_option(*app_argv)) { + error("option '%s' at field code '%s'", *app_argv, want); + goto EXIT; + } + ++app_argv; + } + } else { + /* Specified number of args */ + for (; code_args > 0; --code_args) { + if (!*app_argv) { + error("missing args for field code '%s'", want); + goto EXIT; + } + if (sailjailclient_is_option(*app_argv)) { + error("option '%s' at field code '%s'", *app_argv, want); + goto EXIT; + } + ++app_argv; + } + } + } + + matching = true; + +EXIT: + return matching; +} + +static bool +sailjailclient_validate_argv(const char *exec, const gchar **app_argv) +{ + bool validated = false; + GError *err = NULL; + gchar **exec_argv = NULL; + + if (!app_argv || !*app_argv) { + error("application argv not defined"); + goto EXIT; + } + + /* Split desktop Exec line into argv */ + if (!g_shell_parse_argv(exec, NULL, &exec_argv, &err)) { + error("Exec line parse failure: %s", err->message); + goto EXIT; + } + + if (!exec_argv || !*exec_argv) { + error("Exec line not defined"); + goto EXIT; + } + + /* Expectation: Exec line might have leading 'wrapper' executables + * such as sailjail, invoker, etc -> make an attempt to skip those + * by looking for argv[0] for command we are about to launch. + */ + const char **tpl_argv = (const char **)exec_argv; + for (; *tpl_argv; ++tpl_argv) { + if (!g_strcmp0(*tpl_argv, app_argv[0])) + break; + } + + if (!*tpl_argv) { + error("Exec line does not contain '%s'", *app_argv); + goto EXIT; + } + + if (!sailjailclient_match_argv(tpl_argv, app_argv)) { + gchar *args = g_strjoinv(" ", (gchar **)app_argv); + error("Application args do not match Exec line template"); + error("exec: %s", exec); + error("args: %s", args); + g_free(args); + goto EXIT; + } + + validated = true; + +EXIT: + g_strfreev(exec_argv); + g_clear_error(&err); + + return validated; +} + +bool +sailjail_verify_launch(const char *desktop, const char **argv) +{ + bool allowed = false; + DBusConnection *con = NULL; + char **granted = NULL; + GHashTable *info = NULL; + const char **requested = NULL; + const char *exec = NULL; + + if (!(con = sailjail_connect_bus())) + goto EXIT; + + if (!(info = sailjail_application_info(con, desktop))) + goto EXIT; + + if (!(exec = appinfo_string(info, DESKTOP_KEY_EXEC))) { + error("no Exec line defined for application '%s'", desktop); + goto EXIT; + } + + if (!sailjailclient_validate_argv(exec, argv)) + goto EXIT; + + if (!(requested = appinfo_strv(info, SAILJAIL_KEY_PERMISSIONS))) { + error("no permissions defined for application '%s'", desktop); + goto EXIT; + } + + debug("prompting permissions for application '%s'", desktop); + if (!(granted = sailjail_prompt_permissions(con, desktop))) + goto EXIT; + + for (int i = 0; requested[i]; ++i) { + if (!g_strv_contains((const gchar *const *)granted, requested[i])) { + error("application '%s' has not been granted '%s' permission", + desktop, requested[i]); + goto EXIT; + } + } + + allowed = true; + +EXIT: + if (info) + g_hash_table_destroy(info); + g_strfreev(granted); + sailjail_disconnect_bus(con); + + return allowed; +} diff --git a/src/common/sailjail.h b/src/common/sailjail.h new file mode 100644 index 0000000..ae1d095 --- /dev/null +++ b/src/common/sailjail.h @@ -0,0 +1,13 @@ +#ifndef SAILJAIL_H_ +# define SAILJAIL_H_ + +# include +# include + +G_BEGIN_DECLS + +bool sailjail_verify_launch(const char *desktop, const char **argv); + +G_END_DECLS + +#endif // SAILJAIL_H_ diff --git a/src/invoker/invokelib.c b/src/invoker/invokelib.c index 2de5013..6a278d7 100644 --- a/src/invoker/invokelib.c +++ b/src/invoker/invokelib.c @@ -53,7 +53,7 @@ bool invoke_recv_msg(int fd, uint32_t *msg) if (numRead == -1) { - debug("%s: Error reading message: %s\n", __FUNCTION__, strerror(errno)); + debug("%s: Error reading message: %m\n", __FUNCTION__); *msg = 0; return false; } diff --git a/src/invoker/invoker.c b/src/invoker/invoker.c index a06910c..90e6152 100644 --- a/src/invoker/invoker.c +++ b/src/invoker/invoker.c @@ -49,11 +49,14 @@ #include "invokelib.h" #include "search.h" +#define BOOSTER_SESSION "silica-session" +#define BOOSTER_GENERIC "generic" + /* Setting VERBOSE_SIGNALS to non-zero value logs receiving of * async-signals - which is useful only when actively debugging * booster / invoker interoperation. */ -#define VERBOSE_SIGNALS 0 +#define VERBOSE_SIGNALS 01 // Utility functions static char *strip(char *str) @@ -223,7 +226,7 @@ static bool shutdown_socket(int socket_fd) * end too. */ - debug("trying to disconnect booster socket...\n"); + info("trying to disconnect booster socket...\n"); if (shutdown(socket_fd, SHUT_WR) == -1) { warning("socket shutdown failed: %m\n"); @@ -243,7 +246,7 @@ static bool shutdown_socket(int socket_fd) .revents = 0, }; - debug("waiting for booster socket input...\n"); + info("waiting for booster socket input...\n"); int rc = poll(&pfd, 1, (int)(timeout - elapsed)); if (rc == 0) @@ -273,7 +276,7 @@ static bool shutdown_socket(int socket_fd) EXIT: if (disconnected) - debug("booster socket was succesfully disconnected\n"); + info("booster socket was succesfully disconnected\n"); else warning("could not disconnect booster socket\n"); @@ -314,7 +317,7 @@ static void kill_application(pid_t pid) FAIL: if (errno == ESRCH) - debug("application (pid=%d) has exited", (int)pid); + info("application (pid=%d) has exited", (int)pid); else warning("application (pid=%d) kill failed: %m", (int)pid); @@ -340,6 +343,8 @@ static bool invoke_recv_ack(int fd) // Inits a socket connection for the given application type static int invoker_init(const char *app_type, const char *app_name) { + info("try type=%s app=%s ...", app_type, app_name); + bool connected = false; int fd = -1; @@ -388,7 +393,7 @@ static int invoker_init(const char *app_type, const char *app_name) goto EXIT; } - debug("connected to: %s\n", sun.sun_path); + info("connected to: %s\n", sun.sun_path); connected = true; EXIT: @@ -469,7 +474,7 @@ static void invoker_send_args(int fd, int argc, char **argv) invoke_send_msg(fd, argc); for (i = 0; i < argc; i++) { - debug("param %d %s \n", i, argv[i]); + info("param %d %s \n", i, argv[i]); invoke_send_str(fd, argv[i]); } } @@ -653,7 +658,7 @@ static int wait_for_launched_process_to_exit(int socket_fd) // coverity[tainted_string_return_content] g_invoked_pid = invoker_recv_pid(socket_fd); - debug("Booster's pid is %d \n ", g_invoked_pid); + info("Booster's pid is %d \n ", g_invoked_pid); // Setup signal handlers sigs_init(); @@ -715,7 +720,7 @@ static int wait_for_launched_process_to_exit(int socket_fd) warning("application (pid=%d) exit(%d) signal(%d)\n", (int)g_invoked_pid, exit_status, exit_signal); else - debug("application (pid=%d) exit(%d) signal(%d)\n", + info("application (pid=%d) exit(%d) signal(%d)\n", (int)g_invoked_pid, exit_status, exit_signal); if (socket_fd != -1) { @@ -853,7 +858,7 @@ static int invoke(InvokeArgs *args) bool tried_session = false; for (size_t i = 0; !tried_session && types[i]; ++i) { - if (strcmp(types[i], "session")) + if (strcmp(types[i], BOOSTER_SESSION)) continue; tried_session = true; fd = invoker_init(types[i], NULL); @@ -866,12 +871,12 @@ static int invoke(InvokeArgs *args) if (fd == -1 && !tried_session) { bool tried_generic = false; for (size_t i = 0; fd == -1 && types[i]; ++i) { - if (!strcmp(types[i], "generic")) + if (!strcmp(types[i], BOOSTER_GENERIC)) tried_generic = true; fd = invoker_init(types[i], args->app_name); } if (fd == -1 && !tried_generic) - fd = invoker_init("generic", args->app_name); + fd = invoker_init(BOOSTER_GENERIC, args->app_name); } if (fd != -1) { @@ -931,6 +936,7 @@ int main(int argc, char *argv[]) {"splash", required_argument, NULL, 'S'}, {"splash-landscape", required_argument, NULL, 'L'}, {"desktop-file", required_argument, NULL, 'F'}, + {"verbose", no_argument, NULL, 'v'}, {0, 0, 0, 0} }; @@ -938,7 +944,7 @@ int main(int argc, char *argv[]) // The use of + for POSIXLY_CORRECT behavior is a GNU extension, but avoids polluting // the environment int opt; - while ((opt = getopt_long(argc, argv, "+hcwnGDsoTd:t:a:r:S:L:F:", longopts, NULL)) != -1) + while ((opt = getopt_long(argc, argv, "+hvcwnGDsoTd:t:a:r:S:L:F:", longopts, NULL)) != -1) { switch(opt) { @@ -946,6 +952,10 @@ int main(int argc, char *argv[]) usage(0); break; + case 'v': + report_set_type(report_get_type() + 1); + break; + case 'w': // nothing to do, it's by default now break; @@ -1077,10 +1087,10 @@ int main(int argc, char *argv[]) // Sleep for delay before exiting if (args.exit_delay) { // DBUS cannot cope some times if the invoker exits too early. - debug("Delaying exit for %d seconds..\n", args.exit_delay); + info("Delaying exit for %d seconds..\n", args.exit_delay); sleep(args.exit_delay); } - debug("invoker exit(%d)\n", ret_val); + info("invoker exit(%d)\n", ret_val); return ret_val; } diff --git a/src/launcherlib/CMakeLists.txt b/src/launcherlib/CMakeLists.txt index 5396614..df440a9 100644 --- a/src/launcherlib/CMakeLists.txt +++ b/src/launcherlib/CMakeLists.txt @@ -5,23 +5,28 @@ set(COMMON ${CMAKE_HOME_DIRECTORY}/src/common) # Find systemd find_package(PkgConfig REQUIRED) pkg_check_modules(SYSTEMD "libsystemd" REQUIRED) +pkg_check_modules(DBUS dbus-1 REQUIRED) +pkg_check_modules(GLIB glib-2.0 REQUIRED) # Set include dirs -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SYSTEMD_INCLUDE_DIRS} ${COMMON}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SYSTEMD_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ${COMMON}) # Hide all symbols except the ones explicitly exported in the code (like main()) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") # Set sources set(SRC appdata.cpp booster.cpp connection.cpp daemon.cpp logger.cpp - singleinstance.cpp socketmanager.cpp) + singleinstance.cpp socketmanager.cpp + ../common/report.c + ../common/sailjail.c) set(HEADERS appdata.h booster.h connection.h daemon.h logger.h launcherlib.h singleinstance.h socketmanager.h ${COMMON}/protocol.h) # Set libraries to be linked. Shared libraries to be preloaded are not linked in anymore, # but dlopen():ed and listed in src/launcher/preload.h instead. -link_libraries(${LIBDL} "-L/lib -lcap") +link_libraries(${GLIB_LDFLAGS} ${DBUS_LDFLAGS} ${LIBDL} "-L/lib -lsystemd -lcap") # Set executable add_library(applauncherd MODULE ${SRC} ${MOC_SRC}) @@ -29,7 +34,6 @@ set_target_properties(applauncherd PROPERTIES VERSION 0.1 SOVERSION 0) target_link_libraries(applauncherd ${SYSTEMD_LIBRARIES}) - # Add install rule install(TARGETS applauncherd DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/applauncherd diff --git a/src/launcherlib/appdata.cpp b/src/launcherlib/appdata.cpp index efc5b3c..4e9b8eb 100644 --- a/src/launcherlib/appdata.cpp +++ b/src/launcherlib/appdata.cpp @@ -24,6 +24,7 @@ #include #include #include +#include AppData::AppData() : m_options(0), @@ -71,7 +72,9 @@ bool AppData::disableOutOfMemAdj() const void AppData::setArgc(int newArgc) { - m_argc = newArgc; + (void)newArgc; // unused + // kept for the sake of binary compatibility + // setArgv() sets also m_argc } int AppData::argc() const @@ -81,12 +84,25 @@ int AppData::argc() const void AppData::setArgv(const char ** newArgv) { - m_argv = newArgv; + for (int i = 0; i < m_argc; ++i) + free(m_argv[i]); + free(m_argv); + m_argc = 0; + m_argv = nullptr; + + if (newArgv) { + while (newArgv[m_argc]) + ++m_argc; + m_argv = (char **)calloc(m_argc + 1, sizeof *m_argv); + for (int i = 0; i < m_argc; ++i) + m_argv[i] = strdup(newArgv[i]); + m_argv[m_argc] = nullptr; + } } const char ** AppData::argv() const { - return m_argv; + return (const char **)m_argv; } void AppData::setAppName(const string & newAppName) @@ -251,4 +267,5 @@ string AppData::privileges() const AppData::~AppData() { + setArgv(nullptr); } diff --git a/src/launcherlib/appdata.h b/src/launcherlib/appdata.h index d840674..6a9ab08 100644 --- a/src/launcherlib/appdata.h +++ b/src/launcherlib/appdata.h @@ -135,7 +135,7 @@ private: uint32_t m_options; int m_argc; - const char ** m_argv; + char **m_argv; string m_appName; string m_fileName; int m_prio; diff --git a/src/launcherlib/booster.cpp b/src/launcherlib/booster.cpp index 61c4f6c..98e7436 100644 --- a/src/launcherlib/booster.cpp +++ b/src/launcherlib/booster.cpp @@ -25,6 +25,7 @@ #include "singleinstance.h" #include "socketmanager.h" #include "logger.h" +#include "report.h" #include #include @@ -50,8 +51,12 @@ #include #include +#include + #include "coverage.h" +#include "sailjail.h" + Booster::Booster() : m_appData(new AppData), m_connection(NULL), @@ -61,7 +66,6 @@ Booster::Booster() : m_boostedApplication("default"), m_bootMode(false) { - Connection::setMountNamespace(Connection::getMountNamespace(getpid())); } Booster::~Booster() @@ -211,11 +215,13 @@ void Booster::sendDataToParent() cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + debug("send to daemon: pid=%d delay=%d fd=%d", (int)pid, delay, fd); } else { msg.msg_control = NULL; msg.msg_controllen = 0; + debug("send to daemon: pid=%d delay=%d fd=NA", (int)pid, delay); } if (sendmsg(boosterLauncherSocket(), &msg, 0) < 0) @@ -240,13 +246,6 @@ bool Booster::receiveDataFromInvoker(int socketFd) // Accept a new invocation. if (m_connection->accept(m_appData)) { - // Check that the caller is allowed to invoke apps - if (!m_connection->isPermitted()) - { - m_connection->close(); - return false; - } - // Receive application data from the invoker if (!m_connection->receiveApplicationData(m_appData)) { @@ -285,6 +284,11 @@ int Booster::run(SocketManager * socketManager) throw std::runtime_error("Booster: Binary doesn't have execute permissions\n"); } + if (boostedApplication() != "default") { + if (!sailjail_verify_launch(boostedApplication().c_str(), m_appData->argv())) + throw std::runtime_error("Booster: Binary doesn't have launch permissions\n"); + } + return launchProcess(); } catch (const std::runtime_error &e) { Logger::logError("Booster: Failed to invoke: %s\n", e.what()); @@ -642,7 +646,6 @@ const string Booster::socketId() const return id; } - pid_t Booster::invokersPid() { if (m_connection->isReportAppExitStatusNeeded()) @@ -726,3 +729,4 @@ std::string Booster::getFinalName(const std::string &name) } return name; } + diff --git a/src/launcherlib/connection.cpp b/src/launcherlib/connection.cpp index a883885..509bd4b 100644 --- a/src/launcherlib/connection.cpp +++ b/src/launcherlib/connection.cpp @@ -21,12 +21,11 @@ #include "connection.h" #include "logger.h" +#include "report.h" #include #include /* for getsockopt */ #include /* for chmod */ -#include -#include #include #include #include @@ -34,10 +33,6 @@ #include #include -#define INVOKER_PATH "/usr/bin/invoker" - -std::pair Connection::s_mntNS = std::pair(); - Connection::Connection(int socketFd, bool testMode) : m_testMode(testMode), m_fd(-1), @@ -71,6 +66,12 @@ Connection::~Connection() m_io[i] = -1; } } + + for (int i = 0; i < m_argc; ++i) + delete[] m_argv[i]; + free(m_argv); + m_argc = 0; + m_argv = nullptr; } @@ -79,8 +80,9 @@ int Connection::getFd() const return m_fd; } -bool Connection::accept(AppData*) +bool Connection::accept(AppData *appData) { + (void)appData; // unused if (!m_testMode) { m_fd = ::accept(m_curSocket, NULL, NULL); @@ -113,47 +115,6 @@ void Connection::close() } } - -bool Connection::isPermitted() -{ - // Check that caller is in the same namespace and it is invoker - // and not something else. This is done to avoid sandboxed app - // that sees the socket from escaping the sandbox through booster - if (m_fd != -1) - { - pid_t pid = peerPid(); - if (pid == 0) - { - Logger::logError("Connection: %s: getting peer pid failed", __FUNCTION__); - } - else - { - // There is a possibility for a race here: if caller can exit - // and then invoke a process with the correct binary path and - // pid at the right time they could fool us - - if (!s_mntNS.first || getMountNamespace(pid) != s_mntNS) - { - Logger::logWarning("Connection: %s: invocation from %s (%d) came from different namespace", __FUNCTION__, getExecutablePath(pid), pid); - } - else - { - std::string executablePath = getExecutablePath(pid); - if (executablePath != INVOKER_PATH) - { - Logger::logWarning("Connection: %s: executable %s (%d) is not invoker", __FUNCTION__, executablePath, pid); - } - else - { - Logger::logDebug("Connection: %s: invoker (%d) called, allowing", __FUNCTION__, pid); - return true; - } - } - } - } - return false; -} - bool Connection::sendMsg(uint32_t msg) { if (!m_testMode) @@ -194,7 +155,7 @@ bool Connection::recvMsg(uint32_t *msg) } } -const char * Connection::recvStr() +char *Connection::recvStr() { if (!m_testMode) { @@ -284,7 +245,7 @@ string Connection::receiveAppName() return string(); } - const char* name = recvStr(); + char *name = recvStr(); if (!name) { Logger::logError("Connection: receiving application name"); @@ -298,7 +259,7 @@ string Connection::receiveAppName() bool Connection::receiveExec() { - const char* filename = recvStr(); + char *filename = recvStr(); if (!filename) return false; @@ -328,51 +289,36 @@ bool Connection::receiveIDs() bool Connection::receiveArgs() { - // Get argc - recvMsg(&m_argc); const uint32_t argMax = 1024; - if (m_argc > 0 && m_argc < argMax) - { - // Reserve memory for argv - m_argv = new const char * [m_argc]; - if (!m_argv) - { - Logger::logError("Connection: reserving memory for argv"); - return false; - } - // Get argv - for (uint i = 0; i < m_argc; i++) - { - m_argv[i] = recvStr(); - if (!m_argv[i]) - { - Logger::logError("Connection: receiving argv[%i]", i); - return false; - } - } - } - else - { + // Clear current args + for (int i = 0; i < m_argc; ++i) + delete[] m_argv[i]; + free(m_argv); + m_argc = 0; + m_argv = nullptr; + + // Get argc + uint32_t argc = 0; + recvMsg(&argc); + if (argc < 1 || argc > argMax) { Logger::logError("Connection: invalid number of parameters %d", m_argc); return false; } + m_argc = argc; + m_argv = (char **)calloc(m_argc + 1, sizeof *m_argv); + for (int i = 0; i < m_argc; ++i) { + if (!(m_argv[i] = recvStr())) { + m_argc = i; + Logger::logError("Connection: receiving argv[%i]", i); + return false; + } + } + m_argv[m_argc] = nullptr; return true; } -// coverity[ +tainted_string_sanitize_content : arg-0 ] -bool putenv_sanitize(const char * s) -{ - return static_cast(strchr(s, '=')); -} - -// coverity[ +free : arg-0 ] -int putenv_wrapper(char * var) -{ - return putenv(var); -} - bool Connection::receiveEnv() { // Have some "reasonable" limit for environment variables to protect from @@ -387,30 +333,28 @@ bool Connection::receiveEnv() // Get environment variables for (uint32_t i = 0; i < n_vars; i++) { - const char * var = recvStr(); + char *var = recvStr(); if (var == NULL) { Logger::logError("Connection: receiving environ[%i]", i); return false; } - - // In case of error, just warn and try to continue, as the other side is - // going to keep sending the reset of the message. - // String pointed to by var shall become part of the environment, so altering - // the string shall change the environment, don't free it - if (putenv_sanitize(var)) - { - if (putenv_wrapper(const_cast(var)) != 0) - { - Logger::logWarning("Connection: putenv failed"); + char *val = strchr(var, '='); + if (val) { + *val++ = 0; + const char *cur = getenv(var); + /* Note: DBUS_SESSION_BUS_ADDRESS is a special case. If we + * are running in sandbox, we already have non-standard path + * that firejail has placed in env. + */ + if (!cur || strcmp(var, "DBUS_SESSION_BUS_ADDRESS")) { + if (!cur || strcmp(cur, val)) { + info("ENV: $%s: %s -> %s", var, cur ?: "n/a", val); + setenv(var, val, true); + } } } - else - { - delete [] var; - var = NULL; - Logger::logWarning("Connection: invalid environment data"); - } + delete [] var; } } else @@ -476,43 +420,49 @@ bool Connection::receiveActions() { Logger::logDebug("Connection: enter: %s", __FUNCTION__); - while (1) + for (;;) { uint32_t action = 0; // Get the action. - recvMsg(&action); + if (!recvMsg(&action)) + return false; switch (action) { case INVOKER_MSG_EXEC: - receiveExec(); + if (!receiveExec()) + return false; break; case INVOKER_MSG_ARGS: - receiveArgs(); + if (!receiveArgs()) + return false; break; case INVOKER_MSG_ENV: - // Clean-up all the env variables - clearenv(); - receiveEnv(); + if (!receiveEnv()) + return false; break; case INVOKER_MSG_PRIO: - receivePriority(); + if (!receivePriority()) + return false; break; case INVOKER_MSG_DELAY: - receiveDelay(); + if (!receiveDelay()) + return false; break; case INVOKER_MSG_IO: - receiveIO(); + if (!receiveIO()) + return false; break; case INVOKER_MSG_IDS: - receiveIDs(); + if (!receiveIDs()) + return false; break; case INVOKER_MSG_SPLASH: @@ -524,11 +474,10 @@ bool Connection::receiveActions() return false; case INVOKER_MSG_END: - sendMsg(INVOKER_MSG_ACK); - - if (m_sendPid) - sendPid(getpid()); - + if (!sendMsg(INVOKER_MSG_ACK)) + return false; + if (m_sendPid && !sendPid(getpid())) + return false; return true; default: @@ -563,7 +512,7 @@ bool Connection::receiveApplicationData(AppData* appData) appData->setPriority(m_priority); appData->setDelay(m_delay); appData->setArgc(m_argc); - appData->setArgv(m_argv); + appData->setArgv((const char **)m_argv); appData->setIODescriptors(vector(m_io, m_io + IO_DESCRIPTOR_COUNT)); appData->setIDs(m_uid, m_gid); } @@ -594,37 +543,3 @@ pid_t Connection::peerPid() return cr.pid; } - -void Connection::setMountNamespace(std::pair mntNS) -{ - s_mntNS = mntNS; -} - -std::pair Connection::getMountNamespace(pid_t pid) -{ - struct stat sb; - std::ostringstream path; - path << "/proc/" << pid << "/ns/mnt"; - if (stat(path.str().c_str(), &sb) == -1) - { - Logger::logError("Connection: %s: stat failed for pid %d: %s", __FUNCTION__, pid, strerror(errno)); - return std::pair(); - } - return std::pair(sb.st_dev, sb.st_ino); -} - -std::string Connection::getExecutablePath(pid_t pid) -{ - std::ostringstream path; - char exe[PATH_MAX]; - ssize_t len; - static_assert(sizeof(INVOKER_PATH) < sizeof(exe)); - path << "/proc/" << pid << "/exe"; - len = readlink(path.str().c_str(), exe, sizeof(exe)); - if (len < 0 || (size_t)len >= sizeof(exe)) - { - Logger::logError("Connection: %s: readlink failed for pid %d: %s", __FUNCTION__, pid, strerror(errno)); - return std::string(); - } - return std::string(exe, len); -} diff --git a/src/launcherlib/connection.h b/src/launcherlib/connection.h index 2fcb49e..cfdafca 100644 --- a/src/launcherlib/connection.h +++ b/src/launcherlib/connection.h @@ -84,15 +84,6 @@ public: //! \brief Send application exit value bool sendExitValue(int value); - //! \brief Check if caller is permitted to invoke apps - bool isPermitted(); - - //! \brief Get device id and inode number pair of a mount namespace - static std::pair getMountNamespace(pid_t pid); - - //! \brief Set required mount namespace for invoker processes - static void setMountNamespace(std::pair mntNS); - private: /*! \brief Receive actions. @@ -142,9 +133,6 @@ private: //! Send process pid bool sendPid(pid_t pid); - //! Helper method: Get executable path for pid - static std::string getExecutablePath(pid_t pid); - //! Send message to a socket. This is a virtual to help unit testing. virtual bool sendMsg(uint32_t msg); @@ -152,7 +140,7 @@ private: virtual bool recvMsg(uint32_t *msg); //! Receive a string. This is a virtual to help unit testing. - virtual const char * recvStr(); + virtual char *recvStr(); //! Run in test mode, if true bool m_testMode; @@ -164,8 +152,8 @@ private: int m_curSocket; string m_fileName; - uint32_t m_argc; - const char ** m_argv; + int m_argc; + char **m_argv; int m_io[IO_DESCRIPTOR_COUNT]; uint32_t m_priority; uint32_t m_delay; @@ -173,7 +161,6 @@ private: gid_t m_gid; uid_t m_uid; - static std::pair s_mntNS; #ifdef UNIT_TEST friend class Ut_Connection; diff --git a/src/launcherlib/daemon.cpp b/src/launcherlib/daemon.cpp index d5c3f16..b70b377 100644 --- a/src/launcherlib/daemon.cpp +++ b/src/launcherlib/daemon.cpp @@ -21,6 +21,7 @@ #include "daemon.h" #include "logger.h" +#include "report.h" #include "connection.h" #include "booster.h" #include "singleinstance.h" @@ -46,6 +47,7 @@ #include #include #include +#include #include "coverage.h" @@ -53,7 +55,7 @@ * async-signals - which is useful only when actively debugging * booster / invoker interoperation. */ -#define VERBOSE_SIGNALS 0 +#define VERBOSE_SIGNALS 01 // Environment @@ -325,13 +327,13 @@ Daemon::Daemon(int & argc, char * argv[]) : throw std::runtime_error("Daemon: Daemon already created!\n"); } - // Parse arguments - parseArgs(ArgVect(argv, argv + argc)); - // Store arguments list m_initialArgv = argv; m_initialArgc = argc; + // Parse arguments + parseArgs(argc, argv); + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, m_boosterLauncherSocket) == -1) { throw std::runtime_error("Daemon: Creating a socket pair for boosters failed!\n"); @@ -550,63 +552,62 @@ void Daemon::run(Booster *booster) void Daemon::readFromBoosterSocket(int fd) { pid_t invokerPid = 0; - int delay = 0; - struct msghdr msg; + int delay = 0; + int socketFd = -1; + + struct iovec iov[2]; + char buf[CMSG_SPACE(sizeof socketFd)]; + struct msghdr msg; struct cmsghdr *cmsg; - struct iovec iov[2]; - char buf[CMSG_SPACE(sizeof(int))]; + + memset(iov, 0, sizeof iov); + memset(buf, 0, sizeof buf); + memset(&msg, 0, sizeof msg); iov[0].iov_base = &invokerPid; - iov[0].iov_len = sizeof(pid_t); + iov[0].iov_len = sizeof invokerPid; iov[1].iov_base = &delay; - iov[1].iov_len = sizeof(int); + iov[1].iov_len = sizeof delay; msg.msg_iov = iov; msg.msg_iovlen = 2; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_control = buf; - msg.msg_controllen = sizeof(buf); + msg.msg_controllen = sizeof buf; - if (recvmsg(fd, &msg, 0) >= 0) - { - Logger::logDebug("Daemon: boosters's pid: %d\n", m_boosterPid); - Logger::logDebug("Daemon: invoker's pid: %d\n", invokerPid); - Logger::logDebug("Daemon: respawn delay: %d \n", delay); - - int newFd = -1; + if (recvmsg(fd, &msg, 0) == -1) { + Logger::logError("Daemon: Critical error communicating with booster. Exiting applauncherd.\n"); + exit(EXIT_FAILURE); + } - if (invokerPid > 0) { - /* invokerPid got filled in at recvmsg() => we have fd too */ - cmsg = CMSG_FIRSTHDR(&msg); - memcpy(&newFd, CMSG_DATA(cmsg), sizeof(int)); - Logger::logDebug("Daemon: socket file descriptor: %d\n", newFd); + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len >= CMSG_LEN(sizeof(socketFd))) { + memcpy(&socketFd, CMSG_DATA(cmsg), sizeof socketFd); } + } - if (m_boosterPid > 0) { - /* We were expecting booster details => update bookkeeping */ - if (newFd != -1) { - // Store booster pid - invoker socket pair - m_boosterPidToInvokerFd[m_boosterPid] = newFd; - newFd = -1; - } - if (invokerPid > 0) { - // Store booster pid - invoker pid pair - m_boosterPidToInvokerPid[m_boosterPid] = invokerPid; - } - } + Logger::logDebug("Daemon: booster=%d invoker=%d socket=%d delay=%d\n", + m_boosterPid, invokerPid, socketFd, delay); - if (newFd != -1) { - /* If we are not going to use the received fd, it needs to be closed */ - Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", newFd); - close(newFd); + if (m_boosterPid > 0) { + /* We were expecting booster details => update bookkeeping */ + if (socketFd != -1) { + // Store booster pid - invoker socket pair + m_boosterPidToInvokerFd[m_boosterPid] = socketFd, socketFd = -1; + } + if (invokerPid > 0) { + // Store booster pid - invoker pid pair + m_boosterPidToInvokerPid[m_boosterPid] = invokerPid; } } - else - { - Logger::logError("Daemon: Nothing read from the socket\n"); - // Critical error communicating with booster. Exiting applauncherd. - _exit(EXIT_FAILURE); + + if (socketFd != -1) { + /* If we are not going to use the received fd, it needs to be closed */ + Logger::logWarning("Daemon: close stray socket file descriptor: %d\n", socketFd); + close(socketFd); } // 2nd param guarantees some time for the just launched application @@ -918,72 +919,93 @@ void Daemon::daemonize() } } -void Daemon::parseArgs(const ArgVect & args) -{ - std::deque queue; - for (ArgVect::const_iterator i(args.begin() + 1); i != args.end(); i++) - queue.push_back(*i); - - while (!queue.empty()) { - string option(queue.front()); - queue.pop_front(); - if (option == "--boot-mode" || option == "-b") - { +void Daemon::parseArgs(int argc, char **argv) +{ + // Options recognized + static const struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "debug", no_argument, NULL, 'v' }, + { "boot-mode", no_argument, NULL, 'b' }, + { "daemon", no_argument, NULL, 'd' }, + { "systemd", no_argument, NULL, 'n' }, + { "application", required_argument, NULL, 'a' }, + { 0, 0, 0, 0} + }; + static const char shortopts[] = + "+" // use posix rules + "h" // --help + "v" // --verbose --debug + "b" // --boot-mode + "d" // --daemon + "n" // --systemd + "a:" // --application= + ; + for (;;) { + int opt = getopt_long(argc, argv, shortopts, longopts, NULL); + if (opt == -1) + break; + switch (opt) { + case 'h': + usage(*argv, EXIT_SUCCESS); + break; + case 'v': + Logger::setDebugMode(true); + m_debugMode = true; + break; + case 'b': Logger::logInfo("Daemon: Boot mode set."); m_bootMode = true; - } - else if (option == "--daemon" || option == "-d") - { + break; + case 'd': m_daemon = true; - } - else if (option == "--debug") - { - Logger::setDebugMode(true); - m_debugMode = true; - } - else if (option == "--help" || option == "-h") - { - usage(args[0].c_str(), EXIT_SUCCESS); - } - else if (option == "--systemd") - { + break; + case 'n': m_notifySystemd = true; - } - else if (option == "--application" || option == "-a") - { - if (!queue.empty()) { - m_boostedApplication = queue.front(); - queue.pop_front(); - } - } - else - { - if (option.find_first_not_of(' ') != string::npos) - usage(args[0].c_str(), EXIT_FAILURE); + break; + case 'a': + m_boostedApplication = optarg; + break; + default: + case '?': + usage(*argv, EXIT_FAILURE); } } + if (optind < argc) + usage(*argv, EXIT_FAILURE); } // Prints the usage and exits with given status void Daemon::usage(const char *name, int status) { - printf("\nUsage: %s [options]\n\n" - "Start the application launcher daemon.\n\n" + name = basename(name); + printf("\n" + "Start the application launcher daemon.\n" + "\n" + "Usage:\n" + " %s [options]\n" + "\n" "Options:\n" - " -b, --boot-mode Start %s in the boot mode. This means that\n" + " -b, --boot-mode\n" + " Start %s in the boot mode. This means that\n" " boosters will not initialize caches and booster\n" " respawn delay is set to zero.\n" " Normal mode is restored by sending SIGUSR1\n" " to the launcher.\n" " Boot mode can be activated also by sending SIGUSR2\n" " to the launcher.\n" - " -d, --daemon Run as %s a daemon.\n" - " -a, --application \n" + " -d, --daemon\n" + " Run as %s a daemon.\n" + " -a, --application=\n" " Run as application specific booster.\n" - " --systemd Notify systemd when initialization is done\n" - " --debug Enable debug messages and log everything also to stdout.\n" - " -h, --help Print this help.\n\n", + " -n, --systemd\n" + " Notify systemd when initialization is done\n" + " -h, --help\n" + " Print this help.\n" + " -v, --verbose, --debug\n" + " Make diagnostic logging more verbose.\n" + "\n", name, name, name); exit(status); diff --git a/src/launcherlib/daemon.h b/src/launcherlib/daemon.h index 75c0915..8947431 100644 --- a/src/launcherlib/daemon.h +++ b/src/launcherlib/daemon.h @@ -113,8 +113,7 @@ private: Daemon & operator= (const Daemon & r); //! Parse arguments - typedef vector ArgVect; - void parseArgs(const ArgVect & args); + void parseArgs(int argc, char **argv); //! Fork to a daemon void daemonize(); diff --git a/src/launcherlib/logger.cpp b/src/launcherlib/logger.cpp index b659938..207c504 100644 --- a/src/launcherlib/logger.cpp +++ b/src/launcherlib/logger.cpp @@ -28,40 +28,14 @@ #include #include "coverage.h" +#include "report.h" bool Logger::m_isOpened = false; bool Logger::m_debugMode = false; -static char *strip(char *str) -{ - if (str) { - char *dst = str; - char *src = str; - while (*src && isspace(*src)) - ++src; - for (;;) { - while (*src && !isspace(*src)) - *dst++ = *src++; - while (*src && isspace(*src)) - ++src; - if (!*src) - break; - *dst++ = ' '; - } - *dst = 0; - } - return str; -} - static bool useSyslog() { - static bool checked = false; - static bool value = false; - if (!checked) { - checked = true; - value = !isatty(STDIN_FILENO); - } - return value; + return report_get_output() == report_syslog; } void Logger::openLog(const char * progName) @@ -86,63 +60,48 @@ void Logger::closeLog() } } -void Logger::writeLog(const int priority, const char * format, va_list ap) +void Logger::writeLog(const int priority, const char *format, va_list va) { - if (useSyslog()) { - if (!Logger::m_isOpened) - Logger::openLog(); - vsyslog(priority, format, ap); - } else { - char *msg = 0; - if (vasprintf(&msg, format, ap) < 0) - msg = 0; - else - strip(msg); - fprintf(stderr, "BOOSTER(%d): %s\n", (int)getpid(), msg ?: format); - fflush(stderr); - free(msg); - } + vreport((enum report_type)priority, format, va); } void Logger::logDebug(const char * format, ...) { - if (m_debugMode) - { - va_list ap; - va_start(ap, format); - writeLog(LOG_DEBUG, format, ap); - va_end(ap); - } + va_list va; + va_start(va, format); + Logger::writeLog(report_debug, format, va); + va_end(va); } void Logger::logInfo(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_INFO, format, ap); - va_end(ap); - // To avoid extra file descriptors in forked boosters closing connection to syslog - Logger::closeLog(); + va_list va; + va_start(va, format); + Logger::writeLog(report_info, format, va); + va_end(va); } void Logger::logWarning(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_WARNING, format, ap); - va_end(ap); + va_list va; + va_start(va, format); + Logger::writeLog(report_warning, format, va); + va_end(va); } void Logger::logError(const char * format, ...) { - va_list ap; - va_start(ap, format); - writeLog(LOG_ERR, format, ap); - va_end(ap); + va_list va; + va_start(va, format); + Logger::writeLog(report_error, format, va); + va_end(va); } void Logger::setDebugMode(bool enable) { - Logger::m_debugMode = enable; + if ((Logger::m_debugMode = enable)) + report_set_type(report_debug); + else + report_set_type(report_warning); } diff --git a/src/launcherlib/logger.h b/src/launcherlib/logger.h index dd9d9b4..725ac50 100644 --- a/src/launcherlib/logger.h +++ b/src/launcherlib/logger.h @@ -100,11 +100,11 @@ private: #endif }; -/* Allow the same logging API to be used in booster and invoker */ -#define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS) -#define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS) -#define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS) -#define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS) +// QUARANTINE /* Allow the same logging API to be used in booster and invoker */ +// QUARANTINE #define error( FMT, ARGS...) Logger::logError( FMT, ##ARGS) +// QUARANTINE #define warning(FMT, ARGS...) Logger::logWarning(FMT, ##ARGS) +// QUARANTINE #define info( FMT, ARGS...) Logger::logInfo( FMT, ##ARGS) +// QUARANTINE #define debug( FMT, ARGS...) Logger::logDebug( FMT, ##ARGS) #endif // LOGGER_H