=== modified file 'Makefile' --- Makefile 2011-11-26 22:22:20 +0000 +++ Makefile 2011-12-21 00:33:12 +0000 @@ -255,7 +255,7 @@ @echo "###################################################################" ./plugin-runner --plugin-dir=plugins.d \ --config-file=plugin-runner.conf \ - --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt \ + --options-for=mandos-client:--seckey=keydir/seckey.txt,--pubkey=keydir/pubkey.txt,--network-hook-dir=network-hooks.d \ $(CLIENTARGS) # Used by run-client @@ -328,6 +328,8 @@ install --mode=u=rwx \ --directory "$(CONFDIR)/plugins.d"; \ fi + install --mode=u=rwx,go=rx --directory \ + "$(CONFDIR)/network-hooks.d" install --mode=u=rwx,go=rx \ --target-directory=$(PREFIX)/lib/mandos plugin-runner install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \ === modified file 'TODO' --- TODO 2011-12-21 00:29:33 +0000 +++ TODO 2011-12-21 00:33:12 +0000 @@ -7,6 +7,9 @@ * mandos-applet * mandos-client +** TODO [#B] Flag fprintf_plus so compiler checks the format string. +** TODO [#A] OpenVPN network hook +** 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() @@ -42,19 +45,19 @@ * mandos (server) ** TODO Document why we ignore sigint ** TODO [#B] Log level :BUGS: -** TODO [#A] Save state to new file and move instead of overwrite - +** TODO [#A] Save state to new file and move instead of overwrite +*** TODO /etc/mandos/clients.d/*.conf + Watch this directory and add/remove/update clients? ** TODO [#C] config for TXT record -** TODO Log level option - syslogger.setLevel(logging.WARNING) - + SetLogLevel D-Bus call +** TODO Log level dbus option + SetLogLevel D-Bus call ** TODO Implement --foreground :BUGS: [[info:standards:Option%20Table][Table of Long Options]] ** TODO Implement --socket [[info:standards:Option%20Table][Table of Long Options]] ** TODO [#C] DBusServiceObjectUsingSuper ** TODO [#B] Global enable/disable flag -** TODO [#B] By-client countdown on secrets given +** TODO [#B] By-client countdown on number of secrets given ** TODO [#B] Support RFC 3339 time duration syntax ** More D-Bus methods *** NeedsPassword(50) - Timeout, default disapprove @@ -70,6 +73,7 @@ ** TODO Generate Client.runtime_expansions from client options + extra ** TODO Allow %%(checker)s as a runtime expansion ** TODO Use python-tlslite? +** TODO D-Bus AddClient() method on server object * mandos.xml ** Add mandos contact info in manual pages === modified file 'debian/mandos-client.README.Debian' --- debian/mandos-client.README.Debian 2011-10-05 16:00:56 +0000 +++ debian/mandos-client.README.Debian 2011-11-28 22:15:04 +0000 @@ -51,6 +51,11 @@ 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. + This can be overcome by writing a "network hook" program to create + the 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". * User-Supplied Plugins @@ -84,4 +89,4 @@ work, "--options-for=mandos-client:--connect=
:" needs to be manually added to the file "/etc/mandos/plugin-runner.conf". - -- Teddy Hogeborn , Wed, 5 Oct 2011 17:50:22 +0200 + -- Teddy Hogeborn , Mon, 28 Nov 2011 23:07:22 +0100 === modified file 'debian/mandos-client.docs' --- debian/mandos-client.docs 2008-10-18 11:17:22 +0000 +++ debian/mandos-client.docs 2011-11-27 02:32:20 +0000 @@ -1,3 +1,4 @@ NEWS README TODO +network-hooks.d === modified file 'debian/rules' --- debian/rules 2010-09-09 18:16:14 +0000 +++ debian/rules 2011-11-27 14:44:28 +0000 @@ -85,7 +85,8 @@ dh_fixperms --exclude etc/keys/mandos \ --exclude etc/mandos/clients.conf \ --exclude etc/mandos/plugins.d \ - --exclude usr/lib/mandos/plugins.d + --exclude usr/lib/mandos/plugins.d \ + --exclude /usr/share/doc/mandos-client/network-hooks.d dh_installdeb dh_shlibdeps dh_gencontrol === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2011-10-05 16:56:06 +0000 +++ initramfs-tools-hook 2011-11-29 18:19:31 +0000 @@ -68,10 +68,11 @@ CONFDIR="/conf/conf.d/mandos" MANDOSDIR="/lib/mandos" PLUGINDIR="${MANDOSDIR}/plugins.d" +HOOKDIR="${MANDOSDIR}/network-hooks.d" # Make directories install --directory --mode=u=rwx,go=rx "${DESTDIR}${CONFDIR}" \ - "${DESTDIR}${MANDOSDIR}" + "${DESTDIR}${MANDOSDIR}" "${DESTDIR}${HOOKDIR}" install --owner=${mandos_user} --group=${mandos_group} --directory \ --mode=u=rwx "${DESTDIR}${PLUGINDIR}" @@ -106,6 +107,46 @@ esac done +# Get DEVICE from initramfs.conf and other files +. /etc/initramfs-tools/initramfs.conf +for conf in /etc/initramfs-tools/conf.d/*; do + if [ -n `basename \"$conf\" | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ + | grep -v '\.dpkg-.*$'` ]; then + [ -f ${conf} ] && . ${conf} + fi +done +export DEVICE + +# Copy network hooks +for hook in /etc/mandos/network-hooks.d/*; do + case "`basename \"$hook\"`" in + "*") continue ;; + *[!A-Za-z0-9_.-]*) continue ;; + *) test -d "$hook" || copy_exec "$hook" "${HOOKDIR}" ;; + esac + if [ -x "$hook" ]; then + # Copy any files needed by the network hook + MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=files \ + VERBOSITY=0 "$hook" files | while read file target; do + if [ ! -e "${file}" ]; then + echo "WARNING: file ${file} not found, requested by Mandos network hook '${hook##*/}'" >&2 + fi + if [ -z "${target}" ]; then + copy_exec "$file" + else + copy_exec "$file" "$target" + fi + done + # Copy and load any modules needed by the network hook + MANDOSNETHOOKDIR=/etc/mandos/network-hooks.d MODE=modules \ + VERBOSITY=0 "$hook" modules | while read module; do + if [ -z "${target}" ]; then + force_load "$module" + fi + done + fi +done + # GPGME needs /usr/bin/gpg if [ ! -e "${DESTDIR}/usr/bin/gpg" \ -a -n "`ls \"${DESTDIR}\"/usr/lib/libgpgme.so* \ === added directory 'network-hooks.d' === added file 'network-hooks.d/bridge' --- network-hooks.d/bridge 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge 2011-11-28 23:40:46 +0000 @@ -0,0 +1,74 @@ +#!/bin/sh +# +# This is an example of a Mandos client network hook. This hook +# brings up a bridge interface as specified in a separate +# configuration file. To be used, this file and any needed +# configuration file(s) should be copied into the +# /etc/mandos/network-hooks.d directory. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -e + +CONFIG="$MANDOSNETHOOKDIR/bridge.conf" + +# Read config file, which must set "BRIDGE", "PORTS", and optionally +# "IPADDRS" and "ROUTES". +if [ -e "$CONFIG" ]; then + . "$CONFIG" +fi + +if [ -z "$BRIDGE" -o -z "$PORTS" ]; then + exit +fi + +if [ -n "$DEVICE" -a "$DEVICE" != "$BRIDGE" ]; then + exit +fi + +for b in /sbin/brctl /usr/sbin/brctl; do + if [ -e "$b" ]; then + brctl="$b" + break + fi +done + +case "$1" in + start) + "$brctl" addbr "$BRIDGE" + for port in $PORTS; do + "$brctl" addif "$BRIDGE" "$port" + ip link set up "$port" + done + ip link set up "$BRIDGE" + sleep "$DELAY" + if [ -n "$IPADDRS" ]; then + for ipaddr in $IPADDRS; do + ip addr add "$ipaddr" dev "$BRIDGE" + done + fi + if [ -n "$ROUTES" ]; then + for route in $ROUTES; do + ip route add "$route" dev "$BRIDGE" + done + fi + ;; + stop) + ip link set down "$BRIDGE" + for port in $PORTS; do + ip link set down "$port" + "$brctl" delif "$BRIDGE" "$port" + done + "$brctl" delbr "$BRIDGE" + ;; + files) + echo /bin/ip + echo "$brctl" + ;; + modules) + echo bridge + ;; +esac === added file 'network-hooks.d/bridge.conf' --- network-hooks.d/bridge.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/bridge.conf 2011-11-24 20:15:24 +0000 @@ -0,0 +1,11 @@ +## Required + +#BRIDGE=br0 + +#PORTS="eth0 eth1" + +## Optional + +#IPADDRS="192.0.2.3/24 2001:DB8::aede:48ff:fe71:f6f2/32" + +#ROUTES="192.0.2.0/24 2001:DB8::/32" === added file 'network-hooks.d/openvpn' --- network-hooks.d/openvpn 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn 2011-12-02 16:52:50 +0000 @@ -0,0 +1,52 @@ +#!/bin/sh +# +# This is an example of a Mandos client network hook. This hook +# brings up an OpenVPN interface as specified in a separate +# configuration file. To be used, this file and any needed +# configuration file(s) should be copied into the +# /etc/mandos/network-hooks.d directory. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +set -e + +CONFIG="openvpn.conf" + +# Extract the "dev" setting from the config file +VPNDEVICE="`sed -n -e 's/#.*//' -e 's/^[[:space:]]*dev[[:space:]]\+//p' \"$MANDOSNETHOOKDIR/$CONFIG\"`" + +PIDFILE=/run/openvpn-mandos.pid + +# Exit if no device set in config +if [ -z "$VPNDEVICE" ]; then + exit +fi + +# Exit if DEVICE is set and it doesn't match the VPN interface +if [ -n "$DEVICE" -a "$DEVICE" = "${DEVICE#$VPNDEVICE}" ]; then + exit +fi + +openvpn=/usr/sbin/openvpn + +case "$1" in + start) + "$openvpn" --cd "$MANDOSNETHOOKDIR" --daemon 'openvpn(Mandos)' --writepid "$PIDFILE" --config "$CONFIG" + sleep "$DELAY" + ;; + stop) + PID="`cat \"$PIDFILE\"`" + if [ "$PID" -gt 0 ]; then + kill "$PID" + fi + ;; + files) + echo "$openvpn" + ;; + modules) + echo tun + ;; +esac === added file 'network-hooks.d/openvpn.conf' --- network-hooks.d/openvpn.conf 1970-01-01 00:00:00 +0000 +++ network-hooks.d/openvpn.conf 2011-12-02 16:52:50 +0000 @@ -0,0 +1,19 @@ +# Sample OpenVPN configuration file +# Uncomment and change - see openvpn(8) + +# Network device. +#dev tun + +# Our remote peer +#remote 192.0.2.3 +#float 192.0.2.3 +#port 1194 + +# VPN endpoints +#ifconfig 10.1.0.1 10.1.0.2 + +# A pre-shared static key +#secret openvpn.key + +# Cipher +#cipher AES-128-CBC === modified file 'plugin-runner.c' --- plugin-runner.c 2011-10-09 12:32:13 +0000 +++ plugin-runner.c 2011-11-24 21:12:35 +0000 @@ -742,7 +742,7 @@ } } - { + if(getuid() == 0){ /* Work around Debian bug #633582: */ int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY); === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2011-10-05 16:00:56 +0000 +++ plugins.d/mandos-client.c 2011-11-28 22:18:37 +0000 @@ -53,7 +53,7 @@ sockaddr_in6, PF_INET6, SOCK_STREAM, uid_t, gid_t, open(), opendir(), DIR */ -#include /* open() */ +#include /* open(), S_ISREG */ #include /* socket(), struct sockaddr_in6, inet_pton(), connect() */ #include /* open() */ @@ -73,7 +73,7 @@ */ #include /* close(), SEEK_SET, off_t, write(), getuid(), getgid(), seteuid(), - setgid(), pause() */ + setgid(), pause(), _exit() */ #include /* inet_pton(), htons, inet_ntop() */ #include /* not, or, and */ #include /* struct argp_option, error_t, struct @@ -85,6 +85,9 @@ raise() */ #include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, EX_NOHOST, EX_IOERR, EX_PROTOCOL */ +#include /* waitpid(), WIFEXITED(), + WEXITSTATUS(), WTERMSIG() */ +#include /* setgroups() */ #ifdef __linux__ #include /* klogctl() */ @@ -108,8 +111,8 @@ init_gnutls_session(), GNUTLS_* */ #include - /* gnutls_certificate_set_openpgp_key_file(), - GNUTLS_OPENPGP_FMT_BASE64 */ + /* gnutls_certificate_set_openpgp_key_file(), + GNUTLS_OPENPGP_FMT_BASE64 */ /* GPGME */ #include /* All GPGME types, constants and @@ -123,6 +126,7 @@ #define PATHDIR "/conf/conf.d/mandos" #define SECKEY "seckey.txt" #define PUBKEY "pubkey.txt" +#define HOOKDIR "/lib/mandos/network-hooks.d" bool debug = false; static const char mandos_protocol_version[] = "1"; @@ -130,6 +134,7 @@ const char *argp_program_bug_address = ""; static const char sys_class_net[] = "/sys/class/net"; char *connect_to = NULL; +const char *hookdir = HOOKDIR; /* Doubly linked list that need to be circularly linked when used */ typedef struct server{ @@ -170,13 +175,22 @@ perror(print_text); } +int fprintf_plus(FILE *stream, const char *format, ...){ + va_list ap; + va_start (ap, format); + + TEMP_FAILURE_RETRY(fprintf(stream, "Mandos plugin %s: ", + program_invocation_short_name)); + return TEMP_FAILURE_RETRY(vfprintf(stream, format, ap)); +} + /* * Make additional room in "buffer" for at least BUFFER_SIZE more * bytes. "buffer_capacity" is how much is currently allocated, * "buffer_length" is how much is already used. */ size_t incbuffer(char **buffer, size_t buffer_length, - size_t buffer_capacity){ + size_t buffer_capacity){ if(buffer_length + BUFFER_SIZE > buffer_capacity){ *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); if(buffer == NULL){ @@ -188,9 +202,8 @@ } /* Add server to set of servers to retry periodically */ -int add_server(const char *ip, uint16_t port, - AvahiIfIndex if_index, - int af){ +int add_server(const char *ip, uint16_t port, AvahiIfIndex if_index, + int af){ int ret; server *new_server = malloc(sizeof(server)); if(new_server == NULL){ @@ -198,9 +211,9 @@ return -1; } *new_server = (server){ .ip = strdup(ip), - .port = port, - .if_index = if_index, - .af = af }; + .port = port, + .if_index = if_index, + .af = af }; if(new_server->ip == NULL){ perror_plus("strdup"); return -1; @@ -228,8 +241,8 @@ /* * Initialize GPGME. */ -static bool init_gpgme(const char *seckey, - const char *pubkey, const char *tempdir){ +static bool init_gpgme(const char *seckey, const char *pubkey, + const char *tempdir){ gpgme_error_t rc; gpgme_engine_info_t engine_info; @@ -250,15 +263,15 @@ rc = gpgme_data_new_from_fd(&pgp_data, fd); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_data_new_from_fd: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } rc = gpgme_op_import(mc.ctx, pgp_data); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_op_import: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_op_import: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } @@ -271,23 +284,23 @@ } if(debug){ - fprintf(stderr, "Initializing GPGME\n"); + fprintf_plus(stderr, "Initializing GPGME\n"); } /* Init GPGME */ gpgme_check_version(NULL); rc = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_engine_check_version: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_engine_check_version: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } /* Set GPGME home directory for the OpenPGP engine only */ rc = gpgme_get_engine_info(&engine_info); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_get_engine_info: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_get_engine_info: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return false; } while(engine_info != NULL){ @@ -299,15 +312,17 @@ engine_info = engine_info->next; } if(engine_info == NULL){ - fprintf(stderr, "Could not set GPGME home dir to %s\n", tempdir); + fprintf_plus(stderr, "Could not set GPGME home dir to %s\n", + tempdir); return false; } /* Create new GPGME "context" */ rc = gpgme_new(&(mc.ctx)); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_new: %s: %s\n", gpgme_strsource(rc), + gpgme_strerror(rc)); return false; } @@ -332,23 +347,24 @@ ssize_t plaintext_length = 0; if(debug){ - fprintf(stderr, "Trying to decrypt OpenPGP data\n"); + fprintf_plus(stderr, "Trying to decrypt OpenPGP data\n"); } /* Create new GPGME data buffer from memory cryptotext */ rc = gpgme_data_new_from_mem(&dh_crypto, cryptotext, crypto_size, 0); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "bad gpgme_data_new_from_mem: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); return -1; } /* Create new empty GPGME data buffer for the plaintext */ rc = gpgme_data_new(&dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_data_new: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + fprintf_plus(stderr, "Mandos plugin mandos-client: " + "bad gpgme_data_new: %s: %s\n", + gpgme_strsource(rc), gpgme_strerror(rc)); gpgme_data_release(dh_crypto); return -1; } @@ -357,31 +373,32 @@ data buffer */ rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); if(rc != GPG_ERR_NO_ERROR){ - fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", - gpgme_strsource(rc), gpgme_strerror(rc)); + 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); if(result == NULL){ - fprintf(stderr, "gpgme_op_decrypt_result failed\n"); + fprintf_plus(stderr, "gpgme_op_decrypt_result failed\n"); } else { - fprintf(stderr, "Unsupported algorithm: %s\n", - result->unsupported_algorithm); - fprintf(stderr, "Wrong key usage: %u\n", - result->wrong_key_usage); + fprintf_plus(stderr, "Unsupported algorithm: %s\n", + result->unsupported_algorithm); + fprintf_plus(stderr, "Wrong key usage: %u\n", + result->wrong_key_usage); if(result->file_name != NULL){ - fprintf(stderr, "File name: %s\n", result->file_name); + fprintf_plus(stderr, "File name: %s\n", result->file_name); } gpgme_recipient_t recipient; recipient = result->recipients; while(recipient != NULL){ - fprintf(stderr, "Public key algorithm: %s\n", - gpgme_pubkey_algo_name(recipient->pubkey_algo)); - fprintf(stderr, "Key ID: %s\n", recipient->keyid); - fprintf(stderr, "Secret key available: %s\n", - recipient->status == GPG_ERR_NO_SECKEY - ? "No" : "Yes"); + fprintf_plus(stderr, "Public key algorithm: %s\n", + gpgme_pubkey_algo_name + (recipient->pubkey_algo)); + fprintf_plus(stderr, "Key ID: %s\n", recipient->keyid); + fprintf_plus(stderr, "Secret key available: %s\n", + recipient->status == GPG_ERR_NO_SECKEY + ? "No" : "Yes"); recipient = recipient->next; } } @@ -390,7 +407,7 @@ } if(debug){ - fprintf(stderr, "Decryption of OpenPGP data succeeded\n"); + fprintf_plus(stderr, "Decryption of OpenPGP data succeeded\n"); } /* Seek back to the beginning of the GPGME plaintext data buffer */ @@ -403,12 +420,12 @@ *plaintext = NULL; while(true){ plaintext_capacity = incbuffer(plaintext, - (size_t)plaintext_length, - plaintext_capacity); + (size_t)plaintext_length, + plaintext_capacity); if(plaintext_capacity == 0){ - perror_plus("incbuffer"); - plaintext_length = -1; - goto decrypt_end; + perror_plus("incbuffer"); + plaintext_length = -1; + goto decrypt_end; } ret = gpgme_data_read(dh_plain, *plaintext + plaintext_length, @@ -427,7 +444,7 @@ } if(debug){ - fprintf(stderr, "Decrypted password is: "); + fprintf_plus(stderr, "Decrypted password is: "); for(ssize_t i = 0; i < plaintext_length; i++){ fprintf(stderr, "%02hhX ", (*plaintext)[i]); } @@ -455,7 +472,7 @@ /* GnuTLS log function callback */ static void debuggnutls(__attribute__((unused)) int level, const char* string){ - fprintf(stderr, "GnuTLS: %s", string); + fprintf_plus(stderr, "GnuTLS: %s", string); } static int init_gnutls_global(const char *pubkeyfilename, @@ -463,13 +480,13 @@ int ret; if(debug){ - fprintf(stderr, "Initializing GnuTLS\n"); + fprintf_plus(stderr, "Initializing GnuTLS\n"); } ret = gnutls_global_init(); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "GnuTLS global_init: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "GnuTLS global_init: %s\n", + safer_gnutls_strerror(ret)); return -1; } @@ -484,41 +501,43 @@ /* OpenPGP credentials */ ret = gnutls_certificate_allocate_credentials(&mc.cred); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "GnuTLS memory error: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "GnuTLS memory error: %s\n", + safer_gnutls_strerror(ret)); gnutls_global_deinit(); return -1; } if(debug){ - fprintf(stderr, "Attempting to use OpenPGP public key %s and" - " secret key %s as GnuTLS credentials\n", pubkeyfilename, - seckeyfilename); + fprintf_plus(stderr, "Attempting to use OpenPGP public key %s and" + " secret key %s as GnuTLS credentials\n", + pubkeyfilename, + seckeyfilename); } ret = gnutls_certificate_set_openpgp_key_file (mc.cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, - "Error[%d] while reading the OpenPGP key pair ('%s'," - " '%s')\n", ret, pubkeyfilename, seckeyfilename); - fprintf(stderr, "The GnuTLS error is: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, + "Error[%d] while reading the OpenPGP key pair ('%s'," + " '%s')\n", ret, pubkeyfilename, seckeyfilename); + fprintf_plus(stderr, "The GnuTLS error is: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } /* GnuTLS server initialization */ ret = gnutls_dh_params_init(&mc.dh_params); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error in GnuTLS DH parameter initialization:" - " %s\n", safer_gnutls_strerror(ret)); + 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); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error in GnuTLS prime generation: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Error in GnuTLS prime generation: %s\n", + safer_gnutls_strerror(ret)); goto globalfail; } @@ -544,8 +563,9 @@ } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error in GnuTLS session initialization: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, + "Error in GnuTLS session initialization: %s\n", + safer_gnutls_strerror(ret)); } { @@ -558,9 +578,9 @@ } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Syntax error at: %s\n", err); - fprintf(stderr, "GnuTLS error: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Syntax error at: %s\n", err); + fprintf_plus(stderr, "GnuTLS error: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -575,8 +595,8 @@ } } while(ret == GNUTLS_E_INTERRUPTED or ret == GNUTLS_E_AGAIN); if(ret != GNUTLS_E_SUCCESS){ - fprintf(stderr, "Error setting GnuTLS credentials: %s\n", - safer_gnutls_strerror(ret)); + fprintf_plus(stderr, "Error setting GnuTLS credentials: %s\n", + safer_gnutls_strerror(ret)); gnutls_deinit(*session); return -1; } @@ -627,7 +647,7 @@ pf = PF_INET; break; default: - fprintf(stderr, "Bad address family: %d\n", af); + fprintf_plus(stderr, "Bad address family: %d\n", af); errno = EINVAL; return -1; } @@ -638,8 +658,8 @@ } if(debug){ - fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16 - "\n", ip, port); + fprintf_plus(stderr, "Setting up a TCP connection to %s, port %" + PRIu16 "\n", ip, port); } tcp_sd = socket(pf, SOCK_STREAM, 0); @@ -671,7 +691,7 @@ } if(ret == 0){ int e = errno; - fprintf(stderr, "Bad address: %s\n", ip); + fprintf_plus(stderr, "Bad address: %s\n", ip); errno = e; goto mandos_end; } @@ -682,10 +702,10 @@ if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and - -Wunreachable-code*/ + -Wunreachable-code*/ if(if_index == AVAHI_IF_UNSPEC){ - fprintf(stderr, "An IPv6 link-local address is incomplete" - " without a network interface\n"); + fprintf_plus(stderr, "An IPv6 link-local address is" + " incomplete without a network interface\n"); errno = EINVAL; goto mandos_end; } @@ -709,12 +729,12 @@ if(if_indextoname((unsigned int)if_index, interface) == NULL){ perror_plus("if_indextoname"); } else { - fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n", - ip, interface, port); + fprintf_plus(stderr, "Connection to: %s%%%s, port %" PRIu16 + "\n", ip, interface, port); } } else { - fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, - port); + fprintf_plus(stderr, "Connection to: %s, port %" PRIu16 "\n", + ip, port); } char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; @@ -730,7 +750,7 @@ perror_plus("inet_ntop"); } else { if(strcmp(addrstr, ip) != 0){ - fprintf(stderr, "Canonical address form: %s\n", addrstr); + fprintf_plus(stderr, "Canonical address form: %s\n", addrstr); } } } @@ -764,7 +784,7 @@ while(true){ size_t out_size = strlen(out); ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, - out_size - written)); + out_size - written)); if(ret == -1){ int e = errno; perror_plus("write"); @@ -790,7 +810,7 @@ } if(debug){ - fprintf(stderr, "Establishing TLS session with %s\n", ip); + fprintf_plus(stderr, "Establishing TLS session with %s\n", ip); } if(quit_now){ @@ -816,7 +836,7 @@ if(ret != GNUTLS_E_SUCCESS){ if(debug){ - fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); + fprintf_plus(stderr, "*** GnuTLS Handshake failed ***\n"); gnutls_perror(ret); } errno = EPROTO; @@ -826,8 +846,8 @@ /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n", - ip); + fprintf_plus(stderr, "Retrieving OpenPGP encrypted password from" + " %s\n", ip); } while(true){ @@ -838,7 +858,7 @@ } buffer_capacity = incbuffer(&buffer, buffer_length, - buffer_capacity); + buffer_capacity); if(buffer_capacity == 0){ int e = errno; perror_plus("incbuffer"); @@ -871,15 +891,16 @@ } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(ret < 0){ - fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); + fprintf_plus(stderr, "*** GnuTLS Re-handshake failed " + "***\n"); gnutls_perror(ret); errno = EPROTO; goto mandos_end; } break; default: - fprintf(stderr, "Unknown error while reading data from" - " encrypted session with Mandos server\n"); + fprintf_plus(stderr, "Unknown error while reading data from" + " encrypted session with Mandos server\n"); gnutls_bye(session, GNUTLS_SHUT_RDWR); errno = EIO; goto mandos_end; @@ -890,7 +911,7 @@ } if(debug){ - fprintf(stderr, "Closing TLS session\n"); + fprintf_plus(stderr, "Closing TLS session\n"); } if(quit_now){ @@ -908,8 +929,7 @@ if(buffer_length > 0){ ssize_t decrypted_buffer_size; - decrypted_buffer_size = pgp_packet_decrypt(buffer, - buffer_length, + decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, &decrypted_buffer); if(decrypted_buffer_size >= 0){ @@ -926,8 +946,8 @@ if(ret == 0 and ferror(stdout)){ int e = errno; if(debug){ - fprintf(stderr, "Error writing encrypted data: %s\n", - strerror(errno)); + fprintf_plus(stderr, "Error writing encrypted data: %s\n", + strerror(errno)); } errno = e; goto mandos_end; @@ -990,9 +1010,10 @@ switch(event){ default: case AVAHI_RESOLVER_FAILURE: - fprintf(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))); + 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))); break; case AVAHI_RESOLVER_FOUND: @@ -1000,9 +1021,9 @@ char ip[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(ip, sizeof(ip), address); if(debug){ - fprintf(stderr, "Mandos server \"%s\" found on %s (%s, %" - PRIdMAX ") on port %" PRIu16 "\n", name, host_name, - ip, (intmax_t)interface, port); + fprintf_plus(stderr, "Mandos server \"%s\" found on %s (%s, %" + 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)); @@ -1040,8 +1061,8 @@ default: case AVAHI_BROWSER_FAILURE: - fprintf(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "(Avahi browser) %s\n", + avahi_strerror(avahi_server_errno(mc.server))); avahi_simple_poll_quit(mc.simple_poll); return; @@ -1054,8 +1075,9 @@ if(avahi_s_service_resolver_new(mc.server, interface, protocol, name, type, domain, protocol, 0, resolve_callback, NULL) == NULL) - fprintf(stderr, "Avahi: Failed to resolve service '%s': %s\n", - name, avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "Avahi: Failed to resolve service '%s':" + " %s\n", name, + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_BROWSER_REMOVE: @@ -1064,7 +1086,8 @@ case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: if(debug){ - fprintf(stderr, "No Mandos server found, still searching...\n"); + fprintf_plus(stderr, "No Mandos server found, still" + " searching...\n"); } break; } @@ -1085,107 +1108,131 @@ errno = old_errno; } -/* - * This function determines if a directory entry in /sys/class/net - * corresponds to an acceptable network device. - * (This function is passed to scandir(3) as a filter function.) - */ -int good_interface(const struct dirent *if_entry){ - ssize_t ssret; - char *flagname = NULL; - if(if_entry->d_name[0] == '.'){ - return 0; - } - int ret = asprintf(&flagname, "%s/%s/flags", sys_class_net, - if_entry->d_name); - if(ret < 0){ - perror_plus("asprintf"); - return 0; - } - int flags_fd = (int)TEMP_FAILURE_RETRY(open(flagname, O_RDONLY)); - if(flags_fd == -1){ - perror_plus("open"); - free(flagname); - return 0; - } - free(flagname); - typedef short ifreq_flags; /* ifreq.ifr_flags in netdevice(7) */ - /* read line from flags_fd */ - ssize_t to_read = 2+(sizeof(ifreq_flags)*2)+1; /* "0x1003\n" */ - char *flagstring = malloc((size_t)to_read+1); /* +1 for final \0 */ - flagstring[(size_t)to_read] = '\0'; - if(flagstring == NULL){ - perror_plus("malloc"); - close(flags_fd); - return 0; - } - while(to_read > 0){ - ssret = (ssize_t)TEMP_FAILURE_RETRY(read(flags_fd, flagstring, - (size_t)to_read)); - if(ssret == -1){ - perror_plus("read"); - free(flagstring); - close(flags_fd); - return 0; - } - to_read -= ssret; - if(ssret == 0){ - break; - } - } - close(flags_fd); - intmax_t tmpmax; - char *tmp; - errno = 0; - tmpmax = strtoimax(flagstring, &tmp, 0); - if(errno != 0 or tmp == flagstring or (*tmp != '\0' - and not (isspace(*tmp))) - or tmpmax != (ifreq_flags)tmpmax){ +bool get_flags(const char *ifname, struct ifreq *ifr){ + int ret; + + int s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(s < 0){ + perror_plus("socket"); + return false; + } + strcpy(ifr->ifr_name, ifname); + ret = ioctl(s, SIOCGIFFLAGS, ifr); + if(ret == -1){ if(debug){ - fprintf(stderr, "Invalid flags \"%s\" for interface \"%s\"\n", - flagstring, if_entry->d_name); + perror_plus("ioctl SIOCGIFFLAGS"); } - free(flagstring); - return 0; + return false; } - free(flagstring); - ifreq_flags flags = (ifreq_flags)tmpmax; + return true; +} + +bool good_flags(const char *ifname, const struct ifreq *ifr){ + /* Reject the loopback device */ - if(flags & IFF_LOOPBACK){ + if(ifr->ifr_flags & IFF_LOOPBACK){ if(debug){ - fprintf(stderr, "Rejecting loopback interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting loopback interface \"%s\"\n", + ifname); } - return 0; + return false; } /* Accept point-to-point devices only if connect_to is specified */ - if(connect_to != NULL and (flags & IFF_POINTOPOINT)){ + if(connect_to != NULL and (ifr->ifr_flags & IFF_POINTOPOINT)){ if(debug){ - fprintf(stderr, "Accepting point-to-point interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Accepting point-to-point interface" + " \"%s\"\n", ifname); } - return 1; + return true; } /* Otherwise, reject non-broadcast-capable devices */ - if(not (flags & IFF_BROADCAST)){ + if(not (ifr->ifr_flags & IFF_BROADCAST)){ if(debug){ - fprintf(stderr, "Rejecting non-broadcast interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting non-broadcast interface" + " \"%s\"\n", ifname); } - return 0; + return false; } /* Reject non-ARP interfaces (including dummy interfaces) */ - if(flags & IFF_NOARP){ + if(ifr->ifr_flags & IFF_NOARP){ if(debug){ - fprintf(stderr, "Rejecting non-ARP interface \"%s\"\n", - if_entry->d_name); + fprintf_plus(stderr, "Rejecting non-ARP interface \"%s\"\n", + ifname); } - return 0; + return false; } + /* Accept this device */ if(debug){ - fprintf(stderr, "Interface \"%s\" is acceptable\n", - if_entry->d_name); + fprintf_plus(stderr, "Interface \"%s\" is good\n", ifname); + } + return true; +} + +/* + * This function determines if a directory entry in /sys/class/net + * corresponds to an acceptable network device. + * (This function is passed to scandir(3) as a filter function.) + */ +int good_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; + } + + if(not good_flags(if_entry->d_name, &ifr)){ + return 0; + } + return 1; +} + +/* + * 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; } @@ -1201,6 +1248,67 @@ return 1; } +/* Is this directory entry a runnable program? */ +int runnable_hook(const struct dirent *direntry){ + int ret; + size_t sret; + struct stat st; + + if((direntry->d_name)[0] == '\0'){ + /* Empty name? */ + return 0; + } + + sret = strspn(direntry->d_name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-"); + if((direntry->d_name)[sret] != '\0'){ + /* Contains non-allowed characters */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" with bad name\n", + direntry->d_name); + } + return 0; + } + + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + return 0; + } + + ret = stat(fullname, &st); + if(ret == -1){ + if(debug){ + perror_plus("Could not stat hook"); + } + return 0; + } + if(not (S_ISREG(st.st_mode))){ + /* Not a regular file */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" - not a file\n", + direntry->d_name); + } + return 0; + } + if(not (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))){ + /* Not executable */ + if(debug){ + fprintf_plus(stderr, "Ignoring hook \"%s\" - not executable\n", + direntry->d_name); + } + return 0; + } + if(debug){ + fprintf_plus(stderr, "Hook \"%s\" is acceptable\n", + direntry->d_name); + } + return 1; +} + int avahi_loop_with_timeout(AvahiSimplePoll *s, int retry_interval){ int ret; struct timespec now; @@ -1210,14 +1318,14 @@ while(true){ if(mc.current_server == NULL){ if (debug){ - fprintf(stderr, - "Wait until first server is found. No timeout!\n"); + fprintf_plus(stderr, "Wait until first server is found." + " No timeout!\n"); } ret = avahi_simple_poll_iterate(s, -1); } else { if (debug){ - fprintf(stderr, "Check current_server if we should run it," - " or wait\n"); + fprintf_plus(stderr, "Check current_server if we should run" + " it, or wait\n"); } /* the current time */ ret = clock_gettime(CLOCK_MONOTONIC, &now); @@ -1239,7 +1347,8 @@ - ((intmax_t)waited_time.tv_nsec / 1000000)); if (debug){ - fprintf(stderr, "Blocking for %" PRIdMAX " ms\n", block_time); + fprintf_plus(stderr, "Blocking for %" PRIdMAX " ms\n", + block_time); } if(block_time <= 0){ @@ -1265,13 +1374,140 @@ ret = avahi_simple_poll_iterate(s, (int)block_time); } if(ret != 0){ - if (ret > 0 or errno != EINTR) { + if (ret > 0 or errno != EINTR){ return (ret != 1) ? ret : 0; } } } } +bool run_network_hooks(const char *mode, const char *interface, + const float delay){ + struct dirent **direntries; + struct dirent *direntry; + int ret; + int numhooks = scandir(hookdir, &direntries, runnable_hook, + alphasort); + if(numhooks == -1){ + perror_plus("scandir"); + } else { + int devnull = open("/dev/null", O_RDONLY); + for(int i = 0; i < numhooks; i++){ + direntry = direntries[i]; + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", hookdir, direntry->d_name); + if(ret < 0){ + perror_plus("asprintf"); + continue; + } + if(debug){ + fprintf_plus(stderr, "Running network hook \"%s\"\n", + direntry->d_name); + } + pid_t hook_pid = fork(); + 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"); + } + /* Set group */ + errno = 0; + ret = setgid(0); + if(ret == -1){ + perror_plus("setgid"); + } + /* Reset supplementary groups */ + errno = 0; + ret = setgroups(0, NULL); + if(ret == -1){ + perror_plus("setgroups"); + } + dup2(devnull, STDIN_FILENO); + close(devnull); + dup2(STDERR_FILENO, STDOUT_FILENO); + ret = setenv("MANDOSNETHOOKDIR", hookdir, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("DEVICE", interface, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("VERBOSE", debug ? "1" : "0", 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + ret = setenv("MODE", mode, 1); + if(ret == -1){ + perror_plus("setenv"); + _exit(EX_OSERR); + } + char *delaystring; + ret = asprintf(&delaystring, "%f", delay); + if(ret == -1){ + perror_plus("asprintf"); + _exit(EX_OSERR); + } + ret = setenv("DELAY", delaystring, 1); + if(ret == -1){ + free(delaystring); + perror_plus("setenv"); + _exit(EX_OSERR); + } + free(delaystring); + ret = execl(fullname, direntry->d_name, mode, NULL); + perror_plus("execl"); + } else { + int status; + if(TEMP_FAILURE_RETRY(waitpid(hook_pid, &status, 0)) == -1){ + perror_plus("waitpid"); + free(fullname); + continue; + } + if(WIFEXITED(status)){ + if(WEXITSTATUS(status) != 0){ + fprintf_plus(stderr, "Warning: network hook \"%s\" exited" + " with status %d\n", direntry->d_name, + WEXITSTATUS(status)); + free(fullname); + continue; + } + } else if(WIFSIGNALED(status)){ + fprintf_plus(stderr, "Warning: network hook \"%s\" died by" + " signal %d\n", direntry->d_name, + WTERMSIG(status)); + free(fullname); + continue; + } else { + fprintf_plus(stderr, "Warning: network hook \"%s\"" + " crashed\n", direntry->d_name); + free(fullname); + continue; + } + } + free(fullname); + if(debug){ + fprintf_plus(stderr, "Network hook \"%s\" ran successfully\n", + direntry->d_name); + } + } + close(devnull); + } + return true; +} + int main(int argc, char *argv[]){ AvahiSServiceBrowser *sb = NULL; int error; @@ -1359,6 +1595,10 @@ .arg = "SECONDS", .doc = "Retry interval used when denied by the mandos server", .group = 2 }, + { .name = "network-hook-dir", .key = 133, + .arg = "DIR", + .doc = "Directory where network hooks are located", + .group = 2 }, /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -1417,6 +1657,9 @@ argp_error(state, "Bad retry interval"); } break; + case 133: /* --network-hook-dir */ + hookdir = arg; + break; /* * These reproduce what we would get without ARGP_NO_HELP */ @@ -1428,7 +1671,9 @@ argp_state_help(state, state->out_stream, ARGP_HELP_USAGE | ARGP_HELP_EXIT_ERR); case 'V': /* --version */ - fprintf(state->out_stream, "%s\n", argp_program_version); + fprintf_plus(state->out_stream, + "Mandos plugin mandos-client: "); + fprintf_plus(state->out_stream, "%s\n", argp_program_version); exit(argp_err_exit_status); break; default: @@ -1461,76 +1706,91 @@ { /* Work around Debian bug #633582: */ - struct stat st; /* Re-raise priviliges */ errno = 0; ret = seteuid(0); if(ret == -1){ perror_plus("seteuid"); - } - - if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ - int seckey_fd = open(seckey, O_RDONLY); - if(seckey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(seckey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - TEMP_FAILURE_RETRY(close(seckey_fd)); - } - } - - if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ - int pubkey_fd = open(pubkey, O_RDONLY); - if(pubkey_fd == -1){ - perror_plus("open"); - } else { - ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); - if(ret == -1){ - perror_plus("fstat"); - } else { - if(S_ISREG(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){ - ret = fchown(pubkey_fd, uid, gid); - if(ret == -1){ - perror_plus("fchown"); - } - } - } - TEMP_FAILURE_RETRY(close(pubkey_fd)); - } - } - - /* Lower privileges */ - errno = 0; - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } + } else { + struct stat st; + + if(strcmp(seckey, PATHDIR "/" SECKEY) == 0){ + int seckey_fd = open(seckey, O_RDONLY); + if(seckey_fd == -1){ + perror_plus("open"); + } else { + ret = (int)TEMP_FAILURE_RETRY(fstat(seckey_fd, &st)); + if(ret == -1){ + perror_plus("fstat"); + } else { + if(S_ISREG(st.st_mode) + and st.st_uid == 0 and st.st_gid == 0){ + ret = fchown(seckey_fd, uid, gid); + if(ret == -1){ + perror_plus("fchown"); + } + } + } + TEMP_FAILURE_RETRY(close(seckey_fd)); + } + } + + if(strcmp(pubkey, PATHDIR "/" PUBKEY) == 0){ + int pubkey_fd = open(pubkey, O_RDONLY); + if(pubkey_fd == -1){ + perror_plus("open"); + } else { + ret = (int)TEMP_FAILURE_RETRY(fstat(pubkey_fd, &st)); + if(ret == -1){ + perror_plus("fstat"); + } else { + if(S_ISREG(st.st_mode) + and st.st_uid == 0 and st.st_gid == 0){ + ret = fchown(pubkey_fd, uid, gid); + if(ret == -1){ + perror_plus("fchown"); + } + } + } + TEMP_FAILURE_RETRY(close(pubkey_fd)); + } + } + + /* Lower privileges */ + errno = 0; + ret = seteuid(uid); + if(ret == -1){ + perror_plus("seteuid"); + } + } + } + + /* Run network hooks */ + if(not run_network_hooks("start", interface, delay)){ + goto end; } if(not debug){ avahi_set_log_function(empty_log); } - + if(interface[0] == '\0'){ struct dirent **direntries; - ret = scandir(sys_class_net, &direntries, good_interface, + /* 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 good interface */ + /* Pick the first interface returned */ interface = strdup(direntries[0]->d_name); if(debug){ - fprintf(stderr, "Using interface \"%s\"\n", interface); + fprintf_plus(stderr, "Using interface \"%s\"\n", interface); } if(interface == NULL){ perror_plus("malloc"); @@ -1541,7 +1801,7 @@ free(direntries); } else { free(direntries); - fprintf(stderr, "Could not find a network interface\n"); + fprintf_plus(stderr, "Could not find a network interface\n"); exitcode = EXIT_FAILURE; goto end; } @@ -1553,7 +1813,8 @@ srand((unsigned int) time(NULL)); mc.simple_poll = avahi_simple_poll_new(); if(mc.simple_poll == NULL){ - fprintf(stderr, "Avahi: Failed to create simple poll object.\n"); + fprintf_plus(stderr, + "Avahi: Failed to create simple poll object.\n"); exitcode = EX_UNAVAILABLE; goto end; } @@ -1625,7 +1886,7 @@ if(strcmp(interface, "none") != 0){ if_index = (AvahiIfIndex) if_nametoindex(interface); if(if_index == 0){ - fprintf(stderr, "No such interface: \"%s\"\n", interface); + fprintf_plus(stderr, "No such interface: \"%s\"\n", interface); exitcode = EX_UNAVAILABLE; goto end; } @@ -1751,18 +2012,10 @@ #endif /* __linux__ */ /* Lower privileges */ errno = 0; - if(take_down_interface){ - /* Lower privileges */ - ret = seteuid(uid); - if(ret == -1){ - perror_plus("seteuid"); - } - } else { - /* Lower privileges permanently */ - ret = setuid(uid); - if(ret == -1){ - perror_plus("setuid"); - } + /* Lower privileges */ + ret = seteuid(uid); + if(ret == -1){ + perror_plus("seteuid"); } } @@ -1772,7 +2025,7 @@ ret = init_gnutls_global(pubkey, seckey); if(ret == -1){ - fprintf(stderr, "init_gnutls_global failed\n"); + fprintf_plus(stderr, "init_gnutls_global failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -1794,7 +2047,7 @@ } if(not init_gpgme(pubkey, seckey, tempdir)){ - fprintf(stderr, "init_gpgme failed\n"); + fprintf_plus(stderr, "init_gpgme failed\n"); exitcode = EX_UNAVAILABLE; goto end; } else { @@ -1810,7 +2063,7 @@ /* (Mainly meant for debugging) */ char *address = strrchr(connect_to, ':'); if(address == NULL){ - fprintf(stderr, "No colon in address\n"); + fprintf_plus(stderr, "No colon in address\n"); exitcode = EX_USAGE; goto end; } @@ -1824,7 +2077,7 @@ tmpmax = strtoimax(address+1, &tmp, 10); if(errno != 0 or tmp == address+1 or *tmp != '\0' or tmpmax != (uint16_t)tmpmax){ - fprintf(stderr, "Bad port number\n"); + fprintf_plus(stderr, "Bad port number\n"); exitcode = EX_USAGE; goto end; } @@ -1860,8 +2113,8 @@ break; } if(debug){ - fprintf(stderr, "Retrying in %d seconds\n", - (int)retry_interval); + fprintf_plus(stderr, "Retrying in %d seconds\n", + (int)retry_interval); } sleep((int)retry_interval); } @@ -1869,7 +2122,7 @@ if (not quit_now){ exitcode = EXIT_SUCCESS; } - + goto end; } @@ -1897,8 +2150,8 @@ /* Check if creating the Avahi server object succeeded */ if(mc.server == NULL){ - fprintf(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(error)); + fprintf_plus(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); exitcode = EX_UNAVAILABLE; goto end; } @@ -1912,8 +2165,8 @@ AVAHI_PROTO_UNSPEC, "_mandos._tcp", NULL, 0, browse_callback, NULL); if(sb == NULL){ - fprintf(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); + fprintf_plus(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); exitcode = EX_UNAVAILABLE; goto end; } @@ -1925,20 +2178,20 @@ /* Run the main loop */ if(debug){ - fprintf(stderr, "Starting Avahi loop search\n"); + fprintf_plus(stderr, "Starting Avahi loop search\n"); } ret = avahi_loop_with_timeout(mc.simple_poll, (int)(retry_interval * 1000)); if(debug){ - fprintf(stderr, "avahi_loop_with_timeout exited %s\n", - (ret == 0) ? "successfully" : "with error"); + fprintf_plus(stderr, "avahi_loop_with_timeout exited %s\n", + (ret == 0) ? "successfully" : "with error"); } end: if(debug){ - fprintf(stderr, "%s exiting\n", argv[0]); + fprintf_plus(stderr, "%s exiting\n", argv[0]); } /* Cleanup things */ @@ -1960,7 +2213,7 @@ if(gpgme_initialized){ gpgme_release(mc.ctx); } - + /* Cleans up the circular linked list of Mandos servers the client has seen */ if(mc.current_server != NULL){ @@ -1972,19 +2225,23 @@ } } - /* Take down the network interface */ - if(take_down_interface){ - /* Re-raise priviliges */ + /* Run network hooks */ + run_network_hooks("stop", interface, delay); + + /* Re-raise priviliges */ + { errno = 0; ret = seteuid(0); if(ret == -1){ perror_plus("seteuid"); } - if(geteuid() == 0){ + + /* 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) { + } else if(network.ifr_flags & IFF_UP){ network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ ret = ioctl(sd, SIOCSIFFLAGS, &network); if(ret == -1){ @@ -1995,14 +2252,14 @@ if(ret == -1){ perror_plus("close"); } - /* Lower privileges permanently */ - errno = 0; - ret = setuid(uid); - if(ret == -1){ - perror_plus("setuid"); - } } } + /* Lower privileges permanently */ + errno = 0; + ret = setuid(uid); + if(ret == -1){ + perror_plus("setuid"); + } /* Removes the GPGME temp directory and all files inside */ if(tempdir_created){ @@ -2022,8 +2279,8 @@ } ret = remove(fullname); if(ret == -1){ - fprintf(stderr, "remove(\"%s\"): %s\n", fullname, - strerror(errno)); + fprintf_plus(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); } free(fullname); } === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2011-10-05 16:00:56 +0000 +++ plugins.d/mandos-client.xml 2011-11-27 14:44:28 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -102,6 +102,11 @@ + + + + @@ -143,6 +148,26 @@ 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 + 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). + + + Before a network interface is selected, all network + hooks are run; see . + + This program is not meant to be run directly; it is really meant to run as a plugin of the Mandos plugin-runner @@ -223,7 +248,8 @@ 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. + by this program, unless created by a network + hook — see . NAME can be the string @@ -311,6 +337,18 @@ + + + + + + Network hook directory. The default directory is + /lib/mandos/network-hooks.d. + + + @@ -377,8 +415,10 @@ plugin-runner 8mandos) is used to run both this program and others in in parallel, - one of which will prompt for passwords on - the system console. + one of which ( + password-prompt + 8mandos) will prompt for + passwords on the system console. @@ -405,6 +445,158 @@ + + NETWORK HOOKS + + If a network interface like a bridge or tunnel is required to + find a Mandos server, this requires the interface to be up and + running before &COMMANDNAME; starts looking + for Mandos servers. This can be accomplished by creating a + network hook program, and placing it in a special + directory. + + + Before the network is used (and again before program exit), any + runnable programs found in the network hook directory are run + with the argument start or + stop. This should bring up or + down, respectively, any network interface which + &COMMANDNAME; should use. + + + REQUIREMENTS + + A network hook must be an executable file, and its name must + consist entirely of upper and lower case letters, digits, + underscores, periods, and hyphens. + + + A network hook will receive one argument, which can be one of + the following: + + + + start + + + This should make the network hook create (if necessary) + and bring up a network interface. + + + + + stop + + + This should make the network hook take down a network + interface, and delete it if it did not exist previously. + + + + + files + + + This should make the network hook print, one + file per line, all the files needed for it to + run. (These files will be copied into the initial RAM + filesystem.) Typical use is for a network hook which is + a shell script to print its needed binaries. + + + It is not necessary to print any non-executable files + already in the network hook directory, these will be + copied implicitly if they otherwise satisfy the name + requirement. + + + + + modules + + + This should make the network hook print, on + separate lines, all the kernel modules needed + for it to run. (These modules will be copied into the + initial RAM filesystem.) For instance, a tunnel + interface needs the + tun module. + + + + + + The network hook will be provided with a number of environment + variables: + + + + MANDOSNETHOOKDIR + + + The network hook directory, specified to + &COMMANDNAME; by the + option. Note: this + should always be used by the + network hook to refer to itself or any files in the hook + directory it may require. + + + + + DEVICE + + + The network interface, 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. + + + + + MODE + + + This will be the same as the first argument; + i.e. start, + stop, + files, or + modules. + + + + + VERBOSITY + + + This will be the 1 if + the option is passed to + &COMMANDNAME;, otherwise + 0. + + + + + DELAY + + + This will be the same as the + option passed to &COMMANDNAME;. + + + + + + A hook may not read from standard input, and should be + restrictive in printing to standard output or standard error + unless VERBOSITY is + 1. + + + + FILES @@ -422,6 +614,17 @@ + + /lib/mandos/network-hooks.d + + + Directory where network hooks are located. Change this + with the option. See + . + + +