=== modified file 'Makefile' --- Makefile 2009-09-17 14:21:18 +0000 +++ Makefile 2009-11-19 18:31:28 +0000 @@ -8,15 +8,22 @@ # -Wunreachable-code #DEBUG=-ggdb3 # For info about _FORTIFY_SOURCE, see -# -FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC -fPIE -LINK_FORTIFY_LD=-z relro -fPIE -LINK_FORTIFY=-pie +# +# and . +FORTIFY=-D_FORTIFY_SOURCE=2 -fstack-protector-all -fPIC +LINK_FORTIFY_LD=-z relro -z now +LINK_FORTIFY= + +# If BROKEN_PIE is set, do not build with -pie +ifndef BROKEN_PIE +FORTIFY += -fPIE +LINK_FORTIFY += -pie +endif #COVERAGE=--coverage OPTIMIZE=-Os LANGUAGE=-std=gnu99 htmldir=man -version=1.0.12 +version=1.0.14 SED=sed ## Use these settings for a traditional /usr/local install @@ -50,7 +57,7 @@ LDFLAGS=$(COVERAGE) $(LINK_FORTIFY) $(foreach flag,$(LINK_FORTIFY_LD),-Xlinker $(flag)) # Commands to format a DocBook document into a manual page -DOCBOOKTOMAN=cd $(dir $<); xsltproc --nonet --xinclude \ +DOCBOOKTOMAN=$(strip cd $(dir $<); xsltproc --nonet --xinclude \ --param man.charmap.use.subset 0 \ --param make.year.ranges 1 \ --param make.single.year.ranges 1 \ @@ -58,11 +65,11 @@ --param man.authors.section.enabled 0 \ /usr/share/xml/docbook/stylesheet/nwalsh/manpages/docbook.xsl \ $(notdir $<); \ - $(MANPOST) $(notdir $@) + $(MANPOST) $(notdir $@)) # DocBook-to-man post-processing to fix a '\n' escape bug MANPOST=$(SED) --in-place --expression='s,\\\\en,\\en,g;s,\\n,\\en,g' -DOCBOOKTOHTML=xsltproc --nonet --xinclude \ +DOCBOOKTOHTML=$(strip xsltproc --nonet --xinclude \ --param make.year.ranges 1 \ --param make.single.year.ranges 1 \ --param man.output.quietly 1 \ @@ -70,7 +77,7 @@ --param citerefentry.link 1 \ --output $@ \ /usr/share/xml/docbook/stylesheet/nwalsh/xhtml/docbook.xsl \ - $<; $(HTMLPOST) $@ + $<; $(HTMLPOST) $@) # Fix citerefentry links HTMLPOST=$(SED) --in-place \ --expression='s/\(\)\([^<]*\)\(<\/span>(\)\([^)]*\)\()<\/span><\/a>\)/\1\3.\5\2\3\4\5\6/g' @@ -151,39 +158,39 @@ # Update all these files with version number $(version) common.ent: Makefile - $(SED) --in-place \ + $(strip $(SED) --in-place \ --expression='s/^\($$/\1$(version)">/' \ - $@ + $@) mandos: Makefile - $(SED) --in-place \ + $(strip $(SED) --in-place \ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@ + $@) mandos-keygen: Makefile - $(SED) --in-place \ + $(strip $(SED) --in-place \ --expression='s/^\(VERSION="\)[^"]*"$$/\1$(version)"/' \ - $@ + $@) mandos-ctl: Makefile - $(SED) --in-place \ + $(strip $(SED) --in-place \ --expression='s/^\(version = "\)[^"]*"$$/\1$(version)"/' \ - $@ + $@) mandos.lsm: Makefile - $(SED) --in-place \ + $(strip $(SED) --in-place \ --expression='s/^\(Version:\).*/\1\t$(version)/' \ - $@ - $(SED) --in-place \ + $@) + $(strip $(SED) --in-place \ --expression='s/^\(Entered-date:\).*/\1\t$(shell date --rfc-3339=date --reference=Makefile)/' \ - $@ - $(SED) --in-place \ + $@) + $(strip $(SED) --in-place \ --expression='s/\(mandos_\)[0-9.]\+\(\.orig\.tar\.gz\)/\1$(version)\2/' \ - $@ + $@) -plugins.d/mandos-client: plugins.d/mandos-client.o - $(LINK.o) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) \ - $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ +plugins.d/mandos-client: plugins.d/mandos-client.c + $(LINK.c) $(GNUTLS_LIBS) $(AVAHI_LIBS) $(GPGME_LIBS) $(strip\ + ) $(COMMON) $^ $(LOADLIBES) $(LDLIBS) -o $@ .PHONY : all doc html clean distclean run-client run-server install \ install-server install-client uninstall uninstall-server \ === modified file 'NEWS' --- NEWS 2009-09-17 14:21:18 +0000 +++ NEWS 2009-10-26 21:16:16 +0000 @@ -1,6 +1,15 @@ This NEWS file records noteworthy changes, very tersely. See the manual for detailed information. +Version 1.0.14 (2009-10-25) +Enable building without -pie and -fPIE if BROKEN_PIE is set. + +Version 1.0.13 (2009-10-22) +* Client +** Security bug fix: If Mandos server is also installed, do not copy + its config files (with encrypted passwords) into the initrd.img-* + files. + Version 1.0.12 (2009-09-17) * Client ** Bug fix: Allow network interface renaming by "udev" by taking down === modified file 'README' --- README 2009-02-23 11:52:42 +0000 +++ README 2009-11-03 00:12:35 +0000 @@ -99,7 +99,7 @@ Multiple Mandos servers can coexist on a network without any trouble. They do not clash, and clients will try all available servers. This means that if just one reboots then the other can - bring it back up, but if both reboots at the same time they will + bring it back up, but if both reboot at the same time they will stay down until someone types in the password on one of them. ** Faking ping replies? === modified file 'TODO' --- TODO 2009-11-16 14:41:32 +0000 +++ TODO 2010-03-27 18:39:02 +0000 @@ -2,85 +2,92 @@ * mandos-client ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#B] Prefix all debug output with argv[0] +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO use error() instead of perror() ** TODO [#B] Retry a server which has a non-definite reply: *** A closed connection during the TLS handshake *** A TCP timeout ** TODO [#B] Use capabilities instead of seteuid(). +** TODO [#A] Retry --connect forever * splashy ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() * usplash +** TODO [#A] Make it work again ** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() * askpass-fifo -** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() ** TODO [#B] Drop privileges after opening FIFO. * password-prompt -** TODO [#B] Prefix all debug output with "Mandos plugin " + argv[0] +** TODO [#B] Prefix all debug output with "Mandos plugin " + program_invocation_short_name +** TODO [#B] use error() instead of perror() +** TODO [#B] lock stdin (with flock()?) -* TODO passdev +* TODO [#B] passdev * plugin-runner ** TODO [#B] use scandir(3) instead of readdir(3) ** TODO [#C] use same file name rules as run-parts(8) ** kernel command line option for debug info +** TODO [#B] use error() instead of perror() * mandos (server) -** 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: - Probably using D-Bus -*** Client class -*** Main server - + SetLogLevel - syslogger.setLevel(logging.WARNING) - + [[http://log.ometer.com/2007-05.html][Best D-Bus practices]] -** TODO Implement --foreground :BUGS: +** TODO [#B] Log level :BUGS: +** TODO Persistent state :BUGS: + /var/lib/mandos/* +*** 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 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 DBusServiceObjectUsingSuper -** TODO Global enable/disable flag -** TODO By-client countdown on secrets given -** TODO Fix problem with fsck taking a really long time +** TODO [#C] DBusServiceObjectUsingSuper +** TODO [#B] Global enable/disable flag +** TODO [#B] By-client countdown on secrets given +** TODO [#B] Fix problem with fsck taking a really long time Whenever a client successfully gets a secret it could get a one-time timeout boost to allow for an fsck-incurred delay -** TODO Delay before client receives key +** TODO [#A] Delay before client receives key This would give an operator opportunity to cancel the request if desired. -** TODO Client manual approval mode +** TODO [#A] Client manual approval mode A client needs manual approval on the server before it gets the secret -** TODO Persistent state - /var/lib/mandos/* +** TODO [#B] Support RFC 3339 time duration syntax * mandos.xml ** [[file:mandos.xml::XXX][Document D-Bus interface]] + Remove mention of lack of such interface in BUGS section ** Add mandos contact info in manual pages -* Provide and install /etc/dbus-1/system.d/mandos.conf +* TODO [#A] Provide and install /etc/dbus-1/system.d/mandos.conf * mandos-ctl *** Handle "no D-Bus server" and/or "no Mandos server found" better *** [#B] --dump option +** TODO Support RFC 3339 time duration syntax * TODO mandos-dispatch Listens for specified D-Bus signals and spawns shell commands with arguments. * mandos-monitor -** D-Bus mail loop w/ signal receiver -** Snack/Newt client data displayer -*** Client Widgets +** Urwid client data displayer + Better view of client data in the listing *** Properties popup * mandos-keygen @@ -91,7 +98,7 @@ For testing decryption before rebooting. * Makefile -** Implement DEB_BUILD_OPTIONS +** TODO [#C] Implement DEB_BUILD_OPTIONS http://www.debian.org/doc/debian-policy/ch-source.html#s-debianrules-options * Package === modified file 'clients.conf' --- clients.conf 2009-01-08 03:54:06 +0000 +++ clients.conf 2009-12-25 23:13:47 +0000 @@ -2,15 +2,15 @@ # values, so uncomment and change them if you want different ones. [DEFAULT] -# How long until a client is considered invalid - that is, ineligible -# to get the data this server holds. +# How long until a client is disabled and not be allowed to get the +# data this server holds. ;timeout = 1h # How often to run the checker to confirm that a client is still up. # Note: a new checker will not be started if an old one is still # running. The server will wait for a checker to complete until the -# above "timeout" occurs, at which time the client will be marked -# invalid, and any running checker killed. +# above "timeout" occurs, at which time the client will be disabled, +# and any running checker killed. ;interval = 5m # What command to run as "the checker". === modified file 'common.ent' --- common.ent 2009-09-17 14:21:18 +0000 +++ common.ent 2009-10-26 21:16:16 +0000 @@ -1,3 +1,3 @@ - + === added file 'dbus-mandos.conf' --- dbus-mandos.conf 1970-01-01 00:00:00 +0000 +++ dbus-mandos.conf 2009-11-09 07:35:16 +0000 @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + === modified file 'debian/changelog' --- debian/changelog 2009-09-17 14:21:18 +0000 +++ debian/changelog 2009-10-26 21:16:16 +0000 @@ -1,3 +1,18 @@ +mandos (1.0.14-1) unstable; urgency=low (HIGH on mips and mipsel) + + * New upstream release. + * debian/rules: Build with BROKEN_PIE set on mips and mipsel + architectures - fixes FTBFS there. + + -- Teddy Hogeborn Sun, 25 Oct 2009 20:10:09 +0100 + +mandos (1.0.13-1) unstable; urgency=high + + * New upstream release. + * Do not copy unnecessary files to initrd (Closes: #551907) + + -- Teddy Hogeborn Thu, 22 Oct 2009 00:53:21 +0200 + mandos (1.0.12-1) unstable; urgency=low * New upstream release. === modified file 'debian/control' --- debian/control 2009-09-17 01:21:27 +0000 +++ debian/control 2010-01-01 15:17:12 +0000 @@ -15,8 +15,7 @@ Package: mandos Architecture: all Depends: ${misc:Depends}, python (>=2.5), python-gnutls, python-dbus, - python-avahi, python-gobject, avahi-daemon, gnupg (< 2), - adduser + python-avahi, python-gobject, avahi-daemon, adduser Recommends: fping Description: a server giving encrypted passwords to Mandos clients This is the server part of the Mandos system, which allows @@ -35,7 +34,8 @@ Package: mandos-client Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, cryptsetup, + gnupg (<< 2) Enhances: cryptsetup Description: do unattended reboots with an encrypted root file system This is the client part of the Mandos system, which allows === modified file 'debian/rules' --- debian/rules 2009-01-18 00:18:50 +0000 +++ debian/rules 2009-11-11 00:25:22 +0000 @@ -15,6 +15,20 @@ # This has to be exported to make some magic below work. export DH_OPTIONS +# -pie was broken briefly on the mips and mipsel architectures, see +# +BINUTILS_V := $(shell dpkg-query --showformat='$${Version}' \ + --show binutils) +ifeq (yes,$(shell dpkg --compare-versions $(BINUTILS_V) lt 2.20-3 \ + && dpkg --compare-versions $(BINUTILS_V) ge 2.19.1-1 \ + && echo yes)) + ifneq (,$(strip $(findstring :$(DEB_HOST_ARCH):,:mips:mipsel:) \ + $(findstring :$(DEB_BUILD_ARCH):,:mips:mipsel:))) + BROKEN_PIE := yes + export BROKEN_PIE + endif +endif + configure: configure-stamp configure-stamp: dh_testdir === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2009-09-07 23:48:17 +0000 +++ initramfs-tools-hook 2009-10-22 00:05:01 +0000 @@ -114,7 +114,7 @@ fi # Config files -for file in /etc/mandos/*; do +for file in /etc/mandos/plugin-runner.conf; do if [ -d "$file" ]; then continue fi === modified file 'mandos' --- mandos 2009-09-21 21:39:25 +0000 +++ mandos 2009-12-25 23:13:47 +0000 @@ -55,10 +55,11 @@ import logging import logging.handlers import pwd -from contextlib import closing +import contextlib import struct import fcntl import functools +import cPickle as pickle import dbus import dbus.service @@ -79,7 +80,7 @@ SO_BINDTODEVICE = None -version = "1.0.12" +version = "1.0.14" logger = logging.Logger(u'mandos') syslogger = (logging.handlers.SysLogHandler @@ -242,7 +243,7 @@ enabled: bool() last_checked_ok: datetime.datetime(); (UTC) or None timeout: datetime.timedelta(); How long from last_checked_ok - until this client is invalid + until this client is disabled interval: datetime.timedelta(); How often to start a new checker disable_hook: If set, called by disable() as disable_hook(self) checker: subprocess.Popen(); a running checker process used @@ -290,9 +291,9 @@ if u"secret" in config: self.secret = config[u"secret"].decode(u"base64") elif u"secfile" in config: - with closing(open(os.path.expanduser - (os.path.expandvars - (config[u"secfile"])))) as secfile: + with open(os.path.expanduser(os.path.expandvars + (config[u"secfile"])), + "rb") as secfile: self.secret = secfile.read() else: raise TypeError(u"No secret or secfile for client %s" @@ -324,19 +325,20 @@ self.checker_initiator_tag = (gobject.timeout_add (self.interval_milliseconds(), self.start_checker)) - # Also start a new checker *right now*. - self.start_checker() # Schedule a disable() when 'timeout' has passed self.disable_initiator_tag = (gobject.timeout_add (self.timeout_milliseconds(), self.disable)) self.enabled = True + # Also start a new checker *right now*. + self.start_checker() - def disable(self): + def disable(self, quiet=True): """Disable this client.""" if not getattr(self, "enabled", False): return False - logger.info(u"Disabling client %s", self.name) + if not quiet: + logger.info(u"Disabling client %s", self.name) if getattr(self, u"disable_initiator_tag", False): gobject.source_remove(self.disable_initiator_tag) self.disable_initiator_tag = None @@ -394,12 +396,17 @@ # client would inevitably timeout, since no checker would get # a chance to run to completion. If we instead leave running # 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. + # than 'timeout' for the client to be disabled, which is as it + # should be. # If a checker exists, make sure it is not a zombie - if self.checker is not None: + try: pid, status = os.waitpid(self.checker.pid, os.WNOHANG) + except (AttributeError, OSError), error: + if (isinstance(error, OSError) + and error.errno != errno.ECHILD): + raise error + else: if pid: logger.warning(u"Checker was a zombie") gobject.source_remove(self.checker_callback_tag) @@ -461,23 +468,13 @@ logger.debug(u"Stopping checker for %(name)s", vars(self)) try: os.kill(self.checker.pid, signal.SIGTERM) - #os.sleep(0.5) + #time.sleep(0.5) #if self.checker.poll() is None: # os.kill(self.checker.pid, signal.SIGKILL) except OSError, error: if error.errno != errno.ESRCH: # No such process raise self.checker = None - - def still_valid(self): - """Has the timeout not yet passed for this client?""" - if not getattr(self, u"enabled", False): - return False - now = datetime.datetime.utcnow() - if self.last_checked_ok is None: - return now < (self.created + self.timeout) - else: - return now < (self.last_checked_ok + self.timeout) def dbus_service_property(dbus_interface, signature=u"v", @@ -492,6 +489,11 @@ dbus.service.method, except there is only "signature", since the type from Get() and the type sent to Set() is the same. """ + # Encoding deeply encoded byte arrays is not supported yet by the + # "Set" method, so we fail early here: + if byte_arrays and signature != u"ay": + raise ValueError(u"Byte arrays not supported for non-'ay'" + u" signature %r" % signature) def decorator(func): func._dbus_is_property = True func._dbus_interface = dbus_interface @@ -583,6 +585,10 @@ if prop._dbus_access == u"read": raise DBusPropertyAccessException(property_name) if prop._dbus_get_args_options[u"byte_arrays"]: + # The byte_arrays option is not supported yet on + # signatures other than "ay". + if prop._dbus_signature != u"ay": + raise ValueError value = dbus.ByteArray(''.join(unichr(byte) for byte in value)) prop(value) @@ -620,24 +626,43 @@ """Standard D-Bus method, overloaded to insert property tags. """ xmlstring = dbus.service.Object.Introspect(self, object_path, - connection) - document = xml.dom.minidom.parseString(xmlstring) - del xmlstring - def make_tag(document, name, prop): - e = document.createElement(u"property") - e.setAttribute(u"name", name) - e.setAttribute(u"type", prop._dbus_signature) - e.setAttribute(u"access", prop._dbus_access) - return e - for if_tag in document.getElementsByTagName(u"interface"): - for tag in (make_tag(document, name, prop) - for name, prop - in self._get_all_dbus_properties() - if prop._dbus_interface - == if_tag.getAttribute(u"name")): - if_tag.appendChild(tag) - xmlstring = document.toxml(u"utf-8") - document.unlink() + connection) + try: + document = xml.dom.minidom.parseString(xmlstring) + def make_tag(document, name, prop): + e = document.createElement(u"property") + e.setAttribute(u"name", name) + e.setAttribute(u"type", prop._dbus_signature) + e.setAttribute(u"access", prop._dbus_access) + return e + for if_tag in document.getElementsByTagName(u"interface"): + for tag in (make_tag(document, name, prop) + for name, prop + in self._get_all_dbus_properties() + if prop._dbus_interface + == if_tag.getAttribute(u"name")): + if_tag.appendChild(tag) + # Add the names to the return values for the + # "org.freedesktop.DBus.Properties" methods + if (if_tag.getAttribute(u"name") + == u"org.freedesktop.DBus.Properties"): + for cn in if_tag.getElementsByTagName(u"method"): + if cn.getAttribute(u"name") == u"Get": + for arg in cn.getElementsByTagName(u"arg"): + if (arg.getAttribute(u"direction") + == u"out"): + arg.setAttribute(u"name", u"value") + elif cn.getAttribute(u"name") == u"GetAll": + for arg in cn.getElementsByTagName(u"arg"): + if (arg.getAttribute(u"direction") + == u"out"): + arg.setAttribute(u"name", u"props") + xmlstring = document.toxml(u"utf-8") + document.unlink() + except (AttributeError, xml.dom.DOMException, + xml.parsers.expat.ExpatError), error: + logger.error(u"Failed to override Introspection method", + error) return xmlstring @@ -680,10 +705,10 @@ variant_level=1)) return r - def disable(self, signal = True): + def disable(self, quiet = False): oldstate = getattr(self, u"enabled", False) - r = Client.disable(self) - if signal and oldstate != self.enabled: + r = Client.disable(self, quiet=quiet) + if not quiet and oldstate != self.enabled: # Emit D-Bus signal self.PropertyChanged(dbus.String(u"enabled"), dbus.Boolean(False, variant_level=1)) @@ -758,6 +783,11 @@ ## D-Bus methods & signals _interface = u"se.bsnet.fukt.Mandos.Client" + # CheckedOK - method + @dbus.service.method(_interface) + def CheckedOK(self): + return self.checked_ok() + # CheckerCompleted - signal @dbus.service.signal(_interface, signature=u"nxs") def CheckerCompleted(self, exitcode, waitstatus, command): @@ -776,9 +806,9 @@ "D-Bus signal" pass - # ReceivedSecret - signal + # GotSecret - signal @dbus.service.signal(_interface) - def ReceivedSecret(self): + def GotSecret(self): "D-Bus signal" pass @@ -788,6 +818,29 @@ "D-Bus signal" pass + # Enable - method + @dbus.service.method(_interface) + def Enable(self): + "D-Bus method" + self.enable() + + # StartChecker - method + @dbus.service.method(_interface) + def StartChecker(self): + "D-Bus method" + self.start_checker() + + # Disable - method + @dbus.service.method(_interface) + def Disable(self): + "D-Bus method" + self.disable() + + # StopChecker - method + @dbus.service.method(_interface) + def StopChecker(self): + self.stop_checker() + # name - property @dbus_service_property(_interface, signature=u"s", access=u"read") def name_dbus_property(self): @@ -833,8 +886,12 @@ self.disable() # last_checked_ok - property - @dbus_service_property(_interface, signature=u"s", access=u"read") - def last_checked_ok_dbus_property(self): + @dbus_service_property(_interface, signature=u"s", + access=u"readwrite") + def last_checked_ok_dbus_property(self, value=None): + if value is not None: + self.checked_ok() + return if self.last_checked_ok is None: return dbus.String(u"") return dbus.String(self._datetime_to_dbus(self @@ -914,7 +971,7 @@ def object_path_dbus_property(self): return self.dbus_object_path # is already a dbus.ObjectPath - # secret = property xxx + # secret = property @dbus_service_property(_interface, signature=u"ay", access=u"write", byte_arrays=True) def secret_dbus_property(self, value): @@ -932,9 +989,13 @@ def handle(self): logger.info(u"TCP connection from: %s", unicode(self.client_address)) - logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1]) + logger.debug(u"IPC Pipe FD: %d", self.server.child_pipe[1]) # Open IPC pipe to parent process - with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc: + with contextlib.nested(os.fdopen(self.server.child_pipe[1], + u"w", 1), + os.fdopen(self.server.parent_pipe[0], + u"r", 0)) as (ipc, + ipc_return): session = (gnutls.connection .ClientSession(self.request, gnutls.connection @@ -975,38 +1036,40 @@ return logger.debug(u"Handshake succeeded") try: - fpr = self.fingerprint(self.peer_certificate(session)) - except (TypeError, gnutls.errors.GNUTLSError), error: - logger.warning(u"Bad certificate: %s", error) - session.bye() - return - logger.debug(u"Fingerprint: %s", fpr) - - for c in self.server.clients: - if c.fingerprint == fpr: - client = c - break - else: - ipc.write(u"NOTFOUND %s %s\n" - % (fpr, unicode(self.client_address))) - session.bye() - return - # Have to check if client.still_valid(), since it is - # possible that the client timed out while establishing - # the GnuTLS session. - if not client.still_valid(): - ipc.write(u"INVALID %s\n" % client.name) - session.bye() - return - ipc.write(u"SENDING %s\n" % client.name) - sent_size = 0 - while sent_size < len(client.secret): - sent = session.send(client.secret[sent_size:]) - logger.debug(u"Sent: %d, remaining: %d", - sent, len(client.secret) - - (sent_size + sent)) - sent_size += sent - session.bye() + try: + fpr = self.fingerprint(self.peer_certificate + (session)) + except (TypeError, gnutls.errors.GNUTLSError), error: + logger.warning(u"Bad certificate: %s", error) + return + logger.debug(u"Fingerprint: %s", fpr) + + for c in self.server.clients: + if c.fingerprint == fpr: + client = c + break + else: + ipc.write(u"NOTFOUND %s %s\n" + % (fpr, unicode(self.client_address))) + return + # Have to check if client.enabled, since it is + # possible that the client was disabled since the + # GnuTLS session was established. + ipc.write(u"GETATTR enabled %s\n" % fpr) + enabled = pickle.load(ipc_return) + if not enabled: + ipc.write(u"DISABLED %s\n" % client.name) + return + ipc.write(u"SENDING %s\n" % client.name) + sent_size = 0 + while sent_size < len(client.secret): + sent = session.send(client.secret[sent_size:]) + logger.debug(u"Sent: %d, remaining: %d", + sent, len(client.secret) + - (sent_size + sent)) + sent_size += sent + finally: + session.bye() @staticmethod def peer_certificate(session): @@ -1072,24 +1135,28 @@ return hex_fpr -class ForkingMixInWithPipe(socketserver.ForkingMixIn, object): - """Like socketserver.ForkingMixIn, but also pass a pipe.""" +class ForkingMixInWithPipes(socketserver.ForkingMixIn, object): + """Like socketserver.ForkingMixIn, but also pass a pipe pair.""" def process_request(self, request, client_address): """Overrides and wraps the original process_request(). This function creates a new pipe in self.pipe """ - self.pipe = os.pipe() - super(ForkingMixInWithPipe, + self.child_pipe = os.pipe() # Child writes here + self.parent_pipe = os.pipe() # Parent writes here + super(ForkingMixInWithPipes, self).process_request(request, client_address) - os.close(self.pipe[1]) # close write end - self.add_pipe(self.pipe[0]) - def add_pipe(self, pipe): + # Close unused ends for parent + os.close(self.parent_pipe[0]) # close read end + os.close(self.child_pipe[1]) # close write end + self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1]) + def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd): """Dummy function; override as necessary""" - os.close(pipe) - - -class IPv6_TCPServer(ForkingMixInWithPipe, + os.close(child_pipe_fd) + os.close(parent_pipe_fd) + + +class IPv6_TCPServer(ForkingMixInWithPipes, socketserver.TCPServer, object): """IPv6-capable TCP server. Accepts 'None' as address and/or port @@ -1180,11 +1247,15 @@ return socketserver.TCPServer.server_activate(self) def enable(self): self.enabled = True - def add_pipe(self, pipe): + def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd): # Call "handle_ipc" for both data and EOF events - gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP, - self.handle_ipc) - def handle_ipc(self, source, condition, file_objects={}): + gobject.io_add_watch(child_pipe_fd, + gobject.IO_IN | gobject.IO_HUP, + functools.partial(self.handle_ipc, + reply_fd + =parent_pipe_fd)) + def handle_ipc(self, source, condition, reply_fd=None, + file_objects={}): condition_names = { gobject.IO_IN: u"IN", # There is data to read. gobject.IO_OUT: u"OUT", # Data can be written (without @@ -1202,16 +1273,20 @@ logger.debug(u"Handling IPC: FD = %d, condition = %s", source, conditions_string) - # Turn the pipe file descriptor into a Python file object + # Turn the pipe file descriptors into Python file objects if source not in file_objects: file_objects[source] = os.fdopen(source, u"r", 1) + if reply_fd not in file_objects: + file_objects[reply_fd] = os.fdopen(reply_fd, u"w", 0) # Read a line from the file object cmdline = file_objects[source].readline() if not cmdline: # Empty line means end of file - # close the IPC pipe + # close the IPC pipes file_objects[source].close() del file_objects[source] + file_objects[reply_fd].close() + del file_objects[reply_fd] # Stop calling this function return False @@ -1222,21 +1297,22 @@ cmd, args = cmdline.rstrip(u"\r\n").split(None, 1) if cmd == u"NOTFOUND": - logger.warning(u"Client not found for fingerprint: %s", - args) + fpr, address = args.split(None, 1) + logger.warning(u"Client not found for fingerprint: %s, ad" + u"dress: %s", fpr, address) if self.use_dbus: # Emit D-Bus signal - mandos_dbus_service.ClientNotFound(args) - elif cmd == u"INVALID": + mandos_dbus_service.ClientNotFound(fpr, address) + elif cmd == u"DISABLED": for client in self.clients: if client.name == args: - logger.warning(u"Client %s is invalid", args) + logger.warning(u"Client %s is disabled", args) if self.use_dbus: # Emit D-Bus signal client.Rejected() break else: - logger.error(u"Unknown client %s is invalid", args) + logger.error(u"Unknown client %s is disabled", args) elif cmd == u"SENDING": for client in self.clients: if client.name == args: @@ -1244,11 +1320,24 @@ client.checked_ok() if self.use_dbus: # Emit D-Bus signal - client.ReceivedSecret() + client.GotSecret() break else: logger.error(u"Sending secret to unknown client %s", args) + elif cmd == u"GETATTR": + attr_name, fpr = args.split(None, 1) + for client in self.clients: + if client.fingerprint == fpr: + attr_value = getattr(client, attr_name, None) + logger.debug("IPC reply: %r", attr_value) + pickle.dump(attr_value, file_objects[reply_fd]) + break + else: + logger.error(u"Client %s on address %s requesting " + u"attribute %s not found", fpr, address, + attr_name) + pickle.dump(None, file_objects[reply_fd]) else: logger.error(u"Unknown IPC command: %r", cmdline) @@ -1288,9 +1377,9 @@ elif suffix == u"w": delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value) else: - raise ValueError - except (ValueError, IndexError): - raise ValueError + raise ValueError(u"Unknown suffix %r" % suffix) + except (ValueError, IndexError), e: + raise ValueError(e.message) timevalue += delta return timevalue @@ -1309,7 +1398,7 @@ def if_nametoindex(interface): "Get an interface index the hard way, i.e. using fcntl()" SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h - with closing(socket.socket()) as s: + with contextlib.closing(socket.socket()) as s: ifreq = fcntl.ioctl(s, SIOCGIFINDEX, struct.pack(str(u"16s16x"), interface)) @@ -1335,7 +1424,8 @@ null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR) if not stat.S_ISCHR(os.fstat(null).st_mode): raise OSError(errno.ENODEV, - u"/dev/null not a character device") + u"%s not a character device" + % os.path.devnull) os.dup2(null, sys.stdin.fileno()) os.dup2(null, sys.stdout.fileno()) os.dup2(null, sys.stderr.fileno()) @@ -1508,7 +1598,14 @@ bus = dbus.SystemBus() # End of Avahi example code if use_dbus: - bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus) + try: + bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", + bus, do_not_queue=True) + except dbus.exceptions.NameExistsException, e: + logger.error(unicode(e) + u", disabling D-Bus") + use_dbus = False + server_settings[u"use_dbus"] = False + tcp_server.use_dbus = False protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET service = AvahiService(name = server_settings[u"servicename"], servicetype = u"_mandos._tcp", @@ -1540,7 +1637,7 @@ daemon() try: - with closing(pidfile): + with pidfile: pid = os.getpid() pidfile.write(str(pid) + "\n") del pidfile @@ -1552,17 +1649,6 @@ pass del pidfilename - def cleanup(): - "Cleanup function; run on exit" - service.cleanup() - - while tcp_server.clients: - client = tcp_server.clients.pop() - client.disable_hook = None - client.disable() - - atexit.register(cleanup) - if not debug: signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit()) @@ -1575,13 +1661,13 @@ dbus.service.Object.__init__(self, bus, u"/") _interface = u"se.bsnet.fukt.Mandos" - @dbus.service.signal(_interface, signature=u"oa{sv}") - def ClientAdded(self, objpath, properties): + @dbus.service.signal(_interface, signature=u"o") + def ClientAdded(self, objpath): "D-Bus signal" pass - @dbus.service.signal(_interface, signature=u"s") - def ClientNotFound(self, fingerprint): + @dbus.service.signal(_interface, signature=u"ss") + def ClientNotFound(self, fingerprint, address): "D-Bus signal" pass @@ -1613,21 +1699,38 @@ tcp_server.clients.remove(c) c.remove_from_connection() # Don't signal anything except ClientRemoved - c.disable(signal=False) + c.disable(quiet=True) # Emit D-Bus signal self.ClientRemoved(object_path, c.name) return - raise KeyError + raise KeyError(object_path) del _interface mandos_dbus_service = MandosDBusService() + def cleanup(): + "Cleanup function; run on exit" + service.cleanup() + + while tcp_server.clients: + client = tcp_server.clients.pop() + if use_dbus: + client.remove_from_connection() + client.disable_hook = None + # Don't signal anything except ClientRemoved + client.disable(quiet=True) + if use_dbus: + # Emit D-Bus signal + mandos_dbus_service.ClientRemoved(client.dbus_object_path, + client.name) + + atexit.register(cleanup) + for client in tcp_server.clients: if use_dbus: # Emit D-Bus signal - mandos_dbus_service.ClientAdded(client.dbus_object_path, - client.GetAll(u"")) + mandos_dbus_service.ClientAdded(client.dbus_object_path) client.enable() tcp_server.enable() @@ -1651,6 +1754,7 @@ service.activate() except dbus.exceptions.DBusException, error: logger.critical(u"DBusException: %s", error) + cleanup() sys.exit(1) # End of Avahi example code @@ -1663,12 +1767,15 @@ main_loop.run() except AvahiError, error: logger.critical(u"AvahiError: %s", error) + cleanup() sys.exit(1) except KeyboardInterrupt: if debug: print >> sys.stderr logger.debug(u"Server received KeyboardInterrupt") logger.debug(u"Server exiting") + # 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 2009-09-17 01:21:27 +0000 +++ mandos-clients.conf.xml 2009-12-25 23:13:47 +0000 @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ /etc/mandos/clients.conf"> - + %common; ]> @@ -63,9 +63,8 @@ >mandos 8, read by it at startup. The file needs to list all clients that should be able to use - the service. All clients listed will be regarded as valid, even - if a client was declared invalid in a previous run of the - server. + the service. All clients listed will be regarded as enabled, + even if a client was disabled in a previous run of the server. The format starts with a [section @@ -110,9 +109,8 @@ The timeout is how long the server will wait (for either a successful checker run or a client receiving its secret) - until a client is considered invalid - that is, ineligible - to get the data this server holds. By default Mandos will - use 1 hour. + until a client is disabled and not allowed to get the data + this server holds. By default Mandos will use 1 hour. The TIME is specified as a @@ -143,8 +141,8 @@ not be started if an old one is still running. The server will wait for a checker to complete until the above timeout occurs, at which - time the client will be marked invalid, and any running - checker killed. The default interval is 5 minutes. + time the client will be disabled, and any running checker + killed. The default interval is 5 minutes. The format of TIME is the same === modified file 'mandos-ctl' --- mandos-ctl 2009-09-17 14:21:18 +0000 +++ mandos-ctl 2009-12-26 01:19:47 +0000 @@ -24,32 +24,33 @@ 'last_enabled': u'Last Enabled', 'checker': u'Checker', } -defaultkeywords = ('name', 'enabled', 'timeout', 'last_checked_ok', - 'checker') +defaultkeywords = ('name', 'enabled', 'timeout', 'last_checked_ok') domain = 'se.bsnet.fukt' busname = domain + '.Mandos' server_path = '/' server_interface = domain + '.Mandos' client_interface = domain + '.Mandos.Client' -version = "1.0.12" +version = "1.0.14" bus = dbus.SystemBus() mandos_dbus_objc = bus.get_object(busname, server_path) mandos_serv = dbus.Interface(mandos_dbus_objc, dbus_interface = server_interface) mandos_clients = mandos_serv.GetAllClientsWithProperties() -def datetime_to_milliseconds(dt): - "Return the 'timeout' attribute in milliseconds" - return ((dt.days * 24 * 60 * 60 * 1000) - + (dt.seconds * 1000) - + (dt.microseconds // 1000)) +def timedelta_to_milliseconds(td): + "Convert a datetime.timedelta object to milliseconds" + return ((td.days * 24 * 60 * 60 * 1000) + + (td.seconds * 1000) + + (td.microseconds // 1000)) def milliseconds_to_string(ms): td = datetime.timedelta(0, 0, 0, ms) - return "%s%02d:%02d:%02d" % (("%dT" % td.days) if td.days else "", # days - td.seconds // 3600, # hours - (td.seconds % 3600) // 60, # minutes - (td.seconds % 60)) # seconds + return (u"%(days)s%(hours)02d:%(minutes)02d:%(seconds)02d" + % { "days": "%dT" % td.days if td.days else "", + "hours": td.seconds // 3600, + "minutes": (td.seconds % 3600) // 60, + "seconds": td.seconds % 60, + }) def string_to_delta(interval): @@ -96,16 +97,18 @@ def valuetostring(value, keyword): if type(value) is dbus.Boolean: return u"Yes" if value else u"No" - if keyword in ("timeout", "interval"): + if keyword in (u"timeout", u"interval"): return milliseconds_to_string(value) return unicode(value) + # Create format string to print table rows format_string = u' '.join(u'%%-%ds' % max(len(tablewords[key]), max(len(valuetostring(client[key], key)) for client in clients)) for key in keywords) + # Print header line print format_string % tuple(tablewords[key] for key in keywords) for client in clients: print format_string % tuple(valuetostring(client[key], key) @@ -124,8 +127,8 @@ help="Start checker for client") parser.add_option("--stop-checker", action="store_true", help="Stop checker for client") -parser.add_option("-V", "--is-valid", action="store_true", - help="Check if client is still valid") +parser.add_option("-V", "--is-enabled", action="store_true", + help="Check if client is enabled") parser.add_option("-r", "--remove", action="store_true", help="Remove client") parser.add_option("-c", "--checker", type="string", @@ -146,9 +149,7 @@ for path, client in mandos_clients.iteritems(): if client['name'] == name: client_objc = bus.get_object(busname, path) - clients.append(dbus.Interface(client_objc, - dbus_interface - = client_interface)) + clients.append(client_objc) break else: print >> sys.stderr, "Client not found on server: %r" % name @@ -167,27 +168,37 @@ if options.remove: mandos_serv.RemoveClient(client.__dbus_object_path__) if options.enable: - client.Enable() + client.Enable(dbus_interface=client_interface) if options.disable: - client.Disable() + client.Disable(dbus_interface=client_interface) if options.bump_timeout: - client.BumpTimeout() + client.CheckedOK(dbus_interface=client_interface) if options.start_checker: - client.StartChecker() + client.StartChecker(dbus_interface=client_interface) if options.stop_checker: - client.StopChecker() - if options.is_valid: - sys.exit(0 if client.IsStillValid() else 1) + client.StopChecker(dbus_interface=client_interface) + if options.is_enabled: + sys.exit(0 if client.Get(client_interface, + u"enabled", + dbus_interface=dbus.PROPERTIES_IFACE) + else 1) if options.checker: - client.SetChecker(options.checker) + client.Set(client_interface, u"checker", options.checker, + dbus_interface=dbus.PROPERTIES_IFACE) if options.host: - client.SetHost(options.host) + client.Set(client_interface, u"host", options.host, + dbus_interface=dbus.PROPERTIES_IFACE) if options.interval: - client.SetInterval(datetime_to_milliseconds - (string_to_delta(options.interval))) + client.Set(client_interface, u"interval", + timedelta_to_milliseconds + (string_to_delta(options.interval)), + dbus_interface=dbus.PROPERTIES_IFACE) if options.timeout: - client.SetTimeout(datetime_to_milliseconds - (string_to_delta(options.timeout))) + client.Set(client_interface, u"timeout", + timedelta_to_milliseconds(string_to_delta + (options.timeout)), + dbus_interface=dbus.PROPERTIES_IFACE) if options.secret: - client.SetSecret(dbus.ByteArray(open(options.secret, 'rb').read())) - + client.Set(client_interface, u"secret", + dbus.ByteArray(open(options.secret, u'rb').read()), + dbus_interface=dbus.PROPERTIES_IFACE) === modified file 'mandos-keygen' --- mandos-keygen 2009-09-17 14:21:18 +0000 +++ mandos-keygen 2009-11-19 18:31:28 +0000 @@ -21,7 +21,7 @@ # Contact the authors at . # -VERSION="1.0.12" +VERSION="1.0.14" KEYDIR="/etc/keys/mandos" KEYTYPE=DSA @@ -217,12 +217,27 @@ %commit EOF + if tty --quiet; then + cat <<-EOF + Note: Due to entropy requirements, key generation could take + anything from a few minutes to SEVERAL HOURS. Please be + patient and/or supply the system with more entropy if needed. + EOF + echo -n "Started: " + date + fi + # Generate a new key in the key rings gpg --quiet --batch --no-tty --no-options --enable-dsa2 \ --homedir "$RINGDIR" --trust-model always \ --gen-key "$BATCHFILE" rm --force "$BATCHFILE" + if tty --quiet; then + echo -n "Finished: " + date + fi + # Backup any old key files if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \ 2>/dev/null; then === added file 'mandos-monitor' --- mandos-monitor 1970-01-01 00:00:00 +0000 +++ mandos-monitor 2009-12-25 23:13:47 +0000 @@ -0,0 +1,567 @@ +#!/usr/bin/python +# -*- mode: python; coding: utf-8 -*- + +from __future__ import division, absolute_import, with_statement + +import sys +import os +import signal + +import datetime + +import urwid.curses_display +import urwid + +from dbus.mainloop.glib import DBusGMainLoop +import gobject + +import dbus + +import UserList + +import locale + +locale.setlocale(locale.LC_ALL, u'') + +# Some useful constants +domain = 'se.bsnet.fukt' +server_interface = domain + '.Mandos' +client_interface = domain + '.Mandos.Client' +version = "1.0.14" + +# Always run in monochrome mode +urwid.curses_display.curses.has_colors = lambda : False + +# Urwid doesn't support blinking, but we want it. Since we have no +# use for underline on its own, we make underline also always blink. +urwid.curses_display.curses.A_UNDERLINE |= ( + urwid.curses_display.curses.A_BLINK) + +class MandosClientPropertyCache(object): + """This wraps a Mandos Client D-Bus proxy object, caches the + properties and calls a hook function when any of them are + changed. + """ + def __init__(self, proxy_object=None, *args, **kwargs): + self.proxy = proxy_object # Mandos Client proxy object + + self.properties = dict() + self.proxy.connect_to_signal(u"PropertyChanged", + self.property_changed, + client_interface, + byte_arrays=True) + + self.properties.update( + self.proxy.GetAll(client_interface, + dbus_interface = dbus.PROPERTIES_IFACE)) + super(MandosClientPropertyCache, self).__init__( + proxy_object=proxy_object, *args, **kwargs) + + def property_changed(self, property=None, value=None): + """This is called whenever we get a PropertyChanged signal + It updates the changed property in the "properties" dict. + """ + # Update properties dict with new value + self.properties[property] = value + + +class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache): + """A Mandos Client which is visible on the screen. + """ + + def __init__(self, server_proxy_object=None, update_hook=None, + delete_hook=None, logger=None, *args, **kwargs): + # Called on update + self.update_hook = update_hook + # Called on delete + self.delete_hook = delete_hook + # Mandos Server proxy object + self.server_proxy_object = server_proxy_object + # Logger + self.logger = logger + + # The widget shown normally + self._text_widget = urwid.Text(u"") + # The widget shown when we have focus + self._focus_text_widget = urwid.Text(u"") + super(MandosClientWidget, self).__init__( + update_hook=update_hook, delete_hook=delete_hook, + *args, **kwargs) + self.update() + self.opened = False + self.proxy.connect_to_signal(u"CheckerCompleted", + self.checker_completed, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"CheckerStarted", + self.checker_started, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"GotSecret", + self.got_secret, + client_interface, + byte_arrays=True) + self.proxy.connect_to_signal(u"Rejected", + self.rejected, + client_interface, + byte_arrays=True) + + def checker_completed(self, exitstatus, condition, command): + if exitstatus == 0: + self.logger(u'Checker for client %s (command "%s")' + u' was successful' + % (self.properties[u"name"], command)) + return + if os.WIFEXITED(condition): + self.logger(u'Checker for client %s (command "%s")' + u' failed with exit code %s' + % (self.properties[u"name"], command, + os.WEXITSTATUS(condition))) + return + if os.WIFSIGNALED(condition): + self.logger(u'Checker for client %s (command "%s")' + u' was killed by signal %s' + % (self.properties[u"name"], command, + os.WTERMSIG(condition))) + return + if os.WCOREDUMP(condition): + self.logger(u'Checker for client %s (command "%s")' + u' dumped core' + % (self.properties[u"name"], command)) + self.logger(u'Checker for client %s completed mysteriously') + + def checker_started(self, command): + self.logger(u'Client %s started checker "%s"' + % (self.properties[u"name"], unicode(command))) + + def got_secret(self): + self.logger(u'Client %s received its secret' + % self.properties[u"name"]) + + def rejected(self): + self.logger(u'Client %s was rejected' + % self.properties[u"name"]) + + def selectable(self): + """Make this a "selectable" widget. + This overrides the method from urwid.FlowWidget.""" + return True + + def rows(self, (maxcol,), focus=False): + """How many rows this widget will occupy might depend on + whether we have focus or not. + This overrides the method from urwid.FlowWidget""" + return self.current_widget(focus).rows((maxcol,), focus=focus) + + def current_widget(self, focus=False): + if focus or self.opened: + return self._focus_widget + return self._widget + + def update(self): + "Called when what is visible on the screen should be updated." + # How to add standout mode to a style + with_standout = { u"normal": u"standout", + u"bold": u"bold-standout", + u"underline-blink": + u"underline-blink-standout", + u"bold-underline-blink": + u"bold-underline-blink-standout", + } + + # Rebuild focus and non-focus widgets using current properties + self._text = (u'%(name)s: %(enabled)s' + % { u"name": self.properties[u"name"], + u"enabled": + (u"enabled" + if self.properties[u"enabled"] + else u"DISABLED")}) + if not urwid.supports_unicode(): + self._text = self._text.encode("ascii", "replace") + textlist = [(u"normal", self._text)] + self._text_widget.set_text(textlist) + self._focus_text_widget.set_text([(with_standout[text[0]], + text[1]) + if isinstance(text, tuple) + else text + for text in textlist]) + self._widget = self._text_widget + self._focus_widget = urwid.AttrWrap(self._focus_text_widget, + "standout") + # Run update hook, if any + if self.update_hook is not None: + self.update_hook() + + def delete(self): + if self.delete_hook is not None: + self.delete_hook(self) + + def render(self, (maxcol,), focus=False): + """Render differently if we have focus. + This overrides the method from urwid.FlowWidget""" + return self.current_widget(focus).render((maxcol,), + focus=focus) + + def keypress(self, (maxcol,), key): + """Handle keys. + This overrides the method from urwid.FlowWidget""" + if key == u"e" or key == u"+": + self.proxy.Enable() + elif key == u"d" or key == u"-": + self.proxy.Disable() + elif key == u"r" or key == u"_" or key == u"ctrl k": + self.server_proxy_object.RemoveClient(self.proxy + .object_path) + elif key == u"s": + self.proxy.StartChecker() + elif key == u"S": + self.proxy.StopChecker() + elif key == u"C": + self.proxy.CheckedOK() + # xxx +# elif key == u"p" or key == "=": +# self.proxy.pause() +# elif key == u"u" or key == ":": +# self.proxy.unpause() +# elif key == u"RET": +# self.open() + else: + return key + + def property_changed(self, property=None, value=None, + *args, **kwargs): + """Call self.update() if old value is not new value. + This overrides the method from MandosClientPropertyCache""" + property_name = unicode(property) + old_value = self.properties.get(property_name) + super(MandosClientWidget, self).property_changed( + property=property, value=value, *args, **kwargs) + if self.properties.get(property_name) != old_value: + self.update() + + +class ConstrainedListBox(urwid.ListBox): + """Like a normal urwid.ListBox, but will consume all "up" or + "down" key presses, thus not allowing any containing widgets to + use them as an excuse to shift focus away from this widget. + """ + def keypress(self, (maxcol, maxrow), key): + ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key) + if ret in (u"up", u"down"): + return + return ret + + +class UserInterface(object): + """This is the entire user interface - the whole screen + with boxes, lists of client widgets, etc. + """ + def __init__(self, max_log_length=1000): + DBusGMainLoop(set_as_default=True) + + self.screen = urwid.curses_display.Screen() + + self.screen.register_palette(( + (u"normal", + u"default", u"default", None), + (u"bold", + u"default", u"default", u"bold"), + (u"underline-blink", + u"default", u"default", u"underline"), + (u"standout", + u"default", u"default", u"standout"), + (u"bold-underline-blink", + u"default", u"default", (u"bold", u"underline")), + (u"bold-standout", + u"default", u"default", (u"bold", u"standout")), + (u"underline-blink-standout", + u"default", u"default", (u"underline", u"standout")), + (u"bold-underline-blink-standout", + u"default", u"default", (u"bold", u"underline", + u"standout")), + )) + + if urwid.supports_unicode(): + self.divider = u"─" # \u2500 + #self.divider = u"━" # \u2501 + else: + #self.divider = u"-" # \u002d + self.divider = u"_" # \u005f + + self.screen.start() + + self.size = self.screen.get_cols_rows() + + self.clients = urwid.SimpleListWalker([]) + self.clients_dict = {} + + # We will add Text widgets to this list + self.log = [] + self.max_log_length = max_log_length + + # We keep a reference to the log widget so we can remove it + # from the ListWalker without it getting destroyed + self.logbox = ConstrainedListBox(self.log) + + # This keeps track of whether self.uilist currently has + # self.logbox in it or not + self.log_visible = True + self.log_wrap = u"any" + + self.rebuild() + self.log_message_raw((u"bold", + u"Mandos Monitor version " + version)) + self.log_message_raw((u"bold", + u"q: Quit ?: Help")) + + self.busname = domain + '.Mandos' + self.main_loop = gobject.MainLoop() + self.bus = dbus.SystemBus() + mandos_dbus_objc = self.bus.get_object( + self.busname, u"/", follow_name_owner_changes=True) + self.mandos_serv = dbus.Interface(mandos_dbus_objc, + dbus_interface + = server_interface) + try: + mandos_clients = (self.mandos_serv + .GetAllClientsWithProperties()) + except dbus.exceptions.DBusException: + mandos_clients = dbus.Dictionary() + + (self.mandos_serv + .connect_to_signal(u"ClientRemoved", + self.find_and_remove_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal(u"ClientAdded", + self.add_new_client, + dbus_interface=server_interface, + byte_arrays=True)) + (self.mandos_serv + .connect_to_signal(u"ClientNotFound", + self.client_not_found, + dbus_interface=server_interface, + byte_arrays=True)) + for path, client in mandos_clients.iteritems(): + client_proxy_object = self.bus.get_object(self.busname, + path) + self.add_client(MandosClientWidget(server_proxy_object + =self.mandos_serv, + proxy_object + =client_proxy_object, + properties=client, + update_hook + =self.refresh, + delete_hook + =self.remove_client, + logger + =self.log_message), + path=path) + + def client_not_found(self, fingerprint, address): + self.log_message((u"Client with address %s and fingerprint %s" + u" could not be found" % (address, + fingerprint))) + + def rebuild(self): + """This rebuilds the User Interface. + Call this when the widget layout needs to change""" + self.uilist = [] + #self.uilist.append(urwid.ListBox(self.clients)) + self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients), + #header=urwid.Divider(), + header=None, + footer=urwid.Divider(div_char=self.divider))) + if self.log_visible: + self.uilist.append(self.logbox) + pass + self.topwidget = urwid.Pile(self.uilist) + + def log_message(self, message): + timestamp = datetime.datetime.now().isoformat() + self.log_message_raw(timestamp + u": " + message) + + def log_message_raw(self, markup): + """Add a log message to the log buffer.""" + self.log.append(urwid.Text(markup, wrap=self.log_wrap)) + if (self.max_log_length + and len(self.log) > self.max_log_length): + del self.log[0:len(self.log)-self.max_log_length-1] + self.logbox.set_focus(len(self.logbox.body.contents), + coming_from=u"above") + self.refresh() + + def toggle_log_display(self): + """Toggle visibility of the log buffer.""" + self.log_visible = not self.log_visible + self.rebuild() + self.log_message(u"Log visibility changed to: " + + unicode(self.log_visible)) + + def change_log_display(self): + """Change type of log display. + Currently, this toggles wrapping of text lines.""" + if self.log_wrap == u"clip": + self.log_wrap = u"any" + else: + self.log_wrap = u"clip" + for textwidget in self.log: + textwidget.set_wrap_mode(self.log_wrap) + self.log_message(u"Wrap mode: " + self.log_wrap) + + def find_and_remove_client(self, path, name): + """Find an client from its object path and remove it. + + This is connected to the ClientRemoved signal from the + Mandos server object.""" + try: + client = self.clients_dict[path] + except KeyError: + # not found? + return + self.remove_client(client, path) + + def add_new_client(self, path): + client_proxy_object = self.bus.get_object(self.busname, path) + self.add_client(MandosClientWidget(server_proxy_object + =self.mandos_serv, + proxy_object + =client_proxy_object, + update_hook + =self.refresh, + delete_hook + =self.remove_client, + logger + =self.log_message), + path=path) + + def add_client(self, client, path=None): + self.clients.append(client) + if path is None: + path = client.proxy.object_path + self.clients_dict[path] = client + self.clients.sort(None, lambda c: c.properties[u"name"]) + self.refresh() + + def remove_client(self, client, path=None): + self.clients.remove(client) + if path is None: + path = client.proxy.object_path + del self.clients_dict[path] + if not self.clients_dict: + # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker + # is completely emptied, we need to recreate it. + self.clients = urwid.SimpleListWalker([]) + self.rebuild() + self.refresh() + + def refresh(self): + """Redraw the screen""" + canvas = self.topwidget.render(self.size, focus=True) + self.screen.draw_screen(self.size, canvas) + + def run(self): + """Start the main loop and exit when it's done.""" + self.refresh() + self._input_callback_tag = (gobject.io_add_watch + (sys.stdin.fileno(), + gobject.IO_IN, + self.process_input)) + self.main_loop.run() + # Main loop has finished, we should close everything now + gobject.source_remove(self._input_callback_tag) + self.screen.stop() + + def stop(self): + self.main_loop.quit() + + def process_input(self, source, condition): + keys = self.screen.get_input() + translations = { u"ctrl n": u"down", # Emacs + u"ctrl p": u"up", # Emacs + u"ctrl v": u"page down", # Emacs + u"meta v": u"page up", # Emacs + u" ": u"page down", # less + u"f": u"page down", # less + u"b": u"page up", # less + u"j": u"down", # vi + u"k": u"up", # vi + } + for key in keys: + try: + key = translations[key] + except KeyError: # :-) + pass + + if key == u"q" or key == u"Q": + self.stop() + break + elif key == u"window resize": + self.size = self.screen.get_cols_rows() + self.refresh() + elif key == u"\f": # Ctrl-L + self.refresh() + elif key == u"l" or key == u"D": + self.toggle_log_display() + self.refresh() + elif key == u"w" or key == u"i": + self.change_log_display() + self.refresh() + elif key == u"?" or key == u"f1" or key == u"esc": + if not self.log_visible: + self.log_visible = True + self.rebuild() + self.log_message_raw((u"bold", + u" ". + join((u"q: Quit", + u"?: Help", + u"l: Log window toggle", + u"TAB: Switch window", + u"w: Wrap (log)")))) + self.log_message_raw((u"bold", + u" " + .join((u"Clients:", + u"e: Enable", + u"d: Disable", + u"r: Remove", + u"s: Start new checker", + u"S: Stop checker", + u"C: Checker OK")))) + self.refresh() + elif key == u"tab": + if self.topwidget.get_focus() is self.logbox: + self.topwidget.set_focus(0) + else: + self.topwidget.set_focus(self.logbox) + self.refresh() + #elif (key == u"end" or key == u"meta >" or key == u"G" + # or key == u">"): + # pass # xxx end-of-buffer + #elif (key == u"home" or key == u"meta <" or key == u"g" + # or key == u"<"): + # pass # xxx beginning-of-buffer + #elif key == u"ctrl e" or key == u"$": + # pass # xxx move-end-of-line + #elif key == u"ctrl a" or key == u"^": + # pass # xxx move-beginning-of-line + #elif key == u"ctrl b" or key == u"meta (" or key == u"h": + # pass # xxx left + #elif key == u"ctrl f" or key == u"meta )" or key == u"l": + # pass # xxx right + #elif key == u"a": + # pass # scroll up log + #elif key == u"z": + # pass # scroll down log + elif self.topwidget.selectable(): + self.topwidget.keypress(self.size, key) + self.refresh() + return True + +ui = UserInterface() +try: + ui.run() +except Exception, e: + ui.log_message(unicode(e)) + ui.screen.stop() + raise === modified file 'mandos.lsm' --- mandos.lsm 2009-09-17 14:21:18 +0000 +++ mandos.lsm 2009-10-26 21:16:16 +0000 @@ -1,7 +1,7 @@ Begin4 Title: Mandos -Version: 1.0.12 -Entered-date: 2009-09-17 +Version: 1.0.14 +Entered-date: 2009-10-25 Description: The Mandos system allows computers to have encrypted root file systems and at the same time be capable of remote and/or unattended reboots. @@ -12,9 +12,9 @@ Maintained-by: teddy@fukt.bsnet.se (Teddy Hogeborn), belorn@fukt.bsnet.se (Björn Påhlsson) Primary-site: http://www.fukt.bsnet.se/mandos - 103K mandos_1.0.12.orig.tar.gz + 103K mandos_1.0.14.orig.tar.gz Alternate-site: ftp://ftp.fukt.bsnet.se/pub/mandos - 103K mandos_1.0.12.orig.tar.gz + 103K mandos_1.0.14.orig.tar.gz Platforms: Requires GCC, GNU libC, Avahi, GnuPG, Python 2.5, and various other libraries. While made for Debian GNU/Linux, it is probably portable to other distributions, but not other Unixes. === modified file 'mandos.xml' --- mandos.xml 2009-09-17 01:21:27 +0000 +++ mandos.xml 2009-12-25 23:13:47 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -453,10 +453,9 @@ backtrace. This could be considered a feature. - Currently, if a client is declared invalid due to - having timed out, the server does not record this fact onto - permanent storage. This has some security implications, see - . + 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 currently no way of querying the server of the current @@ -549,19 +548,18 @@ If a client is compromised, its downtime should be duly noted - by the server which would therefore declare the client - invalid. 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 valid, 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. + 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 === modified file 'plugin-runner.c' --- plugin-runner.c 2009-09-21 13:51:11 +0000 +++ plugin-runner.c 2010-03-27 18:39:02 +0000 @@ -23,14 +23,14 @@ */ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), - asprintf() */ + asprintf(), O_CLOEXEC */ #include /* size_t, NULL */ -#include /* malloc(), exit(), EXIT_FAILURE, - EXIT_SUCCESS, realloc() */ +#include /* malloc(), exit(), EXIT_SUCCESS, + realloc() */ #include /* bool, true, false */ #include /* perror, fileno(), fprintf(), stderr, STDOUT_FILENO */ -#include /* DIR, opendir(), stat(), struct +#include /* DIR, fdopendir(), stat(), struct stat, waitpid(), WIFEXITED(), WEXITSTATUS(), wait(), pid_t, uid_t, gid_t, getuid(), getgid(), @@ -42,7 +42,7 @@ WCOREDUMP() */ #include /* struct stat, stat(), S_ISREG() */ #include /* and, or, not */ -#include /* DIR, struct dirent, opendir(), +#include /* DIR, struct dirent, fdopendir(), readdir(), closedir(), dirfd() */ #include /* struct stat, stat(), S_ISREG(), fcntl(), setuid(), setgid(), @@ -68,6 +68,8 @@ */ #include /* errno, EBADF */ #include /* intmax_t, PRIdMAX, strtoimax() */ +#include /* EX_OSERR, EX_USAGE, EX_IOERR, + EX_CONFIG, EX_UNAVAILABLE, EX_OK */ #define BUFFER_SIZE 256 @@ -102,7 +104,7 @@ /* Gets an existing plugin based on name, or if none is found, creates a new one */ static plugin *getplugin(char *name){ - /* Check for exiting plugin with that name */ + /* Check for existing plugin with that name */ for(plugin *p = plugin_list; p != NULL; p = p->next){ if((p->name == name) or (p->name and name and (strcmp(p->name, name) == 0))){ @@ -123,7 +125,9 @@ copy_name = strdup(name); } while(copy_name == NULL and errno == EINTR); if(copy_name == NULL){ + int e = errno; free(new_plugin); + errno = e; return NULL; } } @@ -137,8 +141,10 @@ new_plugin->argv = malloc(sizeof(char *) * 2); } while(new_plugin->argv == NULL and errno == EINTR); if(new_plugin->argv == NULL){ + int e = errno; free(copy_name); free(new_plugin); + errno = e; return NULL; } new_plugin->argv[0] = copy_name; @@ -148,9 +154,11 @@ new_plugin->environ = malloc(sizeof(char *)); } while(new_plugin->environ == NULL and errno == EINTR); if(new_plugin->environ == NULL){ + int e = errno; free(copy_name); free(new_plugin->argv); free(new_plugin); + errno = e; return NULL; } new_plugin->environ[0] = NULL; @@ -350,13 +358,13 @@ ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD); if(ret == -1){ perror("sigaddset"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action); if(ret == -1){ perror("sigaction"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } @@ -394,119 +402,137 @@ .doc = "Group ID the plugins will run as", .group = 3 }, { .name = "debug", .key = 132, .doc = "Debug mode", .group = 4 }, + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + { .name = "help", .key = '?', + .doc = "Give this help list", .group = -1 }, + { .name = "usage", .key = -3, + .doc = "Give a short usage message", .group = -1 }, + { .name = "version", .key = 'V', + .doc = "Print program version", .group = -1 }, { .name = NULL } }; - error_t parse_opt(int key, char *arg, __attribute__((unused)) - struct argp_state *state){ + error_t parse_opt(int key, char *arg, struct argp_state *state){ + errno = 0; switch(key){ char *tmp; intmax_t tmpmax; case 'g': /* --global-options */ - if(arg != NULL){ + { char *plugin_option; while((plugin_option = strsep(&arg, ",")) != NULL){ - if(plugin_option[0] == '\0'){ - continue; - } if(not add_argument(getplugin(NULL), plugin_option)){ - perror("add_argument"); - return ARGP_ERR_UNKNOWN; + break; } } } break; case 'G': /* --global-env */ - if(arg == NULL){ - break; - } - if(not add_environment(getplugin(NULL), arg, true)){ - perror("add_environment"); - } + add_environment(getplugin(NULL), arg, true); break; case 'o': /* --options-for */ - if(arg != NULL){ - 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; + { + char *option_list = strchr(arg, ':'); + if(option_list == NULL){ + argp_error(state, "No colon in \"%s\"", arg); + errno = EINVAL; + break; + } + *option_list = '\0'; + option_list++; + if(arg[0] == '\0'){ + argp_error(state, "Empty plugin name"); + errno = EINVAL; + break; + } + char *option; + while((option = strsep(&option_list, ",")) != NULL){ + if(not add_argument(getplugin(arg), option)){ + break; } } } break; case 'E': /* --env-for */ - if(arg == NULL){ - break; - } { char *envdef = strchr(arg, ':'); if(envdef == NULL){ + argp_error(state, "No colon in \"%s\"", arg); + errno = EINVAL; break; } *envdef = '\0'; - if(not add_environment(getplugin(arg), envdef+1, true)){ - perror("add_environment"); + envdef++; + if(arg[0] == '\0'){ + argp_error(state, "Empty plugin name"); + errno = EINVAL; + break; } + add_environment(getplugin(arg), envdef, true); } break; case 'd': /* --disable */ - if(arg != NULL){ + { plugin *p = getplugin(arg); - if(p == NULL){ - return ARGP_ERR_UNKNOWN; + if(p != NULL){ + p->disabled = true; } - p->disabled = true; } break; case 'e': /* --enable */ - if(arg != NULL){ + { plugin *p = getplugin(arg); - if(p == NULL){ - return ARGP_ERR_UNKNOWN; + if(p != NULL){ + p->disabled = false; } - p->disabled = false; } break; case 128: /* --plugin-dir */ free(plugindir); plugindir = strdup(arg); - if(plugindir == NULL){ - perror("strdup"); - } break; case 129: /* --config-file */ /* This is already done by parse_opt_config_file() */ break; case 130: /* --userid */ - 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 { - uid = (uid_t)tmpmax; + argp_error(state, "Bad user ID number: \"%s\", using %" + PRIdMAX, arg, (intmax_t)uid); + break; } + uid = (uid_t)tmpmax; break; case 131: /* --groupid */ - 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 { - gid = (gid_t)tmpmax; + argp_error(state, "Bad group ID number: \"%s\", using %" + PRIdMAX, arg, (intmax_t)gid); + break; } + gid = (gid_t)tmpmax; break; case 132: /* --debug */ debug = true; break; + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + case '?': /* --help */ + state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ + argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); + case -3: /* --usage */ + state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */ + argp_state_help(state, state->out_stream, + ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK); + case 'V': /* --version */ + fprintf(state->out_stream, "%s\n", argp_program_version); + exit(EXIT_SUCCESS); + break; /* * When adding more options before this line, remember to also add a * "case" to the "parse_opt_config_file" function below. @@ -515,16 +541,13 @@ /* Cryptsetup always passes an argument, which is an empty string if "none" was specified in /etc/crypttab. So if argument was empty, we ignore it silently. */ - if(arg[0] != '\0'){ - fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg); + if(arg[0] == '\0'){ + break; } - break; - case ARGP_KEY_END: - break; default: return ARGP_ERR_UNKNOWN; } - return 0; + return errno; /* Set to 0 at start */ } /* This option parser is the same as parse_opt() above, except it @@ -532,6 +555,7 @@ error_t parse_opt_config_file(int key, char *arg, __attribute__((unused)) struct argp_state *state){ + errno = 0; switch(key){ case 'g': /* --global-options */ case 'G': /* --global-env */ @@ -544,20 +568,19 @@ case 129: /* --config-file */ free(argfile); argfile = strdup(arg); - if(argfile == NULL){ - perror("strdup"); - } break; case 130: /* --userid */ case 131: /* --groupid */ case 132: /* --debug */ + case '?': /* --help */ + case -3: /* --usage */ + case 'V': /* --version */ case ARGP_KEY_ARG: - case ARGP_KEY_END: break; default: return ARGP_ERR_UNKNOWN; } - return 0; + return errno; } struct argp argp = { .options = options, @@ -567,10 +590,20 @@ /* Parse using parse_opt_config_file() in order to get the custom config file location, if any. */ - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL); - if(ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - exitstatus = EXIT_FAILURE; + ret = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, + NULL, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror("argp_parse"); + exitstatus = EX_OSERR; + goto fallback; + case EINVAL: + exitstatus = EX_USAGE; goto fallback; } @@ -594,7 +627,7 @@ custom_argv = malloc(sizeof(char*) * 2); if(custom_argv == NULL){ perror("malloc"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } custom_argv[0] = argv[0]; @@ -617,7 +650,7 @@ new_arg = strdup(p); if(new_arg == NULL){ perror("strdup"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; free(org_line); goto fallback; } @@ -627,7 +660,7 @@ * ((unsigned int) custom_argc + 1)); if(custom_argv == NULL){ perror("realloc"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; free(org_line); goto fallback; } @@ -640,7 +673,7 @@ } while(ret == EOF and errno == EINTR); if(ret == EOF){ perror("fclose"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_IOERR; goto fallback; } free(org_line); @@ -649,28 +682,47 @@ not affect opening plugins */ if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){ perror("fopen"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } } - /* If there was any arguments from configuration file, - pass them to parser as command arguments */ + /* If there were any arguments from the configuration file, pass + them to parser as command line arguments */ if(custom_argv != NULL){ - ret = argp_parse(&argp, custom_argc, custom_argv, ARGP_IN_ORDER, - 0, NULL); - if(ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - exitstatus = EXIT_FAILURE; + ret = argp_parse(&argp, custom_argc, custom_argv, + ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, + NULL, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror("argp_parse"); + exitstatus = EX_OSERR; + goto fallback; + case EINVAL: + exitstatus = EX_CONFIG; goto fallback; } } /* Parse actual command line arguments, to let them override the config file */ - ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL); - if(ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - exitstatus = EXIT_FAILURE; + ret = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP, + NULL, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror("argp_parse"); + exitstatus = EX_OSERR; + goto fallback; + case EINVAL: + exitstatus = EX_USAGE; goto fallback; } @@ -698,28 +750,49 @@ perror("setuid"); } - if(plugindir == NULL){ - dir = opendir(PDIR); - } else { - dir = opendir(plugindir); - } - - if(dir == NULL){ - perror("Could not open plugin dir"); - exitstatus = EXIT_FAILURE; - goto fallback; - } - - /* Set the FD_CLOEXEC flag on the directory, if possible */ + /* Open plugin directory with close_on_exec flag */ { - int dir_fd = dirfd(dir); - if(dir_fd >= 0){ - ret = set_cloexec_flag(dir_fd); - if(ret < 0){ - perror("set_cloexec_flag"); - exitstatus = EXIT_FAILURE; - goto fallback; - } + int dir_fd = -1; + if(plugindir == NULL){ + dir_fd = open(PDIR, O_RDONLY | +#ifdef O_CLOEXEC + O_CLOEXEC +#else /* not O_CLOEXEC */ + 0 +#endif /* not O_CLOEXEC */ + ); + } else { + dir_fd = open(plugindir, O_RDONLY | +#ifdef O_CLOEXEC + O_CLOEXEC +#else /* not O_CLOEXEC */ + 0 +#endif /* not O_CLOEXEC */ + ); + } + if(dir_fd == -1){ + perror("Could not open plugin dir"); + exitstatus = EX_UNAVAILABLE; + goto fallback; + } + +#ifndef O_CLOEXEC + /* Set the FD_CLOEXEC flag on the directory */ + ret = set_cloexec_flag(dir_fd); + if(ret < 0){ + perror("set_cloexec_flag"); + TEMP_FAILURE_RETRY(close(dir_fd)); + exitstatus = EX_OSERR; + goto fallback; + } +#endif /* O_CLOEXEC */ + + dir = fdopendir(dir_fd); + if(dir == NULL){ + perror("Could not open plugin dir"); + TEMP_FAILURE_RETRY(close(dir_fd)); + exitstatus = EX_OSERR; + goto fallback; } } @@ -735,7 +808,7 @@ if(dirst == NULL){ if(errno == EBADF){ perror("readdir"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_IOERR; goto fallback; } break; @@ -865,20 +938,20 @@ ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd)); if(ret == -1){ perror("pipe"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } /* Ask OS to automatic close the pipe on exec */ ret = set_cloexec_flag(pipefd[0]); if(ret < 0){ perror("set_cloexec_flag"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } ret = set_cloexec_flag(pipefd[1]); if(ret < 0){ perror("set_cloexec_flag"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } /* Block SIGCHLD until process is safely in process list */ @@ -887,7 +960,7 @@ NULL)); if(ret < 0){ perror("sigprocmask"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } /* Starting a new process to be watched */ @@ -897,7 +970,7 @@ } while(pid == -1 and errno == EINTR); if(pid == -1){ perror("fork"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } if(pid == 0){ @@ -905,18 +978,18 @@ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret < 0){ perror("sigaction"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL); if(ret < 0){ perror("sigprocmask"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ perror("dup2"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } if(dirfd(dir) < 0){ @@ -927,12 +1000,12 @@ if(p->environ[0] == NULL){ if(execv(filename, p->argv) < 0){ perror("execv"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } } else { if(execve(filename, p->argv, p->environ) < 0){ perror("execve"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } } /* no return */ @@ -950,7 +1023,7 @@ if(ret < 0){ perror("sigprocmask"); } - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } @@ -964,7 +1037,7 @@ NULL)); if(ret < 0){ perror("sigprocmask"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } @@ -997,7 +1070,7 @@ int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL); if(select_ret == -1 and errno != EINTR){ perror("select"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } /* OK, now either a process completed, or something can be read @@ -1039,7 +1112,7 @@ NULL)); if(ret < 0){ perror("sigprocmask"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } @@ -1053,7 +1126,7 @@ &sigchld_action.sa_mask, NULL))); if(ret < 0){ perror("sigprocmask"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } @@ -1070,7 +1143,7 @@ proc->buffer_length); if(not bret){ perror("print_out_password"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_IOERR; } goto fallback; } @@ -1089,7 +1162,7 @@ + (size_t) BUFFER_SIZE); if(proc->buffer == NULL){ perror("malloc"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; goto fallback; } proc->buffer_size += BUFFER_SIZE; @@ -1116,7 +1189,8 @@ fallback: - if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){ + if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS + and exitstatus != EX_OK)){ /* Fallback if all plugins failed, none are found or an error occured */ bool bret; @@ -1131,7 +1205,7 @@ bret = print_out_password(passwordbuffer, len); if(not bret){ perror("print_out_password"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_IOERR; } } @@ -1139,7 +1213,7 @@ ret = sigaction(SIGCHLD, &old_sigchld_action, NULL); if(ret == -1){ perror("sigaction"); - exitstatus = EXIT_FAILURE; + exitstatus = EX_OSERR; } if(custom_argv != NULL){ === modified file 'plugins.d/askpass-fifo.c' --- plugins.d/askpass-fifo.c 2009-09-16 23:28:39 +0000 +++ plugins.d/askpass-fifo.c 2009-10-18 16:09:05 +0000 @@ -26,13 +26,19 @@ #include /* ssize_t */ #include /* mkfifo(), S_IRUSR, S_IWUSR */ #include /* and */ -#include /* errno, EEXIST */ +#include /* errno, EACCES, ENOTDIR, ELOOP, + ENAMETOOLONG, ENOSPC, EROFS, + ENOENT, EEXIST, EFAULT, EMFILE, + ENFILE, ENOMEM, EBADF, EINVAL, EIO, + EISDIR, EFBIG */ #include /* perror() */ #include /* EXIT_FAILURE, NULL, size_t, free(), realloc(), EXIT_SUCCESS */ #include /* open(), O_RDONLY */ #include /* read(), close(), write(), STDOUT_FILENO */ +#include /* EX_OSERR, EX_OSFILE, + EX_UNAVAILABLE, EX_IOERR */ int main(__attribute__((unused))int argc, @@ -43,16 +49,46 @@ /* Create FIFO */ const char passfifo[] = "/lib/cryptsetup/passfifo"; ret = mkfifo(passfifo, S_IRUSR | S_IWUSR); - if(ret == -1 and errno != EEXIST){ + if(ret == -1){ + int e = errno; perror("mkfifo"); - return EXIT_FAILURE; + switch(e){ + case EACCES: + case ENOTDIR: + case ELOOP: + return EX_OSFILE; + case ENAMETOOLONG: + case ENOSPC: + case EROFS: + default: + return EX_OSERR; + case ENOENT: + return EX_UNAVAILABLE; /* no "/lib/cryptsetup"? */ + case EEXIST: + break; /* not an error */ + } } /* Open FIFO */ int fifo_fd = open(passfifo, O_RDONLY); if(fifo_fd == -1){ + int e = errno; perror("open"); - return EXIT_FAILURE; + switch(e){ + case EACCES: + case ENOENT: + case EFAULT: + return EX_UNAVAILABLE; + case ENAMETOOLONG: + case EMFILE: + case ENFILE: + case ENOMEM: + default: + return EX_OSERR; + case ENOTDIR: + case ELOOP: + return EX_OSFILE; + } } /* Read from FIFO */ @@ -67,16 +103,28 @@ if(tmp == NULL){ perror("realloc"); free(buf); - return EXIT_FAILURE; + return EX_OSERR; } buf = tmp; buf_allocated += blocksize; } sret = read(fifo_fd, buf + buf_len, buf_allocated - buf_len); if(sret == -1){ + int e = errno; + free(buf); + errno = e; perror("read"); - free(buf); - return EXIT_FAILURE; + switch(e){ + case EBADF: + case EFAULT: + case EINVAL: + default: + return EX_OSERR; + case EIO: + return EX_IOERR; + case EISDIR: + return EX_UNAVAILABLE; + } } buf_len += (size_t)sret; } while(sret != 0); @@ -90,13 +138,37 @@ while(written < buf_len){ sret = write(STDOUT_FILENO, buf + written, buf_len - written); if(sret == -1){ + int e = errno; + free(buf); + errno = e; perror("write"); - free(buf); - return EXIT_FAILURE; + switch(e){ + case EBADF: + case EFAULT: + case EINVAL: + return EX_OSFILE; + case EFBIG: + case EIO: + case ENOSPC: + default: + return EX_IOERR; + } } written += (size_t)sret; } free(buf); + ret = close(STDOUT_FILENO); + if(ret == -1){ + int e = errno; + perror("close"); + switch(e){ + case EBADF: + return EX_OSFILE; + case EIO: + default: + return EX_IOERR; + } + } return EXIT_SUCCESS; } === modified file 'plugins.d/mandos-client.c' --- plugins.d/mandos-client.c 2009-09-17 11:22:28 +0000 +++ plugins.d/mandos-client.c 2009-11-01 00:10:28 +0000 @@ -43,8 +43,8 @@ stdout, ferror(), remove() */ #include /* uint16_t, uint32_t */ #include /* NULL, size_t, ssize_t */ -#include /* free(), EXIT_SUCCESS, EXIT_FAILURE, - srand(), strtof(), abort() */ +#include /* free(), EXIT_SUCCESS, srand(), + strtof(), abort() */ #include /* bool, false, true */ #include /* memset(), strcmp(), strlen(), strerror(), asprintf(), strcpy() */ @@ -82,6 +82,8 @@ #include /* sigemptyset(), sigaddset(), sigaction(), SIGTERM, sig_atomic_t, raise() */ +#include /* EX_OSERR, EX_USAGE, EX_UNAVAILABLE, + EX_NOHOST, EX_IOERR, EX_PROTOCOL */ #ifdef __linux__ #include /* klogctl() */ @@ -552,7 +554,10 @@ gnutls_session_t session; int pf; /* Protocol family */ + errno = 0; + if(quit_now){ + errno = EINTR; return -1; } @@ -565,6 +570,7 @@ break; default: fprintf(stderr, "Bad address family: %d\n", af); + errno = EINVAL; return -1; } @@ -580,11 +586,14 @@ tcp_sd = socket(pf, SOCK_STREAM, 0); if(tcp_sd < 0){ + int e = errno; perror("socket"); + errno = e; goto mandos_end; } if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -597,11 +606,15 @@ ret = inet_pton(af, ip, &to.in.sin_addr); } if(ret < 0 ){ + int e = errno; perror("inet_pton"); + errno = e; goto mandos_end; } if(ret == 0){ + int e = errno; fprintf(stderr, "Bad address: %s\n", ip); + errno = e; goto mandos_end; } if(af == AF_INET6){ @@ -615,6 +628,7 @@ if(if_index == AVAHI_IF_UNSPEC){ fprintf(stderr, "An IPv6 link-local address is incomplete" " without a network interface\n"); + errno = EINVAL; goto mandos_end; } /* Set the network interface number as scope */ @@ -627,6 +641,7 @@ } if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -663,6 +678,7 @@ } if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -672,11 +688,14 @@ ret = connect(tcp_sd, &to.in, sizeof(to)); /* IPv4 */ } if(ret < 0){ + int e = errno; perror("connect"); + errno = e; goto mandos_end; } if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -687,7 +706,9 @@ ret = (int)TEMP_FAILURE_RETRY(write(tcp_sd, out + written, out_size - written)); if(ret == -1){ + int e = errno; perror("write"); + errno = e; goto mandos_end; } written += (size_t)ret; @@ -703,6 +724,7 @@ } if(quit_now){ + errno = EINTR; goto mandos_end; } } @@ -712,18 +734,21 @@ } if(quit_now){ + errno = EINTR; goto mandos_end; } gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) tcp_sd); if(quit_now){ + errno = EINTR; goto mandos_end; } do { ret = gnutls_handshake(session); if(quit_now){ + errno = EINTR; goto mandos_end; } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); @@ -733,6 +758,7 @@ fprintf(stderr, "*** GnuTLS Handshake failed ***\n"); gnutls_perror(ret); } + errno = EPROTO; goto mandos_end; } @@ -746,17 +772,21 @@ while(true){ if(quit_now){ + errno = EINTR; goto mandos_end; } buffer_capacity = incbuffer(&buffer, buffer_length, buffer_capacity); if(buffer_capacity == 0){ + int e = errno; perror("incbuffer"); + errno = e; goto mandos_end; } if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -775,12 +805,14 @@ ret = gnutls_handshake(session); if(quit_now){ + errno = EINTR; goto mandos_end; } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); if(ret < 0){ fprintf(stderr, "*** GnuTLS Re-handshake failed ***\n"); gnutls_perror(ret); + errno = EPROTO; goto mandos_end; } break; @@ -788,6 +820,7 @@ fprintf(stderr, "Unknown error while reading data from" " encrypted session with Mandos server\n"); gnutls_bye(session, GNUTLS_SHUT_RDWR); + errno = EIO; goto mandos_end; } } else { @@ -800,12 +833,14 @@ } if(quit_now){ + errno = EINTR; goto mandos_end; } do { ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); if(quit_now){ + errno = EINTR; goto mandos_end; } } while(ret == GNUTLS_E_AGAIN or ret == GNUTLS_E_INTERRUPTED); @@ -820,6 +855,7 @@ written = 0; while(written < (size_t) decrypted_buffer_size){ if(quit_now){ + errno = EINTR; goto mandos_end; } @@ -827,10 +863,12 @@ (size_t)decrypted_buffer_size - written, stdout); if(ret == 0 and ferror(stdout)){ + int e = errno; if(debug){ fprintf(stderr, "Error writing encrypted data: %s\n", strerror(errno)); } + errno = e; goto mandos_end; } written += (size_t)ret; @@ -842,17 +880,25 @@ /* Shutdown procedure */ mandos_end: - free(decrypted_buffer); - free(buffer); - if(tcp_sd >= 0){ - ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd)); - } - if(ret == -1){ - perror("close"); - } - gnutls_deinit(session); - if(quit_now){ - retval = -1; + { + int e = errno; + free(decrypted_buffer); + free(buffer); + if(tcp_sd >= 0){ + ret = (int)TEMP_FAILURE_RETRY(close(tcp_sd)); + } + if(ret == -1){ + if(e == 0){ + e = errno; + } + perror("close"); + } + gnutls_deinit(session); + if(quit_now){ + e = EINTR; + retval = -1; + } + errno = e; } return retval; } @@ -1056,11 +1102,21 @@ .arg = "SECONDS", .doc = "Maximum delay to wait for interface startup", .group = 2 }, + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + { .name = "help", .key = '?', + .doc = "Give this help list", .group = -1 }, + { .name = "usage", .key = -3, + .doc = "Give a short usage message", .group = -1 }, + { .name = "version", .key = 'V', + .doc = "Print program version", .group = -1 }, { .name = NULL } }; error_t parse_opt(int key, char *arg, struct argp_state *state){ + errno = 0; switch(key){ case 128: /* --debug */ debug = true; @@ -1082,8 +1138,7 @@ 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); + argp_error(state, "Bad number of DH bits"); } mc.dh_bits = (typeof(mc.dh_bits))tmpmax; break; @@ -1094,28 +1149,46 @@ errno = 0; delay = strtof(arg, &tmp); if(errno != 0 or tmp == arg or *tmp != '\0'){ - fprintf(stderr, "Bad delay\n"); - exit(EXIT_FAILURE); + argp_error(state, "Bad delay"); } break; - case ARGP_KEY_ARG: - argp_usage(state); - case ARGP_KEY_END: + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + case '?': /* --help */ + argp_state_help(state, state->out_stream, + (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) + & ~(unsigned int)ARGP_HELP_EXIT_OK); + case -3: /* --usage */ + 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); + exit(argp_err_exit_status); break; default: return ARGP_ERR_UNKNOWN; } - return 0; + return errno; } 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; + ret = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_HELP, 0, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror("argp_parse"); + exitcode = EX_OSERR; + goto end; + case EINVAL: + exitcode = EX_USAGE; goto end; } } @@ -1131,7 +1204,7 @@ 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; + exitcode = EX_UNAVAILABLE; goto end; } @@ -1139,19 +1212,19 @@ ret = sigaddset(&sigterm_action.sa_mask, SIGINT); if(ret == -1){ perror("sigaddset"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } ret = sigaddset(&sigterm_action.sa_mask, SIGHUP); if(ret == -1){ perror("sigaddset"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } ret = sigaddset(&sigterm_action.sa_mask, SIGTERM); if(ret == -1){ perror("sigaddset"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } /* Need to check if the handler is SIG_IGN before handling: @@ -1161,39 +1234,39 @@ ret = sigaction(SIGINT, NULL, &old_sigterm_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &sigterm_action, NULL); if(ret == -1){ perror("sigaction"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } } ret = sigaction(SIGHUP, NULL, &old_sigterm_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &sigterm_action, NULL); if(ret == -1){ perror("sigaction"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } } ret = sigaction(SIGTERM, NULL, &old_sigterm_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_sigterm_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &sigterm_action, NULL); if(ret == -1){ perror("sigaction"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; goto end; } } @@ -1203,7 +1276,7 @@ if_index = (AvahiIfIndex) if_nametoindex(interface); if(if_index == 0){ fprintf(stderr, "No such interface: \"%s\"\n", interface); - exitcode = EXIT_FAILURE; + exitcode = EX_UNAVAILABLE; goto end; } @@ -1220,7 +1293,7 @@ #ifdef __linux__ /* Lower kernel loglevel to KERN_NOTICE to avoid KERN_INFO - messages to mess up the prompt */ + messages about the network interface to mess up the prompt */ ret = klogctl(8, NULL, 5); bool restore_loglevel = true; if(ret == -1){ @@ -1232,7 +1305,7 @@ sd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); if(sd < 0){ perror("socket"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; #ifdef __linux__ if(restore_loglevel){ ret = klogctl(7, NULL, 0); @@ -1261,7 +1334,7 @@ } } #endif /* __linux__ */ - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; /* Lower privileges */ errno = 0; ret = seteuid(uid); @@ -1277,7 +1350,7 @@ if(ret == -1){ take_down_interface = false; perror("ioctl SIOCSIFFLAGS"); - exitcode = EXIT_FAILURE; + exitcode = EX_OSERR; #ifdef __linux__ if(restore_loglevel){ ret = klogctl(7, NULL, 0); @@ -1349,7 +1422,7 @@ ret = init_gnutls_global(pubkey, seckey); if(ret == -1){ fprintf(stderr, "init_gnutls_global failed\n"); - exitcode = EXIT_FAILURE; + exitcode = EX_UNAVAILABLE; goto end; } else { gnutls_initialized = true; @@ -1372,7 +1445,7 @@ if(not init_gpgme(pubkey, seckey, tempdir)){ fprintf(stderr, "init_gpgme failed\n"); - exitcode = EXIT_FAILURE; + exitcode = EX_UNAVAILABLE; goto end; } else { gpgme_initialized = true; @@ -1388,7 +1461,7 @@ char *address = strrchr(connect_to, ':'); if(address == NULL){ fprintf(stderr, "No colon in address\n"); - exitcode = EXIT_FAILURE; + exitcode = EX_USAGE; goto end; } @@ -1402,7 +1475,7 @@ if(errno != 0 or tmp == address+1 or *tmp != '\0' or tmpmax != (uint16_t)tmpmax){ fprintf(stderr, "Bad port number\n"); - exitcode = EXIT_FAILURE; + exitcode = EX_USAGE; goto end; } @@ -1427,7 +1500,25 @@ ret = start_mandos_communication(address, port, if_index, af); if(ret < 0){ - exitcode = EXIT_FAILURE; + switch(errno){ + case ENETUNREACH: + case EHOSTDOWN: + case EHOSTUNREACH: + exitcode = EX_NOHOST; + break; + case EINVAL: + exitcode = EX_USAGE; + break; + case EIO: + exitcode = EX_IOERR; + break; + case EPROTO: + exitcode = EX_PROTOCOL; + break; + default: + exitcode = EX_OSERR; + break; + } } else { exitcode = EXIT_SUCCESS; } @@ -1460,7 +1551,7 @@ if(mc.server == NULL){ fprintf(stderr, "Failed to create Avahi server: %s\n", avahi_strerror(error)); - exitcode = EXIT_FAILURE; + exitcode = EX_UNAVAILABLE; goto end; } @@ -1475,7 +1566,7 @@ if(sb == NULL){ fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_server_errno(mc.server))); - exitcode = EXIT_FAILURE; + exitcode = EX_UNAVAILABLE; goto end; } @@ -1530,7 +1621,7 @@ if(ret == -1){ perror("ioctl SIOCGIFFLAGS"); } else if(network.ifr_flags & IFF_UP) { - network.ifr_flags &= ~IFF_UP; /* clear flag */ + network.ifr_flags &= ~(short)IFF_UP; /* clear flag */ ret = ioctl(sd, SIOCSIFFLAGS, &network); if(ret == -1){ perror("ioctl SIOCSIFFLAGS"); === modified file 'plugins.d/password-prompt.c' --- plugins.d/password-prompt.c 2009-09-21 13:51:11 +0000 +++ plugins.d/password-prompt.c 2010-03-27 18:39:02 +0000 @@ -37,11 +37,13 @@ #include /* NULL, size_t, ssize_t */ #include /* ssize_t */ #include /* EXIT_SUCCESS, EXIT_FAILURE, - getopt_long, getenv() */ + getenv() */ #include /* fprintf(), stderr, getline(), - stdin, feof(), perror(), fputc(), - stdout, getopt_long */ -#include /* errno, EINVAL */ + stdin, feof(), perror(), fputc() + */ +#include /* errno, EBADF, ENOTTY, EINVAL, + EFAULT, EFBIG, EIO, ENOSPC, EINTR + */ #include /* or, not */ #include /* bool, false, true */ #include /* strlen, rindex */ @@ -50,6 +52,8 @@ argp_parse(), error_t, ARGP_KEY_ARG, ARGP_KEY_END, ARGP_ERR_UNKNOWN */ +#include /* EX_SOFTWARE, EX_OSERR, + EX_UNAVAILABLE, EX_IOERR, EX_OK */ volatile sig_atomic_t quit_now = 0; int signal_received; @@ -82,10 +86,20 @@ .doc = "Prefix shown before the prompt", .group = 2 }, { .name = "debug", .key = 128, .doc = "Debug mode", .group = 3 }, + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + { .name = "help", .key = '?', + .doc = "Give this help list", .group = -1 }, + { .name = "usage", .key = -3, + .doc = "Give a short usage message", .group = -1 }, + { .name = "version", .key = 'V', + .doc = "Print program version", .group = -1 }, { .name = NULL } }; error_t parse_opt (int key, char *arg, struct argp_state *state){ + errno = 0; switch (key){ case 'p': prefix = arg; @@ -93,25 +107,42 @@ case 128: debug = true; break; - case ARGP_KEY_ARG: - argp_usage(state); - break; - case ARGP_KEY_END: + /* + * These reproduce what we would get without ARGP_NO_HELP + */ + case '?': /* --help */ + argp_state_help(state, state->out_stream, + (ARGP_HELP_STD_HELP | ARGP_HELP_EXIT_ERR) + & ~(unsigned int)ARGP_HELP_EXIT_OK); + case -3: /* --usage */ + 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); + exit(argp_err_exit_status); break; default: return ARGP_ERR_UNKNOWN; } - return 0; + return errno; } struct argp argp = { .options = options, .parser = parse_opt, .args_doc = "", .doc = "Mandos password-prompt -- Read and" " output a password" }; - ret = argp_parse(&argp, argc, argv, 0, 0, NULL); - if(ret == ARGP_ERR_UNKNOWN){ - fprintf(stderr, "Unknown error while parsing arguments\n"); - return EXIT_FAILURE; + ret = argp_parse(&argp, argc, argv, + ARGP_IN_ORDER | ARGP_NO_HELP, NULL, NULL); + switch(ret){ + case 0: + break; + case ENOMEM: + default: + errno = ret; + perror("argp_parse"); + return EX_OSERR; + case EINVAL: + return EX_USAGE; } } @@ -123,25 +154,32 @@ } if(tcgetattr(STDIN_FILENO, &t_old) != 0){ + int e = errno; perror("tcgetattr"); - return EXIT_FAILURE; + switch(e){ + case EBADF: + case ENOTTY: + return EX_UNAVAILABLE; + default: + return EX_OSERR; + } } sigemptyset(&new_action.sa_mask); ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ perror("sigaddset"); - return EXIT_FAILURE; + return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ perror("sigaddset"); - return EXIT_FAILURE; + return EX_OSERR; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ perror("sigaddset"); - return EXIT_FAILURE; + return EX_OSERR; } /* Need to check if the handler is SIG_IGN before handling: | [[info:libc:Initial Signal Actions]] | @@ -150,37 +188,37 @@ ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ perror("sigaction"); - return EXIT_FAILURE; + return EX_OSERR; } } @@ -192,10 +230,18 @@ t_new = t_old; t_new.c_lflag &= ~(tcflag_t)ECHO; if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &t_new) != 0){ + int e = errno; perror("tcsetattr-echo"); - return EXIT_FAILURE; + switch(e){ + case EBADF: + case ENOTTY: + return EX_UNAVAILABLE; + case EINVAL: + default: + return EX_OSERR; + } } - + if(debug){ fprintf(stderr, "Waiting for input from stdin \n"); } @@ -212,22 +258,31 @@ fprintf(stderr, "%s ", prefix); } { - const char *cryptsource = getenv("cryptsource"); - const char *crypttarget = getenv("crypttarget"); - const char *const prompt - = "Enter passphrase to unlock the disk"; + const char *cryptsource = getenv("CRYPTTAB_SOURCE"); + const char *crypttarget = getenv("CRYPTTAB_NAME"); + /* Before cryptsetup 1.1.0~rc2 */ + if(cryptsource == NULL){ + cryptsource = getenv("cryptsource"); + } + if(crypttarget == NULL){ + crypttarget = getenv("crypttarget"); + } + const char *const prompt1 = "Unlocking the disk"; + const char *const prompt2 = "Enter passphrase"; if(cryptsource == NULL){ if(crypttarget == NULL){ - fprintf(stderr, "%s: ", prompt); + fprintf(stderr, "%s to unlock the disk: ", prompt2); } else { - fprintf(stderr, "%s (%s): ", prompt, crypttarget); + fprintf(stderr, "%s (%s)\n%s: ", prompt1, crypttarget, + prompt2); } } else { if(crypttarget == NULL){ - fprintf(stderr, "%s %s: ", prompt, cryptsource); + fprintf(stderr, "%s %s\n%s: ", prompt1, cryptsource, + prompt2); } else { - fprintf(stderr, "%s %s (%s): ", prompt, cryptsource, - crypttarget); + fprintf(stderr, "%s %s (%s)\n%s: ", prompt1, cryptsource, + crypttarget, prompt2); } } } @@ -237,7 +292,7 @@ /* Make n = data size instead of allocated buffer size */ n = (size_t)ret; /* Strip final newline */ - if(n>0 and buffer[n-1] == '\n'){ + if(n > 0 and buffer[n-1] == '\n'){ buffer[n-1] = '\0'; /* not strictly necessary */ n--; } @@ -245,18 +300,55 @@ while(written < n){ ret = write(STDOUT_FILENO, buffer + written, n - written); if(ret < 0){ + int e = errno; perror("write"); - status = EXIT_FAILURE; + switch(e){ + case EBADF: + case EFAULT: + case EINVAL: + case EFBIG: + case EIO: + case ENOSPC: + default: + status = EX_IOERR; + break; + case EINTR: + status = EXIT_FAILURE; + break; + } break; } written += (size_t)ret; } + ret = close(STDOUT_FILENO); + if(ret == -1){ + int e = errno; + perror("close"); + switch(e){ + case EBADF: + status = EX_OSFILE; + break; + case EIO: + default: + status = EX_IOERR; + break; + } + } break; } if(ret < 0){ + int e = errno; if(errno != EINTR and not feof(stdin)){ perror("getline"); - status = EXIT_FAILURE; + switch(e){ + case EBADF: + status = EX_UNAVAILABLE; + case EIO: + case EINVAL: + default: + status = EX_IOERR; + break; + } break; } } @@ -293,7 +385,7 @@ fprintf(stderr, "%s is exiting with status %d\n", argv[0], status); } - if(status == EXIT_SUCCESS){ + if(status == EXIT_SUCCESS or status == EX_OK){ fputc('\n', stderr); } === modified file 'plugins.d/password-prompt.xml' --- plugins.d/password-prompt.xml 2009-01-04 21:54:55 +0000 +++ plugins.d/password-prompt.xml 2009-10-30 16:23:43 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -183,8 +183,8 @@ ENVIRONMENT - cryptsource - crypttarget + CRYPTTAB_SOURCE + CRYPTTAB_NAME If set, these environment variables will be assumed to === modified file 'plugins.d/splashy.c' --- plugins.d/splashy.c 2009-09-16 23:28:39 +0000 +++ plugins.d/splashy.c 2009-10-18 16:09:05 +0000 @@ -43,9 +43,16 @@ STDOUT_FILENO, _exit(), pause() */ #include /* memcmp() */ -#include /* errno */ +#include /* errno, EACCES, ENOTDIR, ELOOP, + ENOENT, ENAMETOOLONG, EMFILE, + ENFILE, ENOMEM, ENOEXEC, EINVAL, + E2BIG, EFAULT, EIO, ETXTBSY, + EISDIR, ELIBBAD, EPERM, EINTR, + ECHILD */ #include /* waitpid(), WIFEXITED(), WEXITSTATUS() */ +#include /* EX_OSERR, EX_OSFILE, + EX_UNAVAILABLE */ sig_atomic_t interrupted_by_signal = 0; int signal_received; @@ -65,6 +72,7 @@ DIR *proc_dir = NULL; pid_t splashy_pid = 0; pid_t splashy_command_pid = 0; + int exitstatus = EXIT_FAILURE; /* Create prompt string */ { @@ -90,6 +98,7 @@ } if(ret == -1){ prompt = NULL; + exitstatus = EX_OSERR; goto failure; } } @@ -99,7 +108,23 @@ const char splashy_name[] = "/sbin/splashy"; proc_dir = opendir("/proc"); if(proc_dir == NULL){ + int e = errno; perror("opendir"); + switch(e){ + case EACCES: + case ENOTDIR: + case ELOOP: + case ENOENT: + default: + exitstatus = EX_OSFILE; + break; + case ENAMETOOLONG: + case EMFILE: + case ENFILE: + case ENOMEM: + exitstatus = EX_OSERR; + break; + } goto failure; } for(struct dirent *proc_ent = readdir(proc_dir); @@ -127,6 +152,7 @@ ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name); if(ret == -1){ perror("asprintf"); + exitstatus = EX_OSERR; goto failure; } @@ -138,8 +164,20 @@ free(exe_link); continue; } + int e = errno; perror("lstat"); free(exe_link); + switch(e){ + case EACCES: + case ENOTDIR: + case ELOOP: + default: + exitstatus = EX_OSFILE; + break; + case ENAMETOOLONG: + exitstatus = EX_OSERR; + break; + } goto failure; } if(not S_ISLNK(exe_stat.st_mode) @@ -163,6 +201,7 @@ proc_dir = NULL; } if(splashy_pid == 0){ + exitstatus = EX_UNAVAILABLE; goto failure; } @@ -175,51 +214,60 @@ ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ perror("sigaddset"); + exitstatus = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ perror("sigaddset"); + exitstatus = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ perror("sigaddset"); + exitstatus = EX_OSERR; goto failure; } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGINT, &new_action, NULL); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } } ret = sigaction(SIGHUP, NULL, &old_action); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGHUP, &new_action, NULL); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } } ret = sigaction(SIGTERM, NULL, &old_action); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } if(old_action.sa_handler != SIG_IGN){ ret = sigaction(SIGTERM, &new_action, NULL); if(ret == -1){ perror("sigaction"); + exitstatus = EX_OSERR; goto failure; } } @@ -236,6 +284,7 @@ } if(splashy_command_pid == -1){ perror("fork"); + exitstatus = EX_OSERR; goto failure; } /* Child */ @@ -243,7 +292,31 @@ if(not interrupted_by_signal){ const char splashy_command[] = "/sbin/splashy_update"; execl(splashy_command, splashy_command, prompt, (char *)NULL); + int e = errno; perror("execl"); + switch(e){ + case EACCES: + case ENOENT: + case ENOEXEC: + case EINVAL: + _exit(EX_UNAVAILABLE); + case ENAMETOOLONG: + case E2BIG: + case ENOMEM: + case EFAULT: + case EIO: + case EMFILE: + case ENFILE: + case ETXTBSY: + default: + _exit(EX_OSERR); + case ENOTDIR: + case ELOOP: + case EISDIR: + case ELIBBAD: + case EPERM: + _exit(EX_OSFILE); + } } free(prompt); _exit(EXIT_FAILURE); @@ -320,12 +393,28 @@ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */ if(ret == -1){ perror("dup2"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } - + execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL); - perror("execl"); - _exit(EXIT_FAILURE); + { + int e = errno; + perror("execl"); + switch(e){ + case EACCES: + case ENOENT: + case ENOEXEC: + default: + _exit(EX_UNAVAILABLE); + case ENAMETOOLONG: + case E2BIG: + case ENOMEM: + _exit(EX_OSERR); + case ENOTDIR: + case ELOOP: + _exit(EX_OSFILE); + } + } } } @@ -348,5 +437,5 @@ TEMP_FAILURE_RETRY(pause()); } - return EXIT_FAILURE; + return exitstatus; } === modified file 'plugins.d/usplash.c' --- plugins.d/usplash.c 2009-09-21 13:51:11 +0000 +++ plugins.d/usplash.c 2010-03-27 18:39:02 +0000 @@ -47,6 +47,7 @@ #include /* opendir(), readdir(), closedir() */ #include /* intmax_t, strtoimax() */ #include /* struct stat, lstat(), S_ISLNK */ +#include /* EX_OSERR, EX_UNAVAILABLE */ sig_atomic_t interrupted_by_signal = 0; int signal_received; @@ -297,9 +298,11 @@ size_t buf_len = 0; pid_t usplash_pid = -1; bool usplash_accessed = false; + int status = EXIT_FAILURE; /* Default failure exit status */ char *prompt = makeprompt(); if(prompt == NULL){ + status = EX_OSERR; goto failure; } @@ -308,6 +311,7 @@ size_t cmdline_len = 0; usplash_pid = find_usplash(&cmdline, &cmdline_len); if(usplash_pid == 0){ + status = EX_UNAVAILABLE; goto failure; } @@ -320,22 +324,26 @@ ret = sigaddset(&new_action.sa_mask, SIGINT); if(ret == -1){ perror("sigaddset"); + status = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGHUP); if(ret == -1){ perror("sigaddset"); + status = EX_OSERR; goto failure; } ret = sigaddset(&new_action.sa_mask, SIGTERM); if(ret == -1){ perror("sigaddset"); + status = EX_OSERR; goto failure; } ret = sigaction(SIGINT, NULL, &old_action); if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -344,6 +352,7 @@ if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -352,6 +361,7 @@ if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -360,6 +370,7 @@ if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -368,6 +379,7 @@ if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -376,6 +388,7 @@ if(ret == -1){ if(errno != EINTR){ perror("sigaction"); + status = EX_OSERR; } goto failure; } @@ -387,6 +400,7 @@ if(not usplash_write(&fifo_fd, "TIMEOUT", "0")){ if(errno != EINTR){ perror("usplash_write"); + status = EX_OSERR; } goto failure; } @@ -398,6 +412,7 @@ if(not usplash_write(&fifo_fd, "INPUTQUIET", prompt)){ if(errno != EINTR){ perror("usplash_write"); + status = EX_OSERR; } goto failure; } @@ -415,6 +430,7 @@ if(outfifo_fd == -1){ if(errno != EINTR){ perror("open"); + status = EX_OSERR; } goto failure; } @@ -433,6 +449,7 @@ if(tmp == NULL){ if(errno != EINTR){ perror("realloc"); + status = EX_OSERR; } goto failure; } @@ -444,6 +461,7 @@ if(sret == -1){ if(errno != EINTR){ perror("read"); + status = EX_OSERR; } TEMP_FAILURE_RETRY(close(outfifo_fd)); goto failure; @@ -458,6 +476,7 @@ if(ret == -1){ if(errno != EINTR){ perror("close"); + status = EX_OSERR; } goto failure; } @@ -470,6 +489,7 @@ if(not usplash_write(&fifo_fd, "TIMEOUT", "15")){ if(errno != EINTR){ perror("usplash_write"); + status = EX_OSERR; } goto failure; } @@ -482,6 +502,7 @@ if(ret == -1){ if(errno != EINTR){ perror("close"); + status = EX_OSERR; } goto failure; } @@ -495,6 +516,7 @@ if(sret == -1){ if(errno != EINTR){ perror("write"); + status = EX_OSERR; } goto failure; } @@ -523,7 +545,7 @@ /* If usplash was never accessed, we can stop now */ if(not usplash_accessed){ - return EXIT_FAILURE; + return status; } /* Close FIFO */ @@ -555,7 +577,7 @@ if(tmp == NULL){ perror("realloc"); free(cmdline_argv); - return EXIT_FAILURE; + return status; } cmdline_argv = tmp; cmdline_argv[cmdline_argc] = cmdline + position; @@ -591,7 +613,7 @@ ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */ if(ret == -1){ perror("dup2"); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } execv(usplash_name, cmdline_argv); @@ -600,7 +622,7 @@ } free(cmdline); free(cmdline_argv); - _exit(EXIT_FAILURE); + _exit(EX_OSERR); } free(cmdline); free(cmdline_argv); @@ -638,5 +660,5 @@ TEMP_FAILURE_RETRY(pause()); } - return EXIT_FAILURE; + return status; }