=== 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 Mandosplugin-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-runner8mandos) 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
+ .
+
+
+