[mapplauncerd] Sandboxed application boosters. JB#53844 OMP#JOLLA-43

When booster is executing in sandbox as an applicatiom booster, it
needs to verify that command line received from invoker matches
Exec line in application desktop file, application launch is allowed,
and permissions granted are as was expected at the time of booster
launch.

Provide booster-generic@.service that can be used for instantiating
sandboxed application boosters.

D-Bus ipc with sailjaild is modified version of similar code in
sailjailclient. The biggest difference is that this version uses
private connection via libdbus to avoid leaving stray dbus connections
or threads behind when transferring control to application code
without use of exec*() functions.

Remove cap_sys_ptrace from booster executable as makes it impossible
to run the booster within a no-new-privs sandbox.

Fix socket passing from booster instance to booster daemon so that it
works also when invoker is running in different namespace than booster
instance (invoker pid might be unresolvable).

Replace ad-hoc booster argument parsing with getopt_long().

Fix issues with argv handling: using const pointers for non-const
data, passing data by reference between objects that might have
different lifespans and never releasing the dynamically allocated
arrays.

Fix issues with env passing: duplicating invoker env at booster
side as-is can lead to problems like loss of customg session
bus socket address that has been set up by firejail.

If booster bumps into command read problems, bailout immediately
instead of relying on out-of sequence data possibly triggering
exit due to unknown commands.

As an enabler for sharing code between invoker (written in c) and
daemon (written in c++), modify Logger class used by c++ code so
that it is just a wrapper for logging functionality used by invoker.

Signed-off-by: Simo Piiroinen <simo.piiroinen@jolla.com>
pull/1/head
Simo Piiroinen 4 years ago
parent f24871adb2
commit 833b551af1

@ -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
==============================

@ -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

@ -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()

@ -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

@ -17,16 +17,39 @@
**
****************************************************************************/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#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, ...)

@ -22,6 +22,13 @@
#ifndef REPORT_H
#define REPORT_H
#include <syslog.h>
#include <stdarg.h>
#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

@ -0,0 +1,559 @@
#include "sailjail.h"
#include "report.h"
#include <stdio.h>
#include <limits.h>
#include <dbus/dbus.h>
/* 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;
}

@ -0,0 +1,13 @@
#ifndef SAILJAIL_H_
# define SAILJAIL_H_
# include <stdbool.h>
# include <glib.h>
G_BEGIN_DECLS
bool sailjail_verify_launch(const char *desktop, const char **argv);
G_END_DECLS
#endif // SAILJAIL_H_

@ -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;
}

@ -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;
}

@ -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

@ -24,6 +24,7 @@
#include <sys/types.h>
#include <fstream>
#include <dirent.h>
#include <string.h>
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);
}

@ -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;

@ -25,6 +25,7 @@
#include "singleinstance.h"
#include "socketmanager.h"
#include "logger.h"
#include "report.h"
#include <cstdlib>
#include <dlfcn.h>
@ -50,8 +51,12 @@
#include <grp.h>
#include <libgen.h>
#include <dbus/dbus.h>
#include "coverage.h"
#include "sailjail.h"
Booster::Booster() :
m_appData(new AppData),
m_connection(NULL),
@ -210,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)
@ -277,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());
@ -634,7 +646,6 @@ const string Booster::socketId() const
return id;
}
pid_t Booster::invokersPid()
{
if (m_connection->isReportAppExitStatusNeeded())
@ -718,3 +729,4 @@ std::string Booster::getFinalName(const std::string &name)
}
return name;
}

@ -21,6 +21,7 @@
#include "connection.h"
#include "logger.h"
#include "report.h"
#include <sys/socket.h>
#include <sys/un.h> /* for getsockopt */
@ -65,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;
}
@ -148,7 +155,7 @@ bool Connection::recvMsg(uint32_t *msg)
}
}
const char * Connection::recvStr()
char *Connection::recvStr()
{
if (!m_testMode)
{
@ -238,7 +245,7 @@ string Connection::receiveAppName()
return string();
}
const char* name = recvStr();
char *name = recvStr();
if (!name)
{
Logger::logError("Connection: receiving application name");
@ -252,7 +259,7 @@ string Connection::receiveAppName()
bool Connection::receiveExec()
{
const char* filename = recvStr();
char *filename = recvStr();
if (!filename)
return false;
@ -282,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<bool>(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
@ -341,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<char *>(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
@ -430,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:
@ -478,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:
@ -517,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<int>(m_io, m_io + IO_DESCRIPTOR_COUNT));
appData->setIDs(m_uid, m_gid);
}

@ -140,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;
@ -152,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;

@ -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 <systemd/sd-daemon.h>
#include <unistd.h>
#include <poll.h>
#include <getopt.h>
#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<string> 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=<APP>
;
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 <application>\n"
" -d, --daemon\n"
" Run as %s a daemon.\n"
" -a, --application=<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);

@ -113,8 +113,7 @@ private:
Daemon & operator= (const Daemon & r);
//! Parse arguments
typedef vector<string> ArgVect;
void parseArgs(const ArgVect & args);
void parseArgs(int argc, char **argv);
//! Fork to a daemon
void daemonize();

@ -28,40 +28,14 @@
#include <ctype.h>
#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);
}

@ -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

Loading…
Cancel
Save