=== modified file 'Makefile' --- Makefile 2012-06-01 18:39:03 +0000 +++ Makefile 2012-06-17 22:26:40 +0000 @@ -23,7 +23,7 @@ OPTIMIZE=-Os LANGUAGE=-std=gnu99 htmldir=man -version=1.5.5 +version=1.6.0 SED=sed USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534))) @@ -68,13 +68,13 @@ --param make.single.year.ranges 1 \ --param man.output.quietly 1 \ --param man.authors.section.enabled 0 \ - /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ + /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ $(notdir $<); \ $(MANPOST) $(notdir $@);\ - if locale --all | grep --regexp='^en_US\.utf8$$' && type man \ - 2>/dev/null; then LANG=en_US.UTF-8 MANWIDTH=80 man \ - --warnings --encoding=UTF-8 --local-file $(notdir $@); fi \ - >/dev/null) + if locale --all 2>/dev/null | grep --regexp='^en_US\.utf8$$' \ + && type man 2>/dev/null; then LANG=en_US.UTF-8 MANWIDTH=80 \ + man --warnings --encoding=UTF-8 --local-file $(notdir $@); \ + fi >/dev/null) # DocBook-to-man post-processing to fix a '\n' escape bug MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' === modified file 'NEWS' --- NEWS 2012-06-01 18:39:03 +0000 +++ NEWS 2012-06-17 22:26:40 +0000 @@ -1,6 +1,16 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. +Version 1.6.0 (2012-06-18) +* Server +** Takes new --foreground option +** Init script supports new "status" action. +* Client +** Now uses all interfaces by default; the --interface option can + still be used to restrict it, and the argument to --interface (as + well as the $DEVICE environment variable for the network hooks) is + now a comma-separated list of interfaces to use. + Version 1.5.5 (2012-06-01) * Server ** Server takes new --socket option === modified file 'TODO' --- TODO 2012-05-26 22:21:17 +0000 +++ TODO 2012-06-17 14:55:31 +0000 @@ -5,13 +5,12 @@ * mandos-applet * mandos-client -** TODO [#A] Wireless network hook ** TODO [#B] Use capabilities instead of seteuid(). ** TODO [#B] Use struct sockaddr_storage instead of a union ** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() ** TODO [#B] Use getnameinfo(serv=NULL, NI_NUMERICHOST) instead of inet_ntop() ** TODO [#B] Prefer /run/tmp over /tmp, if it exists -** TODO [#B] Use in_port_t instead of uint16_t for port numbers. +** TODO [#C] Make start_mandos_communication() take "struct server". * splashy ** TODO [#B] use scandir(3) instead of readdir(3) @@ -19,7 +18,6 @@ * usplash (Deprecated) ** TODO [#A] Make it work again ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO Use [[info:libc:Argz%20Functions][argz_extract]] * askpass-fifo ** TODO [#B] Drop privileges after opening FIFO. @@ -33,7 +31,7 @@ * plugin-runner ** TODO handle printing for errors for plugins -*** Hook up stderr of plugins, buffer them, and prepend mandos pluig [plugin name] +*** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" ** TODO [#B] use scandir(3) instead of readdir(3) ** TODO [#C] use same file name rules as run-parts(8) ** kernel command line option for debug info @@ -46,8 +44,6 @@ ** TODO [#C] config for TXT record ** TODO Log level dbus option SetLogLevel D-Bus call -** TODO Implement --foreground :BUGS: - [[info:standards:Option%20Table][Table of Long Options]] ** TODO [#C] DBusServiceObjectUsingSuper ** TODO [#B] Global enable/disable flag ** TODO [#B] By-client countdown on number of secrets given === modified file 'common.ent' --- common.ent 2012-06-01 18:39:03 +0000 +++ common.ent 2012-06-17 22:26:40 +0000 @@ -1,3 +1,3 @@ - + === modified file 'debian/changelog' --- debian/changelog 2012-06-01 18:39:03 +0000 +++ debian/changelog 2012-06-17 22:26:40 +0000 @@ -1,3 +1,18 @@ +mandos (1.6.0-1) unstable; urgency=low + + * New upstream release. + * debian/copyright (Copyright): Join the two lines to a single line. + * debian/mandos-client.README.Debian: Update to refer to the new + location of the example network hooks, and the new feature of using + all network interfaces. + * debian/mandos-client.docs (network-hooks.d): Removed. + * debian/mandos-client.examples (network-hooks.d): New. + * debian/rules (binary-common): Added "dh_installexamples". + (binary-common/dh_fixperms): Exclude new location of + "network-hooks.d". + + -- Teddy Hogeborn Mon, 18 Jun 2012 00:15:23 +0200 + mandos (1.5.5-1) unstable; urgency=low * New upstream release. === modified file 'debian/copyright' --- debian/copyright 2012-05-24 18:10:10 +0000 +++ debian/copyright 2012-06-01 21:48:12 +0000 @@ -5,7 +5,7 @@ Files: * Copyright: Copyright © 2008-2012 Teddy Hogeborn -Copyright: Copyright © 2008-2012 Björn Påhlsson + Copyright © 2008-2012 Björn Påhlsson License: GPL-3+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2012-01-01 04:52:07 +0000 +++ debian/mandos-client.README.Debian 2012-06-16 19:30:08 +0000 @@ -32,9 +32,9 @@ * Specifying a Client Network Interface - At boot time the network interface to use will by default be - automatically detected. If this should result in an incorrect - interface, edit the DEVICE setting in the + At boot time the network interfaces to use will by default be + automatically detected. If this should result in incorrect + interfaces, edit the DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" file. (The default setting is empty, meaning it will autodetect the interface.) *If* the DEVICE setting is changed, it will be necessary to update the initrd image @@ -42,21 +42,21 @@ update-initramfs -k all -u - The device can be overridden at boot time on the Linux kernel + The device can also be overridden at boot time on the Linux kernel command line using the sixth colon-separated field of the "ip=" option; for exact syntax, read the documentation in the file "/usr/share/doc/linux-doc-*/Documentation/filesystems/nfsroot.txt", available in the "linux-doc-*" package. - Note that since this network interface is used in the initial RAM - disk environment, the network interface *must* exist at that stage. - Thus, the interface can *not* be a pseudo-interface such as "br0" or - "tun0"; instead, only real interface (such as "eth0") can be used. + Note that since the network interfaces are used in the initial RAM + disk environment, the network interfaces *must* exist at that stage. + Thus, an interface can *not* be a pseudo-interface such as "br0" or + "tun0"; instead, only real interfaces (such as "eth0") can be used. This can be overcome by writing a "network hook" program to create - the interface (see mandos-client(8mandos)) and placing it in + an interface (see mandos-client(8mandos)) and placing it in "/etc/mandos/network-hooks.d", from where it will be copied into the initial RAM disk. Example network hook scripts can be found in - "/usr/share/doc/mandos-client/network-hooks.d". + "/usr/share/doc/mandos-client/examples/network-hooks.d". * User-Supplied Plugins @@ -90,4 +90,4 @@ work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". - -- Teddy Hogeborn , Mon, 28 Nov 2011 23:07:22 +0100 + -- Teddy Hogeborn , Sat, 16 Jun 2012 13:09:58 +0200 === modified file 'debian/mandos-client.docs' --- debian/mandos-client.docs 2011-11-27 02:32:20 +0000 +++ debian/mandos-client.docs 2012-06-01 21:48:12 +0000 @@ -1,4 +1,3 @@ NEWS README TODO -network-hooks.d === added file 'debian/mandos-client.examples' --- debian/mandos-client.examples 1970-01-01 00:00:00 +0000 +++ debian/mandos-client.examples 2012-06-01 21:48:12 +0000 @@ -0,0 +1,1 @@ +network-hooks.d === modified file 'debian/rules' --- debian/rules 2012-02-21 21:20:12 +0000 +++ debian/rules 2012-06-01 21:48:12 +0000 @@ -79,6 +79,7 @@ dh_testroot dh_installchangelogs dh_installdocs + dh_installexamples dh_link dh_strip dh_compress @@ -86,7 +87,7 @@ --exclude etc/mandos/clients.conf \ --exclude etc/mandos/plugins.d \ --exclude usr/lib/mandos/plugins.d \ - --exclude usr/share/doc/mandos-client/network-hooks.d + --exclude usr/share/doc/mandos-client/examples/network-hooks.d dh_installdeb dh_shlibdeps dh_gencontrol === modified file 'init.d-mandos' --- init.d-mandos 2011-10-05 16:00:56 +0000 +++ init.d-mandos 2012-06-01 21:48:12 +0000 @@ -149,6 +149,9 @@ ;; esac ;; + status) + status_of_proc "$DAEMON" "$NAME" -p "$PIDFILE" + ;; *) #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2 === modified file 'mandos' --- mandos 2012-06-01 18:39:03 +0000 +++ mandos 2012-06-17 22:26:40 +0000 @@ -88,7 +88,7 @@ except ImportError: SO_BINDTODEVICE = None -version = "1.5.5" +version = "1.6.0" stored_state_file = "clients.pickle" logger = logging.getLogger() @@ -1948,21 +1948,18 @@ try: self.socket.setsockopt(socket.SOL_SOCKET, SO_BINDTODEVICE, - str(self.interface - + '\0')) + str(self.interface + '\0')) except socket.error as error: if error.errno == errno.EPERM: - logger.error("No permission to" - " bind to interface %s", - self.interface) + logger.error("No permission to bind to" + " interface %s", self.interface) elif error.errno == errno.ENOPROTOOPT: logger.error("SO_BINDTODEVICE not available;" " cannot bind to interface %s", self.interface) elif error.errno == errno.ENODEV: - logger.error("Interface %s does not" - " exist, cannot bind", - self.interface) + logger.error("Interface %s does not exist," + " cannot bind", self.interface) else: raise # Only bind(2) the socket if we really need to. @@ -2201,6 +2198,8 @@ " socket to use instead of creating one") parser.add_argument("--statedir", metavar="DIR", help="Directory to save/restore state in") + parser.add_argument("--foreground", action="store_true", + help="Run in foreground") options = parser.parse_args() @@ -2222,7 +2221,8 @@ "debuglevel": "", "restore": "True", "socket": "", - "statedir": "/var/lib/mandos" + "statedir": "/var/lib/mandos", + "foreground": "False", } # Parse config file for server-global settings @@ -2233,7 +2233,7 @@ # Convert the SafeConfigParser object to a dict server_settings = server_config.defaults() # Use the appropriate methods on the non-string config options - for option in ("debug", "use_dbus", "use_ipv6"): + for option in ("debug", "use_dbus", "use_ipv6", "foreground"): server_settings[option] = server_config.getboolean("DEFAULT", option) if server_settings["port"]: @@ -2255,7 +2255,7 @@ for option in ("interface", "address", "port", "debug", "priority", "servicename", "configdir", "use_dbus", "use_ipv6", "debuglevel", "restore", - "statedir", "socket"): + "statedir", "socket", "foreground"): value = getattr(options, option) if value is not None: server_settings[option] = value @@ -2264,6 +2264,9 @@ for option in server_settings.keys(): if type(server_settings[option]) is str: server_settings[option] = unicode(server_settings[option]) + # Debug implies foreground + if server_settings["debug"]: + server_settings["foreground"] = True # Now we have our good server settings in "server_settings" ################################################################## @@ -2275,6 +2278,7 @@ use_ipv6 = server_settings["use_ipv6"] stored_state_path = os.path.join(server_settings["statedir"], stored_state_file) + foreground = server_settings["foreground"] if debug: initlogger(debug, logging.DEBUG) @@ -2312,8 +2316,9 @@ use_dbus=use_dbus, socketfd=(server_settings["socket"] or None)) - if not debug: + if not foreground: pidfilename = "/var/run/mandos.pid" + pidfile = None try: pidfile = open(pidfilename, "w") except IOError as e: @@ -2358,7 +2363,7 @@ os.close(null) # Need to fork before connecting to D-Bus - if not debug: + if not foreground: # Close all input and output, do double fork, etc. daemon() @@ -2497,18 +2502,16 @@ if not tcp_server.clients: logger.warning("No clients defined") - if not debug: - try: - with pidfile: - pid = os.getpid() - pidfile.write(str(pid) + "\n".encode("utf-8")) - del pidfile - except IOError: - logger.error("Could not write to file %r with PID %d", - pidfilename, pid) - except NameError: - # "pidfile" was never created - pass + if not foreground: + if pidfile is not None: + try: + with pidfile: + pid = os.getpid() + pidfile.write(str(pid) + "\n".encode("utf-8")) + except IOError: + logger.error("Could not write to file %r with PID %d", + pidfilename, pid) + del pidfile del pidfilename signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) === modified file 'mandos-ctl' --- mandos-ctl 2012-06-01 18:39:03 +0000 +++ mandos-ctl 2012-06-17 22:26:40 +0000 @@ -63,7 +63,7 @@ server_path = "/" server_interface = domain + ".Mandos" client_interface = domain + ".Mandos.Client" -version = "1.5.5" +version = "1.6.0" def timedelta_to_milliseconds(td): """Convert a datetime.timedelta object to milliseconds""" === modified file 'mandos-keygen' --- mandos-keygen 2012-06-01 18:39:03 +0000 +++ mandos-keygen 2012-06-17 22:26:40 +0000 @@ -21,7 +21,7 @@ # Contact the authors at . # -VERSION="1.5.5" +VERSION="1.6.0" KEYDIR="/etc/keys/mandos" KEYTYPE=DSA === modified file 'mandos-monitor' --- mandos-monitor 2012-06-01 18:39:03 +0000 +++ mandos-monitor 2012-06-17 22:26:40 +0000 @@ -55,7 +55,7 @@ domain = 'se.recompile' server_interface = domain + '.Mandos' client_interface = domain + '.Mandos.Client' -version = "1.5.5" +version = "1.6.0" # Always run in monochrome mode urwid.curses_display.curses.has_colors = lambda : False === modified file 'mandos-options.xml' --- mandos-options.xml 2012-05-26 22:21:17 +0000 +++ mandos-options.xml 2012-06-17 14:55:31 +0000 @@ -103,4 +103,11 @@ default, the server will create a new network socket. + + This option will make the server run in the foreground and not + write a PID file. The default is to not run + in the foreground, except in mode, which + implies this option. + + === modified file 'mandos.conf' --- mandos.conf 2011-11-26 22:22:20 +0000 +++ mandos.conf 2012-06-17 14:55:31 +0000 @@ -42,3 +42,6 @@ # The directory where state is saved ;statedir = /var/lib/mandos + +# Whether to run in the foreground +;foreground = False === modified file 'mandos.lsm' --- mandos.lsm 2012-06-01 18:39:03 +0000 +++ mandos.lsm 2012-06-17 22:26:40 +0000 @@ -1,7 +1,7 @@ Begin4 Title: Mandos -Version: 1.5.5 -Entered-date: 2012-06-01 +Version: 1.6.0 +Entered-date: 2012-06-18 Description: The Mandos system allows computers to have encrypted root file systems and at the same time be capable of remote and/or unattended reboots. @@ -12,9 +12,9 @@ Maintained-by: teddy@recompile.se (Teddy Hogeborn), belorn@recompile.se (Björn Påhlsson) Primary-site: http://www.recompile.se/mandos - 148K mandos_1.5.5.orig.tar.gz + 150K mandos_1.6.0.orig.tar.gz Alternate-site: ftp://ftp.recompile.se/pub/mandos - 148K mandos_1.5.5.orig.tar.gz + 150K mandos_1.6.0.orig.tar.gz Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.6, and various other libraries. While made for Debian GNU/Linux, it is probably portable to other distributions, but not other Unixes. === modified file 'mandos.xml' --- mandos.xml 2012-05-26 22:21:17 +0000 +++ mandos.xml 2012-06-17 14:55:31 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -103,6 +103,8 @@ + + &COMMANDNAME; @@ -311,6 +313,14 @@ + + + + + + + @@ -561,9 +571,6 @@ There is no fine-grained control over logging and debug output. - Debug mode is conflated with running in the foreground. - - This server does not check the expire time of clients’ OpenPGP keys. === modified file 'network-hooks.d/bridge' --- network-hooks.d/bridge 2012-04-24 06:55:34 +0000 +++ network-hooks.d/bridge 2012-06-13 22:06:57 +0000 @@ -33,8 +33,11 @@ exit fi -if [ -n "$DEVICE" -a "$DEVICE" != "$BRIDGE" ]; then - exit +if [ -n "$DEVICE" ]; then + case "$DEVICE" in + *,"$BRIDGE"|*,"$BRIDGE",*|"$BRIDGE",*|"$BRIDGE") :;; + *) exit;; + esac fi brctl="/sbin/brctl" === modified file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 2012-04-24 06:55:34 +0000 +++ network-hooks.d/openvpn 2012-06-13 22:06:57 +0000 @@ -31,8 +31,11 @@ fi # Exit if DEVICE is set and it doesn't match the VPN interface -if [ -n "$DEVICE" -a "$DEVICE" = "${DEVICE#$VPNDEVICE}" ]; then - exit +if [ -n "$DEVICE" ]; then + case "$DEVICE" in + *,"$VPNDEVICE"*|"$VPNDEVICE"*) :;; + *) exit;; + esac fi openvpn=/usr/sbin/openvpn === modified file 'network-hooks.d/wireless' --- network-hooks.d/wireless 2012-05-25 15:59:39 +0000 +++ network-hooks.d/wireless 2012-06-13 22:06:57 +0000 @@ -43,9 +43,11 @@ for KEY in $ifkeys; do ADDRESS=`eval 'echo "$ADDRESS_'"$KEY"\"` INTERFACE=`addrtoif "$ADDRESS"` - if [ "$INTERFACE" = "$DEVICE" ]; then - break 2 - fi + + case "$DEVICE" in + *,"$INTERFACE"|*,"$INTERFACE",*|"$INTERFACE",*|"$INTERFACE") + break 2;; + esac done exit done @@ -121,7 +123,7 @@ ROUTES=`eval 'echo "$ROUTES_'"$KEY"\"` if [ -n "$ROUTES" ]; then for route in $ROUTES; do - "$ip" route add "$route" dev "$BRIDGE" + "$ip" route add "$route" dev "$INTERFACE" done fi done === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2012-05-24 18:45:45 +0000 +++ plugins.d/mandos-client.c 2012-06-17 12:23:31 +0000 @@ -61,7 +61,6 @@ */ #include /* PRIu16, PRIdMAX, intmax_t, strtoimax() */ -#include /* assert() */ #include /* perror(), errno, program_invocation_short_name */ #include /* nanosleep(), time(), sleep() */ @@ -88,6 +87,10 @@ #include /* waitpid(), WIFEXITED(), WEXITSTATUS(), WTERMSIG() */ #include /* setgroups() */ +#include /* argz_add_sep(), argz_next(), + argz_delete(), argz_append(), + argz_stringify(), argz_add(), + argz_count() */ #ifdef __linux__ #include /* klogctl() */ @@ -135,11 +138,13 @@ static const char sys_class_net[] = "/sys/class/net"; char *connect_to = NULL; const char *hookdir = HOOKDIR; +uid_t uid = 65534; +gid_t gid = 65534; /* Doubly linked list that need to be circularly linked when used */ typedef struct server{ const char *ip; - uint16_t port; + in_port_t port; AvahiIfIndex if_index; int af; struct timespec last_seen; @@ -149,7 +154,6 @@ /* Used for passing in values through the Avahi callback functions */ typedef struct { - AvahiSimplePoll *simple_poll; AvahiServer *server; gnutls_certificate_credentials_t cred; unsigned int dh_bits; @@ -157,13 +161,12 @@ const char *priority; gpgme_ctx_t ctx; server *current_server; + char *interfaces; + size_t interfaces_size; } mandos_context; -/* global context so signal handler can reach it*/ -mandos_context mc = { .simple_poll = NULL, .server = NULL, - .dh_bits = 1024, .priority = "SECURE256" - ":!CTYPE-X.509:+CTYPE-OPENPGP", - .current_server = NULL }; +/* global so signal handler can reach it*/ +AvahiSimplePoll *simple_poll; sig_atomic_t quit_now = 0; int signal_received = 0; @@ -205,8 +208,8 @@ } /* Add server to set of servers to retry periodically */ -bool add_server(const char *ip, uint16_t port, AvahiIfIndex if_index, - int af){ +bool add_server(const char *ip, in_port_t port, AvahiIfIndex if_index, + int af, server **current_server){ int ret; server *new_server = malloc(sizeof(server)); if(new_server == NULL){ @@ -222,18 +225,18 @@ return false; } /* Special case of first server */ - if (mc.current_server == NULL){ + if(*current_server == NULL){ new_server->next = new_server; new_server->prev = new_server; - mc.current_server = new_server; + *current_server = new_server; /* Place the new server last in the list */ } else { - new_server->next = mc.current_server; - new_server->prev = mc.current_server->prev; + new_server->next = *current_server; + new_server->prev = (*current_server)->prev; new_server->prev->next = new_server; - mc.current_server->prev = new_server; + (*current_server)->prev = new_server; } - ret = clock_gettime(CLOCK_MONOTONIC, &mc.current_server->last_seen); + ret = clock_gettime(CLOCK_MONOTONIC, &(*current_server)->last_seen); if(ret == -1){ perror_plus("clock_gettime"); return false; @@ -245,11 +248,10 @@ * Initialize GPGME. */ static bool init_gpgme(const char *seckey, const char *pubkey, - const char *tempdir){ + const char *tempdir, mandos_context *mc){ gpgme_error_t rc; gpgme_engine_info_t engine_info; - /* * Helper function to insert pub and seckey to the engine keyring. */ @@ -271,7 +273,7 @@ return false; } - rc = gpgme_op_import(mc.ctx, pgp_data); + rc = gpgme_op_import(mc->ctx, pgp_data); if(rc != GPG_ERR_NO_ERROR){ fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); @@ -321,7 +323,7 @@ } /* Create new GPGME "context" */ - rc = gpgme_new(&(mc.ctx)); + rc = gpgme_new(&(mc->ctx)); if(rc != GPG_ERR_NO_ERROR){ fprintf_plus(stderr, "Mandos plugin mandos-client: " "bad gpgme_new: %s: %s\n", gpgme_strsource(rc), @@ -342,7 +344,8 @@ */ static ssize_t pgp_packet_decrypt(const char *cryptotext, size_t crypto_size, - char **plaintext){ + char **plaintext, + mandos_context *mc){ gpgme_data_t dh_crypto, dh_plain; gpgme_error_t rc; ssize_t ret; @@ -374,14 +377,14 @@ /* Decrypt data from the cryptotext data buffer to the plaintext data buffer */ - rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); + rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain); if(rc != GPG_ERR_NO_ERROR){ fprintf_plus(stderr, "bad gpgme_op_decrypt: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; if(debug){ gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc.ctx); + result = gpgme_op_decrypt_result(mc->ctx); if(result == NULL){ fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); } else { @@ -465,8 +468,7 @@ } static const char * safer_gnutls_strerror(int value){ - const char *ret = gnutls_strerror(value); /* Spurious warning from - -Wunreachable-code */ + const char *ret = gnutls_strerror(value); if(ret == NULL) ret = "(unknown)"; return ret; @@ -479,7 +481,8 @@ } static int init_gnutls_global(const char *pubkeyfilename, - const char *seckeyfilename){ + const char *seckeyfilename, + mandos_context *mc){ int ret; if(debug){ @@ -502,7 +505,7 @@ } /* OpenPGP credentials */ - ret = gnutls_certificate_allocate_credentials(&mc.cred); + ret = gnutls_certificate_allocate_credentials(&mc->cred); if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, "GnuTLS memory error: %s\n", safer_gnutls_strerror(ret)); @@ -518,7 +521,7 @@ } ret = gnutls_certificate_set_openpgp_key_file - (mc.cred, pubkeyfilename, seckeyfilename, + (mc->cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, @@ -530,33 +533,34 @@ } /* GnuTLS server initialization */ - ret = gnutls_dh_params_init(&mc.dh_params); + ret = gnutls_dh_params_init(&mc->dh_params); if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, "Error in GnuTLS DH parameter" " initialization: %s\n", safer_gnutls_strerror(ret)); goto globalfail; } - ret = gnutls_dh_params_generate2(mc.dh_params, mc.dh_bits); + ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); if(ret != GNUTLS_E_SUCCESS){ fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n", safer_gnutls_strerror(ret)); goto globalfail; } - gnutls_certificate_set_dh_params(mc.cred, mc.dh_params); + gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); return 0; globalfail: - gnutls_certificate_free_credentials(mc.cred); + gnutls_certificate_free_credentials(mc->cred); gnutls_global_deinit(); - gnutls_dh_params_deinit(mc.dh_params); + gnutls_dh_params_deinit(mc->dh_params); return -1; } -static int init_gnutls_session(gnutls_session_t *session){ +static int init_gnutls_session(gnutls_session_t *session, + mandos_context *mc){ int ret; /* GnuTLS session creation */ do { @@ -574,7 +578,7 @@ { const char *err; do { - ret = gnutls_priority_set_direct(*session, mc.priority, &err); + ret = gnutls_priority_set_direct(*session, mc->priority, &err); if(quit_now){ gnutls_deinit(*session); return -1; @@ -591,7 +595,7 @@ do { ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc.cred); + mc->cred); if(quit_now){ gnutls_deinit(*session); return -1; @@ -607,7 +611,7 @@ /* ignore client certificate if any. */ gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); - gnutls_dh_set_prime_bits(*session, mc.dh_bits); + gnutls_dh_set_prime_bits(*session, mc->dh_bits); return 0; } @@ -617,9 +621,9 @@ __attribute__((unused)) const char *txt){} /* Called when a Mandos server is found */ -static int start_mandos_communication(const char *ip, uint16_t port, +static int start_mandos_communication(const char *ip, in_port_t port, AvahiIfIndex if_index, - int af){ + int af, mandos_context *mc){ int ret, tcp_sd = -1; ssize_t sret; union { @@ -655,14 +659,46 @@ return -1; } - ret = init_gnutls_session(&session); + /* If the interface is specified and we have a list of interfaces */ + if(if_index != AVAHI_IF_UNSPEC and mc->interfaces != NULL){ + /* Check if the interface is one of the interfaces we are using */ + bool match = false; + { + char *interface = NULL; + while((interface=argz_next(mc->interfaces, mc->interfaces_size, + interface))){ + if(if_nametoindex(interface) == (unsigned int)if_index){ + match = true; + break; + } + } + } + if(not match){ + /* This interface does not match any in the list, so we don't + connect to the server */ + if(debug){ + char interface[IF_NAMESIZE]; + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + perror_plus("if_indextoname"); + } else { + fprintf_plus(stderr, "Skipping server on non-used interface" + " \"%s\"\n", + if_indextoname((unsigned int)if_index, + interface)); + } + } + return -1; + } + } + + ret = init_gnutls_session(&session, mc); if(ret != 0){ return -1; } if(debug){ fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" - PRIu16 "\n", ip, port); + PRIuMAX "\n", ip, (uintmax_t)port); } tcp_sd = socket(pf, SOCK_STREAM, 0); @@ -699,10 +735,7 @@ goto mandos_end; } if(af == AF_INET6){ - to.in6.sin6_port = htons(port); /* Spurious warnings from - -Wconversion and - -Wunreachable-code */ - + to.in6.sin6_port = htons(port); if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and -Wunreachable-code*/ @@ -732,12 +765,12 @@ if(if_indextoname((unsigned int)if_index, interface) == NULL){ perror_plus("if_indextoname"); } else { - fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIu16 - "\n", ip, interface, port); + fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIuMAX + "\n", ip, interface, (uintmax_t)port); } } else { - fprintf_plus(stderr, "Connection to: %s, port %" PRIu16 "\n", - ip, port); + fprintf_plus(stderr, "Connection to: %s, port %" PRIuMAX "\n", + ip, (uintmax_t)port); } char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; @@ -936,7 +969,7 @@ if(buffer_length > 0){ ssize_t decrypted_buffer_size; decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, - &decrypted_buffer); + &decrypted_buffer, mc); if(decrypted_buffer_size >= 0){ written = 0; @@ -1003,8 +1036,10 @@ AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - AVAHI_GCC_UNUSED void* userdata){ - assert(r); + void* mc){ + if(r == NULL){ + return; + } /* Called whenever a service has been resolved successfully or timed out */ @@ -1019,7 +1054,8 @@ fprintf_plus(stderr, "(Avahi Resolver) Failed to resolve service " "'%s' of type '%s' in domain '%s': %s\n", name, type, domain, - avahi_strerror(avahi_server_errno(mc.server))); + avahi_strerror(avahi_server_errno + (((mandos_context*)mc)->server))); break; case AVAHI_RESOLVER_FOUND: @@ -1031,13 +1067,16 @@ PRIdMAX ") on port %" PRIu16 "\n", name, host_name, ip, (intmax_t)interface, port); } - int ret = start_mandos_communication(ip, port, interface, - avahi_proto_to_af(proto)); + int ret = start_mandos_communication(ip, (in_port_t)port, + interface, + avahi_proto_to_af(proto), + mc); if(ret == 0){ - avahi_simple_poll_quit(mc.simple_poll); + avahi_simple_poll_quit(simple_poll); } else { - if(not add_server(ip, port, interface, - avahi_proto_to_af(proto))){ + if(not add_server(ip, (in_port_t)port, interface, + avahi_proto_to_af(proto), + &((mandos_context*)mc)->current_server)){ fprintf_plus(stderr, "Failed to add server \"%s\" to server" " list\n", name); } @@ -1056,8 +1095,10 @@ const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - AVAHI_GCC_UNUSED void* userdata){ - assert(b); + void* mc){ + if(b == NULL){ + return; + } /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ @@ -1071,8 +1112,9 @@ case AVAHI_BROWSER_FAILURE: fprintf_plus(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno(mc.server))); - avahi_simple_poll_quit(mc.simple_poll); + avahi_strerror(avahi_server_errno + (((mandos_context*)mc)->server))); + avahi_simple_poll_quit(simple_poll); return; case AVAHI_BROWSER_NEW: @@ -1081,12 +1123,14 @@ the callback function is called the Avahi server will free the resolver for us. */ - if(avahi_s_service_resolver_new(mc.server, interface, protocol, - name, type, domain, protocol, 0, - resolve_callback, NULL) == NULL) + if(avahi_s_service_resolver_new(((mandos_context*)mc)->server, + interface, protocol, name, type, + domain, protocol, 0, + resolve_callback, mc) == NULL) fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':" " %s\n", name, - avahi_strerror(avahi_server_errno(mc.server))); + avahi_strerror(avahi_server_errno + (((mandos_context*)mc)->server))); break; case AVAHI_BROWSER_REMOVE: @@ -1111,25 +1155,30 @@ signal_received = sig; int old_errno = errno; /* set main loop to exit */ - if(mc.simple_poll != NULL){ - avahi_simple_poll_quit(mc.simple_poll); + if(simple_poll != NULL){ + avahi_simple_poll_quit(simple_poll); } errno = old_errno; } bool get_flags(const char *ifname, struct ifreq *ifr){ int ret; + error_t ret_errno; int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); if(s < 0){ + ret_errno = errno; perror_plus("socket"); + errno = ret_errno; return false; } strcpy(ifr->ifr_name, ifname); ret = ioctl(s, SIOCGIFFLAGS, ifr); if(ret == -1){ if(debug){ + ret_errno = errno; perror_plus("ioctl SIOCGIFFLAGS"); + errno = ret_errno; } return false; } @@ -1204,46 +1253,35 @@ } /* - * This function determines if a directory entry in /sys/class/net - * corresponds to an acceptable network device which is up. - * (This function is passed to scandir(3) as a filter function.) - */ -int up_interface(const struct dirent *if_entry){ - if(if_entry->d_name[0] == '.'){ - return 0; - } - - struct ifreq ifr; - if(not get_flags(if_entry->d_name, &ifr)){ - if(debug){ - fprintf_plus(stderr, "Failed to get flags for interface " - "\"%s\"\n", if_entry->d_name); - } - return 0; - } - - /* Reject down interfaces */ - if(not (ifr.ifr_flags & IFF_UP)){ - if(debug){ - fprintf_plus(stderr, "Rejecting down interface \"%s\"\n", - if_entry->d_name); - } - return 0; - } - - /* Reject non-running interfaces */ - if(not (ifr.ifr_flags & IFF_RUNNING)){ - if(debug){ - fprintf_plus(stderr, "Rejecting non-running interface \"%s\"\n", - if_entry->d_name); - } - return 0; - } - - if(not good_flags(if_entry->d_name, &ifr)){ - return 0; - } - return 1; + * This function determines if a network interface is up. + */ +bool interface_is_up(const char *interface){ + struct ifreq ifr; + if(not get_flags(interface, &ifr)){ + if(debug){ + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + } + return false; + } + + return (bool)(ifr.ifr_flags & IFF_UP); +} + +/* + * This function determines if a network interface is running + */ +bool interface_is_running(const char *interface){ + struct ifreq ifr; + if(not get_flags(interface, &ifr)){ + if(debug){ + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + } + return false; + } + + return (bool)(ifr.ifr_flags & IFF_RUNNING); } int notdotentries(const struct dirent *direntry){ @@ -1318,14 +1356,15 @@ return 1; } -int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){ +int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval, + mandos_context *mc){ int ret; struct timespec now; struct timespec waited_time; intmax_t block_time; while(true){ - if(mc.current_server == NULL){ + if(mc->current_server == NULL){ if (debug){ fprintf_plus(stderr, "Wait until first server is found." " No timeout!\n"); @@ -1345,9 +1384,9 @@ /* Calculating in ms how long time between now and server who we visted longest time ago. Now - last seen. */ waited_time.tv_sec = (now.tv_sec - - mc.current_server->last_seen.tv_sec); + - mc->current_server->last_seen.tv_sec); waited_time.tv_nsec = (now.tv_nsec - - mc.current_server->last_seen.tv_nsec); + - mc->current_server->last_seen.tv_nsec); /* total time is 10s/10,000ms. Converting to s from ms by dividing by 1,000, and ns to ms by dividing by 1,000,000. */ @@ -1361,21 +1400,21 @@ } if(block_time <= 0){ - ret = start_mandos_communication(mc.current_server->ip, - mc.current_server->port, - mc.current_server->if_index, - mc.current_server->af); + ret = start_mandos_communication(mc->current_server->ip, + mc->current_server->port, + mc->current_server->if_index, + mc->current_server->af, mc); if(ret == 0){ - avahi_simple_poll_quit(mc.simple_poll); + avahi_simple_poll_quit(s); return 0; } ret = clock_gettime(CLOCK_MONOTONIC, - &mc.current_server->last_seen); + &mc->current_server->last_seen); if(ret == -1){ perror_plus("clock_gettime"); return -1; } - mc.current_server = mc.current_server->next; + mc->current_server = mc->current_server->next; block_time = 0; /* Call avahi to find new Mandos servers, but don't block */ } @@ -1390,6 +1429,58 @@ } } +/* Set effective uid to 0, return errno */ +error_t raise_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(0) == -1){ + ret_errno = errno; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Set effective and real user ID to 0. Return errno. */ +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; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Set effective user ID to unprivileged saved user ID */ +error_t lower_privileges(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(seteuid(uid) == -1){ + ret_errno = errno; + perror_plus("seteuid"); + } + errno = old_errno; + return ret_errno; +} + +/* Lower privileges permanently */ +error_t lower_privileges_permanently(void){ + error_t old_errno = errno; + error_t ret_errno = 0; + if(setuid(uid) == -1){ + ret_errno = errno; + perror_plus("setuid"); + } + errno = old_errno; + return ret_errno; +} + bool run_network_hooks(const char *mode, const char *interface, const float delay){ struct dirent **direntries; @@ -1398,7 +1489,14 @@ int numhooks = scandir(hookdir, &direntries, runnable_hook, alphasort); if(numhooks == -1){ - perror_plus("scandir"); + if(errno == ENOENT){ + if(debug){ + fprintf_plus(stderr, "Network hook directory \"%s\" not" + " found\n", hookdir); + } + } else { + perror_plus("scandir"); + } } else { int devnull = open("/dev/null", O_RDONLY); for(int i = 0; i < numhooks; i++){ @@ -1417,17 +1515,7 @@ if(hook_pid == 0){ /* Child */ /* Raise privileges */ - errno = 0; - ret = seteuid(0); - if(ret == -1){ - perror_plus("seteuid"); - } - /* Raise privileges even more */ - errno = 0; - ret = setuid(0); - if(ret == -1){ - perror_plus("setuid"); - } + raise_privileges_permanently(); /* Set group */ errno = 0; ret = setgid(0); @@ -1526,24 +1614,198 @@ return true; } +error_t bring_up_interface(const char *const interface, + const float delay){ + int sd = -1; + error_t old_errno = errno; + error_t ret_errno = 0; + int ret, ret_setflags; + struct ifreq network; + unsigned int if_index = if_nametoindex(interface); + if(if_index == 0){ + fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); + errno = old_errno; + return ENXIO; + } + + if(quit_now){ + errno = old_errno; + return EINTR; + } + + if(not interface_is_up(interface)){ + if(not get_flags(interface, &network) and debug){ + ret_errno = errno; + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + return ret_errno; + } + network.ifr_flags |= IFF_UP; + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + ret_errno = errno; + perror_plus("socket"); + errno = old_errno; + return ret_errno; + } + + if(quit_now){ + close(sd); + errno = old_errno; + return EINTR; + } + + if(debug){ + fprintf_plus(stderr, "Bringing up interface \"%s\"\n", + interface); + } + + /* Raise priviliges */ + raise_privileges(); + +#ifdef __linux__ + /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO + messages about the network interface to mess up the prompt */ + int ret_linux = klogctl(8, NULL, 5); + bool restore_loglevel = true; + if(ret_linux == -1){ + restore_loglevel = false; + perror_plus("klogctl"); + } +#endif /* __linux__ */ + ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); + ret_errno = errno; +#ifdef __linux__ + if(restore_loglevel){ + ret_linux = klogctl(7, NULL, 0); + if(ret_linux == -1){ + perror_plus("klogctl"); + } + } +#endif /* __linux__ */ + + /* Lower privileges */ + lower_privileges(); + + /* Close the socket */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror_plus("close"); + } + + if(ret_setflags == -1){ + errno = ret_errno; + perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); + errno = old_errno; + return ret_errno; + } + } else if(debug){ + fprintf_plus(stderr, "Interface \"%s\" is already up; good\n", + interface); + } + + /* Sleep checking until interface is running. + Check every 0.25s, up to total time of delay */ + for(int i=0; i < delay * 4; i++){ + if(interface_is_running(interface)){ + break; + } + struct timespec sleeptime = { .tv_nsec = 250000000 }; + ret = nanosleep(&sleeptime, NULL); + if(ret == -1 and errno != EINTR){ + perror_plus("nanosleep"); + } + } + + errno = old_errno; + return 0; +} + +error_t take_down_interface(const char *const interface){ + int sd = -1; + error_t old_errno = errno; + error_t ret_errno = 0; + int ret, ret_setflags; + struct ifreq network; + unsigned int if_index = if_nametoindex(interface); + if(if_index == 0){ + fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); + errno = old_errno; + return ENXIO; + } + if(interface_is_up(interface)){ + if(not get_flags(interface, &network) and debug){ + ret_errno = errno; + fprintf_plus(stderr, "Failed to get flags for interface " + "\"%s\"\n", interface); + return ret_errno; + } + network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + ret_errno = errno; + perror_plus("socket"); + errno = old_errno; + return ret_errno; + } + + if(debug){ + fprintf_plus(stderr, "Taking down interface \"%s\"\n", + interface); + } + + /* Raise priviliges */ + raise_privileges(); + + ret_setflags = ioctl(sd, SIOCSIFFLAGS, &network); + ret_errno = errno; + + /* Lower privileges */ + lower_privileges(); + + /* Close the socket */ + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror_plus("close"); + } + + if(ret_setflags == -1){ + errno = ret_errno; + perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); + errno = old_errno; + return ret_errno; + } + } else if(debug){ + fprintf_plus(stderr, "Interface \"%s\" is already down; odd\n", + interface); + } + + errno = old_errno; + return 0; +} + int main(int argc, char *argv[]){ + mandos_context mc = { .server = NULL, .dh_bits = 1024, + .priority = "SECURE256:!CTYPE-X.509:" + "+CTYPE-OPENPGP", .current_server = NULL, + .interfaces = NULL, .interfaces_size = 0 }; AvahiSServiceBrowser *sb = NULL; - int error; + error_t ret_errno; int ret; intmax_t tmpmax; char *tmp; int exitcode = EXIT_SUCCESS; - const char *interface = ""; - struct ifreq network; - int sd = -1; - bool take_down_interface = false; - uid_t uid; - gid_t gid; + char *interfaces_to_take_down = NULL; + size_t interfaces_to_take_down_size = 0; char tempdir[] = "/tmp/mandosXXXXXX"; bool tempdir_created = false; AvahiIfIndex if_index = AVAHI_IF_UNSPEC; const char *seckey = PATHDIR "/" SECKEY; const char *pubkey = PATHDIR "/" PUBKEY; + char *interfaces_hooks = NULL; + size_t interfaces_hooks_size = 0; bool gnutls_initialized = false; bool gpgme_initialized = false; @@ -1640,7 +1902,11 @@ connect_to = arg; break; case 'i': /* --interface */ - interface = arg; + ret_errno = argz_add_sep(&mc.interfaces, &mc.interfaces_size, + arg, (int)','); + if(ret_errno != 0){ + argp_error(state, "%s", strerror(ret_errno)); + } break; case 's': /* --seckey */ seckey = arg; @@ -1724,11 +1990,7 @@ */ /* Re-raise priviliges */ - errno = 0; - ret = seteuid(0); - if(ret == -1){ - perror_plus("seteuid"); - } else { + if(raise_privileges() == 0){ struct stat st; if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ @@ -1774,61 +2036,56 @@ } /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); + lower_privileges(); + } + } + + /* Remove invalid interface names (except "none") */ + { + char *interface = NULL; + while((interface = argz_next(mc.interfaces, mc.interfaces_size, + interface))){ + if(strcmp(interface, "none") != 0 + and if_nametoindex(interface) == 0){ + if(interface[0] != '\0'){ + fprintf_plus(stderr, "Not using nonexisting interface" + " \"%s\"\n", interface); + } + argz_delete(&mc.interfaces, &mc.interfaces_size, interface); + interface = NULL; } } } /* Run network hooks */ - if(not run_network_hooks("start", interface, delay)){ - goto end; + { + if(mc.interfaces != NULL){ + interfaces_hooks = malloc(mc.interfaces_size); + if(interfaces_hooks == NULL){ + perror_plus("malloc"); + goto end; + } + memcpy(interfaces_hooks, mc.interfaces, mc.interfaces_size); + interfaces_hooks_size = mc.interfaces_size; + argz_stringify(interfaces_hooks, interfaces_hooks_size, + (int)','); + } + if(not run_network_hooks("start", interfaces_hooks != NULL ? + interfaces_hooks : "", delay)){ + goto end; + } } if(not debug){ avahi_set_log_function(empty_log); } - if(interface[0] == '\0'){ - struct dirent **direntries; - /* First look for interfaces that are up */ - ret = scandir(sys_class_net, &direntries, up_interface, - alphasort); - if(ret == 0){ - /* No up interfaces, look for any good interfaces */ - free(direntries); - ret = scandir(sys_class_net, &direntries, good_interface, - alphasort); - } - if(ret >= 1){ - /* Pick the first interface returned */ - interface = strdup(direntries[0]->d_name); - if(debug){ - fprintf_plus(stderr, "Using interface \"%s\"\n", interface); - } - if(interface == NULL){ - perror_plus("malloc"); - free(direntries); - exitcode = EXIT_FAILURE; - goto end; - } - free(direntries); - } else { - free(direntries); - fprintf_plus(stderr, "Could not find a network interface\n"); - exitcode = EXIT_FAILURE; - goto end; - } - } - /* Initialize Avahi early so avahi_simple_poll_quit() can be called from the signal handler */ /* Initialize the pseudo-RNG for Avahi */ srand((unsigned int) time(NULL)); - mc.simple_poll = avahi_simple_poll_new(); - if(mc.simple_poll == NULL){ + simple_poll = avahi_simple_poll_new(); + if(simple_poll == NULL){ fprintf_plus(stderr, "Avahi: Failed to create simple poll object.\n"); exitcode = EX_UNAVAILABLE; @@ -1898,148 +2155,88 @@ } } - /* If the interface is down, bring it up */ - if(strcmp(interface, "none") != 0){ - if_index = (AvahiIfIndex) if_nametoindex(interface); - if(if_index == 0){ - fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); - exitcode = EX_UNAVAILABLE; - goto end; - } - - if(quit_now){ - goto end; - } - - /* Re-raise priviliges */ - errno = 0; - ret = seteuid(0); - if(ret == -1){ - perror_plus("seteuid"); - } - -#ifdef __linux__ - /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO - messages about the network interface to mess up the prompt */ - ret = klogctl(8, NULL, 5); - bool restore_loglevel = true; - if(ret == -1){ - restore_loglevel = false; - perror_plus("klogctl"); - } -#endif /* __linux__ */ - - sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd < 0){ - perror_plus("socket"); - exitcode = EX_OSERR; -#ifdef __linux__ - if(restore_loglevel){ - ret = klogctl(7, NULL, 0); - if(ret == -1){ - perror_plus("klogctl"); - } - } -#endif /* __linux__ */ - /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - goto end; - } - strcpy(network.ifr_name, interface); - ret = ioctl(sd, SIOCGIFFLAGS, &network); - if(ret == -1){ - perror_plus("ioctl SIOCGIFFLAGS"); -#ifdef __linux__ - if(restore_loglevel){ - ret = klogctl(7, NULL, 0); - if(ret == -1){ - perror_plus("klogctl"); - } - } -#endif /* __linux__ */ - exitcode = EX_OSERR; - /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - goto end; - } - if((network.ifr_flags & IFF_UP) == 0){ - network.ifr_flags |= IFF_UP; - take_down_interface = true; - ret = ioctl(sd, SIOCSIFFLAGS, &network); - if(ret == -1){ - take_down_interface = false; - perror_plus("ioctl SIOCSIFFLAGS +IFF_UP"); - exitcode = EX_OSERR; -#ifdef __linux__ - if(restore_loglevel){ - ret = klogctl(7, NULL, 0); - if(ret == -1){ - perror_plus("klogctl"); + /* If no interfaces were specified, make a list */ + if(mc.interfaces == NULL){ + struct dirent **direntries; + /* Look for any good interfaces */ + ret = scandir(sys_class_net, &direntries, good_interface, + alphasort); + if(ret >= 1){ + /* Add all found interfaces to interfaces list */ + for(int i = 0; i < ret; ++i){ + ret_errno = argz_add(&mc.interfaces, &mc.interfaces_size, + direntries[i]->d_name); + if(ret_errno != 0){ + perror_plus("argz_add"); + continue; + } + if(debug){ + fprintf_plus(stderr, "Will use interface \"%s\"\n", + direntries[i]->d_name); + } + } + free(direntries); + } else { + free(direntries); + fprintf_plus(stderr, "Could not find a network interface\n"); + exitcode = EXIT_FAILURE; + goto end; + } + } + + /* Bring up interfaces which are down, and remove any "none"s */ + { + char *interface = NULL; + while((interface = argz_next(mc.interfaces, mc.interfaces_size, + interface))){ + /* If interface name is "none", stop bringing up interfaces. + Also remove all instances of "none" from the list */ + if(strcmp(interface, "none") == 0){ + argz_delete(&mc.interfaces, &mc.interfaces_size, + interface); + interface = NULL; + while((interface = argz_next(mc.interfaces, + mc.interfaces_size, interface))){ + if(strcmp(interface, "none") == 0){ + argz_delete(&mc.interfaces, &mc.interfaces_size, + interface); + interface = NULL; } } -#endif /* __linux__ */ - /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - goto end; - } - } - /* Sleep checking until interface is running. - Check every 0.25s, up to total time of delay */ - for(int i=0; i < delay * 4; i++){ - ret = ioctl(sd, SIOCGIFFLAGS, &network); - if(ret == -1){ - perror_plus("ioctl SIOCGIFFLAGS"); - } else if(network.ifr_flags & IFF_RUNNING){ break; } - struct timespec sleeptime = { .tv_nsec = 250000000 }; - ret = nanosleep(&sleeptime, NULL); - if(ret == -1 and errno != EINTR){ - perror_plus("nanosleep"); - } - } - if(not take_down_interface){ - /* We won't need the socket anymore */ - ret = (int)TEMP_FAILURE_RETRY(close(sd)); - if(ret == -1){ - perror_plus("close"); - } - } -#ifdef __linux__ - if(restore_loglevel){ - /* Restores kernel loglevel to default */ - ret = klogctl(7, NULL, 0); - if(ret == -1){ - perror_plus("klogctl"); - } - } -#endif /* __linux__ */ - /* Lower privileges */ - errno = 0; - /* Lower privileges */ - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } + bool interface_was_up = interface_is_up(interface); + ret = bring_up_interface(interface, delay); + if(not interface_was_up){ + if(ret != 0){ + errno = ret; + perror_plus("Failed to bring up interface"); + } else { + ret_errno = argz_add(&interfaces_to_take_down, + &interfaces_to_take_down_size, + interface); + } + } + } + if(debug and (interfaces_to_take_down == NULL)){ + fprintf_plus(stderr, "No interfaces were brought up\n"); + } + } + + /* If we only got one interface, explicitly use only that one */ + if(argz_count(mc.interfaces, mc.interfaces_size) == 1){ + if(debug){ + fprintf_plus(stderr, "Using only interface \"%s\"\n", + mc.interfaces); + } + if_index = (AvahiIfIndex)if_nametoindex(mc.interfaces); } if(quit_now){ goto end; } - ret = init_gnutls_global(pubkey, seckey); + ret = init_gnutls_global(pubkey, seckey, &mc); if(ret == -1){ fprintf_plus(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; @@ -2062,7 +2259,7 @@ goto end; } - if(not init_gpgme(pubkey, seckey, tempdir)){ + if(not init_gpgme(pubkey, seckey, tempdir, &mc)){ fprintf_plus(stderr, "init_gpgme failed\n"); exitcode = EX_UNAVAILABLE; goto end; @@ -2078,6 +2275,7 @@ /* Connect directly, do not use Zeroconf */ /* (Mainly meant for debugging) */ char *address = strrchr(connect_to, ':'); + if(address == NULL){ fprintf_plus(stderr, "No colon in address\n"); exitcode = EX_USAGE; @@ -2088,21 +2286,21 @@ goto end; } - uint16_t port; + in_port_t port; errno = 0; tmpmax = strtoimax(address+1, &tmp, 10); if(errno != 0 or tmp == address+1 or *tmp != '\0' - or tmpmax != (uint16_t)tmpmax){ + or tmpmax != (in_port_t)tmpmax){ fprintf_plus(stderr, "Bad port number\n"); exitcode = EX_USAGE; goto end; } - + if(quit_now){ goto end; } - port = (uint16_t)tmpmax; + port = (in_port_t)tmpmax; *address = '\0'; /* Colon in address indicates IPv6 */ int af; @@ -2124,7 +2322,8 @@ } while(not quit_now){ - ret = start_mandos_communication(address, port, if_index, af); + ret = start_mandos_communication(address, port, if_index, af, + &mc); if(quit_now or ret == 0){ break; } @@ -2156,9 +2355,8 @@ config.publish_domain = 0; /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get - (mc.simple_poll), &config, NULL, - NULL, &error); + mc.server = avahi_server_new(avahi_simple_poll_get(simple_poll), + &config, NULL, NULL, &ret_errno); /* Free the Avahi configuration data */ avahi_server_config_free(&config); @@ -2167,7 +2365,7 @@ /* Check if creating the Avahi server object succeeded */ if(mc.server == NULL){ fprintf_plus(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(error)); + avahi_strerror(ret_errno)); exitcode = EX_UNAVAILABLE; goto end; } @@ -2179,7 +2377,8 @@ /* Create the Avahi service browser */ sb = avahi_s_service_browser_new(mc.server, if_index, AVAHI_PROTO_UNSPEC, "_mandos._tcp", - NULL, 0, browse_callback, NULL); + NULL, 0, browse_callback, + (void *)&mc); if(sb == NULL){ fprintf_plus(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(mc.server))); @@ -2197,8 +2396,8 @@ fprintf_plus(stderr, "Starting Avahi loop search\n"); } - ret = avahi_loop_with_timeout(mc.simple_poll, - (int)(retry_interval * 1000)); + ret = avahi_loop_with_timeout(simple_poll, + (int)(retry_interval * 1000), &mc); if(debug){ fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n", (ret == 0) ? "successfully" : "with error"); @@ -2211,14 +2410,16 @@ } /* Cleanup things */ + free(mc.interfaces); + if(sb != NULL) avahi_s_service_browser_free(sb); if(mc.server != NULL) avahi_server_free(mc.server); - if(mc.simple_poll != NULL) - avahi_simple_poll_free(mc.simple_poll); + if(simple_poll != NULL) + avahi_simple_poll_free(simple_poll); if(gnutls_initialized){ gnutls_certificate_free_credentials(mc.cred); @@ -2241,41 +2442,37 @@ } } - /* Run network hooks */ - run_network_hooks("stop", interface, delay); - /* Re-raise priviliges */ { - errno = 0; - ret = seteuid(0); - if(ret == -1){ - perror_plus("seteuid"); - } - - /* Take down the network interface */ - if(take_down_interface and geteuid() == 0){ - ret = ioctl(sd, SIOCGIFFLAGS, &network); - if(ret == -1){ - perror_plus("ioctl SIOCGIFFLAGS"); - } else if(network.ifr_flags & IFF_UP){ - network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ - ret = ioctl(sd, SIOCSIFFLAGS, &network); - if(ret == -1){ - perror_plus("ioctl SIOCSIFFLAGS -IFF_UP"); + raise_privileges(); + + /* Run network hooks */ + run_network_hooks("stop", interfaces_hooks != NULL ? + interfaces_hooks : "", delay); + + /* Take down the network interfaces which were brought up */ + { + char *interface = NULL; + while((interface=argz_next(interfaces_to_take_down, + interfaces_to_take_down_size, + interface))){ + ret_errno = take_down_interface(interface); + if(ret_errno != 0){ + errno = ret_errno; + perror_plus("Failed to take down interface"); } } - ret = (int)TEMP_FAILURE_RETRY(close(sd)); - if(ret == -1){ - perror_plus("close"); + if(debug and (interfaces_to_take_down == NULL)){ + fprintf_plus(stderr, "No interfaces needed to be taken" + " down\n"); } } - } - /* Lower privileges permanently */ - errno = 0; - ret = setuid(uid); - if(ret == -1){ - perror_plus("setuid"); - } + + lower_privileges_permanently(); + } + + free(interfaces_to_take_down); + free(interfaces_hooks); /* Removes the GPGME temp directory and all files inside */ if(tempdir_created){ === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2012-05-27 07:30:49 +0000 +++ plugins.d/mandos-client.xml 2012-06-17 02:30:59 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -64,11 +64,13 @@ >PORT - + - + NAME,NAME + @@ -137,10 +139,10 @@ communicates with mandos8 to get a password. In slightly more detail, this client program - brings up a network interface, uses the interface’s IPv6 - link-local address to get network connectivity, uses Zeroconf to - find servers on the local network, and communicates with servers - using TLS with an OpenPGP key to ensure authenticity and + brings up network interfaces, uses the interfaces’ IPv6 + link-local addresses to get network connectivity, uses Zeroconf + to find servers on the local network, and communicates with + servers using TLS with an OpenPGP key to ensure authenticity and confidentiality. This client program keeps running, trying all servers on the network, until it receives a satisfactory reply or a TERM signal. After all servers have been tried, all @@ -148,23 +150,21 @@ will wait indefinitely for new servers to appear. - The network interface is selected like this: If an interface is - specified using the option, that - interface is used. Otherwise, &COMMANDNAME; - will choose any interface that is up and running and is not a - loopback interface, is not a point-to-point interface, is - capable of broadcasting and does not have the NOARP flag (see + The network interfaces are selected like this: If any interfaces + are specified using the option, + those interface are used. Otherwise, + &COMMANDNAME; will use all interfaces that + are not loopback interfaces, are not point-to-point interfaces, + are capable of broadcasting and do not have the NOARP flag (see netdevice 7). (If the option is used, point-to-point - interfaces and non-broadcast interfaces are accepted.) If no - acceptable interfaces are found, re-run the check but without - the up and running requirement, and manually take - the selected interface up (and later take it down on program - exit). + interfaces and non-broadcast interfaces are accepted.) If any + used interfaces are not up and running, they are first taken up + (and later taken down again on program exit). - Before a network interface is selected, all network + Before network interfaces are selected, all network hooks are run; see . @@ -226,37 +226,42 @@ + >NAME,NAME + NAME,NAME - Network interface that will be brought up and scanned for - Mandos servers to connect to. The default is the empty - string, which will automatically choose an appropriate - interface. + Comma separated list of network interfaces that will be + brought up and scanned for Mandos servers to connect to. + The default is the empty string, which will automatically + use all appropriate interfaces. - If the option is used, this - specifies the interface to use to connect to the address - given. + If the option is used, and + exactly one interface name is specified (except + none), this specifies + the interface to use to connect to the address given. Note that since this program will normally run in the initial RAM disk environment, the interface must be an interface which exists at that stage. Thus, the interface - can not be a pseudo-interface such as br0 - or tun0; such interfaces will not exist - until much later in the boot process, and can not be used - by this program, unless created by a network - hook — see . + can normally not be a pseudo-interface such as + br0 or tun0; such interfaces + will not exist until much later in the boot process, and + can not be used by this program, unless created by a + network hook — see . NAME can be the string - none; this will not use - any specific interface, and will not bring up an interface - on startup. This is not recommended, and only meant for - advanced users. + none; this will make + &COMMANDNAME; not bring up + any interfaces specified + after this string. This is not + recommended, and only meant for advanced users. @@ -314,7 +319,7 @@ >SECONDS - After bringing the network interface up, the program waits + After bringing a network interface up, the program waits for the interface to arrive in a running state before proceeding. During this time, the kernel log level will be lowered to reduce clutter on the system @@ -547,11 +552,12 @@ DEVICE - The network interface, as specified to + The network interfaces, as specified to &COMMANDNAME; by the - option. If this is not the - interface a hook will bring up, there is no reason for a - hook to continue. + option, combined to one + string and separated by commas. If this is set, and + does not contain the interface a hook will bring up, + there is no reason for a hook to continue.