=== modified file 'Makefile' --- Makefile 2014-10-05 20:08:58 +0000 +++ Makefile 2015-07-06 20:09:47 +0000 @@ -69,6 +69,8 @@ GPGME_CFLAGS=$(shell gpgme-config --cflags; getconf LFS_CFLAGS) GPGME_LIBS=$(shell gpgme-config --libs; getconf LFS_LIBS; \ getconf LFS_LDFLAGS) +LIBNL3_CFLAGS=$(shell pkg-config --cflags-only-I libnl-route-3.0) +LIBNL3_LIBS=$(shell pkg-config --libs libnl-route-3.0) # Do not change these two CFLAGS+=$(WARN) $(DEBUG) $(FORTIFY) $(COVERAGE) $(OPTIMIZE) \ @@ -106,7 +108,8 @@ PLUGINS=plugins.d/password-prompt plugins.d/mandos-client \ plugins.d/usplash plugins.d/splashy plugins.d/askpass-fifo \ plugins.d/plymouth -CPROGS=plugin-runner $(PLUGINS) +PLUGIN_HELPERS=plugin-helpers/mandos-client-iprouteadddel +CPROGS=plugin-runner $(PLUGINS) $(PLUGIN_HELPERS) PROGS=mandos mandos-keygen mandos-ctl mandos-monitor $(CPROGS) DOCS=mandos.8 mandos-keygen.8 mandos-monitor.8 mandos-ctl.8 \ mandos.conf.5 mandos-clients.conf.5 plugin-runner.8mandos \ @@ -239,6 +242,10 @@ $(LINK.c) $^ -lrt $(GNUTLS_LIBS) $(AVAHI_LIBS) $(strip\ ) $(GPGME_LIBS) $(LOADLIBES) $(LDLIBS) -o $@ +plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c + $(LINK.c) $(LIBNL3_CFLAGS) $^ $(LIBNL3_LIBS) $(strip\ + ) $(LOADLIBES) $(LDLIBS) -o $@ + .PHONY : all doc html clean distclean mostlyclean maintainer-clean \ check run-client run-server install install-html \ install-server install-client-nokey install-client uninstall \ @@ -273,6 +280,7 @@ @echo "###################################################################" # We set GNOME_KEYRING_CONTROL to block pam_gnome_keyring ./plugin-runner --plugin-dir=plugins.d \ + --plugin-helper-dir=plugin-helpers \ --config-file=plugin-runner.conf \ --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ --env-for=mandos-client:GNOME_KEYRING_CONTROL= \ @@ -352,10 +360,12 @@ install-client-nokey: all doc install --directory $(LIBDIR)/mandos $(CONFDIR) install --directory --mode=u=rwx $(KEYDIR) \ - $(LIBDIR)/mandos/plugins.d + $(LIBDIR)/mandos/plugins.d \ + $(LIBDIR)/mandos/plugin-helpers if [ "$(CONFDIR)" != "$(LIBDIR)/mandos" ]; then \ install --mode=u=rwx \ --directory "$(CONFDIR)/plugins.d"; \ + install --directory "$(CONFDIR)/plugin-helpers"; \ fi install --mode=u=rwx,go=rx --directory \ "$(CONFDIR)/network-hooks.d" @@ -381,6 +391,9 @@ install --mode=u=rwxs,go=rx \ --target-directory=$(LIBDIR)/mandos/plugins.d \ plugins.d/plymouth + install --mode=u=rwxs,go=rx \ + --target-directory=$(LIBDIR)/mandos/plugin-helpers \ + plugin-helpers/mandos-client-iprouteadddel install initramfs-tools-hook \ $(INITRAMFSTOOLS)/hooks/mandos install --mode=u=rw,go=r initramfs-tools-hook-conf \ === modified file 'TODO' --- TODO 2015-07-06 20:14:45 +0000 +++ TODO 2015-07-06 20:29:34 +0000 @@ -52,9 +52,6 @@ ** TODO [#C] Remove code for GNU libc < 2.15 * mandos (server) -** TODO [#B] Work around Avahi issue - Avahi does not announce link-local addresses if any global - addresses exist: http://lists.freedesktop.org/archives/avahi/2010-March/001863.html ** TODO [#B] --notify-command This would allow the mandos.service to use --notify-command="systemd-notify --pid READY=1" @@ -72,7 +69,7 @@ + Approve(False) -> Close client connection immediately ** TODO [#C] python-parsedatetime ** TODO Separate logging logic to own object -** TODO [#A] Limit approval_delay to max gnutls/tls timeout value +** TODO [#B] Limit approval_delay to max gnutls/tls timeout value ** TODO [#B] break the wait on approval_delay if connection dies ** TODO Generate Client.runtime_expansions from client options + extra ** TODO Allow %%(checker)s as a runtime expansion === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2013-10-28 10:04:05 +0000 +++ debian/mandos-client.README.Debian 2015-07-01 20:01:26 +0000 @@ -16,6 +16,8 @@ is possible to verify that the correct password will be received by this client by running the command, on the client: + MANDOSPLUGINHELPERDIR=/usr/lib/$(dpkg-architecture \ + -qDEB_HOST_MULTIARCH)/mandos/plugin-helpers \ /usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH \ )/mandos/plugins.d/mandos-client \ --pubkey=/etc/keys/mandos/pubkey.txt \ @@ -91,4 +93,4 @@ work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". - -- Teddy Hogeborn , Mon, 28 Oct 2013 11:02:26 +0100 + -- Teddy Hogeborn , Mon, 29 Jun 2015 18:17:41 +0200 === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2014-07-16 23:44:54 +0000 +++ initramfs-tools-hook 2015-07-06 20:09:47 +0000 @@ -70,11 +70,13 @@ CONFDIR="/conf/conf.d/mandos" MANDOSDIR="/lib/mandos" PLUGINDIR="${MANDOSDIR}/plugins.d" +PLUGINHELPERDIR="${MANDOSDIR}/plugin-helpers" HOOKDIR="${MANDOSDIR}/network-hooks.d" # Make directories install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ - "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" + "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" \ + "${DESTDIR}${PLUGINHELPERDIR}" install --owner=${mandos_user} --group=${mandos_group} --directory \ --mode=u=rwx "${DESTDIR}${PLUGINDIR}" @@ -98,6 +100,21 @@ esac done +# Copy the packaged plugin helpers +for file in "$libdir"/mandos/plugin-helpers/*; do + base="`basename \"$file\"`" + # Is this plugin overridden? + if [ -e "/etc/mandos/plugin-helpers/$base" ]; then + continue + fi + case "$base" in + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; + "*") : ;; + *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; + esac +done + # Copy any user-supplied plugins for file in /etc/mandos/plugins.d/*; do base="`basename \"$file\"`" @@ -109,6 +126,17 @@ esac done +# Copy any user-supplied plugin helpers +for file in /etc/mandos/plugin-helpers/*; do + base="`basename \"$file\"`" + case "$base" in + *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) + : ;; + "*") : ;; + *) copy_exec "$file" "${PLUGINHELPERDIR}" ;; + esac +done + # Get DEVICE from initramfs.conf and other files . /etc/initramfs-tools/initramfs.conf for conf in /etc/initramfs-tools/conf.d/*; do === added directory 'plugin-helpers' === added file 'plugin-helpers/mandos-client-iprouteadddel.c' --- plugin-helpers/mandos-client-iprouteadddel.c 1970-01-01 00:00:00 +0000 +++ plugin-helpers/mandos-client-iprouteadddel.c 2015-07-05 21:38:01 +0000 @@ -0,0 +1,282 @@ +/* -*- coding: utf-8 -*- */ +/* + * iprouteadddel - Add or delete direct route to a local IP address + * + * Copyright © 2015 Teddy Hogeborn + * Copyright © 2015 Björn Påhlsson + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + * + * Contact the authors at . + */ + +#define _GNU_SOURCE /* asprintf(), + program_invocation_short_name */ +#include /* bool, false, true */ +#include /* fprintf(), stderr, FILE, vfprintf */ +#include /* program_invocation_short_name, + errno, perror(), EINVAL, ENOMEM */ +#include /* va_list, va_start */ +#include /* EXIT_SUCCESS */ +#include /* struct argp_option, error_t, struct + argp_state, ARGP_KEY_ARG, + argp_usage(), ARGP_KEY_END, + ARGP_ERR_UNKNOWN, struct argp, + argp_parse() */ +#include /* EX_USAGE, EX_OSERR */ +#include /* sa_family_t, AF_INET6, AF_INET */ +#include /* PRIdMAX, intmax_t */ + +#include /* struct nl_addr, nl_addr_parse(), + nl_geterror(), + nl_addr_get_family(), + nl_addr_put() */ +#include /* struct rtnl_route, + struct rtnl_nexthop, + rtnl_route_alloc(), + rtnl_route_set_family(), + rtnl_route_set_protocol(), + RTPROT_BOOT, + rtnl_route_set_scope(), + RT_SCOPE_LINK, + rtnl_route_set_type(), + RTN_UNICAST, + rtnl_route_set_dst(), + rtnl_route_set_table(), + RT_TABLE_MAIN, + rtnl_route_nh_alloc(), + rtnl_route_nh_set_ifindex(), + rtnl_route_add_nexthop(), + rtnl_route_add(), + rtnl_route_delete(), + rtnl_route_put(), + rtnl_route_nh_free() */ +#include /* struct nl_sock, nl_socket_alloc(), + nl_connect(), nl_socket_free() */ +#include /* rtnl_link_get_kernel(), + rtnl_link_get_ifindex(), + rtnl_link_put() */ + +bool debug = false; +const char *argp_program_version = "mandos-client-iprouteadddel " VERSION; +const char *argp_program_bug_address = ""; + +/* Function to use when printing errors */ +void perror_plus(const char *print_text){ + int e = errno; + fprintf(stderr, "Mandos plugin helper %s: ", + program_invocation_short_name); + errno = e; + perror(print_text); +} + +__attribute__((format (gnu_printf, 2, 3), nonnull)) +int fprintf_plus(FILE *stream, const char *format, ...){ + va_list ap; + va_start (ap, format); + + fprintf(stream, "Mandos plugin helper %s: ", + program_invocation_short_name); + return vfprintf(stream, format, ap); +} + +int main(int argc, char *argv[]){ + int ret; + int exitcode = EXIT_SUCCESS; + struct arguments { + bool add; /* true: add, false: delete */ + char *address; /* IP address as string */ + struct nl_addr *nl_addr; /* Netlink IP address */ + char *interface; /* interface name */ + } arguments = { .add = true, .address = NULL, .interface = NULL }; + struct argp_option options[] = { + { .name = "debug", .key = 128, + .doc = "Debug mode" }, + { .name = NULL } + }; + struct rtnl_route *route = NULL; + struct rtnl_nexthop *nexthop = NULL; + struct nl_sock *sk = NULL; + + error_t parse_opt(int key, char *arg, struct argp_state *state){ + int lret; + errno = 0; + switch(key){ + case 128: /* --debug */ + debug = true; + break; + case ARGP_KEY_ARG: + switch(state->arg_num){ + case 0: + if(strcasecmp(arg, "add") == 0){ + ((struct arguments *)(state->input))->add = true; + } else if(strcasecmp(arg, "delete") == 0){ + ((struct arguments *)(state->input))->add = false; + } else { + fprintf_plus(stderr, "Unrecognized command: %s\n", arg); + argp_usage(state); + } + break; + case 1: + ((struct arguments *)(state->input))->address = arg; + lret = nl_addr_parse(arg, AF_UNSPEC, &(((struct arguments *) + (state->input)) + ->nl_addr)); + if(lret != 0){ + fprintf_plus(stderr, "Failed to parse address %s: %s\n", + arg, nl_geterror(lret)); + argp_usage(state); + } + break; + case 2: + ((struct arguments *)(state->input))->interface = arg; + break; + default: + argp_usage(state); + } + break; + case ARGP_KEY_END: + if(state->arg_num < 3){ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return errno; + } + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = "[ add | delete ] ADDRESS INTERFACE", + .doc = "Mandos client helper -- Add or delete" + " local route to IP address on interface" }; + + ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, &arguments); + switch(ret){ + case 0: + break; + case EINVAL: + exit(EX_USAGE); + case ENOMEM: + default: + errno = ret; + perror_plus("argp_parse"); + exitcode = EX_OSERR; + goto end; + } + /* Get netlink socket */ + sk = nl_socket_alloc(); + if(sk == NULL){ + fprintf_plus(stderr, "Failed to allocate netlink socket: %s\n", + nl_geterror(ret)); + exitcode = EX_OSERR; + goto end; + } + /* Connect socket to netlink */ + ret = nl_connect(sk, NETLINK_ROUTE); + if(ret < 0){ + fprintf_plus(stderr, "Failed to connect socket to netlink: %s\n", + nl_geterror(ret)); + exitcode = EX_OSERR; + goto end; + } + /* Get link object of specified interface */ + struct rtnl_link *link = NULL; + ret = rtnl_link_get_kernel(sk, 0, arguments.interface, &link); + if(ret < 0){ + fprintf_plus(stderr, "Failed to use interface %s: %s\n", + arguments.interface, nl_geterror(ret)); + exitcode = EX_OSERR; + goto end; + } + /* Get netlink route object */ + route = rtnl_route_alloc(); + if(route == NULL){ + fprintf_plus(stderr, "Failed to get netlink route:\n"); + exitcode = EX_OSERR; + goto end; + } + /* Get address family of specified address */ + sa_family_t af = (sa_family_t)nl_addr_get_family(arguments.nl_addr); + if(debug){ + fprintf_plus(stderr, "Address family of %s is %s (%" PRIdMAX + ")\n", arguments.address, + af == AF_INET6 ? "AF_INET6" : + ( af == AF_INET ? "AF_INET" : "UNKNOWN"), + (intmax_t)af); + } + /* Set route parameters: */ + rtnl_route_set_family(route, (uint8_t)af); /* Address family */ + rtnl_route_set_protocol(route, RTPROT_BOOT); /* protocol - see + ip-route(8) */ + rtnl_route_set_scope(route, RT_SCOPE_LINK); /* link scope */ + rtnl_route_set_type(route, RTN_UNICAST); /* normal unicast + address route */ + rtnl_route_set_dst(route, arguments.nl_addr); /* Destination + address */ + rtnl_route_set_table(route, RT_TABLE_MAIN); /* "main" routing + table */ + /* Create nexthop */ + nexthop = rtnl_route_nh_alloc(); + if(nexthop == NULL){ + fprintf_plus(stderr, "Failed to get netlink route nexthop\n"); + exitcode = EX_OSERR; + goto end; + } + /* Get index number of specified interface */ + int ifindex = rtnl_link_get_ifindex(link); + if(debug){ + fprintf_plus(stderr, "ifindex of %s is %d\n", arguments.interface, + ifindex); + } + /* Set interface index number on nexthop object */ + rtnl_route_nh_set_ifindex(nexthop, ifindex); + /* Set route tu use nexthop object */ + rtnl_route_add_nexthop(route, nexthop); + /* Add or delete route? */ + if(arguments.add){ + ret = rtnl_route_add(sk, route, NLM_F_EXCL); + } else { + ret = rtnl_route_delete(sk, route, 0); + } + if(ret < 0){ + fprintf_plus(stderr, "Failed to %s route: %s\n", + arguments.add ? "add" : "delete", + nl_geterror(ret)); + exitcode = EX_OSERR; + goto end; + } + end: + /* Deallocate route */ + if(route){ + rtnl_route_put(route); + } else if(nexthop) { + /* Deallocate route nexthop */ + rtnl_route_nh_free(nexthop); + } + /* Deallocate parsed address */ + if(arguments.nl_addr){ + nl_addr_put(arguments.nl_addr); + } + /* Deallocate link struct */ + if(link){ + rtnl_link_put(link); + } + /* Deallocate netlink socket struct */ + if(sk){ + nl_socket_free(sk); + } + return exitcode; +} === modified file 'plugin-runner.c' --- plugin-runner.c 2014-07-16 01:41:23 +0000 +++ plugin-runner.c 2015-06-28 16:35:27 +0000 @@ -76,6 +76,7 @@ #define BUFFER_SIZE 256 #define PDIR "/lib/mandos/plugins.d" +#define PHDIR "/lib/mandos/plugin-helpers" #define AFILE "/conf/conf.d/mandos/plugin-runner.conf" const char *argp_program_version = "plugin-runner " VERSION; @@ -347,6 +348,7 @@ int main(int argc, char *argv[]){ char *plugindir = NULL; + char *pluginhelperdir = NULL; char *argfile = NULL; FILE *conffp; struct dirent **direntries = NULL; @@ -414,6 +416,10 @@ .doc = "Group ID the plugins will run as", .group = 3 }, { .name = "debug", .key = 132, .doc = "Debug mode", .group = 4 }, + { .name = "plugin-helper-dir", .key = 133, + .arg = "DIRECTORY", + .doc = "Specify a different plugin helper directory", + .group = 2 }, /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -545,6 +551,13 @@ case 132: /* --debug */ debug = true; break; + case 133: /* --plugin-helper-dir */ + free(pluginhelperdir); + pluginhelperdir = strdup(arg); + if(pluginhelperdir != NULL){ + errno = 0; + } + break; /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -601,6 +614,7 @@ case 130: /* --userid */ case 131: /* --groupid */ case 132: /* --debug */ + case 133: /* --plugin-helper-dir */ case '?': /* --help */ case -3: /* --usage */ case 'V': /* --version */ @@ -761,6 +775,24 @@ goto fallback; } + { + char *pluginhelperenv; + bool bret = true; + ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s", + pluginhelperdir != NULL ? pluginhelperdir : PHDIR); + if(ret != -1){ + bret = add_environment(getplugin(NULL), pluginhelperenv, true); + } + if(ret == -1 or not bret){ + error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR" + " environment variable to \"%s\" for all plugins\n", + pluginhelperdir != NULL ? pluginhelperdir : PHDIR); + } + if(ret != -1){ + free(pluginhelperenv); + } + } + if(debug){ for(plugin *p = plugin_list; p != NULL; p=p->next){ fprintf(stderr, "Plugin: %s has %d arguments\n", @@ -1336,6 +1368,7 @@ free_plugin_list(); free(plugindir); + free(pluginhelperdir); free(argfile); return exitstatus; === modified file 'plugin-runner.xml' --- plugin-runner.xml 2015-07-06 20:14:45 +0000 +++ plugin-runner.xml 2015-07-06 20:29:34 +0000 @@ -114,6 +114,9 @@ + + @@ -320,6 +323,21 @@ + + + + Specify a different plugin helper directory. The default + is /lib/mandos/plugin-helpers, which + will exist in the initial RAM disk + environment. (This will simply be passed to all plugins + via the MANDOSPLUGINHELPERDIR environment + variable. See ) + + + + + @@ -426,7 +444,11 @@ The plugin will run in the initial RAM disk environment, so care must be taken not to depend on any files or running - services not available there. + services not available there. Any helper executables required + by the plugin (which are not in the PATH) can + be placed in the plugin helper directory, the name of which + will be made available to the plugin via the + MANDOSPLUGINHELPERDIR environment variable. The plugin must exit cleanly and free all allocated resources @@ -475,7 +497,9 @@ only passes on its environment to all the plugins. The environment passed to plugins can be modified using the and - options. + options. Also, the option + will affect the environment variable + MANDOSPLUGINHELPERDIR for the plugins. === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2015-03-10 18:52:09 +0000 +++ plugins.d/mandos-client.c 2015-07-06 20:29:34 +0000 @@ -9,8 +9,8 @@ * "browse_callback", and parts of "main". * * Everything else is - * Copyright © 2008-2014 Teddy Hogeborn - * Copyright © 2008-2014 Björn Påhlsson + * Copyright © 2008-2015 Teddy Hogeborn + * Copyright © 2008-2015 Björn Påhlsson * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -757,6 +757,223 @@ static void empty_log(__attribute__((unused)) AvahiLogLevel level, __attribute__((unused)) const char *txt){} +/* Set effective uid to 0, return errno */ +__attribute__((warn_unused_result)) +error_t raise_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(0) == -1){ + ret_errno = errno; + } + errno = old_errno; + return ret_errno; +} + +/* Set effective and real user ID to 0. Return errno. */ +__attribute__((warn_unused_result)) +error_t raise_privileges_permanently(void){ + error_t old_errno = errno; + error_t ret_errno = raise_privileges(); + if(ret_errno != 0){ + errno = old_errno; + return ret_errno; + } + if(setuid(0) == -1){ + ret_errno = errno; + } + errno = old_errno; + return ret_errno; +} + +/* Set effective user ID to unprivileged saved user ID */ +__attribute__((warn_unused_result)) +error_t lower_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(uid) == -1){ + ret_errno = errno; + } + errno = old_errno; + return ret_errno; +} + +/* Lower privileges permanently */ +__attribute__((warn_unused_result)) +error_t lower_privileges_permanently(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(setuid(uid) == -1){ + ret_errno = errno; + } + errno = old_errno; + return ret_errno; +} + +/* Helper function to add_local_route() and delete_local_route() */ +__attribute__((nonnull, warn_unused_result)) +static bool add_delete_local_route(const bool add, + const char *address, + AvahiIfIndex if_index){ + int ret; + char helper[] = "mandos-client-iprouteadddel"; + char add_arg[] = "add"; + char delete_arg[] = "delete"; + char debug_flag[] = "--debug"; + char *pluginhelperdir = getenv("MANDOSPLUGINHELPERDIR"); + if(pluginhelperdir == NULL){ + if(debug){ + fprintf_plus(stderr, "MANDOSPLUGINHELPERDIR environment" + " variable not set; cannot run helper\n"); + } + return false; + } + + char interface[IF_NAMESIZE]; + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + perror_plus("if_indextoname"); + return false; + } + + int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); + if(devnull == -1){ + perror_plus("open(\"/dev/null\", O_RDONLY)"); + return false; + } + pid_t pid = fork(); + if(pid == 0){ + /* Child */ + /* Raise privileges */ + errno = raise_privileges_permanently(); + if(errno != 0){ + perror_plus("Failed to raise privileges"); + /* _exit(EX_NOPERM); */ + } else { + /* Set group */ + errno = 0; + ret = setgid(0); + if(ret == -1){ + perror_plus("setgid"); + _exit(EX_NOPERM); + } + /* Reset supplementary groups */ + errno = 0; + ret = setgroups(0, NULL); + if(ret == -1){ + perror_plus("setgroups"); + _exit(EX_NOPERM); + } + } + ret = dup2(devnull, STDIN_FILENO); + if(ret == -1){ + perror_plus("dup2(devnull, STDIN_FILENO)"); + _exit(EX_OSERR); + } + ret = (int)TEMP_FAILURE_RETRY(close(devnull)); + if(ret == -1){ + perror_plus("close"); + _exit(EX_OSERR); + } + ret = dup2(STDERR_FILENO, STDOUT_FILENO); + if(ret == -1){ + perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); + _exit(EX_OSERR); + } + int helperdir_fd = (int)TEMP_FAILURE_RETRY(open(pluginhelperdir, + O_RDONLY + | O_DIRECTORY + | O_PATH + | O_CLOEXEC)); + if(helperdir_fd == -1){ + perror_plus("open"); + _exit(EX_UNAVAILABLE); + } + int helper_fd = (int)TEMP_FAILURE_RETRY(openat(helperdir_fd, + helper, O_RDONLY)); + if(helper_fd == -1){ + perror_plus("openat"); + _exit(EX_UNAVAILABLE); + } + TEMP_FAILURE_RETRY(close(helperdir_fd)); +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + if(fexecve(helper_fd, (char *const []) + { helper, add ? add_arg : delete_arg, (char *)address, + interface, debug ? debug_flag : NULL, NULL }, + environ) == -1){ +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + perror_plus("fexecve"); + _exit(EXIT_FAILURE); + } + } + if(pid == -1){ + perror_plus("fork"); + return false; + } + int status; + pid_t pret = -1; + errno = 0; + do { + pret = waitpid(pid, &status, 0); + if(pret == -1 and errno == EINTR and quit_now){ + int errno_raising = 0; + if((errno = raise_privileges()) != 0){ + errno_raising = errno; + perror_plus("Failed to raise privileges in order to" + " kill helper program"); + } + if(kill(pid, SIGTERM) == -1){ + perror_plus("kill"); + } + if((errno_raising == 0) and (errno = lower_privileges()) != 0){ + perror_plus("Failed to lower privileges after killing" + " helper program"); + } + return false; + } + } while(pret == -1 and errno == EINTR); + if(pret == -1){ + perror_plus("waitpid"); + return false; + } + if(WIFEXITED(status)){ + if(WEXITSTATUS(status) != 0){ + fprintf_plus(stderr, "Error: iprouteadddel exited" + " with status %d\n", WEXITSTATUS(status)); + return false; + } + return true; + } + if(WIFSIGNALED(status)){ + fprintf_plus(stderr, "Error: iprouteadddel died by" + " signal %d\n", WTERMSIG(status)); + return false; + } + fprintf_plus(stderr, "Error: iprouteadddel crashed\n"); + return false; +} + +__attribute__((nonnull, warn_unused_result)) +static bool add_local_route(const char *address, + AvahiIfIndex if_index){ + if(debug){ + fprintf_plus(stderr, "Adding route to %s\n", address); + } + return add_delete_local_route(true, address, if_index); +} + +__attribute__((nonnull, warn_unused_result)) +static bool delete_local_route(const char *address, + AvahiIfIndex if_index){ + if(debug){ + fprintf_plus(stderr, "Removing route to %s\n", address); + } + return add_delete_local_route(false, address, if_index); +} + /* Called when a Mandos server is found */ __attribute__((nonnull, warn_unused_result)) static int start_mandos_communication(const char *ip, in_port_t port, @@ -773,6 +990,7 @@ int retval = -1; gnutls_session_t session; int pf; /* Protocol family */ + bool route_added = false; errno = 0; @@ -836,7 +1054,7 @@ PRIuMAX "\n", ip, (uintmax_t)port); } - tcp_sd = socket(pf, SOCK_STREAM, 0); + tcp_sd = socket(pf, SOCK_STREAM | SOCK_CLOEXEC, 0); if(tcp_sd < 0){ int e = errno; perror_plus("socket"); @@ -931,25 +1149,61 @@ goto mandos_end; } - if(af == AF_INET6){ - ret = connect(tcp_sd, (struct sockaddr *)&to, - sizeof(struct sockaddr_in6)); - } else { - ret = connect(tcp_sd, (struct sockaddr *)&to, /* IPv4 */ - sizeof(struct sockaddr_in)); - } - if(ret < 0){ - if((errno != ECONNREFUSED and errno != ENETUNREACH) or debug){ - int e = errno; - perror_plus("connect"); - errno = e; - } - goto mandos_end; - } - - if(quit_now){ - errno = EINTR; - goto mandos_end; + while(true){ + if(af == AF_INET6){ + ret = connect(tcp_sd, (struct sockaddr *)&to, + sizeof(struct sockaddr_in6)); + } else { + ret = connect(tcp_sd, (struct sockaddr *)&to, /* IPv4 */ + sizeof(struct sockaddr_in)); + } + if(ret < 0){ + if(errno == ENETUNREACH + and if_index != AVAHI_IF_UNSPEC + and connect_to == NULL + and not route_added and + ((af == AF_INET6 and not + IN6_IS_ADDR_LINKLOCAL(&(((struct sockaddr_in6 *) + &to)->sin6_addr))) + or (af == AF_INET and + /* Not a a IPv4LL address */ + (ntohl(((struct sockaddr_in *)&to)->sin_addr.s_addr) + & 0xFFFF0000L) != 0xA9FE0000L))){ + /* Work around Avahi bug - Avahi does not announce link-local + addresses if it has a global address, so local hosts with + *only* a link-local address (e.g. Mandos clients) cannot + connect to a Mandos server announced by Avahi on a server + host with a global address. Work around this by retrying + with an explicit route added with the server's address. + + Avahi bug reference: + http://lists.freedesktop.org/archives/avahi/2010-February/001833.html + https://bugs.debian.org/587961 + */ + if(debug){ + fprintf_plus(stderr, "Mandos server unreachable, trying" + " direct route\n"); + } + int e = errno; + route_added = add_local_route(ip, if_index); + if(route_added){ + continue; + } + errno = e; + } + if(errno != ECONNREFUSED or debug){ + int e = errno; + perror_plus("connect"); + errno = e; + } + goto mandos_end; + } + + if(quit_now){ + errno = EINTR; + goto mandos_end; + } + break; } const char *out = mandos_protocol_version; @@ -1138,6 +1392,12 @@ mandos_end: { + if(route_added){ + if(not delete_local_route(ip, if_index)){ + fprintf_plus(stderr, "Failed to delete local route to %s on" + " interface %d", ip, if_index); + } + } int e = errno; free(decrypted_buffer); free(buffer); @@ -1569,64 +1829,13 @@ } } -/* Set effective uid to 0, return errno */ -__attribute__((warn_unused_result)) -error_t raise_privileges(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(seteuid(0) == -1){ - ret_errno = errno; - } - errno = old_errno; - return ret_errno; -} - -/* Set effective and real user ID to 0. Return errno. */ -__attribute__((warn_unused_result)) -error_t raise_privileges_permanently(void){ - error_t old_errno = errno; - error_t ret_errno = raise_privileges(); - if(ret_errno != 0){ - errno = old_errno; - return ret_errno; - } - if(setuid(0) == -1){ - ret_errno = errno; - } - errno = old_errno; - return ret_errno; -} - -/* Set effective user ID to unprivileged saved user ID */ -__attribute__((warn_unused_result)) -error_t lower_privileges(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(seteuid(uid) == -1){ - ret_errno = errno; - } - errno = old_errno; - return ret_errno; -} - -/* Lower privileges permanently */ -__attribute__((warn_unused_result)) -error_t lower_privileges_permanently(void){ - error_t old_errno = errno; - error_t ret_errno = 0; - if(setuid(uid) == -1){ - ret_errno = errno; - } - errno = old_errno; - return ret_errno; -} - __attribute__((nonnull)) void run_network_hooks(const char *mode, const char *interface, const float delay){ struct dirent **direntries = NULL; if(hookdir_fd == -1){ - hookdir_fd = open(hookdir, O_RDONLY); + hookdir_fd = open(hookdir, O_RDONLY | O_DIRECTORY | O_PATH + | O_CLOEXEC); if(hookdir_fd == -1){ if(errno == ENOENT){ if(debug){ @@ -1657,7 +1866,11 @@ } struct dirent *direntry; int ret; - int devnull = open("/dev/null", O_RDONLY); + int devnull = (int)TEMP_FAILURE_RETRY(open("/dev/null", O_RDONLY)); + if(devnull == -1){ + perror_plus("open(\"/dev/null\", O_RDONLY)"); + return; + } for(int i = 0; i < numhooks; i++){ direntry = direntries[i]; if(debug){ @@ -1687,21 +1900,6 @@ perror_plus("setgroups"); _exit(EX_NOPERM); } - ret = dup2(devnull, STDIN_FILENO); - if(ret == -1){ - perror_plus("dup2(devnull, STDIN_FILENO)"); - _exit(EX_OSERR); - } - ret = close(devnull); - if(ret == -1){ - perror_plus("close"); - _exit(EX_OSERR); - } - ret = dup2(STDERR_FILENO, STDOUT_FILENO); - if(ret == -1){ - perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); - _exit(EX_OSERR); - } ret = setenv("MANDOSNETHOOKDIR", hookdir, 1); if(ret == -1){ perror_plus("setenv"); @@ -1742,7 +1940,9 @@ _exit(EX_OSERR); } } - int hook_fd = openat(hookdir_fd, direntry->d_name, O_RDONLY); + int hook_fd = (int)TEMP_FAILURE_RETRY(openat(hookdir_fd, + direntry->d_name, + O_RDONLY)); if(hook_fd == -1){ perror_plus("openat"); _exit(EXIT_FAILURE); @@ -1751,6 +1951,21 @@ perror_plus("close"); _exit(EXIT_FAILURE); } + ret = dup2(devnull, STDIN_FILENO); + if(ret == -1){ + perror_plus("dup2(devnull, STDIN_FILENO)"); + _exit(EX_OSERR); + } + ret = (int)TEMP_FAILURE_RETRY(close(devnull)); + if(ret == -1){ + perror_plus("close"); + _exit(EX_OSERR); + } + ret = dup2(STDERR_FILENO, STDOUT_FILENO); + if(ret == -1){ + perror_plus("dup2(STDERR_FILENO, STDOUT_FILENO)"); + _exit(EX_OSERR); + } if(fexecve(hook_fd, (char *const []){ direntry->d_name, NULL }, environ) == -1){ perror_plus("fexecve"); @@ -2204,7 +2419,7 @@ goto end; } } - + { /* Work around Debian bug #633582: */ @@ -2237,7 +2452,7 @@ TEMP_FAILURE_RETRY(close(seckey_fd)); } } - + if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ int pubkey_fd = open(pubkey, O_RDONLY); if(pubkey_fd == -1){ @@ -2258,7 +2473,7 @@ TEMP_FAILURE_RETRY(close(pubkey_fd)); } } - + /* Lower privileges */ ret_errno = lower_privileges(); if(ret_errno != 0){ @@ -2733,8 +2948,10 @@ /* Removes the GPGME temp directory and all files inside */ if(tempdir != NULL){ struct dirent **direntries = NULL; - int tempdir_fd = (int)TEMP_FAILURE_RETRY(open(tempdir, O_RDONLY | - O_NOFOLLOW)); + int tempdir_fd = (int)TEMP_FAILURE_RETRY(open(tempdir, O_RDONLY + | O_NOFOLLOW + | O_DIRECTORY + | O_PATH)); if(tempdir_fd == -1){ perror_plus("open"); } else { === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2015-03-10 18:52:09 +0000 +++ plugins.d/mandos-client.xml 2015-07-06 20:29:34 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -36,6 +36,7 @@ 2012 2013 2014 + 2015 Teddy Hogeborn Björn Påhlsson @@ -445,9 +446,22 @@ ENVIRONMENT + + + MANDOSPLUGINHELPERDIR + + + This environment variable will be assumed to contain the + directory containing any helper executables. The use and + nature of these helper executables, if any, is + purposefully not documented. + + + + - This program does not use any environment variables, not even - the ones provided by cryptsetup8 .