=== modified file 'DBUS-API'
--- DBUS-API 2011-10-02 19:18:24 +0000
+++ DBUS-API 2011-11-26 23:08:17 +0000
@@ -112,7 +112,7 @@
Disable().
f) The date and time this client will be disabled, as an RFC 3339
- string, or an empty string if this has not happened.
+ string, or an empty string if this is not scheduled.
g) The date and time of the last approval request, as an RFC 3339
string, or an empty string if this has not happened.
@@ -152,6 +152,9 @@
*** Rejected(s: Reason)
This client was not given its secret for a specified Reason.
+*** NewRequest(s: IP address)
+ A client at IP adress has asked for its password.
+
* Copyright
Copyright © 2010-2011 Teddy Hogeborn
=== modified file 'Makefile'
--- Makefile 2011-10-22 00:46:35 +0000
+++ Makefile 2011-12-21 00:33:12 +0000
@@ -26,12 +26,16 @@
version=1.4.1
SED=sed
+USER=$(firstword $(subst :, ,$(shell getent passwd _mandos || getent passwd nobody || echo 65534)))
+GROUP=$(firstword $(subst :, ,$(shell getent group _mandos || getent group nobody || echo 65534)))
+
## Use these settings for a traditional /usr/local install
# PREFIX=$(DESTDIR)/usr/local
# CONFDIR=$(DESTDIR)/etc/mandos
# KEYDIR=$(DESTDIR)/etc/mandos/keys
# MANDIR=$(PREFIX)/man
# INITRAMFSTOOLS=$(DESTDIR)/etc/initramfs-tools
+# STATEDIR=$(DESTDIR)/var/lib/mandos
##
## These settings are for a package-type install
@@ -40,6 +44,7 @@
KEYDIR=$(DESTDIR)/etc/keys/mandos
MANDIR=$(PREFIX)/share/man
INITRAMFSTOOLS=$(DESTDIR)/usr/share/initramfs-tools
+STATEDIR=$(DESTDIR)/var/lib/mandos
##
GNUTLS_CFLAGS=$(shell pkg-config --cflags-only-I gnutls)
@@ -230,7 +235,7 @@
distclean: clean
mostlyclean: clean
maintainer-clean: clean
- -rm --force --recursive keydir confdir
+ -rm --force --recursive keydir confdir statedir
check: all
./mandos --check
@@ -250,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
@@ -260,7 +265,8 @@
# Run the server with a local config
run-server: confdir/mandos.conf confdir/clients.conf
- ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS)
+ ./mandos --debug --no-dbus --configdir=confdir \
+ --statedir=statedir $(SERVERARGS)
# Used by run-server
confdir/mandos.conf: mandos.conf
@@ -271,6 +277,8 @@
install --mode=u=rw $< $@
# Add a client password
./mandos-keygen --dir keydir --password >> $@
+statedir:
+ install --directory statedir
install: install-server install-client-nokey
@@ -281,6 +289,8 @@
install-server: doc
install --directory $(CONFDIR)
+ install --directory --mode=u=rwx --owner=$(USER) \
+ --group=$(GROUP) $(STATEDIR)
install --mode=u=rwx,go=rx mandos $(PREFIX)/sbin/mandos
install --mode=u=rwx,go=rx --target-directory=$(PREFIX)/sbin \
mandos-ctl
@@ -318,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-10-22 00:46:35 +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()
@@ -40,22 +43,21 @@
** TODO [#B] Use openat()
* mandos (server)
+** TODO Document why we ignore sigint
** TODO [#B] Log level :BUGS:
-** TODO Persistent state :BUGS:
- /var/lib/mandos/*
+** 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
@@ -66,12 +68,12 @@
http://0pointer.de/blog/projects/systemd.html
http://wiki.debian.org/systemd
** TODO Separate logging logic to own object
-** TODO make clients to a dict!
** TODO [#A] Limit approval_delay to max gnutls/tls timeout value
** TODO [#B] break the wait on approval_delay if connection dies
** TODO Generate Client.runtime_expansions from client options + extra
** TODO Allow %%(checker)s as a runtime expansion
** TODO Use python-tlslite?
+** TODO D-Bus AddClient() method on server object
* mandos.xml
** Add mandos contact info in manual pages
=== modified file 'clients.conf'
--- clients.conf 2011-09-19 09:42:55 +0000
+++ clients.conf 2011-11-26 23:08:17 +0000
@@ -30,6 +30,9 @@
# How long one approval will last.
;approval_duration = 1s
+# Whether this client is enabled by default
+;enabled = True
+
;####
;# Example client
=== modified file 'debian/control'
--- debian/control 2011-12-21 17:08:29 +0000
+++ debian/control 2011-12-21 17:09:12 +0000
@@ -17,7 +17,8 @@
Architecture: all
Depends: ${misc:Depends}, python (>=2.6), python-gnutls, python-dbus,
python-avahi, python-gobject, avahi-daemon, adduser,
- python-urwid, python (>=2.7) | python-argparse
+ python-urwid, python (>=2.7) | python-argparse,
+ python-gnupginterface
Recommends: fping
Description: server giving encrypted passwords to Mandos clients
This is the server part of the Mandos system, which allows
=== 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/mandos.dirs'
--- debian/mandos.dirs 2010-09-15 17:33:14 +0000
+++ debian/mandos.dirs 2011-11-26 22:22:20 +0000
@@ -4,3 +4,4 @@
etc/default
etc/dbus-1/system.d
usr/sbin
+var/lib/mandos
=== modified file 'debian/mandos.postinst'
--- debian/mandos.postinst 2011-10-10 20:29:58 +0000
+++ debian/mandos.postinst 2011-11-26 22:22:20 +0000
@@ -35,11 +35,12 @@
--disabled-password --gecos "Mandos password system" \
_mandos
fi
+ chown _mandos:_mandos /var/lib/mandos
;;
-
+
abort-upgrade|abort-deconfigure|abort-remove)
;;
-
+
*)
echo "$0 called with unknown argument '$1'" 1>&2
exit 1
=== 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* \
=== modified file 'mandos'
--- mandos 2011-10-22 00:46:35 +0000
+++ mandos 2011-12-21 16:33:08 +0000
@@ -63,6 +63,8 @@
import cPickle as pickle
import multiprocessing
import types
+import binascii
+import tempfile
import dbus
import dbus.service
@@ -73,6 +75,7 @@
import ctypes.util
import xml.dom.minidom
import inspect
+import GnuPGInterface
try:
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
@@ -82,24 +85,126 @@
except ImportError:
SO_BINDTODEVICE = None
-
version = "1.4.1"
+stored_state_file = "clients.pickle"
logger = logging.getLogger()
syslogger = (logging.handlers.SysLogHandler
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
address = str("/dev/log")))
-syslogger.setFormatter(logging.Formatter
- ('Mandos [%(process)d]: %(levelname)s:'
- ' %(message)s'))
-logger.addHandler(syslogger)
-
-console = logging.StreamHandler()
-console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
- ' [%(process)d]:'
- ' %(levelname)s:'
- ' %(message)s'))
-logger.addHandler(console)
+
+try:
+ if_nametoindex = (ctypes.cdll.LoadLibrary
+ (ctypes.util.find_library("c"))
+ .if_nametoindex)
+except (OSError, AttributeError):
+ def if_nametoindex(interface):
+ "Get an interface index the hard way, i.e. using fcntl()"
+ SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
+ with contextlib.closing(socket.socket()) as s:
+ ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
+ struct.pack(str("16s16x"),
+ interface))
+ interface_index = struct.unpack(str("I"),
+ ifreq[16:20])[0]
+ return interface_index
+
+
+def initlogger(level=logging.WARNING):
+ """init logger and add loglevel"""
+
+ syslogger.setFormatter(logging.Formatter
+ ('Mandos [%(process)d]: %(levelname)s:'
+ ' %(message)s'))
+ logger.addHandler(syslogger)
+
+ console = logging.StreamHandler()
+ console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
+ ' [%(process)d]:'
+ ' %(levelname)s:'
+ ' %(message)s'))
+ logger.addHandler(console)
+ logger.setLevel(level)
+
+
+class PGPError(Exception):
+ """Exception if encryption/decryption fails"""
+ pass
+
+
+class PGPEngine(object):
+ """A simple class for OpenPGP symmetric encryption & decryption"""
+ def __init__(self):
+ self.gnupg = GnuPGInterface.GnuPG()
+ self.tempdir = tempfile.mkdtemp(prefix="mandos-")
+ self.gnupg = GnuPGInterface.GnuPG()
+ self.gnupg.options.meta_interactive = False
+ self.gnupg.options.homedir = self.tempdir
+ self.gnupg.options.extra_args.extend(['--force-mdc',
+ '--quiet'])
+
+ def __enter__(self):
+ return self
+
+ def __exit__ (self, exc_type, exc_value, traceback):
+ self._cleanup()
+ return False
+
+ def __del__(self):
+ self._cleanup()
+
+ def _cleanup(self):
+ if self.tempdir is not None:
+ # Delete contents of tempdir
+ for root, dirs, files in os.walk(self.tempdir,
+ topdown = False):
+ for filename in files:
+ os.remove(os.path.join(root, filename))
+ for dirname in dirs:
+ os.rmdir(os.path.join(root, dirname))
+ # Remove tempdir
+ os.rmdir(self.tempdir)
+ self.tempdir = None
+
+ def password_encode(self, password):
+ # Passphrase can not be empty and can not contain newlines or
+ # NUL bytes. So we prefix it and hex encode it.
+ return b"mandos" + binascii.hexlify(password)
+
+ def encrypt(self, data, password):
+ self.gnupg.passphrase = self.password_encode(password)
+ with open(os.devnull) as devnull:
+ try:
+ proc = self.gnupg.run(['--symmetric'],
+ create_fhs=['stdin', 'stdout'],
+ attach_fhs={'stderr': devnull})
+ with contextlib.closing(proc.handles['stdin']) as f:
+ f.write(data)
+ with contextlib.closing(proc.handles['stdout']) as f:
+ ciphertext = f.read()
+ proc.wait()
+ except IOError as e:
+ raise PGPError(e)
+ self.gnupg.passphrase = None
+ return ciphertext
+
+ def decrypt(self, data, password):
+ self.gnupg.passphrase = self.password_encode(password)
+ with open(os.devnull) as devnull:
+ try:
+ proc = self.gnupg.run(['--decrypt'],
+ create_fhs=['stdin', 'stdout'],
+ attach_fhs={'stderr': devnull})
+ with contextlib.closing(proc.handles['stdin'] ) as f:
+ f.write(data)
+ with contextlib.closing(proc.handles['stdout']) as f:
+ decrypted_plaintext = f.read()
+ proc.wait()
+ except IOError as e:
+ raise PGPError(e)
+ self.gnupg.passphrase = None
+ return decrypted_plaintext
+
class AvahiError(Exception):
@@ -222,7 +327,7 @@
try:
self.group.Free()
except (dbus.exceptions.UnknownMethodException,
- dbus.exceptions.DBusException) as e:
+ dbus.exceptions.DBusException):
pass
self.group = None
self.remove()
@@ -272,7 +377,7 @@
% self.name))
return ret
-def _timedelta_to_milliseconds(td):
+def timedelta_to_milliseconds(td):
"Convert a datetime.timedelta() to milliseconds"
return ((td.days * 24 * 60 * 60 * 1000)
+ (td.seconds * 1000)
@@ -282,7 +387,7 @@
"""A representation of a client host served by this server.
Attributes:
- _approved: bool(); 'None' if not yet approved/disapproved
+ approved: bool(); 'None' if not yet approved/disapproved
approval_delay: datetime.timedelta(); Time to wait for approval
approval_duration: datetime.timedelta(); Duration of one approval
checker: subprocess.Popen(); a running checker process used
@@ -295,8 +400,9 @@
instance %(name)s can be used in the command.
checker_initiator_tag: a gobject event source tag, or None
created: datetime.datetime(); (UTC) object creation
+ client_structure: Object describing what attributes a client has
+ and is used for storing the client at exit
current_checker_command: string; current running checker_command
- disable_hook: If set, called by disable() as disable_hook(self)
disable_initiator_tag: a gobject event source tag, or None
enabled: bool()
fingerprint: string (40 or 32 hexadecimal digits); used to
@@ -305,7 +411,10 @@
interval: datetime.timedelta(); How often to start a new checker
last_approval_request: datetime.datetime(); (UTC) or None
last_checked_ok: datetime.datetime(); (UTC) or None
- last_enabled: datetime.datetime(); (UTC)
+ last_checker_status: integer between 0 and 255 reflecting exit
+ status of last checker. -1 reflects crashed
+ checker, or None.
+ last_enabled: datetime.datetime(); (UTC) or None
name: string; from the config file, used in log messages and
D-Bus identifiers
secret: bytestring; sent verbatim (over TLS) to client
@@ -321,81 +430,126 @@
"created", "enabled", "fingerprint",
"host", "interval", "last_checked_ok",
"last_enabled", "name", "timeout")
+ client_defaults = { "timeout": "5m",
+ "extended_timeout": "15m",
+ "interval": "2m",
+ "checker": "fping -q -- %%(host)s",
+ "host": "",
+ "approval_delay": "0s",
+ "approval_duration": "1s",
+ "approved_by_default": "True",
+ "enabled": "True",
+ }
def timeout_milliseconds(self):
"Return the 'timeout' attribute in milliseconds"
- return _timedelta_to_milliseconds(self.timeout)
+ return timedelta_to_milliseconds(self.timeout)
def extended_timeout_milliseconds(self):
"Return the 'extended_timeout' attribute in milliseconds"
- return _timedelta_to_milliseconds(self.extended_timeout)
+ return timedelta_to_milliseconds(self.extended_timeout)
def interval_milliseconds(self):
"Return the 'interval' attribute in milliseconds"
- return _timedelta_to_milliseconds(self.interval)
+ return timedelta_to_milliseconds(self.interval)
def approval_delay_milliseconds(self):
- return _timedelta_to_milliseconds(self.approval_delay)
-
- def __init__(self, name = None, disable_hook=None, config=None):
+ return timedelta_to_milliseconds(self.approval_delay)
+
+ @staticmethod
+ def config_parser(config):
+ """ Construct a new dict of client settings of this form:
+ { client_name: {setting_name: value, ...}, ...}
+ with exceptions for any special settings as defined above"""
+ settings = {}
+ for client_name in config.sections():
+ section = dict(config.items(client_name))
+ client = settings[client_name] = {}
+
+ client["host"] = section["host"]
+ # Reformat values from string types to Python types
+ client["approved_by_default"] = config.getboolean(
+ client_name, "approved_by_default")
+ client["enabled"] = config.getboolean(client_name, "enabled")
+
+ client["fingerprint"] = (section["fingerprint"].upper()
+ .replace(" ", ""))
+ if "secret" in section:
+ client["secret"] = section["secret"].decode("base64")
+ elif "secfile" in section:
+ with open(os.path.expanduser(os.path.expandvars
+ (section["secfile"])),
+ "rb") as secfile:
+ client["secret"] = secfile.read()
+ else:
+ raise TypeError("No secret or secfile for section %s"
+ % section)
+ client["timeout"] = string_to_delta(section["timeout"])
+ client["extended_timeout"] = string_to_delta(
+ section["extended_timeout"])
+ client["interval"] = string_to_delta(section["interval"])
+ client["approval_delay"] = string_to_delta(
+ section["approval_delay"])
+ client["approval_duration"] = string_to_delta(
+ section["approval_duration"])
+ client["checker_command"] = section["checker"]
+ client["last_approval_request"] = None
+ client["last_checked_ok"] = None
+ client["last_checker_status"] = None
+ if client["enabled"]:
+ client["last_enabled"] = datetime.datetime.utcnow()
+ client["expires"] = (datetime.datetime.utcnow()
+ + client["timeout"])
+ else:
+ client["last_enabled"] = None
+ client["expires"] = None
+
+ return settings
+
+
+ def __init__(self, settings, name = None):
"""Note: the 'checker' key in 'config' sets the
'checker_command' attribute and *not* the 'checker'
attribute."""
self.name = name
- if config is None:
- config = {}
+ # adding all client settings
+ for setting, value in settings.iteritems():
+ setattr(self, setting, value)
+
logger.debug("Creating client %r", self.name)
# Uppercase and remove spaces from fingerprint for later
# comparison purposes with return value from the fingerprint()
# function
- self.fingerprint = (config["fingerprint"].upper()
- .replace(" ", ""))
logger.debug(" Fingerprint: %s", self.fingerprint)
- if "secret" in config:
- self.secret = config["secret"].decode("base64")
- elif "secfile" in config:
- with open(os.path.expanduser(os.path.expandvars
- (config["secfile"])),
- "rb") as secfile:
- self.secret = secfile.read()
- else:
- raise TypeError("No secret or secfile for client %s"
- % self.name)
- self.host = config.get("host", "")
- self.created = datetime.datetime.utcnow()
- self.enabled = False
- self.last_approval_request = None
- self.last_enabled = None
- self.last_checked_ok = None
- self.timeout = string_to_delta(config["timeout"])
- self.extended_timeout = string_to_delta(config
- ["extended_timeout"])
- self.interval = string_to_delta(config["interval"])
- self.disable_hook = disable_hook
+ self.created = settings.get("created", datetime.datetime.utcnow())
+
+ # attributes specific for this server instance
self.checker = None
self.checker_initiator_tag = None
self.disable_initiator_tag = None
- self.expires = None
self.checker_callback_tag = None
- self.checker_command = config["checker"]
self.current_checker_command = None
- self.last_connect = None
- self._approved = None
- self.approved_by_default = config.get("approved_by_default",
- True)
+ self.approved = None
self.approvals_pending = 0
- self.approval_delay = string_to_delta(
- config["approval_delay"])
- self.approval_duration = string_to_delta(
- config["approval_duration"])
self.changedstate = (multiprocessing_manager
.Condition(multiprocessing_manager
.Lock()))
+ self.client_structure = [attr for attr in
+ self.__dict__.iterkeys()
+ if not attr.startswith("_")]
+ self.client_structure.append("client_structure")
+
+ for name, t in inspect.getmembers(type(self),
+ lambda obj:
+ isinstance(obj,
+ property)):
+ if not name.startswith("_"):
+ self.client_structure.append(name)
+ # Send notice to process children that client state has changed
def send_changedstate(self):
- self.changedstate.acquire()
- self.changedstate.notify_all()
- self.changedstate.release()
+ with self.changedstate:
+ self.changedstate.notify_all()
def enable(self):
"""Start this client's checker and timeout hooks"""
@@ -403,20 +557,10 @@
# Already enabled
return
self.send_changedstate()
- # Schedule a new checker to be started an 'interval' from now,
- # and every interval from then on.
- self.checker_initiator_tag = (gobject.timeout_add
- (self.interval_milliseconds(),
- self.start_checker))
- # Schedule a disable() when 'timeout' has passed
self.expires = datetime.datetime.utcnow() + self.timeout
- self.disable_initiator_tag = (gobject.timeout_add
- (self.timeout_milliseconds(),
- self.disable))
self.enabled = True
self.last_enabled = datetime.datetime.utcnow()
- # Also start a new checker *right now*.
- self.start_checker()
+ self.init_checker()
def disable(self, quiet=True):
"""Disable this client."""
@@ -434,23 +578,33 @@
gobject.source_remove(self.checker_initiator_tag)
self.checker_initiator_tag = None
self.stop_checker()
- if self.disable_hook:
- self.disable_hook(self)
self.enabled = False
# Do not run this again if called by a gobject.timeout_add
return False
def __del__(self):
- self.disable_hook = None
self.disable()
+ def init_checker(self):
+ # Schedule a new checker to be started an 'interval' from now,
+ # and every interval from then on.
+ self.checker_initiator_tag = (gobject.timeout_add
+ (self.interval_milliseconds(),
+ self.start_checker))
+ # Schedule a disable() when 'timeout' has passed
+ self.disable_initiator_tag = (gobject.timeout_add
+ (self.timeout_milliseconds(),
+ self.disable))
+ # Also start a new checker *right now*.
+ self.start_checker()
+
def checker_callback(self, pid, condition, command):
"""The checker has completed, so take appropriate actions."""
self.checker_callback_tag = None
self.checker = None
if os.WIFEXITED(condition):
- exitstatus = os.WEXITSTATUS(condition)
- if exitstatus == 0:
+ self.last_checker_status = os.WEXITSTATUS(condition)
+ if self.last_checker_status == 0:
logger.info("Checker for %(name)s succeeded",
vars(self))
self.checked_ok()
@@ -458,6 +612,7 @@
logger.info("Checker for %(name)s failed",
vars(self))
else:
+ self.last_checker_status = -1
logger.warning("Checker for %(name)s crashed?",
vars(self))
@@ -474,7 +629,7 @@
gobject.source_remove(self.disable_initiator_tag)
if getattr(self, "enabled", False):
self.disable_initiator_tag = (gobject.timeout_add
- (_timedelta_to_milliseconds
+ (timedelta_to_milliseconds
(timeout), self.disable))
self.expires = datetime.datetime.utcnow() + timeout
@@ -697,7 +852,7 @@
Note: Will not include properties with access="write".
"""
- all = {}
+ properties = {}
for name, prop in self._get_all_dbus_properties():
if (interface_name
and interface_name != prop._dbus_interface):
@@ -708,11 +863,11 @@
continue
value = prop()
if not hasattr(value, "variant_level"):
- all[name] = value
+ properties[name] = value
continue
- all[name] = type(value)(value, variant_level=
- value.variant_level+1)
- return dbus.Dictionary(all, signature="sv")
+ properties[name] = type(value)(value, variant_level=
+ value.variant_level+1)
+ return dbus.Dictionary(properties, signature="sv")
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
out_signature="s",
@@ -769,6 +924,7 @@
return dbus.String(dt.isoformat(),
variant_level=variant_level)
+
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
.__metaclass__):
"""Applied to an empty subclass of a D-Bus object, this metaclass
@@ -866,6 +1022,7 @@
attribute.func_closure)))
return type.__new__(mcs, name, bases, attr)
+
class ClientDBus(Client, DBusObjectWithProperties):
"""A Client class using D-Bus
@@ -880,9 +1037,11 @@
# dbus.service.Object doesn't use super(), so we can't either.
def __init__(self, bus = None, *args, **kwargs):
- self._approvals_pending = 0
self.bus = bus
Client.__init__(self, *args, **kwargs)
+ self._approvals_pending = 0
+
+ self._approvals_pending = 0
# Only now, when this client is initialized, can it show up on
# the D-Bus
client_object_name = unicode(self.name).translate(
@@ -898,7 +1057,7 @@
variant_level=1):
""" Modify a variable so that it's a property which announces
its changes to DBus.
-
+
transform_fun: Function that takes a value and a variant_level
and transforms it to a D-Bus type.
dbus_name: D-Bus name of the variable
@@ -938,24 +1097,24 @@
datetime_to_dbus, "LastApprovalRequest")
approved_by_default = notifychangeproperty(dbus.Boolean,
"ApprovedByDefault")
- approval_delay = notifychangeproperty(dbus.UInt16,
+ approval_delay = notifychangeproperty(dbus.UInt64,
"ApprovalDelay",
type_func =
- _timedelta_to_milliseconds)
+ timedelta_to_milliseconds)
approval_duration = notifychangeproperty(
- dbus.UInt16, "ApprovalDuration",
- type_func = _timedelta_to_milliseconds)
+ dbus.UInt64, "ApprovalDuration",
+ type_func = timedelta_to_milliseconds)
host = notifychangeproperty(dbus.String, "Host")
- timeout = notifychangeproperty(dbus.UInt16, "Timeout",
+ timeout = notifychangeproperty(dbus.UInt64, "Timeout",
type_func =
- _timedelta_to_milliseconds)
+ timedelta_to_milliseconds)
extended_timeout = notifychangeproperty(
- dbus.UInt16, "ExtendedTimeout",
- type_func = _timedelta_to_milliseconds)
- interval = notifychangeproperty(dbus.UInt16,
+ dbus.UInt64, "ExtendedTimeout",
+ type_func = timedelta_to_milliseconds)
+ interval = notifychangeproperty(dbus.UInt64,
"Interval",
type_func =
- _timedelta_to_milliseconds)
+ timedelta_to_milliseconds)
checker_command = notifychangeproperty(dbus.String, "Checker")
del notifychangeproperty
@@ -1003,13 +1162,13 @@
return r
def _reset_approved(self):
- self._approved = None
+ self.approved = None
return False
def approve(self, value=True):
self.send_changedstate()
- self._approved = value
- gobject.timeout_add(_timedelta_to_milliseconds
+ self.approved = value
+ gobject.timeout_add(timedelta_to_milliseconds
(self.approval_duration),
self._reset_approved)
@@ -1058,6 +1217,14 @@
"D-Bus signal"
return self.need_approval()
+ # NeRwequest - signal
+ @dbus.service.signal(_interface, signature="s")
+ def NewRequest(self, ip):
+ """D-Bus signal
+ Is sent after a client request a password.
+ """
+ pass
+
## Methods
# Approve - method
@@ -1121,7 +1288,7 @@
access="readwrite")
def ApprovalDuration_dbus_property(self, value=None):
if value is None: # get
- return dbus.UInt64(_timedelta_to_milliseconds(
+ return dbus.UInt64(timedelta_to_milliseconds(
self.approval_duration))
self.approval_duration = datetime.timedelta(0, 0, 0, value)
@@ -1141,12 +1308,12 @@
def Host_dbus_property(self, value=None):
if value is None: # get
return dbus.String(self.host)
- self.host = value
+ self.host = unicode(value)
# Created - property
@dbus_service_property(_interface, signature="s", access="read")
def Created_dbus_property(self):
- return dbus.String(datetime_to_dbus(self.created))
+ return datetime_to_dbus(self.created)
# LastEnabled - property
@dbus_service_property(_interface, signature="s", access="read")
@@ -1196,11 +1363,11 @@
gobject.source_remove(self.disable_initiator_tag)
self.disable_initiator_tag = None
self.expires = None
- time_to_die = _timedelta_to_milliseconds((self
- .last_checked_ok
- + self.timeout)
- - datetime.datetime
- .utcnow())
+ time_to_die = timedelta_to_milliseconds((self
+ .last_checked_ok
+ + self.timeout)
+ - datetime.datetime
+ .utcnow())
if time_to_die <= 0:
# The timeout has passed
self.disable()
@@ -1228,11 +1395,12 @@
self.interval = datetime.timedelta(0, 0, 0, value)
if getattr(self, "checker_initiator_tag", None) is None:
return
- # Reschedule checker run
- gobject.source_remove(self.checker_initiator_tag)
- self.checker_initiator_tag = (gobject.timeout_add
- (value, self.start_checker))
- self.start_checker() # Start one now, too
+ if self.enabled:
+ # Reschedule checker run
+ gobject.source_remove(self.checker_initiator_tag)
+ self.checker_initiator_tag = (gobject.timeout_add
+ (value, self.start_checker))
+ self.start_checker() # Start one now, too
# Checker - property
@dbus_service_property(_interface, signature="s",
@@ -1240,7 +1408,7 @@
def Checker_dbus_property(self, value=None):
if value is None: # get
return dbus.String(self.checker_command)
- self.checker_command = value
+ self.checker_command = unicode(value)
# CheckerRunning - property
@dbus_service_property(_interface, signature="b",
@@ -1275,7 +1443,7 @@
raise KeyError()
def __getattribute__(self, name):
- if(name == '_pipe'):
+ if name == '_pipe':
return super(ProxyClient, self).__getattribute__(name)
self._pipe.send(('getattr', name))
data = self._pipe.recv()
@@ -1288,13 +1456,15 @@
return func
def __setattr__(self, name, value):
- if(name == '_pipe'):
+ if name == '_pipe':
return super(ProxyClient, self).__setattr__(name, value)
self._pipe.send(('setattr', name, value))
+
class ClientDBusTransitional(ClientDBus):
__metaclass__ = AlternateDBusNamesMetaclass
+
class ClientHandler(socketserver.BaseRequestHandler, object):
"""A class to handle client connections.
@@ -1368,6 +1538,10 @@
except KeyError:
return
+ if self.server.use_dbus:
+ # Emit D-Bus signal
+ client.NewRequest(str(self.client_address))
+
if client.approval_delay:
delay = client.approval_delay
client.approvals_pending += 1
@@ -1382,10 +1556,10 @@
client.Rejected("Disabled")
return
- if client._approved or not client.approval_delay:
+ if client.approved or not client.approval_delay:
#We are approved or approval is disabled
break
- elif client._approved is None:
+ elif client.approved is None:
logger.info("Client %s needs approval",
client.name)
if self.server.use_dbus:
@@ -1405,7 +1579,7 @@
time = datetime.datetime.now()
client.changedstate.acquire()
(client.changedstate.wait
- (float(client._timedelta_to_milliseconds(delay)
+ (float(client.timedelta_to_milliseconds(delay)
/ 1000)))
client.changedstate.release()
time2 = datetime.datetime.now()
@@ -1510,7 +1684,7 @@
# Convert the buffer to a Python bytestring
fpr = ctypes.string_at(buf, buf_len.value)
# Convert the bytestring to hexadecimal notation
- hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
+ hex_fpr = binascii.hexlify(fpr).upper()
return hex_fpr
@@ -1519,7 +1693,7 @@
def sub_process_main(self, request, address):
try:
self.finish_request(request, address)
- except:
+ except Exception:
self.handle_error(request, address)
self.close_request(request)
@@ -1630,7 +1804,7 @@
self.enabled = False
self.clients = clients
if self.clients is None:
- self.clients = set()
+ self.clients = {}
self.use_dbus = use_dbus
self.gnutls_priority = gnutls_priority
IPv6_TCPServer.__init__(self, server_address,
@@ -1683,7 +1857,7 @@
fpr = request[1]
address = request[2]
- for c in self.clients:
+ for c in self.clients.itervalues():
if c.fingerprint == fpr:
client = c
break
@@ -1773,30 +1947,6 @@
return timevalue
-def if_nametoindex(interface):
- """Call the C function if_nametoindex(), or equivalent
-
- Note: This function cannot accept a unicode string."""
- global if_nametoindex
- try:
- if_nametoindex = (ctypes.cdll.LoadLibrary
- (ctypes.util.find_library("c"))
- .if_nametoindex)
- except (OSError, AttributeError):
- logger.warning("Doing if_nametoindex the hard way")
- def if_nametoindex(interface):
- "Get an interface index the hard way, i.e. using fcntl()"
- SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
- with contextlib.closing(socket.socket()) as s:
- ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
- struct.pack(str("16s16x"),
- interface))
- interface_index = struct.unpack(str("I"),
- ifreq[16:20])[0]
- return interface_index
- return if_nametoindex(interface)
-
-
def daemon(nochdir = False, noclose = False):
"""See daemon(3). Standard BSD Unix function.
@@ -1857,6 +2007,12 @@
" system bus interface")
parser.add_argument("--no-ipv6", action="store_false",
dest="use_ipv6", help="Do not use IPv6")
+ parser.add_argument("--no-restore", action="store_false",
+ dest="restore", help="Do not restore stored"
+ " state")
+ parser.add_argument("--statedir", metavar="DIR",
+ help="Directory to save/restore state in")
+
options = parser.parse_args()
if options.check:
@@ -1875,6 +2031,8 @@
"use_dbus": "True",
"use_ipv6": "True",
"debuglevel": "",
+ "restore": "True",
+ "statedir": "/var/lib/mandos"
}
# Parse config file for server-global settings
@@ -1897,7 +2055,8 @@
# options, if set.
for option in ("interface", "address", "port", "debug",
"priority", "servicename", "configdir",
- "use_dbus", "use_ipv6", "debuglevel"):
+ "use_dbus", "use_ipv6", "debuglevel", "restore",
+ "statedir"):
value = getattr(options, option)
if value is not None:
server_settings[option] = value
@@ -1915,6 +2074,17 @@
debuglevel = server_settings["debuglevel"]
use_dbus = server_settings["use_dbus"]
use_ipv6 = server_settings["use_ipv6"]
+ stored_state_path = os.path.join(server_settings["statedir"],
+ stored_state_file)
+
+ if debug:
+ initlogger(logging.DEBUG)
+ else:
+ if not debuglevel:
+ initlogger()
+ else:
+ level = getattr(logging, debuglevel.upper())
+ initlogger(level)
if server_settings["servicename"] != "Mandos":
syslogger.setFormatter(logging.Formatter
@@ -1923,15 +2093,7 @@
% server_settings["servicename"]))
# Parse config file with clients
- client_defaults = { "timeout": "5m",
- "extended_timeout": "15m",
- "interval": "2m",
- "checker": "fping -q -- %%(host)s",
- "host": "",
- "approval_delay": "0s",
- "approval_duration": "1s",
- }
- client_config = configparser.SafeConfigParser(client_defaults)
+ client_config = configparser.SafeConfigParser(Client.client_defaults)
client_config.read(os.path.join(server_settings["configdir"],
"clients.conf"))
@@ -1975,14 +2137,7 @@
if error[0] != errno.EPERM:
raise error
- if not debug and not debuglevel:
- logger.setLevel(logging.WARNING)
- if debuglevel:
- level = getattr(logging, debuglevel.upper())
- logger.setLevel(level)
-
if debug:
- logger.setLevel(logging.DEBUG)
# Enable all possible GnuTLS debugging
# "Use a log level over 10 to enable all debugging options."
@@ -2010,6 +2165,8 @@
# Close all input and output, do double fork, etc.
daemon()
+ gobject.threads_init()
+
global main_loop
# From the Avahi example code
DBusGMainLoop(set_as_default=True )
@@ -2044,23 +2201,91 @@
if use_dbus:
client_class = functools.partial(ClientDBusTransitional,
bus = bus)
- def client_config_items(config, section):
- special_settings = {
- "approved_by_default":
- lambda: config.getboolean(section,
- "approved_by_default"),
- }
- for name, value in config.items(section):
+
+ client_settings = Client.config_parser(client_config)
+ old_client_settings = {}
+ clients_data = {}
+
+ # Get client data and settings from last running state.
+ if server_settings["restore"]:
+ try:
+ with open(stored_state_path, "rb") as stored_state:
+ clients_data, old_client_settings = (pickle.load
+ (stored_state))
+ os.remove(stored_state_path)
+ except IOError as e:
+ logger.warning("Could not load persistent state: {0}"
+ .format(e))
+ if e.errno != errno.ENOENT:
+ raise
+
+ with PGPEngine() as pgp:
+ for client_name, client in clients_data.iteritems():
+ # Decide which value to use after restoring saved state.
+ # We have three different values: Old config file,
+ # new config file, and saved state.
+ # New config value takes precedence if it differs from old
+ # config value, otherwise use saved state.
+ for name, value in client_settings[client_name].items():
+ try:
+ # For each value in new config, check if it
+ # differs from the old config value (Except for
+ # the "secret" attribute)
+ if (name != "secret" and
+ value != old_client_settings[client_name]
+ [name]):
+ client[name] = value
+ except KeyError:
+ pass
+
+ # Clients who has passed its expire date can still be
+ # enabled if its last checker was successful. Clients
+ # whose checker failed before we stored its state is
+ # assumed to have failed all checkers during downtime.
+ if client["enabled"]:
+ if datetime.datetime.utcnow() >= client["expires"]:
+ if not client["last_checked_ok"]:
+ logger.warning(
+ "disabling client {0} - Client never "
+ "performed a successfull checker"
+ .format(client["name"]))
+ client["enabled"] = False
+ elif client["last_checker_status"] != 0:
+ logger.warning(
+ "disabling client {0} - Client "
+ "last checker failed with error code {1}"
+ .format(client["name"],
+ client["last_checker_status"]))
+ client["enabled"] = False
+ else:
+ client["expires"] = (datetime.datetime
+ .utcnow()
+ + client["timeout"])
+
try:
- yield (name, special_settings[name]())
- except KeyError:
- yield (name, value)
-
- tcp_server.clients.update(set(
- client_class(name = section,
- config= dict(client_config_items(
- client_config, section)))
- for section in client_config.sections()))
+ client["secret"] = (
+ pgp.decrypt(client["encrypted_secret"],
+ client_settings[client_name]
+ ["secret"]))
+ except PGPError:
+ # If decryption fails, we use secret from new settings
+ logger.debug("Failed to decrypt {0} old secret"
+ .format(client_name))
+ client["secret"] = (
+ client_settings[client_name]["secret"])
+
+
+ # Add/remove clients based on new changes made to config
+ for client_name in set(old_client_settings) - set(client_settings):
+ del clients_data[client_name]
+ for client_name in set(client_settings) - set(old_client_settings):
+ clients_data[client_name] = client_settings[client_name]
+
+ # Create clients all clients
+ for client_name, client in clients_data.iteritems():
+ tcp_server.clients[client_name] = client_class(
+ name = client_name, settings = client)
+
if not tcp_server.clients:
logger.warning("No clients defined")
@@ -2077,7 +2302,6 @@
# "pidfile" was never created
pass
del pidfilename
-
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
@@ -2109,7 +2333,8 @@
def GetAllClients(self):
"D-Bus method"
return dbus.Array(c.dbus_object_path
- for c in tcp_server.clients)
+ for c in
+ tcp_server.clients.itervalues())
@dbus.service.method(_interface,
out_signature="a{oa{sv}}")
@@ -2117,15 +2342,15 @@
"D-Bus method"
return dbus.Dictionary(
((c.dbus_object_path, c.GetAll(""))
- for c in tcp_server.clients),
+ for c in tcp_server.clients.itervalues()),
signature="oa{sv}")
@dbus.service.method(_interface, in_signature="o")
def RemoveClient(self, object_path):
"D-Bus method"
- for c in tcp_server.clients:
+ for c in tcp_server.clients.itervalues():
if c.dbus_object_path == object_path:
- tcp_server.clients.remove(c)
+ del tcp_server.clients[c.name]
c.remove_from_connection()
# Don't signal anything except ClientRemoved
c.disable(quiet=True)
@@ -2145,11 +2370,53 @@
service.cleanup()
multiprocessing.active_children()
+ if not (tcp_server.clients or client_settings):
+ return
+
+ # Store client before exiting. Secrets are encrypted with key
+ # based on what config file has. If config file is
+ # removed/edited, old secret will thus be unrecovable.
+ clients = {}
+ with PGPEngine() as pgp:
+ for client in tcp_server.clients.itervalues():
+ key = client_settings[client.name]["secret"]
+ client.encrypted_secret = pgp.encrypt(client.secret,
+ key)
+ client_dict = {}
+
+ # A list of attributes that can not be pickled
+ # + secret.
+ exclude = set(("bus", "changedstate", "secret",
+ "checker"))
+ for name, typ in (inspect.getmembers
+ (dbus.service.Object)):
+ exclude.add(name)
+
+ client_dict["encrypted_secret"] = (client
+ .encrypted_secret)
+ for attr in client.client_structure:
+ if attr not in exclude:
+ client_dict[attr] = getattr(client, attr)
+
+ clients[client.name] = client_dict
+ del client_settings[client.name]["secret"]
+
+ try:
+ with os.fdopen(os.open(stored_state_path,
+ os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
+ 0600), "wb") as stored_state:
+ pickle.dump((clients, client_settings), stored_state)
+ except (IOError, OSError) as e:
+ logger.warning("Could not save persistent state: {0}"
+ .format(e))
+ if e.errno not in (errno.ENOENT, errno.EACCES):
+ raise
+
+ # Delete all clients, and settings from config
while tcp_server.clients:
- client = tcp_server.clients.pop()
+ name, client = tcp_server.clients.popitem()
if use_dbus:
client.remove_from_connection()
- client.disable_hook = None
# Don't signal anything except ClientRemoved
client.disable(quiet=True)
if use_dbus:
@@ -2157,14 +2424,17 @@
mandos_dbus_service.ClientRemoved(client
.dbus_object_path,
client.name)
+ client_settings.clear()
atexit.register(cleanup)
- for client in tcp_server.clients:
+ for client in tcp_server.clients.itervalues():
if use_dbus:
# Emit D-Bus signal
mandos_dbus_service.ClientAdded(client.dbus_object_path)
- client.enable()
+ # Need to initiate checking of clients
+ if client.enabled:
+ client.init_checker()
tcp_server.enable()
tcp_server.server_activate()
@@ -2210,6 +2480,5 @@
# Must run before the D-Bus bus name gets deregistered
cleanup()
-
if __name__ == '__main__':
main()
=== modified file 'mandos-clients.conf.xml'
--- mandos-clients.conf.xml 2011-10-10 20:29:58 +0000
+++ mandos-clients.conf.xml 2011-11-26 23:08:17 +0000
@@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
/etc/mandos/clients.conf">
-
+
%common;
]>
@@ -344,6 +344,20 @@
+
+
+
+
+ Whether this client should be enabled by default. The
+ default is true
.
+
+
+
+
=== modified file 'mandos-ctl'
--- mandos-ctl 2011-10-15 16:48:03 +0000
+++ mandos-ctl 2011-12-21 00:29:33 +0000
@@ -93,29 +93,23 @@
>>> string_to_delta("5m 30s")
datetime.timedelta(0, 330)
"""
- timevalue = datetime.timedelta(0)
- regexp = re.compile("\d+[dsmhw]")
+ value = datetime.timedelta(0)
+ regexp = re.compile("(\d+)([dsmhw]?)")
- for s in regexp.findall(interval):
- try:
- suffix = unicode(s[-1])
- value = int(s[:-1])
- if suffix == "d":
- delta = datetime.timedelta(value)
- elif suffix == "s":
- delta = datetime.timedelta(0, value)
- elif suffix == "m":
- delta = datetime.timedelta(0, 0, 0, 0, value)
- elif suffix == "h":
- delta = datetime.timedelta(0, 0, 0, 0, 0, value)
- elif suffix == "w":
- delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
- else:
- raise ValueError
- except (ValueError, IndexError):
- raise ValueError
- timevalue += delta
- return timevalue
+ for num, suffix in regexp.findall(interval):
+ if suffix == "d":
+ value += datetime.timedelta(int(num))
+ elif suffix == "s":
+ value += datetime.timedelta(0, int(num))
+ elif suffix == "m":
+ value += datetime.timedelta(0, 0, 0, 0, int(num))
+ elif suffix == "h":
+ value += datetime.timedelta(0, 0, 0, 0, 0, int(num))
+ elif suffix == "w":
+ value += datetime.timedelta(0, 0, 0, 0, 0, 0, int(num))
+ elif suffix == "":
+ value += datetime.timedelta(0, 0, 0, int(num))
+ return value
def print_clients(clients, keywords):
def valuetostring(value, keyword):
=== modified file 'mandos-keygen.xml'
--- mandos-keygen.xml 2011-10-05 16:00:56 +0000
+++ mandos-keygen.xml 2011-11-12 18:14:55 +0000
@@ -214,7 +214,7 @@
Target directory for key files. Default is
- /etc/mandos.
+ /etc/mandos.
@@ -411,7 +411,7 @@
- /tmp
+ /tmp
Temporary files will be written here if
@@ -452,9 +452,9 @@
- Prompt for a password, encrypt it with the key in
- /etc/mandos and output a section suitable
- for clients.conf.
+ Prompt for a password, encrypt it with the key in /etc/mandos and output a section
+ suitable for clients.conf.
&COMMANDNAME; --password
=== modified file 'mandos-options.xml'
--- mandos-options.xml 2009-02-13 05:38:21 +0000
+++ mandos-options.xml 2011-11-26 22:22:20 +0000
@@ -86,4 +86,15 @@
advanced users should consider changing this option.
+
+ This option controls whether the server will restore any state from
+ last time it ran. Default is to try restore last state.
+
+
+
+ Directory to save (and restore) state in. Default is
+ /var/lib/mandos
.
+
+
=== modified file 'mandos.conf'
--- mandos.conf 2009-02-13 05:38:21 +0000
+++ mandos.conf 2011-11-26 22:22:20 +0000
@@ -4,33 +4,27 @@
# These are the default values for the server, uncomment and change
# them if needed.
-
# If "interface" is set, the server will only listen to a specific
# network interface.
;interface =
-
# If "address" is set, the server will only listen to a specific
# address. This must currently be an IPv6 address; an IPv4 address
# can be specified using the "::FFFF:192.0.2.3" syntax. Also, if this
# is a link-local address, an interface should be set above.
;address =
-
# If "port" is set, the server to bind to that port. By default, the
# server will listen to an arbitrary port.
;port =
-
# If "debug" is true, the server will run in the foreground and print
# a lot of debugging information.
;debug = False
-
# GnuTLS priority for the TLS handshake. See gnutls_priority_init(3).
;priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP
-
# Zeroconf service name. You need to change this if you for some
# reason want to run more than one server on the same *host*.
# If there are name collisions on the same *network*, the server will
@@ -42,3 +36,9 @@
# Whether to use IPv6. (Changing this is NOT recommended.)
;use_ipv6 = True
+
+# Whether to restore saved state on startup
+;restore = True
+
+# The directory where state is saved
+;statedir = /var/lib/mandos
=== modified file 'mandos.conf.xml'
--- mandos.conf.xml 2011-10-05 16:00:56 +0000
+++ mandos.conf.xml 2011-11-26 22:22:20 +0000
@@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
/etc/mandos/mandos.conf">
-
+
%common;
]>
@@ -154,6 +154,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -198,6 +217,8 @@
servicename = Daena
use_dbus = False
use_ipv6 = True
+restore = True
+statedir = /var/lib/mandos
=== modified file 'mandos.xml'
--- mandos.xml 2011-10-22 00:46:35 +0000
+++ mandos.xml 2011-12-21 00:29:33 +0000
@@ -2,7 +2,7 @@
-
+
%common;
]>
@@ -94,6 +94,11 @@
+
+
+
+
&COMMANDNAME;
@@ -275,6 +280,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -387,7 +407,7 @@
LOGGING
The server will send log message with various severity levels to
- /dev/log. With the
+ /dev/log. With the
option, it will log even more messages,
and also show them on the console.
@@ -469,6 +489,20 @@
+ /dev/log
+
+
+ /var/lib/mandos
+
+
+ Directory where persistent state will be saved. Change
+ this with the option. See
+ also the option.
+
+
+
+
/dev/log
@@ -498,11 +532,6 @@
backtrace. This could be considered a feature.
- Currently, if a client is disabled due to having timed out, the
- server does not record this fact onto permanent storage. This
- has some security implications, see .
-
-
There is no fine-grained control over logging and debug output.
@@ -527,9 +556,9 @@
Run the server in debug mode, read configuration files from
- the ~/mandos directory, and use the
- Zeroconf service name Test
to not collide with
- any other official Mandos server on this host:
+ the ~/mandos directory,
+ and use the Zeroconf service name Test
to not
+ collide with any other official Mandos server on this host:
@@ -584,21 +613,6 @@
compromised if they are gone for too long.
- If a client is compromised, its downtime should be duly noted
- by the server which would therefore disable the client. But
- if the server was ever restarted, it would re-read its client
- list from its configuration file and again regard all clients
- therein as enabled, and hence eligible to receive their
- passwords. Therefore, be careful when restarting servers if
- it is suspected that a client has, in fact, been compromised
- by parties who may now be running a fake Mandos client with
- the keys from the non-encrypted initial RAM
- image of the client host. What should be done in that case
- (if restarting the server program really is necessary) is to
- stop the server program, edit the configuration file to omit
- any suspect clients, and restart the server program.
-
-
For more details on client-side security, see
mandos-client
8mandos.
=== 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
+ .
+
+
+
=== modified file 'plugins.d/plymouth.xml'
--- plugins.d/plymouth.xml 2011-10-05 16:00:56 +0000
+++ plugins.d/plymouth.xml 2011-11-12 18:14:55 +0000
@@ -161,7 +161,7 @@
- /proc
+ /proc
To find the running
- /proc
+ /proc
To find the running