=== modified file 'INSTALL' --- INSTALL 2008-10-28 18:00:20 +0000 +++ INSTALL 2009-01-27 00:07:26 +0000 @@ -43,13 +43,14 @@ + Python-GnuTLS 1.1.5 http://pypi.python.org/pypi/python-gnutls/ + dbus-python 0.82.4 http://dbus.freedesktop.org/doc/dbus-python/ + python-ctypes 1.0.0 http://pypi.python.org/pypi/ctypes + + PyGObject 2.14.2 http://library.gnome.org/devel/pygobject/ Strongly recommended: + fping 2.4b2-to-ipv6 http://www.fping.com/ Package names: python-gnutls avahi-daemon python python-avahi python-dbus - python-ctypes + python-ctypes python-gobject *** Mandos Client + initramfs-tools 0.85i === modified file 'Makefile' --- Makefile 2009-01-17 02:12:05 +0000 +++ Makefile 2009-02-09 02:01:13 +0000 @@ -1,6 +1,6 @@ WARN=-O -Wall -Wformat=2 -Winit-self -Wmissing-include-dirs \ -Wswitch-default -Wswitch-enum -Wunused-parameter \ - -Wstrict-aliasing=2 -Wextra -Wfloat-equal -Wundef -Wshadow \ + -Wstrict-aliasing=1 -Wextra -Wfloat-equal -Wundef -Wshadow \ -Wunsafe-loop-optimizations -Wpointer-arith \ -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings \ -Wconversion -Wstrict-prototypes -Wold-style-definition \ @@ -213,7 +213,7 @@ # Run the server with a local config run-server: confdir/mandos.conf confdir/clients.conf - ./mandos --debug --configdir=confdir $(SERVERARGS) + ./mandos --debug --no-dbus --configdir=confdir $(SERVERARGS) # Used by run-server confdir/mandos.conf: mandos.conf @@ -285,7 +285,7 @@ install --mode=u=rw,go=r initramfs-tools-hook-conf \ $(INITRAMFSTOOLS)/conf-hooks.d/mandos install initramfs-tools-script \ - $(INITRAMFSTOOLS)/scripts/local-top/mandos + $(INITRAMFSTOOLS)/scripts/init-premount/mandos install --mode=u=rw,go=r plugin-runner.conf $(CONFDIR) gzip --best --to-stdout mandos-keygen.8 \ > $(MANDIR)/man8/mandos-keygen.8.gz @@ -332,7 +332,7 @@ $(PREFIX)/lib/mandos/plugins.d/askpass-fifo \ $(INITRAMFSTOOLS)/hooks/mandos \ $(INITRAMFSTOOLS)/conf-hooks.d/mandos \ - $(INITRAMFSTOOLS)/scripts/local-top/mandos \ + $(INITRAMFSTOOLS)/scripts/init-premount/mandos \ $(MANDIR)/man8/plugin-runner.8mandos.gz \ $(MANDIR)/man8/mandos-keygen.8.gz \ $(MANDIR)/man8/password-prompt.8mandos.gz \ === modified file 'TODO' --- TODO 2009-01-18 00:16:57 +0000 +++ TODO 2009-02-09 19:26:19 +0000 @@ -1,53 +1,43 @@ -*- org -*- * mandos-client -** TODO [#B] Temporarily lower kernel log level - for less printouts during sucessfull boot. - klogctl(6, NULL, 0); klogctl(7, NULL, 0); -** TODO [#C] IPv4 support +** TODO [#A] Clean up /tmp directory on signal * plugin-runner ** TODO [#B] use scandir(3) instead of readdir(3) * mandos (server) -** TODO [#B] Log level :bugs: +** TODO [#B] Log level :BUGS: ** TODO /etc/mandos/clients.d/*.conf Watch this directory and add/remove/update clients? ** TODO config for TXT record -** TODO [#B] Run-time communication with server :bugs: +** TODO [#B] Run-time communication with server :BUGS: Probably using D-Bus - See also [[*Mandos-tools]] *** Client class *** Main server + SetLogLevel syslogger.setLevel(logging.WARNING) - + Quit + [[http://log.ometer.com/2007-05.html][Best D-Bus practices]] -** TODO Implement --foreground :bugs: +** TODO Implement --foreground :BUGS: [[info:standards:Option%20Table][Table of Long Options]] ** TODO Implement --socket [[info:standards:Option%20Table][Table of Long Options]] -** TODO Date+time on console log messages :bugs: +** TODO Date+time on console log messages :BUGS: Is this the default? -** TODO delete hook when clients fall out by timeout - This will not be strictly necessary when the D-Bus interface is - implemented. * mandos.xml ** [[file:mandos.xml::XXX][Document D-Bus interface]] * Provide and install /etc/dbus-1/system.d/mandos.conf -* mandos-list +* mandos-ctl *** Handle "no D-Bus server" and/or "no Mandos server found" better *** [#B] --dump option -** TODO Disable client -** TODO Enable client -** TODO Reset timer * Curses interface * mandos-keygen +** TODO Loop until passwords match when run interactively ** TODO "--secfile" option Using the "secfile" option instead of "secret" ** TODO [#B] "--test" option @@ -56,8 +46,7 @@ * Package ** /usr/share/initramfs-tools/hooks/mandos *** TODO [#C] Do not install in initrd.img if configured not to. - Use "/etc/initramfs-tools/conf.d/mandos"? Definitely a debconf - question. + Use "/etc/initramfs-tools/hooksconf.d/mandos"? ** TODO [#C] /etc/bash_completion.d/mandos From XML sources directly? === modified file 'debian/control' --- debian/control 2009-01-06 20:39:35 +0000 +++ debian/control 2009-01-26 23:47:44 +0000 @@ -15,7 +15,8 @@ Package: mandos Architecture: all Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus, - python-avahi, avahi-daemon, gnupg (< 2), adduser + python-avahi, python-gobject, avahi-daemon, gnupg (< 2), + adduser Recommends: fping Description: a 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 2009-01-15 02:52:02 +0000 +++ debian/mandos-client.README.Debian 2009-02-09 02:01:13 +0000 @@ -7,10 +7,13 @@ * Use the Correct Network Interface - If some other network interface than "eth0" is used, it will be - necessary to edit /etc/mandos/plugin-runner.conf to uncomment and - change the line there. If this is done, it will be necessary to - update the initrd image by doing "update-initramfs -k all -u". + Make sure that the correct network interface is specified in the + DEVICE setting in the "/etc/initramfs-tools/initramfs.conf" file. + If this is changed, it will be necessary to update the initrd image + by doing "update-initramfs -k all -u". This setting can be + overridden at boot time on the Linux kernel command line using the + sixth colon-separated field of the "ip=" option; for exact syntax, + see the file "Documentation/nfsroot.txt" in the Linux source tree. * Test the Server @@ -23,19 +26,19 @@ --seckey=/etc/keys/mandos/seckey.txt; echo This command should retrieve the password from the server, decrypt - it, and output it to standard output. It is now possible to verify - the correctness of the password before rebooting. + it, and output it to standard output. There it can be verified to + be the correct password, before rebooting. * User-Supplied Plugins - + Any plugins found in /etc/mandos/plugins.d will override and add to the normal Mandos plugins. When adding or changing plugins, do not forget to update the initital RAM disk image: - + # update-initramfs -k all -u * Do *NOT* Edit /etc/crypttab - + It is NOT necessary to edit /etc/crypttab to specify /usr/lib/mandos/plugin-runner as a keyscript for the root file system; if no keyscript is given for the root file system, the @@ -48,4 +51,22 @@ prevented from running at startup by passing the parameter "mandos=off" to the kernel. - -- Teddy Hogeborn , Mon, 12 Jan 2009 02:29:10 +0100 +* Non-local Connection (Not Using ZeroConf) + + If the "ip=" kernel command line option is used to specify a + complete IP address and device name, as noted above, it then becomes + possible to specify a specific IP address and port to connect to, + instead of using ZeroConf. The syntax for doing this is + "mandos=connect::". + + Warning: this will cause the client to make exactly one attempt at + connecting, and then fail if it does not succeed. + + For very advanced users, it it possible to specify simply + "mandos=connect" on the kernel command line to make the system only + set up the network (using the data in the "ip=" option) and not pass + any extra "--connect" options to mandos-client at boot. For this to + work, "--options-for=mandos-client:--connect=
:" needs + to be manually added to the file "/etc/mandos/plugin-runner.conf". + + -- Teddy Hogeborn , Mon, 9 Feb 2009 00:36:55 +0100 === modified file 'debian/mandos-client.dirs' --- debian/mandos-client.dirs 2008-09-17 00:34:09 +0000 +++ debian/mandos-client.dirs 2009-02-07 04:50:39 +0000 @@ -2,4 +2,4 @@ usr/sbin usr/share/initramfs-tools/hooks usr/share/initramfs-tools/conf-hooks.d -usr/share/initramfs-tools/scripts/local-top +usr/share/initramfs-tools/scripts/init-premount === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2008-12-10 01:26:02 +0000 +++ initramfs-tools-hook 2009-02-09 02:13:58 +0000 @@ -92,8 +92,8 @@ fi case "$base" in *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) : ;; - "*") :;; - *) copy_exec "$file" "${PLUGINDIR}";; + "*") : ;; + *) copy_exec "$file" "${PLUGINDIR}" ;; esac done @@ -102,8 +102,8 @@ base="`basename \"$file\"`" case "$base" in *~|.*|\#*\#|*.dpkg-old|*.dpkg-bak|*.dpkg-new|*.dpkg-divert) : ;; - "*") :;; - *) copy_exec "$file" "${PLUGINDIR}";; + "*") : ;; + *) copy_exec "$file" "${PLUGINDIR}" ;; esac done @@ -124,16 +124,22 @@ if [ ${mandos_user} != 65534 ]; then PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf" - echo "--userid=${mandos_user}" >> "$PLUGINRUNNERCONF" + cat <<-EOF >> "$PLUGINRUNNERCONF" + + --userid=${mandos_user} +EOF fi if [ ${mandos_group} != 65534 ]; then PLUGINRUNNERCONF="${DESTDIR}${CONFDIR}/plugin-runner.conf" - echo "--groupid=${mandos_group}" >> "$PLUGINRUNNERCONF" + cat <<-EOF >> "$PLUGINRUNNERCONF" + + --groupid=${mandos_group} +EOF fi # Key files -for file in "$keydir"/*; do +for file in "$keydir"/*; do if [ -d "$file" ]; then continue fi @@ -168,7 +174,9 @@ chmod a+rX "${DESTDIR}$dir" fi done -for dir in /lib /usr/lib; do - find "${DESTDIR}$dir" \! -perm -u+rw,g+r -prune -or -print0 \ - | xargs --null --no-run-if-empty chmod a+rX +for dir in "${DESTDIR}"/lib* "${DESTDIR}"/usr/lib*; do + if [ -d "$dir" ]; then + find "$dir" \! -perm -u+rw,g+r -prune -or -print0 \ + | xargs --null --no-run-if-empty chmod a+rX + fi done === modified file 'initramfs-tools-script' --- initramfs-tools-script 2009-01-15 02:52:02 +0000 +++ initramfs-tools-script 2009-02-09 02:01:13 +0000 @@ -6,33 +6,103 @@ # # This script should be installed as -# "/usr/share/initramfs-tools/scripts/local-top/mandos" which will -# eventually be "/scripts/local-top/mandos" in the initrd.img file. +# "/usr/share/initramfs-tools/scripts/init-premount/mandos" which will +# eventually be "/scripts/init-premount/mandos" in the initrd.img +# file. -# No initramfs pre-requirements; we must instead run BEFORE cryptroot. -# This is not a problem, since cryptroot forces itself to run LAST. -PREREQ="" +# No initramfs pre-requirements. +PREREQ="udev" prereqs() { - echo "$PREREQ" + echo "$PREREQ" } case $1 in prereqs) - prereqs - exit 0 - ;; + prereqs + exit 0 + ;; esac +. /scripts/functions + for param in `cat /proc/cmdline`; do case "$param" in - mandos=off) exit 0;; + ip=*) IPOPTS="${param#ip=}" ;; + mandos=*) + # Split option line on commas + old_ifs="$IFS" + IFS="$IFS," + for mpar in ${param#mandos=}; do + IFS="$old_ifs" + case "$mpar" in + off) exit 0 ;; + connect) connect="" ;; + connect:*) connect="${mpar#connect:}" ;; + *) log_warning_msg "$0: Bad option ${mpar}" ;; + esac + done + unset mpar + IFS="$old_ifs" + unset old_ifs + ;; esac done +unset param chmod a=rwxt /tmp -test -w /conf/conf.d/cryptroot +test -r /conf/conf.d/cryptroot +test -w /conf/conf.d + +# Get DEVICE from /conf/initramfs.conf and other files +. /conf/initramfs.conf +for conf in /conf/conf.d/*; do + [ -f ${conf} ] && . ${conf} +done +if [ -e /conf/param.conf ]; then + . /conf/param.conf +fi + +# Override DEVICE from sixth field of ip= kernel option, if passed +case "$IPOPTS" in + *:*:*:*:*:*) # At least six fields + # Remove the first five fields + device="${IPOPTS#*:*:*:*:*:}" + # Remove all fields except the first one + DEVICE="${device%%:*}" + ;; +esac + +# Add device setting (if any) to plugin-runner.conf +if [ "${DEVICE+set}" = set ]; then + # Did we get the device from an ip= option? + if [ "${device+set}" = set ]; then + # Let ip= option override local config; append: + cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf + + --options-for=mandos-client:--interface=${DEVICE} +EOF + else + # Prepend device setting so any later options would override: + sed -i -e \ + '1i--options-for=mandos-client:--interface='"${DEVICE}" \ + /conf/conf.d/mandos/plugin-runner.conf + fi +fi +unset device + +# If we are connecting directly, run "configure_networking" (from +# /scripts/functions); it needs IPOPTS and DEVICE +if [ "${connect+set}" = set ]; then + configure_networking + if [ -n "$connect" ]; then + cat <<-EOF >>/conf/conf.d/mandos/plugin-runner.conf + + --options-for=mandos-client:--connect=${connect} +EOF + fi +fi # Do not replace cryptroot file unless we need to. replace_cryptroot=no === modified file 'mandos' --- mandos 2009-01-23 16:49:50 +0000 +++ mandos 2009-02-13 08:00:47 +0000 @@ -73,12 +73,13 @@ (facility = logging.handlers.SysLogHandler.LOG_DAEMON, address = "/dev/log")) syslogger.setFormatter(logging.Formatter - ('Mandos: %(levelname)s: %(message)s')) + ('Mandos [%(process)d]: %(levelname)s:' + ' %(message)s')) logger.addHandler(syslogger) console = logging.StreamHandler() -console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:' - ' %(message)s')) +console.setFormatter(logging.Formatter('%(name)s [%(process)d]:' + ' %(levelname)s: %(message)s')) logger.addHandler(console) class AvahiError(Exception): @@ -113,7 +114,8 @@ """ def __init__(self, interface = avahi.IF_UNSPEC, name = None, servicetype = None, port = None, TXT = None, - domain = "", host = "", max_renames = 32768): + domain = "", host = "", max_renames = 32768, + protocol = avahi.PROTO_UNSPEC): self.interface = interface self.name = name self.type = servicetype @@ -123,6 +125,7 @@ self.host = host self.rename_count = 0 self.max_renames = max_renames + self.protocol = protocol def rename(self): """Derived from the Avahi example code""" if self.rename_count >= self.max_renames: @@ -157,7 +160,7 @@ service.name, service.type) group.AddService( self.interface, # interface - avahi.PROTO_INET6, # protocol + self.protocol, # protocol dbus.UInt32(0), # flags self.name, self.type, self.domain, self.host, @@ -202,6 +205,7 @@ client lives. %() expansions are done at runtime with vars(self) as dict, so that for instance %(name)s can be used in the command. + current_checker_command: string; current running checker_command use_dbus: bool(); Whether to provide D-Bus interface and signals dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus """ @@ -256,6 +260,7 @@ self.disable_initiator_tag = None self.checker_callback_tag = None self.checker_command = config["checker"] + self.current_checker_command = None self.last_connect = None # Only now, when this client is initialized, can it show up on # the D-Bus @@ -376,6 +381,16 @@ # checkers alone, the checker would have to take more time # than 'timeout' for the client to be declared invalid, which # is as it should be. + + # If a checker exists, make sure it is not a zombie + if self.checker is not None: + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + logger.warning("Checker was a zombie") + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, + self.current_checker_command) + # Start a new checker if needed if self.checker is None: try: # In case checker_command has exactly one % operator @@ -391,6 +406,7 @@ logger.error(u'Could not format string "%s":' u' %s', self.checker_command, error) return True # Try again later + self.current_checker_command = command try: logger.info(u"Starting checker %r for %s", command, self.name) @@ -411,6 +427,12 @@ (self.checker.pid, self.checker_callback, data=command)) + # The checker may have completed before the gobject + # watch was added. Check for this. + pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + if pid: + gobject.source_remove(self.checker_callback_tag) + self.checker_callback(pid, status, command) except OSError, error: logger.error(u"Failed to start subprocess: %s", error) @@ -595,10 +617,13 @@ != gnutls.library.constants.GNUTLS_CRT_OPENPGP): # ...do the normal thing return session.peer_certificate - list_size = ctypes.c_uint() + list_size = ctypes.c_uint(1) cert_list = (gnutls.library.functions .gnutls_certificate_get_peers (session._c_object, ctypes.byref(list_size))) + if not bool(cert_list) and list_size.value != 0: + raise gnutls.errors.GNUTLSError("error getting peer" + " certificate") if list_size.value == 0: return None cert = cert_list[0] @@ -673,8 +698,8 @@ # using OpenPGP certificates. #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC", - # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", - # "+DHE-DSS")) + # "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP", + # "+DHE-DSS")) # Use a fallback default, since this MUST be set. priority = self.server.settings.get("priority", "NORMAL") (gnutls.library.functions @@ -688,6 +713,7 @@ # Do not run session.bye() here: the session is not # established. Just abandon the request. return + logger.debug(u"Handshake succeeded") try: fpr = fingerprint(peer_certificate(session)) except (TypeError, gnutls.errors.GNUTLSError), error: @@ -695,6 +721,7 @@ session.bye() return logger.debug(u"Fingerprint: %s", fpr) + for c in self.server.clients: if c.fingerprint == fpr: client = c @@ -726,7 +753,7 @@ class IPv6_TCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer, object): - """IPv6 TCP server. Accepts 'None' as address and/or port. + """IPv6-capable TCP server. Accepts 'None' as address and/or port Attributes: settings: Server settings clients: Set() of Client objects @@ -740,6 +767,10 @@ if "clients" in kwargs: self.clients = kwargs["clients"] del kwargs["clients"] + if "use_ipv6" in kwargs: + if not kwargs["use_ipv6"]: + self.address_family = socket.AF_INET + del kwargs["use_ipv6"] self.enabled = False super(IPv6_TCPServer, self).__init__(*args, **kwargs) def server_bind(self): @@ -759,12 +790,15 @@ u" bind to interface %s", self.settings["interface"]) else: - raise error + raise # Only bind(2) the socket if we really need to. if self.server_address[0] or self.server_address[1]: if not self.server_address[0]: - in6addr_any = "::" - self.server_address = (in6addr_any, + if self.address_family == socket.AF_INET6: + any_address = "::" # in6addr_any + else: + any_address = socket.INADDR_ANY + self.server_address = (any_address, self.server_address[1]) elif not self.server_address[1]: self.server_address = (self.server_address[0], @@ -786,7 +820,7 @@ def string_to_delta(interval): """Parse a string and return a datetime.timedelta - + >>> string_to_delta('7d') datetime.timedelta(7) >>> string_to_delta('60s') @@ -916,6 +950,8 @@ parser.add_option("--no-dbus", action="store_false", dest="use_dbus", help=optparse.SUPPRESS_HELP) # XXX: Not done yet + parser.add_option("--no-ipv6", action="store_false", + dest="use_ipv6", help="Do not use IPv6") options = parser.parse_args()[0] if options.check: @@ -932,6 +968,7 @@ "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP", "servicename": "Mandos", "use_dbus": "True", + "use_ipv6": "True", } # Parse config file for server-global settings @@ -940,18 +977,23 @@ server_config.read(os.path.join(options.configdir, "mandos.conf")) # Convert the SafeConfigParser object to a dict server_settings = server_config.defaults() - # Use getboolean on the boolean config options - server_settings["debug"] = (server_config.getboolean - ("DEFAULT", "debug")) - server_settings["use_dbus"] = (server_config.getboolean - ("DEFAULT", "use_dbus")) + # Use the appropriate methods on the non-string config options + server_settings["debug"] = server_config.getboolean("DEFAULT", + "debug") + server_settings["use_dbus"] = server_config.getboolean("DEFAULT", + "use_dbus") + server_settings["use_ipv6"] = server_config.getboolean("DEFAULT", + "use_ipv6") + if server_settings["port"]: + server_settings["port"] = server_config.getint("DEFAULT", + "port") del server_config # Override the settings from the config file with command line # options, if set. for option in ("interface", "address", "port", "debug", "priority", "servicename", "configdir", - "use_dbus"): + "use_dbus", "use_ipv6"): value = getattr(options, option) if value is not None: server_settings[option] = value @@ -962,6 +1004,7 @@ debug = server_settings["debug"] use_dbus = server_settings["use_dbus"] use_dbus = False # XXX: Not done yet + use_ipv6 = server_settings["use_ipv6"] if not debug: syslogger.setLevel(logging.WARNING) @@ -988,11 +1031,11 @@ server_settings["port"]), TCP_handler, settings=server_settings, - clients=clients) + clients=clients, use_ipv6=use_ipv6) pidfilename = "/var/run/mandos.pid" try: pidfile = open(pidfilename, "w") - except IOError, error: + except IOError: logger.error("Could not open file %r", pidfilename) try: @@ -1010,15 +1053,30 @@ uid = 65534 gid = 65534 try: + os.setgid(gid) os.setuid(uid) - os.setgid(gid) except OSError, error: if error[0] != errno.EPERM: raise error + # Enable all possible GnuTLS debugging + if debug: + # "Use a log level over 10 to enable all debugging options." + # - GnuTLS manual + gnutls.library.functions.gnutls_global_set_log_level(11) + + @gnutls.library.types.gnutls_log_func + def debug_gnutls(level, string): + logger.debug("GnuTLS: %s", string[:-1]) + + (gnutls.library.functions + .gnutls_global_set_log_function(debug_gnutls)) + global service + protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET service = AvahiService(name = server_settings["servicename"], - servicetype = "_mandos._tcp", ) + servicetype = "_mandos._tcp", + protocol = protocol) if server_settings["interface"]: service.interface = (if_nametoindex (server_settings["interface"])) @@ -1110,10 +1168,12 @@ @dbus.service.method(_interface, out_signature="ao") def GetAllClients(self): + "D-Bus method" return dbus.Array(c.dbus_object_path for c in clients) @dbus.service.method(_interface, out_signature="a{oa{sv}}") def GetAllClientsWithProperties(self): + "D-Bus method" return dbus.Dictionary( ((c.dbus_object_path, c.GetAllProperties()) for c in clients), @@ -1121,6 +1181,7 @@ @dbus.service.method(_interface, in_signature="o") def RemoveClient(self, object_path): + "D-Bus method" for c in clients: if c.dbus_object_path == object_path: clients.remove(c) @@ -1148,8 +1209,13 @@ # Find out what port we got service.port = tcp_server.socket.getsockname()[1] - logger.info(u"Now listening on address %r, port %d, flowinfo %d," - u" scope_id %d" % tcp_server.socket.getsockname()) + if use_ipv6: + logger.info(u"Now listening on address %r, port %d," + " flowinfo %d, scope_id %d" + % tcp_server.socket.getsockname()) + else: # IPv4 + logger.info(u"Now listening on address %r, port %d" + % tcp_server.socket.getsockname()) #service.interface = tcp_server.socket.getsockname()[3] @@ -1175,7 +1241,9 @@ sys.exit(1) except KeyboardInterrupt: if debug: - print + print >> sys.stderr + logger.debug("Server received KeyboardInterrupt") + logger.debug("Server exiting") if __name__ == '__main__': main() === modified file 'mandos-ctl' (properties changed: -x to +x) --- mandos-ctl 2009-01-17 02:12:05 +0000 +++ mandos-ctl 2009-02-06 01:15:52 +0000 @@ -26,12 +26,12 @@ } defaultkeywords = ('name', 'enabled', 'timeout', 'last_checked_ok', 'checker') -busname = 'org.mandos-system.Mandos' -server_path = '/Mandos' -server_interface = 'org.mandos_system.Mandos' -client_interface = 'org.mandos_system.Mandos.Client' +domain = 'se.bsnet.fukt' +busname = domain + '.Mandos' +server_path = '/' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' version = "1.0.5" - bus = dbus.SystemBus() mandos_dbus_objc = bus.get_object(busname, server_path) mandos_serv = dbus.Interface(mandos_dbus_objc, @@ -115,29 +115,32 @@ parser.add_option("-a", "--all", action="store_true", help="Print all fields") parser.add_option("-e", "--enable", action="store_true", - help="Enable specified client") + help="Enable client") parser.add_option("-d", "--disable", action="store_true", - help="disable specified client") + help="disable client") parser.add_option("-b", "--bump-timeout", action="store_true", - help="Bump timeout of specified client") + help="Bump timeout for client") parser.add_option("--start-checker", action="store_true", - help="Start checker for specified client") + help="Start checker for client") parser.add_option("--stop-checker", action="store_true", - help="Stop checker for specified client") -parser.add_option("-v", "--is-valid", action="store_true", - help="Stop checker for specified client") + help="Stop checker for client") +parser.add_option("-V", "--is-valid", action="store_true", + help="Check if client is still valid") +parser.add_option("-r", "--remove", action="store_true", + help="Remove client") parser.add_option("-c", "--checker", type="string", - help="Set checker command for specified client") + help="Set checker command for client") parser.add_option("-t", "--timeout", type="string", - help="Set timeout for specified client") + help="Set timeout for client") parser.add_option("-i", "--interval", type="string", - help="Set checker interval for specified client") + help="Set checker interval for client") parser.add_option("-H", "--host", type="string", - help="Set host for specified client") + help="Set host for client") parser.add_option("-s", "--secret", type="string", - help="Set password blob (file) for specified client") + help="Set password blob (file) for client") options, client_names = parser.parse_args() +# Compile list of clients to process clients=[] for name in client_names: for path, client in mandos_clients.iteritems(): @@ -151,15 +154,18 @@ print >> sys.stderr, "Client not found on server: %r" % name sys.exit(1) -if not clients: +if not clients and mandos_clients.values(): keywords = defaultkeywords if options.all: keywords = ('name', 'enabled', 'timeout', 'last_checked_ok', 'created', 'interval', 'host', 'fingerprint', 'checker_running', 'last_enabled', 'checker') print_clients(mandos_clients.values()) - + +# Process each client in the list by all selected options for client in clients: + if options.remove: + mandos_serv.RemoveClient(client.__dbus_object_path__) if options.enable: client.Enable() if options.disable: === modified file 'mandos-options.xml' --- mandos-options.xml 2008-12-29 02:44:54 +0000 +++ mandos-options.xml 2009-02-13 05:38:21 +0000 @@ -26,10 +26,12 @@ specified IPv6 address. If a link-local address is specified, an interface should be set, since a link-local address is only valid on a single interface. By default, the server will listen to all - available addresses. If set, this must be an IPv6 address; an - IPv4 address can only be specified using IPv4-mapped IPv6 address - syntax: ::FFFF:192.0.2.3. + available addresses. If set, this must normally be an IPv6 + address; an IPv4 address can only be specified using IPv4-mapped + IPv6 address syntax: ::FFFF:192.0.2.3. (Only if IPv6 usage is + disabled (see below) must this be an IPv4 + address.) @@ -65,11 +67,23 @@ rename itself to Mandos #2, and so on; therefore, this option is not needed in that case. - + This option controls whether the server will provide a D-Bus system bus interface. The default is to provide such an interface. + + This option controls whether the server will use IPv6 sockets and + addresses. The default is to use IPv6. This option should + never normally be turned off, even in + IPv4-only environments. This is because + mandos-client + 8mandos will normally use + IPv6 link-local addresses, and will not be able to find or connect + to the server if this option is turned off. Only + advanced users should consider changing this option. + + === modified file 'mandos.conf' --- mandos.conf 2008-12-29 02:44:54 +0000 +++ mandos.conf 2009-02-13 05:38:21 +0000 @@ -39,3 +39,6 @@ # Whether to provide a D-Bus system bus interface or not ;use_dbus = True + +# Whether to use IPv6. (Changing this is NOT recommended.) +;use_ipv6 = True === modified file 'mandos.conf.xml' --- mandos.conf.xml 2009-01-04 21:54:55 +0000 +++ mandos.conf.xml 2009-02-13 05:38:21 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/mandos.conf"> - + %common; ]> @@ -142,6 +142,17 @@ + + + + + + + @@ -179,12 +190,13 @@ [DEFAULT] # A configuration example interface = eth0 -address = 2001:db8:f983:bd0b:30de:ae4a:71f2:f672 +address = fe80::aede:48ff:fe71:f6f2 port = 1025 debug = true priority = SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP servicename = Daena use_dbus = False +use_ipv6 = True === modified file 'mandos.xml' --- mandos.xml 2009-01-23 16:49:50 +0000 +++ mandos.xml 2009-02-13 08:00:47 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -85,6 +85,8 @@ DIRECTORY + + &COMMANDNAME; @@ -229,6 +231,13 @@ + + + + + + + === modified file 'plugin-runner.c' --- plugin-runner.c 2009-01-17 02:34:57 +0000 +++ plugin-runner.c 2009-02-12 19:08:35 +0000 @@ -62,9 +62,10 @@ #include /* struct sigaction, sigemptyset(), sigaddset(), sigaction(), sigprocmask(), SIG_BLOCK, SIGCHLD, - SIG_UNBLOCK, kill() */ + SIG_UNBLOCK, kill(), sig_atomic_t + */ #include /* errno, EBADF */ -#include /* intmax_t, SCNdMAX, PRIdMAX, */ +#include /* intmax_t, PRIdMAX, strtoimax() */ #define BUFFER_SIZE 256 @@ -81,7 +82,7 @@ char **environ; int envc; bool disabled; - + /* Variables used for running processes*/ pid_t pid; int fd; @@ -89,8 +90,8 @@ size_t buffer_size; size_t buffer_length; bool eof; - volatile bool completed; - volatile int status; + volatile sig_atomic_t completed; + int status; struct plugin *next; } plugin; @@ -120,10 +121,10 @@ } } - *new_plugin = (plugin) { .name = copy_name, - .argc = 1, - .disabled = false, - .next = plugin_list }; + *new_plugin = (plugin){ .name = copy_name, + .argc = 1, + .disabled = false, + .next = plugin_list }; new_plugin->argv = malloc(sizeof(char *) * 2); if(new_plugin->argv == NULL){ @@ -223,6 +224,7 @@ /* Mark processes as completed when they exit, and save their exit status. */ static void handle_sigchld(__attribute__((unused)) int sig){ + int old_errno = errno; while(true){ plugin *proc = plugin_list; int status; @@ -232,11 +234,11 @@ break; } if(pid == -1){ - if(errno != ECHILD){ - perror("waitpid"); + if(errno == ECHILD){ + /* No child processes */ + break; } - /* No child processes */ - break; + perror("waitpid"); } /* A child exited, find it in process_list */ @@ -248,8 +250,9 @@ continue; } proc->status = status; - proc->completed = true; + proc->completed = 1; } + errno = old_errno; } /* Prints out a password to stdout */ @@ -277,7 +280,7 @@ } free(plugin_node->environ); free(plugin_node->buffer); - + /* Removes the plugin from the singly-linked list */ if(plugin_node == plugin_list){ /* First one - simple */ @@ -310,7 +313,7 @@ struct dirent *dirst; struct stat st; fd_set rfds_all; - int ret, numchars, maxfd = 0; + int ret, maxfd = 0; ssize_t sret; intmax_t tmpmax; uid_t uid = 65534; @@ -376,16 +379,17 @@ }; error_t parse_opt(int key, char *arg, __attribute__((unused)) - struct argp_state *state) { - switch(key) { + struct argp_state *state){ + char *tmp; + switch(key){ case 'g': /* --global-options */ if(arg != NULL){ - char *p; - while((p = strsep(&arg, ",")) != NULL){ - if(p[0] == '\0'){ + char *plugin_option; + while((plugin_option = strsep(&arg, ",")) != NULL){ + if(plugin_option[0] == '\0'){ continue; } - if(not add_argument(getplugin(NULL), p)){ + if(not add_argument(getplugin(NULL), plugin_option)){ perror("add_argument"); return ARGP_ERR_UNKNOWN; } @@ -402,20 +406,13 @@ break; case 'o': /* --options-for */ if(arg != NULL){ - char *p_name = strsep(&arg, ":"); - if(p_name[0] == '\0' or arg == NULL){ - break; - } - char *opt = strsep(&arg, ":"); - if(opt[0] == '\0' or opt == NULL){ - break; - } - char *p; - while((p = strsep(&opt, ",")) != NULL){ - if(p[0] == '\0'){ - continue; - } - if(not add_argument(getplugin(p_name), p)){ + char *plugin_name = strsep(&arg, ":"); + if(plugin_name[0] == '\0'){ + break; + } + char *plugin_option; + while((plugin_option = strsep(&arg, ",")) != NULL){ + if(not add_argument(getplugin(plugin_name), plugin_option)){ perror("add_argument"); return ARGP_ERR_UNKNOWN; } @@ -466,9 +463,10 @@ /* This is already done by parse_opt_config_file() */ break; case 130: /* --userid */ - ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars); - if(ret < 1 or tmpmax != (uid_t)tmpmax - or arg[numchars] != '\0'){ + errno = 0; + tmpmax = strtoimax(arg, &tmp, 10); + if(errno != 0 or tmp == arg or *tmp != '\0' + or tmpmax != (uid_t)tmpmax){ fprintf(stderr, "Bad user ID number: \"%s\", using %" PRIdMAX "\n", arg, (intmax_t)uid); } else { @@ -476,9 +474,10 @@ } break; case 131: /* --groupid */ - ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars); - if(ret < 1 or tmpmax != (gid_t)tmpmax - or arg[numchars] != '\0'){ + errno = 0; + tmpmax = strtoimax(arg, &tmp, 10); + if(errno != 0 or tmp == arg or *tmp != '\0' + or tmpmax != (gid_t)tmpmax){ fprintf(stderr, "Bad group ID number: \"%s\", using %" PRIdMAX "\n", arg, (intmax_t)gid); } else { @@ -512,8 +511,8 @@ ignores everything but the --config-file option. */ error_t parse_opt_config_file(int key, char *arg, __attribute__((unused)) - struct argp_state *state) { - switch(key) { + struct argp_state *state){ + switch(key){ case 'g': /* --global-options */ case 'G': /* --global-env */ case 'o': /* --options-for */ @@ -570,7 +569,7 @@ size_t size = 0; const char whitespace_delims[] = " \r\t\f\v\n"; const char comment_delim[] = "#"; - + custom_argc = 1; custom_argv = malloc(sizeof(char*) * 2); if(custom_argv == NULL){ @@ -580,7 +579,7 @@ } custom_argv[0] = argv[0]; custom_argv[1] = NULL; - + /* for each line in the config file, strip whitespace and ignore commented text */ while(true){ @@ -588,7 +587,7 @@ if(sret == -1){ break; } - + line = org_line; arg = strsep(&line, comment_delim); while((p = strsep(&arg, whitespace_delims)) != NULL){ @@ -662,13 +661,13 @@ } /* Strip permissions down to nobody */ + setgid(gid); + if(ret == -1){ + perror("setgid"); + } ret = setuid(uid); if(ret == -1){ perror("setuid"); - } - setgid(gid); - if(ret == -1){ - perror("setgid"); } if(plugindir == NULL){ @@ -757,7 +756,7 @@ continue; } } - + char *filename; if(plugindir == NULL){ ret = asprintf(&filename, PDIR "/%s", dirst->d_name); @@ -775,7 +774,7 @@ free(filename); continue; } - + /* Ignore non-executable files */ if(not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){ if(debug){ @@ -934,6 +933,7 @@ closedir(dir); dir = NULL; + free_plugin(getplugin(NULL)); for(plugin *p = plugin_list; p != NULL; p = p->next){ if(p->pid != 0){ @@ -959,30 +959,32 @@ from one of them */ for(plugin *proc = plugin_list; proc != NULL;){ /* Is this process completely done? */ - if(proc->eof and proc->completed){ + if(proc->completed and proc->eof){ /* Only accept the plugin output if it exited cleanly */ if(not WIFEXITED(proc->status) or WEXITSTATUS(proc->status) != 0){ /* Bad exit by plugin */ - + if(debug){ if(WIFEXITED(proc->status)){ - fprintf(stderr, "Plugin %" PRIdMAX " exited with status" - " %d\n", (intmax_t) (proc->pid), + fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with" + " status %d\n", proc->name, + (intmax_t) (proc->pid), WEXITSTATUS(proc->status)); - } else if(WIFSIGNALED(proc->status)) { - fprintf(stderr, "Plugin %" PRIdMAX " killed by signal" - " %d\n", (intmax_t) (proc->pid), + } else if(WIFSIGNALED(proc->status)){ + fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by" + " signal %d\n", proc->name, + (intmax_t) (proc->pid), WTERMSIG(proc->status)); } else if(WCOREDUMP(proc->status)){ - fprintf(stderr, "Plugin %" PRIdMAX " dumped core\n", - (intmax_t) (proc->pid)); + fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped" + " core\n", proc->name, (intmax_t) (proc->pid)); } } /* Remove the plugin */ FD_CLR(proc->fd, &rfds_all); - + /* Block signal while modifying process_list */ ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ @@ -1055,8 +1057,8 @@ } } } - - + + fallback: if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){ === modified file 'plugin-runner.conf' --- plugin-runner.conf 2008-10-05 17:38:31 +0000 +++ plugin-runner.conf 2009-02-09 02:01:13 +0000 @@ -4,8 +4,6 @@ ## ## After editing this file, the initrd image file must be updated for ## the changes to take effect! -## -## The default network interface for mandos-client(8mandos) is -## "eth0". Uncomment this line and change it if necessary. -## -#--options-for=mandos-client:--interface=eth0 + +## Example: +#--options-for=mandos-client:--debug === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2009-01-14 14:20:17 +0000 +++ plugins.d/mandos-client.c 2009-02-12 23:17:14 +0000 @@ -36,44 +36,52 @@ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), asprintf() */ #include /* fprintf(), stderr, fwrite(), - stdout, ferror(), sscanf */ + stdout, ferror(), remove() */ #include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ #include /* free(), EXIT_SUCCESS, EXIT_FAILURE, - srand() */ -#include /* bool, true */ + srand(), strtof() */ +#include /* bool, false, true */ #include /* memset(), strcmp(), strlen(), strerror(), asprintf(), strcpy() */ -#include /* ioctl */ +#include /* ioctl */ #include /* socket(), inet_pton(), sockaddr, sockaddr_in6, PF_INET6, - SOCK_STREAM, INET6_ADDRSTRLEN, - uid_t, gid_t, open(), opendir(), - DIR */ + SOCK_STREAM, uid_t, gid_t, open(), + opendir(), DIR */ #include /* open() */ #include /* socket(), struct sockaddr_in6, - struct in6_addr, inet_pton(), - connect() */ + inet_pton(), connect() */ #include /* open() */ #include /* opendir(), struct dirent, readdir() */ -#include /* PRIu16, intmax_t, SCNdMAX */ +#include /* PRIu16, PRIdMAX, intmax_t, + strtoimax() */ #include /* assert() */ #include /* perror(), errno */ -#include /* time() */ +#include /* nanosleep(), time() */ #include /* ioctl, ifreq, SIOCGIFFLAGS, IFF_UP, SIOCSIFFLAGS, if_indextoname(), if_nametoindex(), IF_NAMESIZE */ -#include +#include /* IN6_IS_ADDR_LINKLOCAL, + INET_ADDRSTRLEN, INET6_ADDRSTRLEN + */ #include /* close(), SEEK_SET, off_t, write(), getuid(), getgid(), setuid(), setgid() */ #include /* inet_pton(), htons */ -#include /* not, and, or */ +#include /* not, or, and */ #include /* struct argp_option, error_t, struct argp_state, struct argp, argp_parse(), ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ +#include /* sigemptyset(), sigaddset(), + sigaction(), SIGTERM, sigaction, + sig_atomic_t */ + +#ifdef __linux__ +#include /* klogctl() */ +#endif /* __linux__ */ /* Avahi */ /* All Avahi types, constants and functions @@ -125,12 +133,17 @@ gpgme_ctx_t ctx; } mandos_context; +/* global context so signal handler can reach it*/ +mandos_context mc = { .simple_poll = NULL, .server = NULL, + .dh_bits = 1024, .priority = "SECURE256" + ":!CTYPE-X.509:+CTYPE-OPENPGP" }; + /* - * Make room in "buffer" for at least BUFFER_SIZE additional bytes. - * "buffer_capacity" is how much is currently allocated, + * 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 adjustbuffer(char **buffer, size_t buffer_length, +size_t incbuffer(char **buffer, size_t buffer_length, size_t buffer_capacity){ if(buffer_length + BUFFER_SIZE > buffer_capacity){ *buffer = realloc(*buffer, buffer_capacity + BUFFER_SIZE); @@ -145,7 +158,7 @@ /* * Initialize GPGME. */ -static bool init_gpgme(mandos_context *mc, const char *seckey, +static bool init_gpgme(const char *seckey, const char *pubkey, const char *tempdir){ int ret; gpgme_error_t rc; @@ -153,7 +166,7 @@ /* - * Helper function to insert pub and seckey to the enigne keyring. + * Helper function to insert pub and seckey to the engine keyring. */ bool import_key(const char *filename){ int fd; @@ -172,7 +185,7 @@ return false; } - rc = gpgme_op_import(mc->ctx, pgp_data); + rc = gpgme_op_import(mc.ctx, pgp_data); if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_op_import: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); @@ -188,7 +201,7 @@ } if(debug){ - fprintf(stderr, "Initialize gpgme\n"); + fprintf(stderr, "Initializing GPGME\n"); } /* Init GPGME */ @@ -221,7 +234,7 @@ } /* Create new GPGME "context" */ - rc = gpgme_new(&(mc->ctx)); + 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)); @@ -239,8 +252,7 @@ * Decrypt OpenPGP data. * Returns -1 on error */ -static ssize_t pgp_packet_decrypt(const mandos_context *mc, - const char *cryptotext, +static ssize_t pgp_packet_decrypt(const char *cryptotext, size_t crypto_size, char **plaintext){ gpgme_data_t dh_crypto, dh_plain; @@ -273,14 +285,14 @@ /* Decrypt data from the cryptotext data buffer to the plaintext data buffer */ - rc = gpgme_op_decrypt(mc->ctx, dh_crypto, dh_plain); + rc = gpgme_op_decrypt(mc.ctx, dh_crypto, dh_plain); if(rc != GPG_ERR_NO_ERROR){ fprintf(stderr, "bad gpgme_op_decrypt: %s: %s\n", gpgme_strsource(rc), gpgme_strerror(rc)); plaintext_length = -1; if(debug){ gpgme_decrypt_result_t result; - result = gpgme_op_decrypt_result(mc->ctx); + result = gpgme_op_decrypt_result(mc.ctx); if(result == NULL){ fprintf(stderr, "gpgme_op_decrypt_result failed\n"); } else { @@ -322,11 +334,11 @@ *plaintext = NULL; while(true){ - plaintext_capacity = adjustbuffer(plaintext, + plaintext_capacity = incbuffer(plaintext, (size_t)plaintext_length, plaintext_capacity); if(plaintext_capacity == 0){ - perror("adjustbuffer"); + perror("incbuffer"); plaintext_length = -1; goto decrypt_end; } @@ -364,7 +376,7 @@ return plaintext_length; } -static const char * safer_gnutls_strerror(int value) { +static const char * safer_gnutls_strerror(int value){ const char *ret = gnutls_strerror(value); /* Spurious warning from -Wunreachable-code */ if(ret == NULL) @@ -378,8 +390,7 @@ fprintf(stderr, "GnuTLS: %s", string); } -static int init_gnutls_global(mandos_context *mc, - const char *pubkeyfilename, +static int init_gnutls_global(const char *pubkeyfilename, const char *seckeyfilename){ int ret; @@ -388,7 +399,7 @@ } ret = gnutls_global_init(); - if(ret != GNUTLS_E_SUCCESS) { + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "GnuTLS global_init: %s\n", safer_gnutls_strerror(ret)); return -1; @@ -403,12 +414,12 @@ } /* OpenPGP credentials */ - gnutls_certificate_allocate_credentials(&mc->cred); + gnutls_certificate_allocate_credentials(&mc.cred); if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "GnuTLS memory error: %s\n", /* Spurious warning - * from - * -Wunreachable-code - */ + from + -Wunreachable-code + */ safer_gnutls_strerror(ret)); gnutls_global_deinit(); return -1; @@ -421,9 +432,9 @@ } ret = gnutls_certificate_set_openpgp_key_file - (mc->cred, pubkeyfilename, seckeyfilename, + (mc.cred, pubkeyfilename, seckeyfilename, GNUTLS_OPENPGP_FMT_BASE64); - if(ret != GNUTLS_E_SUCCESS) { + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error[%d] while reading the OpenPGP key pair ('%s'," " '%s')\n", ret, pubkeyfilename, seckeyfilename); @@ -433,33 +444,32 @@ } /* GnuTLS server initialization */ - ret = gnutls_dh_params_init(&mc->dh_params); - if(ret != GNUTLS_E_SUCCESS) { + 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)); goto globalfail; } - ret = gnutls_dh_params_generate2(mc->dh_params, mc->dh_bits); - if(ret != GNUTLS_E_SUCCESS) { + 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)); goto globalfail; } - gnutls_certificate_set_dh_params(mc->cred, mc->dh_params); + gnutls_certificate_set_dh_params(mc.cred, mc.dh_params); return 0; globalfail: - gnutls_certificate_free_credentials(mc->cred); + gnutls_certificate_free_credentials(mc.cred); gnutls_global_deinit(); - gnutls_dh_params_deinit(mc->dh_params); + gnutls_dh_params_deinit(mc.dh_params); return -1; } -static int init_gnutls_session(mandos_context *mc, - gnutls_session_t *session){ +static int init_gnutls_session(gnutls_session_t *session){ int ret; /* GnuTLS session creation */ ret = gnutls_init(session, GNUTLS_SERVER); @@ -470,8 +480,8 @@ { const char *err; - ret = gnutls_priority_set_direct(*session, mc->priority, &err); - if(ret != GNUTLS_E_SUCCESS) { + ret = gnutls_priority_set_direct(*session, mc.priority, &err); + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Syntax error at: %s\n", err); fprintf(stderr, "GnuTLS error: %s\n", safer_gnutls_strerror(ret)); @@ -481,8 +491,8 @@ } ret = gnutls_credentials_set(*session, GNUTLS_CRD_CERTIFICATE, - mc->cred); - if(ret != GNUTLS_E_SUCCESS) { + mc.cred); + if(ret != GNUTLS_E_SUCCESS){ fprintf(stderr, "Error setting GnuTLS credentials: %s\n", safer_gnutls_strerror(ret)); gnutls_deinit(*session); @@ -493,7 +503,7 @@ gnutls_certificate_server_set_request(*session, GNUTLS_CERT_IGNORE); - gnutls_dh_set_prime_bits(*session, mc->dh_bits); + gnutls_dh_set_prime_bits(*session, mc.dh_bits); return 0; } @@ -505,10 +515,13 @@ /* Called when a Mandos server is found */ static int start_mandos_communication(const char *ip, uint16_t port, AvahiIfIndex if_index, - mandos_context *mc){ + int af){ int ret, tcp_sd; ssize_t sret; - union { struct sockaddr in; struct sockaddr_in6 in6; } to; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + } to; char *buffer = NULL; char *decrypted_buffer; size_t buffer_length = 0; @@ -516,38 +529,45 @@ ssize_t decrypted_buffer_size; size_t written; int retval = 0; - char interface[IF_NAMESIZE]; gnutls_session_t session; - - ret = init_gnutls_session(mc, &session); + int pf; /* Protocol family */ + + switch(af){ + case AF_INET6: + pf = PF_INET6; + break; + case AF_INET: + pf = PF_INET; + break; + default: + fprintf(stderr, "Bad address family: %d\n", af); + return -1; + } + + ret = init_gnutls_session(&session); if(ret != 0){ return -1; } if(debug){ - fprintf(stderr, "Setting up a tcp connection to %s, port %" PRIu16 + fprintf(stderr, "Setting up a TCP connection to %s, port %" PRIu16 "\n", ip, port); } - tcp_sd = socket(PF_INET6, SOCK_STREAM, 0); - if(tcp_sd < 0) { + tcp_sd = socket(pf, SOCK_STREAM, 0); + if(tcp_sd < 0){ perror("socket"); return -1; } - if(debug){ - if(if_indextoname((unsigned int)if_index, interface) == NULL){ - perror("if_indextoname"); - return -1; - } - fprintf(stderr, "Binding to interface %s\n", interface); - } - memset(&to, 0, sizeof(to)); - to.in6.sin6_family = AF_INET6; - /* It would be nice to have a way to detect if we were passed an - IPv4 address here. Now we assume an IPv6 address. */ - ret = inet_pton(AF_INET6, ip, &to.in6.sin6_addr); + if(af == AF_INET6){ + to.in6.sin6_family = (uint16_t)af; + ret = inet_pton(af, ip, &to.in6.sin6_addr); + } else { /* IPv4 */ + to.in.sin_family = (sa_family_t)af; + ret = inet_pton(af, ip, &to.in.sin_addr); + } if(ret < 0 ){ perror("inet_pton"); return -1; @@ -556,18 +576,52 @@ fprintf(stderr, "Bad address: %s\n", ip); return -1; } - to.in6.sin6_port = htons(port); /* Spurious warnings from + if(af == AF_INET6){ + to.in6.sin6_port = htons(port); /* Spurious warnings from + -Wconversion and + -Wunreachable-code */ + + if(IN6_IS_ADDR_LINKLOCAL /* Spurious warnings from */ + (&to.in6.sin6_addr)){ /* -Wstrict-aliasing=2 or lower and + -Wunreachable-code*/ + if(if_index == AVAHI_IF_UNSPEC){ + fprintf(stderr, "An IPv6 link-local address is incomplete" + " without a network interface\n"); + return -1; + } + /* Set the network interface number as scope */ + to.in6.sin6_scope_id = (uint32_t)if_index; + } + } else { + to.in.sin_port = htons(port); /* Spurious warnings from -Wconversion and -Wunreachable-code */ - - to.in6.sin6_scope_id = (uint32_t)if_index; + } if(debug){ - fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, - port); - char addrstr[INET6_ADDRSTRLEN] = ""; - if(inet_ntop(to.in6.sin6_family, &(to.in6.sin6_addr), addrstr, - sizeof(addrstr)) == NULL){ + if(af == AF_INET6 and if_index != AVAHI_IF_UNSPEC){ + char interface[IF_NAMESIZE]; + if(if_indextoname((unsigned int)if_index, interface) == NULL){ + perror("if_indextoname"); + } else { + fprintf(stderr, "Connection to: %s%%%s, port %" PRIu16 "\n", + ip, interface, port); + } + } else { + fprintf(stderr, "Connection to: %s, port %" PRIu16 "\n", ip, + port); + } + char addrstr[(INET_ADDRSTRLEN > INET6_ADDRSTRLEN) ? + INET_ADDRSTRLEN : INET6_ADDRSTRLEN] = ""; + const char *pcret; + if(af == AF_INET6){ + pcret = inet_ntop(af, &(to.in6.sin6_addr), addrstr, + sizeof(addrstr)); + } else { + pcret = inet_ntop(af, &(to.in.sin_addr), addrstr, + sizeof(addrstr)); + } + if(pcret == NULL){ perror("inet_ntop"); } else { if(strcmp(addrstr, ip) != 0){ @@ -576,7 +630,11 @@ } } - ret = connect(tcp_sd, &to.in, sizeof(to)); + if(af == AF_INET6){ + ret = connect(tcp_sd, &to.in6, sizeof(to)); + } else { + ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ + } if(ret < 0){ perror("connect"); return -1; @@ -628,15 +686,15 @@ /* Read OpenPGP packet that contains the wanted password */ if(debug){ - fprintf(stderr, "Retrieving pgp encrypted password from %s\n", + fprintf(stderr, "Retrieving OpenPGP encrypted password from %s\n", ip); } while(true){ - buffer_capacity = adjustbuffer(&buffer, buffer_length, + buffer_capacity = incbuffer(&buffer, buffer_length, buffer_capacity); if(buffer_capacity == 0){ - perror("adjustbuffer"); + perror("incbuffer"); retval = -1; goto mandos_end; } @@ -681,7 +739,7 @@ gnutls_bye(session, GNUTLS_SHUT_RDWR); if(buffer_length > 0){ - decrypted_buffer_size = pgp_packet_decrypt(mc, buffer, + decrypted_buffer_size = pgp_packet_decrypt(buffer, buffer_length, &decrypted_buffer); if(decrypted_buffer_size >= 0){ @@ -722,7 +780,7 @@ static void resolve_callback(AvahiSServiceResolver *r, AvahiIfIndex interface, - AVAHI_GCC_UNUSED AvahiProtocol protocol, + AvahiProtocol proto, AvahiResolverEvent event, const char *name, const char *type, @@ -733,19 +791,18 @@ AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, - void* userdata) { - mandos_context *mc = userdata; + AVAHI_GCC_UNUSED void* userdata){ assert(r); /* Called whenever a service has been resolved successfully or timed out */ - switch(event) { + 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))); + avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_RESOLVER_FOUND: @@ -757,38 +814,38 @@ PRIdMAX ") on port %" PRIu16 "\n", name, host_name, ip, (intmax_t)interface, port); } - int ret = start_mandos_communication(ip, port, interface, mc); + int ret = start_mandos_communication(ip, port, interface, + avahi_proto_to_af(proto)); if(ret == 0){ - avahi_simple_poll_quit(mc->simple_poll); + avahi_simple_poll_quit(mc.simple_poll); } } } avahi_s_service_resolver_free(r); } -static void browse_callback( AvahiSServiceBrowser *b, - AvahiIfIndex interface, - AvahiProtocol protocol, - AvahiBrowserEvent event, - const char *name, - const char *type, - const char *domain, - AVAHI_GCC_UNUSED AvahiLookupResultFlags - flags, - void* userdata) { - mandos_context *mc = userdata; +static void browse_callback(AvahiSServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags + flags, + AVAHI_GCC_UNUSED void* userdata){ assert(b); /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ - switch(event) { + switch(event){ default: case AVAHI_BROWSER_FAILURE: fprintf(stderr, "(Avahi browser) %s\n", - avahi_strerror(avahi_server_errno(mc->server))); - avahi_simple_poll_quit(mc->simple_poll); + avahi_strerror(avahi_server_errno(mc.server))); + avahi_simple_poll_quit(mc.simple_poll); return; case AVAHI_BROWSER_NEW: @@ -797,12 +854,11 @@ the callback function is called the Avahi server will free the resolver for us. */ - if(!(avahi_s_service_resolver_new(mc->server, interface, - protocol, name, type, domain, - AVAHI_PROTO_INET6, 0, - resolve_callback, mc))) + 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))); + name, avahi_strerror(avahi_server_errno(mc.server))); break; case AVAHI_BROWSER_REMOVE: @@ -817,342 +873,471 @@ } } +sig_atomic_t quit_now = 0; + +/* stop main loop after sigterm has been called */ +static void handle_sigterm(__attribute__((unused)) int sig){ + if(quit_now){ + return; + } + quit_now = 1; + int old_errno = errno; + if(mc.simple_poll != NULL){ + avahi_simple_poll_quit(mc.simple_poll); + } + errno = old_errno; +} + int main(int argc, char *argv[]){ - AvahiSServiceBrowser *sb = NULL; - int error; - int ret; - intmax_t tmpmax; - int numchars; - int exitcode = EXIT_SUCCESS; - const char *interface = "eth0"; - struct ifreq network; - int sd; - uid_t uid; - gid_t gid; - char *connect_to = NULL; - char tempdir[] = "/tmp/mandosXXXXXX"; - AvahiIfIndex if_index = AVAHI_IF_UNSPEC; - const char *seckey = PATHDIR "/" SECKEY; - const char *pubkey = PATHDIR "/" PUBKEY; - - mandos_context mc = { .simple_poll = NULL, .server = NULL, - .dh_bits = 1024, .priority = "SECURE256" - ":!CTYPE-X.509:+CTYPE-OPENPGP" }; - bool gnutls_initalized = false; - bool gpgme_initalized = false; - - { - struct argp_option options[] = { - { .name = "debug", .key = 128, - .doc = "Debug mode", .group = 3 }, - { .name = "connect", .key = 'c', - .arg = "ADDRESS:PORT", - .doc = "Connect directly to a specific Mandos server", - .group = 1 }, - { .name = "interface", .key = 'i', - .arg = "NAME", - .doc = "Interface that will be used to search for Mandos" - " servers", - .group = 1 }, - { .name = "seckey", .key = 's', - .arg = "FILE", - .doc = "OpenPGP secret key file base name", - .group = 1 }, - { .name = "pubkey", .key = 'p', - .arg = "FILE", - .doc = "OpenPGP public key file base name", - .group = 2 }, - { .name = "dh-bits", .key = 129, - .arg = "BITS", - .doc = "Bit length of the prime number used in the" - " Diffie-Hellman key exchange", - .group = 2 }, - { .name = "priority", .key = 130, - .arg = "STRING", - .doc = "GnuTLS priority string for the TLS handshake", - .group = 1 }, - { .name = NULL } - }; - - error_t parse_opt(int key, char *arg, - struct argp_state *state) { - switch(key) { - case 128: /* --debug */ - debug = true; - break; - case 'c': /* --connect */ - connect_to = arg; - break; - case 'i': /* --interface */ - interface = arg; - break; - case 's': /* --seckey */ - seckey = arg; - break; - case 'p': /* --pubkey */ - pubkey = arg; - break; - case 129: /* --dh-bits */ - ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars); - if(ret < 1 or tmpmax != (typeof(mc.dh_bits))tmpmax - or arg[numchars] != '\0'){ - fprintf(stderr, "Bad number of DH bits\n"); - exit(EXIT_FAILURE); + AvahiSServiceBrowser *sb = NULL; + int error; + int ret; + intmax_t tmpmax; + char *tmp; + int exitcode = EXIT_SUCCESS; + const char *interface = "eth0"; + struct ifreq network; + int sd; + uid_t uid; + gid_t gid; + char *connect_to = NULL; + char tempdir[] = "/tmp/mandosXXXXXX"; + bool tempdir_created = false; + AvahiIfIndex if_index = AVAHI_IF_UNSPEC; + const char *seckey = PATHDIR "/" SECKEY; + const char *pubkey = PATHDIR "/" PUBKEY; + + bool gnutls_initialized = false; + bool gpgme_initialized = false; + float delay = 2.5f; + + struct sigaction old_sigterm_action; + struct sigaction sigterm_action = { .sa_handler = handle_sigterm }; + + { + struct argp_option options[] = { + { .name = "debug", .key = 128, + .doc = "Debug mode", .group = 3 }, + { .name = "connect", .key = 'c', + .arg = "ADDRESS:PORT", + .doc = "Connect directly to a specific Mandos server", + .group = 1 }, + { .name = "interface", .key = 'i', + .arg = "NAME", + .doc = "Network interface that will be used to search for" + " Mandos servers", + .group = 1 }, + { .name = "seckey", .key = 's', + .arg = "FILE", + .doc = "OpenPGP secret key file base name", + .group = 1 }, + { .name = "pubkey", .key = 'p', + .arg = "FILE", + .doc = "OpenPGP public key file base name", + .group = 2 }, + { .name = "dh-bits", .key = 129, + .arg = "BITS", + .doc = "Bit length of the prime number used in the" + " Diffie-Hellman key exchange", + .group = 2 }, + { .name = "priority", .key = 130, + .arg = "STRING", + .doc = "GnuTLS priority string for the TLS handshake", + .group = 1 }, + { .name = "delay", .key = 131, + .arg = "SECONDS", + .doc = "Maximum delay to wait for interface startup", + .group = 2 }, + { .name = NULL } + }; + + error_t parse_opt(int key, char *arg, + struct argp_state *state){ + switch(key){ + case 128: /* --debug */ + debug = true; + break; + case 'c': /* --connect */ + connect_to = arg; + break; + case 'i': /* --interface */ + interface = arg; + break; + case 's': /* --seckey */ + seckey = arg; + break; + case 'p': /* --pubkey */ + pubkey = arg; + break; + case 129: /* --dh-bits */ + errno = 0; + tmpmax = strtoimax(arg, &tmp, 10); + if(errno != 0 or tmp == arg or *tmp != '\0' + or tmpmax != (typeof(mc.dh_bits))tmpmax){ + fprintf(stderr, "Bad number of DH bits\n"); + exit(EXIT_FAILURE); + } + mc.dh_bits = (typeof(mc.dh_bits))tmpmax; + break; + case 130: /* --priority */ + mc.priority = arg; + break; + case 131: /* --delay */ + errno = 0; + delay = strtof(arg, &tmp); + if(errno != 0 or tmp == arg or *tmp != '\0'){ + fprintf(stderr, "Bad delay\n"); + exit(EXIT_FAILURE); + } + break; + case ARGP_KEY_ARG: + argp_usage(state); + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + struct argp argp = { .options = options, .parser = parse_opt, + .args_doc = "", + .doc = "Mandos client -- Get and decrypt" + " passwords from a Mandos server" }; + ret = argp_parse(&argp, argc, argv, 0, 0, NULL); + if(ret == ARGP_ERR_UNKNOWN){ + fprintf(stderr, "Unknown error while parsing arguments\n"); + exitcode = EXIT_FAILURE; + goto end; + } + } + + if(not debug){ + avahi_set_log_function(empty_log); + } + + /* Initialize Avahi early so avahi_simple_poll_quit() can be called + from the signal handler */ + /* Initialize the pseudo-RNG for Avahi */ + srand((unsigned int) time(NULL)); + mc.simple_poll = avahi_simple_poll_new(); + if(mc.simple_poll == NULL){ + fprintf(stderr, "Avahi: Failed to create simple poll object.\n"); + exitcode = EXIT_FAILURE; + goto end; + } + + sigemptyset(&sigterm_action.sa_mask); + ret = sigaddset(&sigterm_action.sa_mask, SIGINT); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); + if(ret == -1){ + perror("sigaddset"); + exitcode = EXIT_FAILURE; + goto end; + } + ret = sigaction(SIGTERM, &sigterm_action, &old_sigterm_action); + if(ret == -1){ + perror("sigaction"); + exitcode = EXIT_FAILURE; + goto end; + } + + /* If the interface is down, bring it up */ + if(interface[0] != '\0'){ +#ifdef __linux__ + /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO + messages to mess up the prompt */ + ret = klogctl(8, NULL, 5); + bool restore_loglevel = true; + if(ret == -1){ + restore_loglevel = false; + perror("klogctl"); + } +#endif /* __linux__ */ + + sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + if(sd < 0){ + perror("socket"); + exitcode = EXIT_FAILURE; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + goto end; + } + strcpy(network.ifr_name, interface); + ret = ioctl(sd, SIOCGIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCGIFFLAGS"); +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); + } + } +#endif /* __linux__ */ + exitcode = EXIT_FAILURE; + goto end; + } + if((network.ifr_flags & IFF_UP) == 0){ + network.ifr_flags |= IFF_UP; + ret = ioctl(sd, SIOCSIFFLAGS, &network); + if(ret == -1){ + perror("ioctl SIOCSIFFLAGS"); + exitcode = EXIT_FAILURE; +#ifdef __linux__ + if(restore_loglevel){ + ret = klogctl(7, NULL, 0); + if(ret == -1){ + perror("klogctl"); } - mc.dh_bits = (typeof(mc.dh_bits))tmpmax; - break; - case 130: /* --priority */ - mc.priority = arg; - break; - case ARGP_KEY_ARG: - argp_usage(state); - case ARGP_KEY_END: - break; - default: - return ARGP_ERR_UNKNOWN; } - return 0; - } - - struct argp argp = { .options = options, .parser = parse_opt, - .args_doc = "", - .doc = "Mandos client -- Get and decrypt" - " passwords from a Mandos server" }; - ret = argp_parse(&argp, argc, argv, 0, 0, NULL); - if(ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - exitcode = EXIT_FAILURE; +#endif /* __linux__ */ goto end; } } - - /* If the interface is down, bring it up */ - { - sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); - if(sd < 0) { - perror("socket"); - exitcode = EXIT_FAILURE; - goto end; - } - strcpy(network.ifr_name, interface); + /* sleep checking until interface is running */ + for(int i=0; i < delay * 4; i++){ ret = ioctl(sd, SIOCGIFFLAGS, &network); if(ret == -1){ perror("ioctl SIOCGIFFLAGS"); - exitcode = EXIT_FAILURE; - goto end; - } - if((network.ifr_flags & IFF_UP) == 0){ - network.ifr_flags |= IFF_UP; - ret = ioctl(sd, SIOCSIFFLAGS, &network); - if(ret == -1){ - perror("ioctl SIOCSIFFLAGS"); - exitcode = EXIT_FAILURE; - goto end; - } - } - ret = (int)TEMP_FAILURE_RETRY(close(sd)); + } else if(network.ifr_flags & IFF_RUNNING){ + break; + } + struct timespec sleeptime = { .tv_nsec = 250000000 }; + ret = nanosleep(&sleeptime, NULL); + if(ret == -1 and errno != EINTR){ + perror("nanosleep"); + } + } + ret = (int)TEMP_FAILURE_RETRY(close(sd)); + if(ret == -1){ + perror("close"); + } +#ifdef __linux__ + if(restore_loglevel){ + /* Restores kernel loglevel to default */ + ret = klogctl(7, NULL, 0); if(ret == -1){ - perror("close"); + perror("klogctl"); } } - - uid = getuid(); - gid = getgid(); - - ret = setuid(uid); - if(ret == -1){ - perror("setuid"); - } - - setgid(gid); - if(ret == -1){ - perror("setgid"); - } - - ret = init_gnutls_global(&mc, pubkey, seckey); - if(ret == -1){ - fprintf(stderr, "init_gnutls_global failed\n"); - exitcode = EXIT_FAILURE; - goto end; - } else { - gnutls_initalized = true; - } - - if(mkdtemp(tempdir) == NULL){ - perror("mkdtemp"); - tempdir[0] = '\0'; - goto end; - } - - if(not init_gpgme(&mc, pubkey, seckey, tempdir)){ - fprintf(stderr, "gpgme_initalized failed\n"); - exitcode = EXIT_FAILURE; - goto end; - } else { - gpgme_initalized = true; - } - +#endif /* __linux__ */ + } + + uid = getuid(); + gid = getgid(); + + errno = 0; + setgid(gid); + if(ret == -1){ + perror("setgid"); + } + + ret = setuid(uid); + if(ret == -1){ + perror("setuid"); + } + + ret = init_gnutls_global(pubkey, seckey); + if(ret == -1){ + fprintf(stderr, "init_gnutls_global failed\n"); + exitcode = EXIT_FAILURE; + goto end; + } else { + gnutls_initialized = true; + } + + if(mkdtemp(tempdir) == NULL){ + perror("mkdtemp"); + goto end; + } + tempdir_created = true; + + if(not init_gpgme(pubkey, seckey, tempdir)){ + fprintf(stderr, "init_gpgme failed\n"); + exitcode = EXIT_FAILURE; + goto end; + } else { + gpgme_initialized = true; + } + + if(interface[0] != '\0'){ if_index = (AvahiIfIndex) if_nametoindex(interface); if(if_index == 0){ fprintf(stderr, "No such interface: \"%s\"\n", interface); - exit(EXIT_FAILURE); - } - - if(connect_to != NULL){ - /* Connect directly, do not use Zeroconf */ - /* (Mainly meant for debugging) */ - char *address = strrchr(connect_to, ':'); - if(address == NULL){ - fprintf(stderr, "No colon in address\n"); - exitcode = EXIT_FAILURE; - goto end; - } - uint16_t port; - ret = sscanf(address+1, "%" SCNdMAX "%n", &tmpmax, &numchars); - if(ret < 1 or tmpmax != (uint16_t)tmpmax - or address[numchars+1] != '\0'){ - fprintf(stderr, "Bad port number\n"); - exitcode = EXIT_FAILURE; - goto end; - } - port = (uint16_t)tmpmax; - *address = '\0'; - address = connect_to; - ret = start_mandos_communication(address, port, if_index, &mc); - if(ret < 0){ - exitcode = EXIT_FAILURE; - } else { - exitcode = EXIT_SUCCESS; - } - goto end; - } - - if(not debug){ - avahi_set_log_function(empty_log); - } - - /* Initialize the pseudo-RNG for Avahi */ - srand((unsigned int) time(NULL)); - - /* Allocate main Avahi loop object */ - mc.simple_poll = avahi_simple_poll_new(); - if(mc.simple_poll == NULL) { - fprintf(stderr, "Avahi: Failed to create simple poll" - " object.\n"); - exitcode = EXIT_FAILURE; - goto end; - } - - { - AvahiServerConfig config; - /* Do not publish any local Zeroconf records */ - avahi_server_config_init(&config); - config.publish_hinfo = 0; - config.publish_addresses = 0; - config.publish_workstation = 0; - config.publish_domain = 0; - - /* Allocate a new server */ - mc.server = avahi_server_new(avahi_simple_poll_get - (mc.simple_poll), &config, NULL, - NULL, &error); - - /* Free the Avahi configuration data */ - avahi_server_config_free(&config); - } - - /* Check if creating the Avahi server object succeeded */ - if(mc.server == NULL) { - fprintf(stderr, "Failed to create Avahi server: %s\n", - avahi_strerror(error)); - exitcode = EXIT_FAILURE; - goto end; - } - - /* Create the Avahi service browser */ - sb = avahi_s_service_browser_new(mc.server, if_index, - AVAHI_PROTO_INET6, - "_mandos._tcp", NULL, 0, - browse_callback, &mc); - if(sb == NULL) { - fprintf(stderr, "Failed to create service browser: %s\n", - avahi_strerror(avahi_server_errno(mc.server))); - exitcode = EXIT_FAILURE; - goto end; - } - - /* Run the main loop */ - - if(debug){ - fprintf(stderr, "Starting Avahi loop search\n"); - } - - avahi_simple_poll_loop(mc.simple_poll); - + exitcode = EXIT_FAILURE; + goto end; + } + } + + if(connect_to != NULL){ + /* Connect directly, do not use Zeroconf */ + /* (Mainly meant for debugging) */ + char *address = strrchr(connect_to, ':'); + if(address == NULL){ + fprintf(stderr, "No colon in address\n"); + exitcode = EXIT_FAILURE; + goto end; + } + uint16_t port; + errno = 0; + tmpmax = strtoimax(address+1, &tmp, 10); + if(errno != 0 or tmp == address+1 or *tmp != '\0' + or tmpmax != (uint16_t)tmpmax){ + fprintf(stderr, "Bad port number\n"); + exitcode = EXIT_FAILURE; + goto end; + } + port = (uint16_t)tmpmax; + *address = '\0'; + address = connect_to; + /* Colon in address indicates IPv6 */ + int af; + if(strchr(address, ':') != NULL){ + af = AF_INET6; + } else { + af = AF_INET; + } + ret = start_mandos_communication(address, port, if_index, af); + if(ret < 0){ + exitcode = EXIT_FAILURE; + } else { + exitcode = EXIT_SUCCESS; + } + goto end; + } + + { + AvahiServerConfig config; + /* Do not publish any local Zeroconf records */ + avahi_server_config_init(&config); + config.publish_hinfo = 0; + config.publish_addresses = 0; + config.publish_workstation = 0; + config.publish_domain = 0; + + /* Allocate a new server */ + mc.server = avahi_server_new(avahi_simple_poll_get + (mc.simple_poll), &config, NULL, + NULL, &error); + + /* Free the Avahi configuration data */ + avahi_server_config_free(&config); + } + + /* Check if creating the Avahi server object succeeded */ + if(mc.server == NULL){ + fprintf(stderr, "Failed to create Avahi server: %s\n", + avahi_strerror(error)); + exitcode = EXIT_FAILURE; + goto end; + } + + /* Create the Avahi service browser */ + sb = avahi_s_service_browser_new(mc.server, if_index, + AVAHI_PROTO_UNSPEC, "_mandos._tcp", + NULL, 0, browse_callback, NULL); + if(sb == NULL){ + fprintf(stderr, "Failed to create service browser: %s\n", + avahi_strerror(avahi_server_errno(mc.server))); + exitcode = EXIT_FAILURE; + goto end; + } + + /* Run the main loop */ + + if(debug){ + fprintf(stderr, "Starting Avahi loop search\n"); + } + + avahi_simple_poll_loop(mc.simple_poll); + end: - - if(debug){ - fprintf(stderr, "%s exiting\n", argv[0]); - } - - /* Cleanup things */ - if(sb != NULL) - avahi_s_service_browser_free(sb); - - if(mc.server != NULL) - avahi_server_free(mc.server); - - if(mc.simple_poll != NULL) - avahi_simple_poll_free(mc.simple_poll); - - if(gnutls_initalized){ - gnutls_certificate_free_credentials(mc.cred); - gnutls_global_deinit(); - gnutls_dh_params_deinit(mc.dh_params); - } - - if(gpgme_initalized){ - gpgme_release(mc.ctx); - } - - /* Removes the temp directory used by GPGME */ - if(tempdir[0] != '\0'){ - DIR *d; - struct dirent *direntry; - d = opendir(tempdir); - if(d == NULL){ - if(errno != ENOENT){ - perror("opendir"); - } - } else { - while(true){ - direntry = readdir(d); - if(direntry == NULL){ - break; - } - if(direntry->d_type == DT_REG){ - char *fullname = NULL; - ret = asprintf(&fullname, "%s/%s", tempdir, - direntry->d_name); - if(ret < 0){ - perror("asprintf"); - continue; - } - ret = unlink(fullname); - if(ret == -1){ - fprintf(stderr, "unlink(\"%s\"): %s", - fullname, strerror(errno)); - } - free(fullname); - } - } - closedir(d); - } - ret = rmdir(tempdir); - if(ret == -1 and errno != ENOENT){ - perror("rmdir"); - } - } - - return exitcode; + + if(debug){ + fprintf(stderr, "%s exiting\n", argv[0]); + } + + /* Cleanup things */ + if(sb != NULL) + avahi_s_service_browser_free(sb); + + if(mc.server != NULL) + avahi_server_free(mc.server); + + if(mc.simple_poll != NULL) + avahi_simple_poll_free(mc.simple_poll); + + if(gnutls_initialized){ + gnutls_certificate_free_credentials(mc.cred); + gnutls_global_deinit(); + gnutls_dh_params_deinit(mc.dh_params); + } + + if(gpgme_initialized){ + gpgme_release(mc.ctx); + } + + /* Removes the temp directory used by GPGME */ + if(tempdir_created){ + DIR *d; + struct dirent *direntry; + d = opendir(tempdir); + if(d == NULL){ + if(errno != ENOENT){ + perror("opendir"); + } + } else { + while(true){ + direntry = readdir(d); + if(direntry == NULL){ + break; + } + /* Skip "." and ".." */ + if(direntry->d_name[0] == '.' + and (direntry->d_name[1] == '\0' + or (direntry->d_name[1] == '.' + and direntry->d_name[2] == '\0'))){ + continue; + } + char *fullname = NULL; + ret = asprintf(&fullname, "%s/%s", tempdir, + direntry->d_name); + if(ret < 0){ + perror("asprintf"); + continue; + } + ret = remove(fullname); + if(ret == -1){ + fprintf(stderr, "remove(\"%s\"): %s\n", fullname, + strerror(errno)); + } + free(fullname); + } + closedir(d); + } + ret = rmdir(tempdir); + if(ret == -1 and errno != ENOENT){ + perror("rmdir"); + } + } + + return exitcode; } === modified file 'plugins.d/mandos-client.xml' --- plugins.d/mandos-client.xml 2009-01-04 21:54:55 +0000 +++ plugins.d/mandos-client.xml 2009-02-09 02:01:13 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -93,6 +93,10 @@ + + + + @@ -122,11 +126,16 @@ &COMMANDNAME; is a client program that communicates with mandos8 - to get a password. It uses IPv6 link-local addresses to get - network connectivity, Zeroconf to find servers, and TLS with an - OpenPGP key to ensure authenticity and confidentiality. It - keeps running, trying all servers on the network, until it - receives a satisfactory reply or a TERM signal is received. + to get a password. In slightly more detail, this client program + brings up a network interface, uses the interface’s IPv6 + link-local address to get network connectivity, uses Zeroconf to + find servers on the local network, and communicates with servers + using TLS with an OpenPGP key to ensure authenticity and + confidentiality. This client program keeps running, trying all + servers on the network, until it receives a satisfactory reply + or a TERM signal is received. If no servers are found, or after + all servers have been tried, it waits indefinitely for new + servers to appear. This program is not meant to be run directly; it is really meant @@ -186,14 +195,14 @@ - + Network interface that will be brought up and scanned for - Mandos servers to connect to. The default it + Mandos servers to connect to. The default is eth0. @@ -201,6 +210,21 @@ specifies the interface to use to connect to the address given. + + Note that since this program will normally run in the + initial RAM disk environment, the interface must be an + interface which exists at that stage. Thus, the interface + can not be a pseudo-interface such as br0 + or tun0; such interfaces will not exist + until much later in the boot process, and can not be used + by this program. + + + NAME can be the empty string; + this will not use any specific interface, and will not + bring up an interface on startup. This is not + recommended, and only meant for advanced users. + @@ -251,6 +275,22 @@ + + + + + + After bringing the network interface up, the program waits + for the interface to arrive in a running + state before proceeding. During this time, the kernel log + level will be lowered to reduce clutter on the system + console, alleviating any other plugins which might be + using the system console. This option sets the upper + limit of seconds to wait. The default is 2.5 seconds. + + + @@ -412,15 +452,15 @@ Run in debug mode, with a custom key, and do not use Zeroconf - to locate a server; connect directly to the IPv6 address - 2001:db8:f983:bd0b:30de:ae4a:71f2:f672, - port 4711, using interface eth2: + to locate a server; connect directly to the IPv6 link-local + address fe80::aede:48ff:fe71:f6f2, port 4711, + using interface eth2: -&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect 2001:db8:f983:bd0b:30de:ae4a:71f2:f672:4711 --interface eth2 +&COMMANDNAME; --debug --pubkey keydir/pubkey.txt --seckey keydir/seckey.txt --connect fe80::aede:48ff:fe71:f6f2:4711 --interface eth2 === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2009-01-13 04:35:19 +0000 +++ plugins.d/password-prompt.c 2009-02-05 02:33:05 +0000 @@ -51,13 +51,13 @@ ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ -volatile bool quit_now = false; +volatile sig_atomic_t quit_now = 0; bool debug = false; const char *argp_program_version = "password-prompt " VERSION; const char *argp_program_bug_address = ""; static void termination_handler(__attribute__((unused))int signum){ - quit_now = true; + quit_now = 1; } int main(int argc, char **argv){ @@ -80,8 +80,8 @@ { .name = NULL } }; - error_t parse_opt (int key, char *arg, struct argp_state *state) { - switch (key) { + error_t parse_opt (int key, char *arg, struct argp_state *state){ + switch (key){ case 'p': prefix = arg; break; @@ -194,7 +194,7 @@ const char *cryptsource = getenv("cryptsource"); const char *crypttarget = getenv("crypttarget"); const char *const prompt - = "Enter passphrase to unlock the disk"; + = "Enter passphrase to unlock the disk"; if(cryptsource == NULL){ if(crypttarget == NULL){ fprintf(stderr, "%s: ", prompt); @@ -242,8 +242,8 @@ /* if(ret == 0), then the only sensible thing to do is to retry to read from stdin */ fputc('\n', stderr); - if(debug and not quit_now){ - /* If quit_now is true, we were interrupted by a signal, and + if(debug and quit_now == 0){ + /* If quit_now is nonzero, we were interrupted by a signal, and will print that later, so no need to show this too. */ fprintf(stderr, "getline() returned 0, retrying.\n"); } === modified file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2009-01-14 14:20:17 +0000 +++ plugins.d/splashy.c 2009-02-12 18:56:52 +0000 @@ -30,13 +30,13 @@ SIG_IGN, kill(), SIGKILL */ #include /* NULL */ #include /* getenv() */ -#include /* asprintf(), perror(), sscanf() */ +#include /* asprintf(), perror() */ #include /* EXIT_FAILURE, free(), EXIT_SUCCESS */ #include /* pid_t, DIR, struct dirent, ssize_t */ #include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, SCNdMAX */ +#include /* intmax_t, strtoimax() */ #include /* struct stat, lstat(), S_ISLNK */ #include /* not, or, and */ #include /* readlink(), fork(), execl(), @@ -101,11 +101,11 @@ pid_t pid; { intmax_t tmpmax; - int numchars; - ret = sscanf(proc_ent->d_name, "%" SCNdMAX "%n", &tmpmax, - &numchars); - if(ret < 1 or tmpmax != (pid_t)tmpmax - or proc_ent->d_name[numchars] != '\0'){ + char *tmp; + errno = 0; + tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); + if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' + or tmpmax != (pid_t)tmpmax){ /* Not a process */ continue; } === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2009-01-14 14:20:17 +0000 +++ plugins.d/usplash.c 2009-02-12 18:56:52 +0000 @@ -36,7 +36,7 @@ dirent */ #include /* NULL */ #include /* strlen(), memcmp() */ -#include /* asprintf(), perror(), sscanf() */ +#include /* asprintf(), perror() */ #include /* close(), write(), readlink(), read(), STDOUT_FILENO, sleep(), fork(), setuid(), geteuid(), @@ -46,7 +46,7 @@ EXIT_SUCCESS, malloc(), _exit() */ #include /* getenv() */ #include /* opendir(), readdir(), closedir() */ -#include /* intmax_t, SCNdMAX */ +#include /* intmax_t, strtoimax() */ #include /* struct stat, lstat(), S_ISLNK */ sig_atomic_t interrupted_by_signal = 0; @@ -173,11 +173,11 @@ pid_t pid; { intmax_t tmpmax; - int numchars; - ret = sscanf(proc_ent->d_name, "%" SCNdMAX "%n", &tmpmax, - &numchars); - if(ret < 1 or tmpmax != (pid_t)tmpmax - or proc_ent->d_name[numchars] != '\0'){ + char *tmp; + errno = 0; + tmpmax = strtoimax(proc_ent->d_name, &tmp, 10); + if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0' + or tmpmax != (pid_t)tmpmax){ /* Not a process */ continue; }