=== modified file '.bzrignore' --- .bzrignore 2019-07-27 10:11:45 +0000 +++ .bzrignore 2019-12-04 23:52:20 +0000 @@ -14,4 +14,3 @@ plugins.d/plymouth plugin-helpers/mandos-client-iprouteadddel dracut-module/password-agent -.tramp_history === modified file 'DBUS-API' --- DBUS-API 2019-02-10 04:20:26 +0000 +++ DBUS-API 2020-01-18 00:57:30 +0000 @@ -55,10 +55,10 @@ | Name | Type | Access | clients.conf | |-------------------------+------+------------+---------------------| - | ApprovedByDefault | b | Read/Write | approved_by_default | | ApprovalDelay (a) | t | Read/Write | approval_delay | | ApprovalDuration (a) | t | Read/Write | approval_duration | | ApprovalPending (b) | b | Read | N/A | + | ApprovedByDefault | b | Read/Write | approved_by_default | | Checker | s | Read/Write | checker | | CheckerRunning (c) | b | Read/Write | N/A | | Created (d) | s | Read | N/A | @@ -66,9 +66,9 @@ | Expires (f) | s | Read | N/A | | ExtendedTimeout (a) | t | Read/Write | extended_timeout | | Fingerprint | s | Read | fingerprint | - | KeyID | s | Read | key_id | | Host | s | Read/Write | host | | Interval (a) | t | Read/Write | interval | + | KeyID | s | Read | key_id | | LastApprovalRequest (g) | s | Read | N/A | | LastCheckedOK (h) | s | Read/Write | N/A | | LastCheckerStatus (i) | n | Read | N/A | === modified file 'INSTALL' --- INSTALL 2019-08-30 21:46:16 +0000 +++ INSTALL 2019-11-03 19:17:57 +0000 @@ -90,7 +90,7 @@ 2. On the computer to run as a Mandos server, run the following command: - For Debian: su -c 'make install-server' + For Debian: su - -c 'make install-server' For Ubuntu: sudo make install-server (This creates a configuration without any clients configured; you @@ -102,14 +102,14 @@ 2. On the computer to run as a Mandos client, run the following command: - For Debian: su -c 'make install-client' + For Debian: su - -c 'make install-client' For Ubuntu: sudo make install-client This will also create an OpenPGP key, which will take some time and entropy, so be patient. 3. Run the following command: - For Debian: su -c 'mandos-keygen --password' + For Debian: su - -c 'mandos-keygen --password' For Ubuntu: sudo mandos-keygen --password When prompted, enter the password/passphrase for the encrypted @@ -127,7 +127,7 @@ # update-initramfs -k all -u 5. On the server computer, start the server by running the command - For Debian: su -c 'invoke-rc.d mandos start' + For Debian: su - -c 'invoke-rc.d mandos start' For Ubuntu: sudo service mandos start At this point, it is possible to verify that the correct password === modified file 'Makefile' --- Makefile 2019-09-03 19:06:41 +0000 +++ Makefile 2020-02-09 03:54:46 +0000 @@ -156,10 +156,13 @@ objects:=$(addsuffix .o,$(CPROGS)) +.PHONY: all all: $(PROGS) mandos.lsm +.PHONY: doc doc: $(DOCS) +.PHONY: html html: $(htmldocs) %.5: %.xml common.ent legalnotice.xml @@ -282,36 +285,32 @@ $@) # Need to add the GnuTLS, Avahi and GPGME libraries -plugins.d/mandos-client: plugins.d/mandos-client.c - $(LINK.c) $^ $(GNUTLS_CFLAGS) $(AVAHI_CFLAGS) $(strip\ - ) $(GPGME_CFLAGS) $(GNUTLS_LIBS) $(strip\ - ) $(AVAHI_LIBS) $(GPGME_LIBS) $(LOADLIBES) $(strip\ - ) $(LDLIBS) -o $@ +plugins.d/mandos-client: CFLAGS += $(GNUTLS_CFLAGS) $(strip \ + ) $(AVAHI_CFLAGS) $(GPGME_CFLAGS) +plugins.d/mandos-client: LDLIBS += $(GNUTLS_LIBS) $(strip \ + ) $(AVAHI_LIBS) $(GPGME_LIBS) # Need to add the libnl-route library -plugin-helpers/mandos-client-iprouteadddel: plugin-helpers/mandos-client-iprouteadddel.c - $(LINK.c) $(LIBNL3_CFLAGS) $^ $(LIBNL3_LIBS) $(strip\ - ) $(LOADLIBES) $(LDLIBS) -o $@ +plugin-helpers/mandos-client-iprouteadddel: CFLAGS += $(LIBNL3_CFLAGS) +plugin-helpers/mandos-client-iprouteadddel: LDLIBS += $(LIBNL3_LIBS) # Need to add the GLib and pthread libraries -dracut-module/password-agent: dracut-module/password-agent.c - $(LINK.c) $(GLIB_CFLAGS) $^ $(GLIB_LIBS) -lpthread $(strip\ - ) $(LOADLIBES) $(LDLIBS) -o $@ - -.PHONY : all doc html clean distclean mostlyclean maintainer-clean \ - check run-client run-server install install-html \ - install-server install-client-nokey install-client uninstall \ - uninstall-server uninstall-client purge purge-server \ - purge-client - +dracut-module/password-agent: CFLAGS += $(GLIB_CFLAGS) +dracut-module/password-agent: LDLIBS += $(GLIB_LIBS) -lpthread + +.PHONY: clean clean: -rm --force $(CPROGS) $(objects) $(htmldocs) $(DOCS) core +.PHONY: distclean distclean: clean +.PHONY: mostlyclean mostlyclean: clean +.PHONY: maintainer-clean maintainer-clean: clean -rm --force --recursive keydir confdir statedir +.PHONY: check check: all ./mandos --check ./mandos-ctl --check @@ -321,6 +320,7 @@ ./dracut-module/password-agent --test # Run the client with a local config and key +.PHONY: run-client run-client: all keydir/seckey.txt keydir/pubkey.txt \ keydir/tls-privkey.pem keydir/tls-pubkey.pem @echo '######################################################' @@ -354,8 +354,15 @@ keydir/seckey.txt keydir/pubkey.txt keydir/tls-privkey.pem keydir/tls-pubkey.pem: mandos-keygen install --directory keydir ./mandos-keygen --dir keydir --force + if ! [ -e keydir/tls-privkey.pem ]; then \ + install --mode=u=rw /dev/null keydir/tls-privkey.pem; \ + fi + if ! [ -e keydir/tls-pubkey.pem ]; then \ + install --mode=u=rw /dev/null keydir/tls-pubkey.pem; \ + fi # Run the server with a local config +.PHONY: run-server run-server: confdir/mandos.conf confdir/clients.conf statedir ./mandos --debug --no-dbus --configdir=confdir \ --statedir=statedir $(SERVERARGS) @@ -372,13 +379,16 @@ statedir: install --directory statedir +.PHONY: install install: install-server install-client-nokey +.PHONY: install-html install-html: html install --directory $(htmldir) install --mode=u=rw,go=r --target-directory=$(htmldir) \ $(htmldocs) +.PHONY: install-server install-server: doc install --directory $(CONFDIR) if install --directory --mode=u=rwx --owner=$(USER) \ @@ -431,6 +441,7 @@ gzip --best --to-stdout intro.8mandos \ > $(MANDIR)/man8/intro.8mandos.gz +.PHONY: install-client-nokey install-client-nokey: all doc install --directory $(LIBDIR)/mandos $(CONFDIR) install --directory --mode=u=rwx $(KEYDIR) \ @@ -515,6 +526,7 @@ gzip --best --to-stdout dracut-module/password-agent.8mandos \ > $(MANDIR)/man8/password-agent.8mandos.gz +.PHONY: install-client install-client: install-client-nokey # Post-installation stuff -$(PREFIX)/sbin/mandos-keygen --dir "$(KEYDIR)" @@ -530,8 +542,10 @@ fi echo "Now run mandos-keygen --password --dir $(KEYDIR)" +.PHONY: uninstall uninstall: uninstall-server uninstall-client +.PHONY: uninstall-server uninstall-server: -rm --force $(PREFIX)/sbin/mandos \ $(PREFIX)/sbin/mandos-ctl \ @@ -544,6 +558,7 @@ update-rc.d -f mandos remove -rmdir $(CONFDIR) +.PHONY: uninstall-client uninstall-client: # Refuse to uninstall client if /etc/crypttab is explicitly configured # to use it. @@ -585,8 +600,10 @@ done; \ fi +.PHONY: purge purge: purge-server purge-client +.PHONY: purge-server purge-server: uninstall-server -rm --force $(CONFDIR)/mandos.conf $(CONFDIR)/clients.conf \ $(DESTDIR)/etc/dbus-1/system.d/mandos.conf @@ -597,6 +614,7 @@ $(DESTDIR)/var/run/mandos.pid -rmdir $(CONFDIR) +.PHONY: purge-client purge-client: uninstall-client -shred --remove $(KEYDIR)/seckey.txt $(KEYDIR)/tls-privkey.pem -rm --force $(CONFDIR)/plugin-runner.conf \ === modified file 'TODO' --- TODO 2019-04-09 19:41:53 +0000 +++ TODO 2020-02-09 03:42:50 +0000 @@ -6,49 +6,47 @@ * mandos-applet * mandos-client -** TODO A --server option which only adds to the server list. - (Unlike --connect, which implicitly disables zeroconf.) -** TODO [#B] Use capabilities instead of seteuid(). - https://forums.grsecurity.net/viewtopic.php?f=7&t=2522 -** TODO [#B] Use getaddrinfo(hints=AI_NUMERICHOST) instead of inet_pton() -** TODO [#C] Make start_mandos_communication() take "struct server". -** TODO [#C] --interfaces=regex,eth*,noregex (bridge-utils-interfaces(5)) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. +** TODO A ~--server~ option which only adds to the server list. + (Unlike ~--connect~, which implicitly disables ZeroConf.) +** TODO [#B] Use [[man:capabilities][capabilities]] instead of [[info:libc#Setting%20User%20ID][seteuid()]]. + [[https://forums.grsecurity.net/viewtopic.php?f=7&t=2522]] +** TODO [#B] Use ~getaddrinfo(hints=AI_NUMERICHOST)~ instead of ~inet_pton()~ +** TODO [#C] Make ~start_mandos_communication()~ take ~struct server~. +** TODO [#C] ~--interfaces=regex,eth*,noregex~ [[man:bridge-utils-interfaces][bridge-utils-interfaces(5)]] +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * splashy -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] use [[info:libc#Scanning%20Directory%20Content][scandir(3)]] instead of [[info:libc#Reading/Closing%20Directory][readdir(3)]] +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * usplash (Deprecated) ** TODO [#B] Make it work again -** TODO [#B] use scandir(3) instead of readdir(3) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] use [[info:libc#Scanning%20Directory%20Content][scandir(3)]] instead of [[info:libc#Reading/Closing%20Directory][readdir(3)]] +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * askpass-fifo -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * password-prompt -** TODO [#B] lock stdin (with flock()?) -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL +** TODO [#B] lock stdin (with [[info:libc#File%20Locks][flock()]]?) +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * plymouth -** TODO [#A] Detect partial writes to stdout and exit with EX_TEMPFAIL -** TODO [#B] Use reallocarray() with GNU LibC 2.29 or later. +** TODO [#A] Detect partial writes to stdout and exit with ~EX_TEMPFAIL~ * TODO [#B] passdev * plugin-runner ** TODO handle printing for errors for plugins *** Hook up stderr of plugins, buffer them, and prepend "Mandos Plugin [plugin name]" -** TODO [#C] use same file name rules as run-parts(8) +** TODO [#C] use same file name rules as [[man:run-parts][run-parts(8)]] ** kernel command line option for debug info -** TODO [#A] Restart plugins which exit with EX_TEMPFAIL +** TODO [#A] Restart plugins which exit with ~EX_TEMPFAIL~ * mandos (server) -** TODO [#B] --notify-command +** TODO [#B] ~--notify-command~ This would allow the mandos.service to use - --notify-command="systemd-notify --pid --ready" + ~--notify-command="systemd-notify --pid --ready"~ ** TODO [#B] python-systemd *** import systemd.daemon; systemd.daemon.notify() ** TODO [#B] Log level :BUGS: @@ -61,29 +59,26 @@ ** TODO [#B] Global enable/disable flag ** TODO [#B] By-client countdown on number of secrets given ** D-Bus Client method NeedsPassword(50) - Timeout, default disapprove - + SetPass(u"gazonk", True) -> Approval, persistent + + SetPass("gazonk", True) -> Approval, persistent + Approve(False) -> Close client connection immediately ** TODO [#C] python-parsedatetime ** TODO Separate logging logic to own object -** TODO [#B] Limit approval_delay to max gnutls/tls timeout value -** TODO [#B] break the wait on approval_delay if connection dies -** TODO Generate Client.runtime_expansions from client options + extra +** TODO [#B] Limit ~approval_delay~ to max GnuTLS/TLS timeout value +** TODO [#B] break the wait on ~approval_delay~ if connection dies +** TODO Generate ~Client.runtime_expansions~ from client options + extra ** TODO Allow %%(checker)s as a runtime expansion ** TODO D-Bus AddClient() method on server object ** TODO Use org.freedesktop.DBus.Method.NoReply annotation on async methods. :2: ** TODO Save state periodically to recover better from hard shutdowns ** TODO CheckerCompleted method, deprecate CheckedOK -** TODO Secret Service API? - https://standards.freedesktop.org/secret-service/ +** TODO [[https://standards.freedesktop.org/secret-service/][Secret Service]] API? ** TODO Remove D-Bus interfaces with old domain name :2: -** TODO Remove old string_to_delta format :2: +** TODO Remove old ~string_to_delta~ format :2: ** TODO http://0pointer.de/blog/projects/stateless.html -*** File in /usr/lib/sysusers.d to create user+group "_mandos" +*** File in /usr/lib/sysusers.d to create user+group "~_mandos~" ** TODO Error handling on error parsing config files ** TODO init.d script error handling ** TODO D-Bus server properties; address, port, interface, etc. :2: -** Python 3 :2: -*** TODO [#C] In Python 3.3, use shlex.quote() instead of re.escape() * mandos-ctl ** TODO Remove old string_to_delta format :2: @@ -93,7 +88,7 @@ arguments. * mandos-monitor -** TODO --servicename :BUGS: +** TODO ~--servicename~ :BUGS: ** TODO help should be toggleable ** Urwid client data displayer Better view of client data in the listing @@ -101,17 +96,17 @@ ** Print a nice "We are sorry" message, save stack trace to log. * mandos-keygen -** TODO "--secfile" option +** TODO "~--secfile~" option Using the "secfile" option instead of "secret" -** TODO [#B] "--test" option +** TODO [#B] "~--test~" option For testing decryption before rebooting. * Package ** /usr/share/initramfs-tools/hooks/mandos -*** TODO [#C] use same file name rules as run-parts(8) +*** TODO [#C] use same file name rules as [[man:run-parts][run-parts(8)]] *** TODO [#C] Do not install in initrd.img if configured not to. Use "/etc/initramfs-tools/hooksconf.d/mandos"? -** TODO [#C] $(pkg-config --variable=completionsdir bash-completion) +** TODO [#C] ~$(pkg-config --variable=completionsdir bash-completion)~ From XML sources directly? * Side Stuff @@ -122,3 +117,4 @@ #+STARTUP: showall +#+FILETAGS: :mandos: === modified file 'debian/control' --- debian/control 2019-08-31 01:50:02 +0000 +++ debian/control 2020-03-21 16:23:36 +0000 @@ -11,7 +11,7 @@ xsltproc, pkg-config, libnl-route-3-dev, systemd Build-Depends-Indep: python3 (>= 3), python3-dbus, python3-gi, po-debconf -Standards-Version: 4.4.0 +Standards-Version: 4.5.0 Vcs-Bzr: https://ftp.recompile.se/pub/mandos/trunk Vcs-Browser: https://bzr.recompile.se/loggerhead/mandos/trunk/files Homepage: https://www.recompile.se/mandos === added file 'debian/po/nl.po' --- debian/po/nl.po 1970-01-01 00:00:00 +0000 +++ debian/po/nl.po 2019-12-05 03:38:07 +0000 @@ -0,0 +1,156 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the mandos package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: mandos_1.8.9-2\n" +"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" +"POT-Creation-Date: 2019-08-05 22:57+0200\n" +"PO-Revision-Date: 2019-11-12 14:26+0100\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Last-Translator: Frans Spiesschaert \n" +"Language-Team: Debian Dutch l10n Team \n" +"X-Generator: Poedit 1.8.11\n" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "New client option \"key_id\" is REQUIRED on server" +msgstr "Nieuwe client-optie \"key_id\" is VERPLICHT op de server" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"A new \"key_id\" client option is REQUIRED in the clients.conf file, " +"otherwise the client most likely will not reboot unattended. This option:" +msgstr "" +"In het bestand clients.conf is een nieuwe client-optie \"key_id\" VERPLICHT, " +"anders zal de client hoogstwaarschijnlijk niet onbeheerd heropstarten. Deze " +"optie:" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid " key_id = " +msgstr " key_id = " + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"must be added in the file /etc/mandos/clients.conf, right before the " +"\"fingerprint\" option, for each Mandos client. You must edit that file and " +"add this option for all clients. To see the correct key ID for each client, " +"run this command (on each client):" +msgstr "" +"moet voor elke Mandos-client toegevoegd worden in het bestand /etc/mandos/" +"clients.conf, net voor de optie \"fingerprint\". U moet dat bestand bewerken " +"en deze optie voor alle clients toevoegen. Om van elke client het correcte " +"key_id te kennen, moet u het volgende commando (op iedere client) uitvoeren:" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid " mandos-keygen -F/dev/null|grep ^key_id" +msgstr " mandos-keygen -F/dev/null|grep ^key_id" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " +"cannot serve passwords for both old and new clients!" +msgstr "" +"Opmerking: alle clients moeten GnuTLS 3.6.6 of recenter gebruiken; de server " +"is niet in staat om wachtwoorden te geven voor zowel oude als nieuwe clients!" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " +"keys as TLS session keys. A new TLS key pair will be generated on each " +"client and will be used as identification, but the key ID of the public key " +"needs to be added to this server, since this will now be used to identify " +"the client to the server." +msgstr "" +"Motivering: met de komst van GnuTLS 3.6.6 werd Mandos gedwongen te stoppen " +"met het gebruik van OpenPGP-sleutels als TLS-sessiesleutels. Op iedere " +"client zal een nieuw TLS-sleutelpaar gegenereerd worden om als identificatie " +"te dienen, maar de sleutel-ID van de publieke sleutel moet op deze server " +"toegevoegd worden, aangezien deze nu gebruikt zal worden om de client bij de " +"server te identificeren." + +#. Type: note +#. Description +#: ../mandos.templates:2001 +msgid "Bad key IDs have been removed from clients.conf" +msgstr "Slechte sleutel-ID's werden uit clients.conf verwijderd" + +#. Type: note +#. Description +#: ../mandos.templates:2001 +msgid "" +"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " +"removed from /etc/mandos/clients.conf" +msgstr "" +"Slechte sleutel-ID's die wegens een bug in Mandos client 1.8.0 gecreëerd " +"werden, werden verwijderd uit /etc/mandos/clients.conf" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "New client option \"${key_id}\" is REQUIRED on server" +msgstr "Nieuwe client-optie \"${key_id}\" is VERPLICHT op de server" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"A new \"key_id\" client option is REQUIRED in the server's clients.conf " +"file, otherwise this computer most likely will not reboot unattended. This " +"option:" +msgstr "" +"In het bestand clients.conf op de server is een nieuwe client-optie \"key_id" +"\" VERPLICHT, anders zal deze computer hoogstwaarschijnlijk niet onbeheerd " +"heropstarten. Deze optie:" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid " ${key_id}" +msgstr " ${key_id}" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"must be added (all on one line!) on the Mandos server host, in the file /etc/" +"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " +"client. You must edit that file on that server and add this option." +msgstr "" +"moet voor deze Mandos-client op de Mandos-servercomputer toegevoegd worden " +"(alles op één regel!) in het bestand /etc/mandos/clients.conf, net voor de " +"optie \"fingerprint\". U moet dat bestand op de server bewerken en deze " +"optie toevoegen." + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " +"session keys. A new TLS key pair has been generated and will be used as " +"identification, but the key ID of the public key needs to be added to the " +"server, since this will now be used to identify the client to the server." +msgstr "" +"Met de komst van GnuTLS 3.6.6 werd Mandos gedwongen te stoppen met het " +"gebruik van OpenPGP-sleutels als TLS-sessiesleutels. Een nieuw TLS-" +"sleutelpaar werd gegenereerd om als identificatie te dienen, maar de sleutel-" +"ID van de publieke sleutel moet op de server toegevoegd worden, aangezien " +"deze nu gebruikt zal worden om de client bij de server te identificeren." === added file 'debian/po/pt.po' --- debian/po/pt.po 1970-01-01 00:00:00 +0000 +++ debian/po/pt.po 2019-10-19 17:37:00 +0000 @@ -0,0 +1,158 @@ +# Translation of mandos debconf messages to European Portuguese +# Copyright (C) 2019 THE mandos'S COPYRIGHT HOLDER +# This file is distributed under the same license as the mandos package. +# +# Américo Monteiro , 2019. +msgid "" +msgstr "" +"Project-Id-Version: mandos 1.8.9-2\n" +"Report-Msgid-Bugs-To: mandos@packages.debian.org\n" +"POT-Creation-Date: 2019-08-05 22:57+0200\n" +"PO-Revision-Date: 2019-10-18 18:45+0000\n" +"Last-Translator: Américo Monteiro \n" +"Language-Team: Portuguese <>\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 2.0\n" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "New client option \"key_id\" is REQUIRED on server" +msgstr "Nova opção \"key_id\" de cliente é NECESSÁRIA no servidor" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"A new \"key_id\" client option is REQUIRED in the clients.conf file, " +"otherwise the client most likely will not reboot unattended. This option:" +msgstr "" +"Uma nova opção de cliente \"key_id\" é NECESSÁRIA no ficheiro clients.conf, " +"caso contrário o mais provável é o cliente não conseguir reinicicar sozinho. " +"Esta opção:" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid " key_id = " +msgstr " key_id = " + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"must be added in the file /etc/mandos/clients.conf, right before the " +"\"fingerprint\" option, for each Mandos client. You must edit that file and " +"add this option for all clients. To see the correct key ID for each client, " +"run this command (on each client):" +msgstr "" +"tem de ser adicionada ao ficheiro /etc/mandos/clients.conf, logo antes " +"da opção \"fingerprint\", para cada cliente Mandos. Você tem de editar esse " +"ficheiro e adicionar esta opção para todos os clientes. Para ver a key ID " +"para cada cliente, corra este comando (em cada cliente):" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid " mandos-keygen -F/dev/null|grep ^key_id" +msgstr " mandos-keygen -F/dev/null|grep ^key_id" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"Note: the clients must all also be using GnuTLS 3.6.6 or later; the server " +"cannot serve passwords for both old and new clients!" +msgstr "" +"Note: os clientes têm de também usar GnuTLS 3.6.6 ou posterior; o servidor " +"não consegue servir palavras passe para ambos clientes antigos e novos!" + +#. Type: note +#. Description +#: ../mandos.templates:1001 +msgid "" +"Rationale: With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP " +"keys as TLS session keys. A new TLS key pair will be generated on each " +"client and will be used as identification, but the key ID of the public key " +"needs to be added to this server, since this will now be used to identify " +"the client to the server." +msgstr "" +"Razão: Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " +"como chaves de sessão TLS. Será gerado um novo par de chaves TLS em cada " +"cliente e será usado como identificação, mas o ID de chave da chave pública " +"precisa de ser adicionada a este servidor, pois esta irá agora ser usada " +"para identificar o cliente no servidor." + +#. Type: note +#. Description +#: ../mandos.templates:2001 +msgid "Bad key IDs have been removed from clients.conf" +msgstr "IDs de chave errados foram removidos de clients.conf" + +#. Type: note +#. Description +#: ../mandos.templates:2001 +msgid "" +"Bad key IDs, which were created by a bug in Mandos client 1.8.0, have been " +"removed from /etc/mandos/clients.conf" +msgstr "" +"IDs de chave errados, que foram criados por um bug no cliente Mandos 1.8.0, " +"foram removidos de /etc/mandos/clients.conf" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "New client option \"${key_id}\" is REQUIRED on server" +msgstr "Nova opção \"${key_id}\" de cliente é NECESSÁRIA no servidor" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"A new \"key_id\" client option is REQUIRED in the server's clients.conf " +"file, otherwise this computer most likely will not reboot unattended. This " +"option:" +msgstr "" +"Uma nova opção \"key_id\" de cliente é NECESSÁRIA no ficheiro clients.conf " +"do servidor, caso contrário, é bem provável que este computador não consiga " +"reiniciar sozinho. Esta opção:" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid " ${key_id}" +msgstr " ${key_id}" + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"must be added (all on one line!) on the Mandos server host, in the file /etc/" +"mandos/clients.conf, right before the \"fingerprint\" option for this Mandos " +"client. You must edit that file on that server and add this option." +msgstr "" +"tem de ser adicionada (toda numa linha) na máquina servidor do Mandos, no " +"ficheiro /etc/mandos/clients.conf, logo antes da opção \"fingerprint\" para " +"este cliente Mandos. Você tem de editar esse ficheiro nesse servidor e " +"adicionar esta opção." + +#. Type: note +#. description +#: ../mandos-client.templates:1001 +msgid "" +"With GnuTLS 3.6.6, Mandos has been forced to stop using OpenPGP keys as TLS " +"session keys. A new TLS key pair has been generated and will be used as " +"identification, but the key ID of the public key needs to be added to the " +"server, since this will now be used to identify the client to the server." +msgstr "" +"Com GnuTLS 3.6.6, o Mandos foi forçado a parar de usar chaves OpenPGP " +"como chaves de sessão TLS. Foi gerado um novo par de chaves TLS e será " +"usado como identificação, mas o ID de chave da chave pública precisa de ser " +"adicionada ao servidor, pois esta irá agora ser usada para identificar o " +"cliente no servidor." + + === modified file 'dracut-module/ask-password-mandos.service' --- dracut-module/ask-password-mandos.service 2019-07-27 10:11:45 +0000 +++ dracut-module/ask-password-mandos.service 2020-02-05 21:39:28 +0000 @@ -48,4 +48,4 @@ ConditionPathIsMountPoint=!/sysroot [Service] -ExecStart=/lib/mandos/password-agent -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem +ExecStart=/lib/mandos/password-agent $PASSWORD_AGENT_OPTIONS -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem $MANDOS_CLIENT_OPTIONS === modified file 'dracut-module/module-setup.sh' --- dracut-module/module-setup.sh 2019-07-27 10:11:45 +0000 +++ dracut-module/module-setup.sh 2020-02-05 21:39:28 +0000 @@ -72,6 +72,10 @@ chmod u-s "${initdir}/${plugindir}/mandos-client" inst "${moddir}/ask-password-mandos.service" \ "${systemdsystemunitdir}/ask-password-mandos.service" + if [ -d /etc/systemd/system/ask-password-mandos.service.d ]; then + inst /etc/systemd/system/ask-password-mandos.service.d + inst_multiple -o /etc/systemd/system/ask-password-mandos.service.d/*.conf + fi if [ ${mandos_user} != 65534 ]; then sed --in-place \ --expression="s,^ExecStart=/lib/mandos/password-agent ,&--user=${mandos_user} ," \ @@ -209,7 +213,7 @@ # Use Diffie-Hellman parameters file if dracut_module_included "systemd"; then sed --in-place \ - --expression='/^ExecStart/s/$/ --dh-params=\/etc\/mandos\/keys\/dhparams.pem/' \ + --expression='/^ExecStart/s/ \$MANDOS_CLIENT_OPTIONS/ --dh-params=\/etc\/mandos\/keys\/dhparams.pem&/' \ "${initdir}/${systemdsystemunitdir}/ask-password-mandos.service" else sed --in-place \ === modified file 'dracut-module/password-agent.c' --- dracut-module/password-agent.c 2019-08-03 12:31:22 +0000 +++ dracut-module/password-agent.c 2020-03-14 03:22:36 +0000 @@ -49,7 +49,7 @@ #include /* EX_USAGE, EX_OSERR, EX_OSFILE */ #include /* errno, error_t, EACCES, ENAMETOOLONG, ENOENT, ENOTDIR, - EEXIST, ECHILD, EPERM, ENOMEM, + ENOMEM, EEXIST, ECHILD, EPERM, EAGAIN, EINTR, ENOBUFS, EADDRINUSE, ECONNREFUSED, ECONNRESET, ETOOMANYREFS, EMSGSIZE, EBADF, @@ -73,6 +73,7 @@ ARGP_ERR_UNKNOWN, ARGP_KEY_ARGS, struct argp, argp_parse(), ARGP_NO_EXIT */ +#include /* SIZE_MAX */ #include /* uid_t, gid_t, close(), pipe2(), fork(), _exit(), dup2(), STDOUT_FILENO, setresgid(), @@ -95,11 +96,10 @@ IN_EXCL_UNLINK, IN_ONLYDIR, struct inotify_event */ #include /* fnmatch(), FNM_FILE_NAME */ -#include /* asprintf(), FILE, fopen(), - getline(), sscanf(), feof(), - ferror(), fclose(), stderr, - rename(), fdopen(), fprintf(), - fscanf() */ +#include /* asprintf(), FILE, stderr, fopen(), + fclose(), getline(), sscanf(), + feof(), ferror(), rename(), + fdopen(), fprintf(), fscanf() */ #include /* GKeyFile, g_key_file_free(), g_key_file_new(), GError, g_key_file_load_from_file(), G_KEY_FILE_NONE, TRUE, G_FILE_ERROR_NOENT, @@ -148,7 +148,7 @@ mono_microsecs next_run; } __attribute__((designated_init)) task_queue; -/* "func_type" - A function type for task functions +/* "task_func" - A function type for task functions I.e. functions for the code which runs when a task is run, all have this type */ @@ -651,6 +651,13 @@ __attribute__((nonnull, warn_unused_result)) bool add_to_queue(task_queue *const queue, const task_context task){ + if((queue->length + 1) > (SIZE_MAX / sizeof(task_context))){ + /* overflow */ + error(0, ENOMEM, "Failed to allocate %" PRIuMAX + " tasks for queue->tasks", (uintmax_t)(queue->length + 1)); + errno = ENOMEM; + return false; + } const size_t needed_size = sizeof(task_context)*(queue->length + 1); if(needed_size > (queue->allocated)){ task_context *const new_tasks = realloc(queue->tasks, @@ -861,6 +868,12 @@ } close(pipefds[1]); + if(pid == -1){ + error(0, errno, "Failed to fork()"); + close(pipefds[0]); + return false; + } + if(not add_to_queue(queue, (task_context){ .func=wait_for_mandos_client_exit, .pid=pid, @@ -1884,6 +1897,29 @@ g_assert_true(queue->tasks[0].func == dummy_func); } +static void test_add_to_queue_overflow(__attribute__((unused)) + test_fixture *fixture, + __attribute__((unused)) + gconstpointer user_data){ + __attribute__((cleanup(cleanup_queue))) + task_queue *queue = create_queue(); + g_assert_nonnull(queue); + g_assert_true(queue->length == 0); + queue->length = SIZE_MAX / sizeof(task_context); /* fake max size */ + + FILE *real_stderr = stderr; + FILE *devnull = fopen("/dev/null", "we"); + g_assert_nonnull(devnull); + stderr = devnull; + const bool ret = add_to_queue(queue, + (task_context){ .func=dummy_func }); + g_assert_true(errno == ENOMEM); + g_assert_false(ret); + stderr = real_stderr; + g_assert_cmpint(fclose(devnull), ==, 0); + queue->length = 0; /* Restore real size */ +} + static void dummy_func(__attribute__((unused)) const task_context task, __attribute__((unused)) @@ -2160,6 +2196,10 @@ } exit(EXIT_SUCCESS); } + if(pid == -1){ + error(EXIT_FAILURE, errno, "Failed to fork()"); + } + int status; waitpid(pid, &status, 0); if(WIFEXITED(status) and (WEXITSTATUS(status) == EXIT_SUCCESS)){ @@ -5918,6 +5958,9 @@ test_fixture *fixture, __attribute__((unused)) gconstpointer user_data){ +#ifndef __amd64__ + g_test_skip("Skipping EMSGSIZE test on non-AMD64 platform"); +#else __attribute__((cleanup(cleanup_close))) const int epoll_fd = epoll_create1(EPOLL_CLOEXEC); g_assert_cmpint(epoll_fd, >=, 0); @@ -5976,6 +6019,7 @@ g_assert_cmpuint((unsigned int)queue->length, ==, 0); g_assert_true(string_set_contains(cancelled_filenames, question_filename)); +#endif } static void test_send_password_to_socket_retry(__attribute__((unused)) @@ -7862,6 +7906,7 @@ test_add("/parse_arguments/mixed", test_parse_arguments_mixed); test_add("/queue/create", test_create_queue); test_add("/queue/add", test_add_to_queue); + test_add("/queue/add/overflow", test_add_to_queue_overflow); test_add("/queue/has_question/empty", test_queue_has_question_empty); test_add("/queue/has_question/false", @@ -8092,10 +8137,10 @@ g_option_context_set_help_enabled(context, FALSE); g_option_context_set_ignore_unknown_options(context, TRUE); - gboolean run_tests = FALSE; + gboolean should_run_tests = FALSE; GOptionEntry entries[] = { { "test", 0, 0, G_OPTION_ARG_NONE, - &run_tests, "Run tests", NULL }, + &should_run_tests, "Run tests", NULL }, { NULL } }; g_option_context_add_main_entries(context, entries, NULL); @@ -8108,5 +8153,5 @@ } g_option_context_free(context); - return run_tests != FALSE; + return should_run_tests != FALSE; } === modified file 'dracut-module/password-agent.xml' --- dracut-module/password-agent.xml 2019-07-27 10:11:45 +0000 +++ dracut-module/password-agent.xml 2019-11-16 15:56:49 +0000 @@ -2,7 +2,7 @@ - + %common; ]> @@ -401,7 +401,7 @@ -&COMMANDNAME; -- /lib/mandos/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem +&COMMANDNAME; -- /lib/mandos/plugins.d/mandos-client --pubkey=/etc/mandos/keys/pubkey.txt --seckey=/etc/mandos/keys/seckey.txt --tls-pubkey=/etc/mandos/keys/tls-pubkey.pem --tls-privkey=/etc/mandos/keys/tls-privkey.pem === modified file 'initramfs-tools-hook' --- initramfs-tools-hook 2018-08-19 14:06:55 +0000 +++ initramfs-tools-hook 2020-02-09 17:57:08 +0000 @@ -142,8 +142,9 @@ # Get DEVICE from initramfs.conf and other files . /etc/initramfs-tools/initramfs.conf for conf in /etc/initramfs-tools/conf.d/*; do - if [ -n `basename \"$conf\" | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ - | grep -v '\.dpkg-.*$'` ]; then + if [ -n "`basename \"$conf\" \ + | grep '^[[:alnum:]][[:alnum:]\._-]*$' \ + | grep -v '\.dpkg-.*$'`" ]; then [ -f "${conf}" ] && . "${conf}" fi done === modified file 'mandos' --- mandos 2019-09-03 19:06:41 +0000 +++ mandos 2020-02-08 14:01:38 +0000 @@ -1,4 +1,4 @@ -#!/usr/bin/python3 -b +#!/usr/bin/python3 -bI # -*- mode: python; after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*- # # Mandos server - give out binary blobs to connecting clients. @@ -78,6 +78,8 @@ import collections import codecs import unittest +import random +import shlex import dbus import dbus.service @@ -91,6 +93,22 @@ if sys.version_info.major == 2: __metaclass__ = type + str = unicode + +# Add collections.abc.Callable if it does not exist +try: + collections.abc.Callable +except AttributeError: + class abc: + Callable = collections.Callable + collections.abc = abc + del abc + +# Add shlex.quote if it does not exist +try: + shlex.quote +except AttributeError: + shlex.quote = re.escape # Show warnings by default if not sys.warnoptions: @@ -122,9 +140,6 @@ # No value found SO_BINDTODEVICE = None -if sys.version_info.major == 2: - str = unicode - if sys.version_info < (3, 2): configparser.Configparser = configparser.SafeConfigParser @@ -1037,7 +1052,8 @@ if self.checker_initiator_tag is not None: GLib.source_remove(self.checker_initiator_tag) self.checker_initiator_tag = GLib.timeout_add( - int(self.interval.total_seconds() * 1000), + random.randrange(int(self.interval.total_seconds() * 1000 + + 1)), self.start_checker) # Schedule a disable() when 'timeout' has passed if self.disable_initiator_tag is not None: @@ -1118,7 +1134,7 @@ if self.checker is None: # Escape attributes for the shell escaped_attrs = { - attr: re.escape(str(getattr(self, attr))) + attr: shlex.quote(str(getattr(self, attr))) for attr in self.runtime_expansions} try: command = self.checker_command % escaped_attrs @@ -1413,8 +1429,7 @@ raise ValueError("Byte arrays not supported for non-" "'ay' signature {!r}" .format(prop._dbus_signature)) - value = dbus.ByteArray(b''.join(chr(byte) - for byte in value)) + value = dbus.ByteArray(bytes(value)) prop(value) @dbus.service.method(dbus.PROPERTIES_IFACE, @@ -2749,7 +2764,7 @@ if command == 'getattr': attrname = request[1] if isinstance(client_object.__getattribute__(attrname), - collections.Callable): + collections.abc.Callable): parent_pipe.send(('function', )) else: parent_pipe.send(( === modified file 'mandos-ctl' --- mandos-ctl 2019-09-03 19:06:41 +0000 +++ mandos-ctl 2020-01-12 01:53:04 +0000 @@ -1,4 +1,4 @@ -#!/usr/bin/python3 -bb +#!/usr/bin/python3 -bbI # -*- after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*- # # Mandos Monitor - Control and monitor the Mandos server @@ -48,20 +48,29 @@ if sys.version_info.major == 2: __metaclass__ = type + str = unicode + +class gi: + """Dummy gi module, for the tests""" + class repository: + class GLib: + class Error(Exception): + pass +dbussy = None +ravel = None +dbus_python = None +pydbus = None try: - import pydbus - import gi - dbus_python = None + import dbussy + import ravel except ImportError: - import dbus as dbus_python - pydbus = None - class gi: - """Dummy gi module, for the tests""" - class repository: - class GLib: - class Error(Exception): - pass + try: + import pydbus + import gi + except ImportError: + import dbus as dbus_python + # Show warnings by default if not sys.warnoptions: @@ -75,7 +84,6 @@ logging.captureWarnings(True) # Show warnings via the logging system if sys.version_info.major == 2: - str = unicode import StringIO io.StringIO = StringIO.StringIO @@ -96,7 +104,9 @@ if options.debug: log.setLevel(logging.DEBUG) - if pydbus is not None: + if dbussy is not None and ravel is not None: + bus = dbussy_adapter.CachingBus(dbussy, ravel) + elif pydbus is not None: bus = pydbus_adapter.CachingBus(pydbus) else: bus = dbus_python_adapter.CachingBus(dbus_python) @@ -487,6 +497,10 @@ self.properties_iface, interface, key, value) + def call_method(self, methodname, busname, objectpath, + interface, *args): + raise NotImplementedError() + class MandosBus(SystemBus): busname_domain = "se.recompile" @@ -682,6 +696,94 @@ return new_object +class dbussy_adapter: + class SystemBus(dbus.SystemBus): + """Use DBussy""" + + def __init__(self, dbussy, ravel): + self.dbussy = dbussy + self.ravel = ravel + self.bus = ravel.system_bus() + + @contextlib.contextmanager + def convert_exception(self, exception_class=dbus.Error): + try: + yield + except self.dbussy.DBusError as e: + # This does what "raise from" would do + exc = exception_class(*e.args) + exc.__cause__ = e + raise exc + + def call_method(self, methodname, busname, objectpath, + interface, *args): + proxy_object = self.get_object(busname, objectpath) + log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath, + interface, methodname, + ", ".join(repr(a) for a in args)) + iface = proxy_object.get_interface(interface) + method = getattr(iface, methodname) + with self.convert_exception(dbus.Error): + value = method(*args) + # DBussy returns values either as an empty list or as a + # tuple: (signature, value) + if value: + return self.type_filter(value[0]) + + def get_object(self, busname, objectpath): + log.debug("D-Bus: Connect to: (busname=%r, path=%r)", + busname, objectpath) + with self.convert_exception(dbus.ConnectFailed): + return self.bus[busname][objectpath] + + def type_filter(self, value): + """Convert the most bothersome types to Python types""" + if isinstance(value, tuple): + if (len(value) == 2 + and isinstance(value[0], + self.dbussy.DBUS.Signature)): + return self.type_filter(value[1]) + elif isinstance(value, self.dbussy.DBUS.ObjectPath): + return str(value) + # Also recurse into dictionaries + elif isinstance(value, dict): + return {self.type_filter(key): + self.type_filter(subval) + for key, subval in value.items()} + return value + + def set_property(self, busname, objectpath, interface, key, + value): + proxy_object = self.get_object(busname, objectpath) + log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname, + objectpath, self.properties_iface, interface, + key, value) + if key == "Secret": + # DBussy wants a Byte Array to be a sequence of + # values, not a byte string + value = tuple(value) + setattr(proxy_object.get_interface(interface), key, value) + + class MandosBus(SystemBus, dbus.MandosBus): + pass + + class CachingBus(MandosBus): + """A caching layer for dbussy_adapter.MandosBus""" + def __init__(self, *args, **kwargs): + self.object_cache = {} + super(dbussy_adapter.CachingBus, self).__init__(*args, + **kwargs) + def get_object(self, busname, objectpath): + try: + return self.object_cache[(busname, objectpath)] + except KeyError: + new_object = super( + dbussy_adapter.CachingBus, + self).get_object(busname, objectpath) + self.object_cache[(busname, objectpath)] = new_object + return new_object + + def commands_from_options(options): commands = list(options.commands) @@ -1530,7 +1632,7 @@ finally: dbus_logger.removeFilter(counting_handler) - self.assertNotIsInstance(e, dbus.ConnectFailed) + self.assertNotIsInstance(e.exception, dbus.ConnectFailed) # Make sure the dbus logger was suppressed self.assertEqual(0, counting_handler.count) @@ -1673,7 +1775,7 @@ self.call_method(bus, "methodname", "busname", "objectpath", "interface") - self.assertNotIsInstance(e, dbus.ConnectFailed) + self.assertNotIsInstance(e.exception, dbus.ConnectFailed) def test_get_converts_to_correct_exception(self): bus = pydbus_adapter.SystemBus( @@ -1774,6 +1876,188 @@ self.assertIs(obj1, obj1b) +class Test_dbussy_adapter_SystemBus(TestCaseWithAssertLogs): + + class dummy_dbussy: + class DBUS: + class ObjectPath(str): + pass + class DBusError(Exception): + pass + + def fake_ravel_func(self, func): + class fake_ravel: + @staticmethod + def system_bus(): + class DBusInterfaceProxy: + @staticmethod + def methodname(*args): + return [func(*args)] + class DBusObject: + @staticmethod + def get_interface(interface): + if interface == "interface": + return DBusInterfaceProxy() + return {"busname": {"objectpath": DBusObject()}} + return fake_ravel + + def call_method(self, bus, methodname, busname, objectpath, + interface, *args): + with self.assertLogs(log, logging.DEBUG): + return bus.call_method(methodname, busname, objectpath, + interface, *args) + + def test_call_method_returns(self): + expected_method_return = Unique() + method_args = (Unique(), Unique()) + def func(*args): + self.assertEqual(len(method_args), len(args)) + for marg, arg in zip(method_args, args): + self.assertIs(marg, arg) + return expected_method_return + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + ret = self.call_method(bus, "methodname", "busname", + "objectpath", "interface", + *method_args) + self.assertIs(ret, expected_method_return) + + def test_call_method_filters_objectpath(self): + def func(): + return method_return + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + method_return = (self.dummy_dbussy.DBUS + .ObjectPath("objectpath")) + ret = self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + self.assertEqual("objectpath", ret) + self.assertNotIsInstance(ret, + self.dummy_dbussy.DBUS.ObjectPath) + + def test_call_method_filters_objectpaths_in_dict(self): + ObjectPath = self.dummy_dbussy.DBUS.ObjectPath + def func(): + return method_return + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + method_return = { + ObjectPath("objectpath_key_1"): + ObjectPath("objectpath_value_1"), + ObjectPath("objectpath_key_2"): + ObjectPath("objectpath_value_2"), + } + ret = self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + expected_method_return = {str(key): str(value) + for key, value in + method_return.items()} + for key, value in ret.items(): + self.assertNotIsInstance(key, ObjectPath) + self.assertNotIsInstance(value, ObjectPath) + self.assertEqual(expected_method_return, ret) + self.assertIsInstance(ret, dict) + + def test_call_method_filters_objectpaths_in_dict_in_dict(self): + ObjectPath = self.dummy_dbussy.DBUS.ObjectPath + def func(): + return method_return + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + method_return = { + ObjectPath("key1"): { + ObjectPath("key11"): ObjectPath("value11"), + ObjectPath("key12"): ObjectPath("value12"), + }, + ObjectPath("key2"): { + ObjectPath("key21"): ObjectPath("value21"), + ObjectPath("key22"): ObjectPath("value22"), + }, + } + ret = self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + expected_method_return = { + "key1": {"key11": "value11", + "key12": "value12"}, + "key2": {"key21": "value21", + "key22": "value22"}, + } + self.assertEqual(expected_method_return, ret) + for key, value in ret.items(): + self.assertIsInstance(value, dict) + self.assertEqual(expected_method_return[key], value) + self.assertNotIsInstance(key, ObjectPath) + for inner_key, inner_value in value.items(): + self.assertIsInstance(value, dict) + self.assertEqual( + expected_method_return[key][inner_key], + inner_value) + self.assertNotIsInstance(key, ObjectPath) + + def test_call_method_filters_objectpaths_in_dict_three_deep(self): + ObjectPath = self.dummy_dbussy.DBUS.ObjectPath + def func(): + return method_return + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + method_return = { + ObjectPath("key1"): { + ObjectPath("key2"): { + ObjectPath("key3"): ObjectPath("value"), + }, + }, + } + ret = self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + expected_method_return = {"key1": {"key2": {"key3": "value"}}} + self.assertEqual(expected_method_return, ret) + self.assertIsInstance(ret, dict) + self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath) + self.assertIsInstance(ret["key1"], dict) + self.assertNotIsInstance(next(iter(ret["key1"].keys())), + ObjectPath) + self.assertIsInstance(ret["key1"]["key2"], dict) + self.assertNotIsInstance( + next(iter(ret["key1"]["key2"].keys())), + ObjectPath) + self.assertEqual("value", ret["key1"]["key2"]["key3"]) + self.assertNotIsInstance(ret["key1"]["key2"]["key3"], + self.dummy_dbussy.DBUS.ObjectPath) + + def test_call_method_handles_exception(self): + def func(): + raise self.dummy_dbussy.DBusError() + + fake_ravel = self.fake_ravel_func(func) + bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel) + + with self.assertRaises(dbus.Error) as e: + self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + + self.assertNotIsInstance(e.exception, dbus.ConnectFailed) + + def test_get_object_converts_to_correct_exception(self): + class fake_ravel_raises_exception_on_connect: + @staticmethod + def system_bus(): + class Bus: + @staticmethod + def __getitem__(key): + if key == "objectpath": + raise self.dummy_dbussy.DBusError() + raise Exception(key) + return {"busname": Bus()} + def func(): + raise self.dummy_dbussy.DBusError() + bus = dbussy_adapter.SystemBus( + self.dummy_dbussy, + fake_ravel_raises_exception_on_connect) + with self.assertRaises(dbus.ConnectFailed): + self.call_method(bus, "methodname", "busname", + "objectpath", "interface") + + class Test_commands_from_options(unittest.TestCase): def setUp(self): === modified file 'mandos-monitor' --- mandos-monitor 2019-09-03 19:06:41 +0000 +++ mandos-monitor 2019-12-04 23:46:59 +0000 @@ -1,4 +1,4 @@ -#!/usr/bin/python3 -bb +#!/usr/bin/python3 -bbI # -*- mode: python; coding: utf-8 -*- # # Mandos Monitor - Control and monitor the Mandos server @@ -35,6 +35,8 @@ import os import warnings import datetime +import locale +import logging import urwid.curses_display import urwid @@ -44,21 +46,24 @@ import dbus -import locale - -import logging - if sys.version_info.major == 2: + __metaclass__ = type str = unicode -locale.setlocale(locale.LC_ALL, '') - -logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL) +log = logging.getLogger(os.path.basename(sys.argv[0])) +logging.basicConfig(level="NOTSET", # Show all messages + format="%(message)s") # Show basic log messages + +logging.captureWarnings(True) # Show warnings via the logging system + +locale.setlocale(locale.LC_ALL, "") + +logging.getLogger("dbus.proxies").setLevel(logging.CRITICAL) # Some useful constants -domain = 'se.recompile' -server_interface = domain + '.Mandos' -client_interface = domain + '.Mandos.Client' +domain = "se.recompile" +server_interface = domain + ".Mandos" +client_interface = domain + ".Mandos.Client" version = "1.8.9" try: @@ -84,7 +89,7 @@ int(fraction*1000000)) # Microseconds -class MandosClientPropertyCache(object): +class MandosClientPropertyCache: """This wraps a Mandos Client D-Bus proxy object, caches the properties and calls a hook function when any of them are changed. @@ -122,15 +127,13 @@ """ def __init__(self, server_proxy_object=None, update_hook=None, - delete_hook=None, logger=None, **kwargs): + delete_hook=None, **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 self._update_timer_callback_tag = None @@ -163,8 +166,7 @@ self.rejected, client_interface, byte_arrays=True)) - self.logger('Created client {}' - .format(self.properties["Name"]), level=0) + log.debug("Created client %s", self.properties["Name"]) def using_timer(self, flag): """Call this method with True or False when timer should be @@ -172,54 +174,49 @@ """ if flag and self._update_timer_callback_tag is None: # Will update the shown timer value every second - self._update_timer_callback_tag = (GLib.timeout_add - (1000, - self.update_timer)) + self._update_timer_callback_tag = ( + GLib.timeout_add(1000, + glib_safely(self.update_timer))) elif not (flag or self._update_timer_callback_tag is None): GLib.source_remove(self._update_timer_callback_tag) self._update_timer_callback_tag = None def checker_completed(self, exitstatus, condition, command): if exitstatus == 0: - self.logger('Checker for client {} (command "{}")' - ' succeeded'.format(self.properties["Name"], - command), level=0) + log.debug('Checker for client %s (command "%s")' + " succeeded", self.properties["Name"], command) self.update() return # Checker failed if os.WIFEXITED(condition): - self.logger('Checker for client {} (command "{}") failed' - ' with exit code {}' - .format(self.properties["Name"], command, - os.WEXITSTATUS(condition))) + log.info('Checker for client %s (command "%s") failed' + " with exit code %d", self.properties["Name"], + command, os.WEXITSTATUS(condition)) elif os.WIFSIGNALED(condition): - self.logger('Checker for client {} (command "{}") was' - ' killed by signal {}' - .format(self.properties["Name"], command, - os.WTERMSIG(condition))) + log.info('Checker for client %s (command "%s") was' + " killed by signal %d", self.properties["Name"], + command, os.WTERMSIG(condition)) self.update() def checker_started(self, command): """Server signals that a checker started.""" - self.logger('Client {} started checker "{}"' - .format(self.properties["Name"], - command), level=0) + log.debug('Client %s started checker "%s"', + self.properties["Name"], command) def got_secret(self): - self.logger('Client {} received its secret' - .format(self.properties["Name"])) + log.info("Client %s received its secret", + self.properties["Name"]) def need_approval(self, timeout, default): if not default: - message = 'Client {} needs approval within {} seconds' + message = "Client %s needs approval within %f seconds" else: - message = 'Client {} will get its secret in {} seconds' - self.logger(message.format(self.properties["Name"], - timeout/1000)) + message = "Client %s will get its secret in %f seconds" + log.info(message, self.properties["Name"], timeout/1000) def rejected(self, reason): - self.logger('Client {} was rejected; reason: {}' - .format(self.properties["Name"], reason)) + log.info("Client %s was rejected; reason: %s", + self.properties["Name"], reason) def selectable(self): """Make this a "selectable" widget. @@ -251,7 +248,7 @@ # Rebuild focus and non-focus widgets using current properties # Base part of a client. Name! - base = '{name}: '.format(name=self.properties["Name"]) + base = "{name}: ".format(name=self.properties["Name"]) if not self.properties["Enabled"]: message = "DISABLED" self.using_timer(False) @@ -279,11 +276,11 @@ timer = datetime.timedelta(0) else: expires = (datetime.datetime.strptime - (expires, '%Y-%m-%dT%H:%M:%S.%f')) + (expires, "%Y-%m-%dT%H:%M:%S.%f")) timer = max(expires - datetime.datetime.utcnow(), datetime.timedelta()) - message = ('A checker has failed! Time until client' - ' gets disabled: {}' + message = ("A checker has failed! Time until client" + " gets disabled: {}" .format(str(timer).rsplit(".", 1)[0])) self.using_timer(True) else: @@ -387,6 +384,16 @@ self.update() +def glib_safely(func, retval=True): + def safe_func(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception: + log.exception("") + return retval + return safe_func + + 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 @@ -400,11 +407,11 @@ return ret -class UserInterface(object): +class UserInterface: """This is the entire user interface - the whole screen with boxes, lists of client widgets, etc. """ - def __init__(self, max_log_length=1000, log_level=1): + def __init__(self, max_log_length=1000): DBusGMainLoop(set_as_default=True) self.screen = urwid.curses_display.Screen() @@ -447,8 +454,6 @@ self.log = urwid.SimpleListWalker([]) self.max_log_length = max_log_length - self.log_level = log_level - # 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) @@ -458,18 +463,19 @@ self.log_visible = True self.log_wrap = "any" + self.loghandler = UILogHandler(self) + self.rebuild() - self.log_message_raw(("bold", - "Mandos Monitor version " + version)) - self.log_message_raw(("bold", - "q: Quit ?: Help")) + self.add_log_line(("bold", + "Mandos Monitor version " + version)) + self.add_log_line(("bold", "q: Quit ?: Help")) - self.busname = domain + '.Mandos' + self.busname = domain + ".Mandos" self.main_loop = GLib.MainLoop() def client_not_found(self, key_id, address): - self.log_message("Client with address {} and key ID {} could" - " not be found".format(address, key_id)) + log.info("Client with address %s and key ID %s could" + " not be found", address, key_id) def rebuild(self): """This rebuilds the User Interface. @@ -486,22 +492,11 @@ self.uilist.append(self.logbox) self.topwidget = urwid.Pile(self.uilist) - def log_message(self, message, level=1): - """Log message formatted with timestamp""" - if level < self.log_level: - return - timestamp = datetime.datetime.now().isoformat() - self.log_message_raw("{}: {}".format(timestamp, message), - level=level) - - def log_message_raw(self, markup, level=1): - """Add a log message to the log buffer.""" - if level < self.log_level: - return + def add_log_line(self, markup): self.log.append(urwid.Text(markup, wrap=self.log_wrap)) if self.max_log_length: if len(self.log) > self.max_log_length: - del self.log[0:len(self.log)-self.max_log_length-1] + del self.log[0:(len(self.log) - self.max_log_length)] self.logbox.set_focus(len(self.logbox.body.contents)-1, coming_from="above") self.refresh() @@ -510,8 +505,7 @@ """Toggle visibility of the log buffer.""" self.log_visible = not self.log_visible self.rebuild() - self.log_message("Log visibility changed to: {}" - .format(self.log_visible), level=0) + log.debug("Log visibility changed to: %s", self.log_visible) def change_log_display(self): """Change type of log display. @@ -522,8 +516,7 @@ self.log_wrap = "clip" for textwidget in self.log: textwidget.set_wrap_mode(self.log_wrap) - self.log_message("Wrap mode: {}".format(self.log_wrap), - level=0) + log.debug("Wrap mode: %s", self.log_wrap) def find_and_remove_client(self, path, interfaces): """Find a client by its object path and remove it. @@ -537,8 +530,7 @@ client = self.clients_dict[path] except KeyError: # not found? - self.log_message("Unknown client {!r} removed" - .format(path)) + log.warning("Unknown client %s removed", path) return client.delete() @@ -557,7 +549,6 @@ proxy_object=client_proxy_object, update_hook=self.refresh, delete_hook=self.remove_client, - logger=self.log_message, properties=dict(ifs_and_props[client_interface])), path=path) @@ -583,6 +574,11 @@ def run(self): """Start the main loop and exit when it's done.""" + log.addHandler(self.loghandler) + self.orig_log_propagate = log.propagate + log.propagate = False + self.orig_log_level = log.level + log.setLevel("INFO") self.bus = dbus.SystemBus() mandos_dbus_objc = self.bus.get_object( self.busname, "/", follow_name_owner_changes=True) @@ -592,11 +588,9 @@ mandos_clients = (self.mandos_serv .GetAllClientsWithProperties()) if not mandos_clients: - self.log_message_raw(("bold", - "Note: Server has no clients.")) + log.warning("Note: Server has no clients.") except dbus.exceptions.DBusException: - self.log_message_raw(("bold", - "Note: No Mandos server running.")) + log.warning("Note: No Mandos server running.") mandos_clients = dbus.Dictionary() (self.mandos_serv @@ -622,8 +616,7 @@ proxy_object=client_proxy_object, properties=client, update_hook=self.refresh, - delete_hook=self.remove_client, - logger=self.log_message), + delete_hook=self.remove_client), path=path) self.refresh() @@ -631,7 +624,7 @@ GLib.io_add_watch( GLib.IOChannel.unix_new(sys.stdin.fileno()), GLib.PRIORITY_DEFAULT, GLib.IO_IN, - self.process_input)) + glib_safely(self.process_input))) self.main_loop.run() # Main loop has finished, we should close everything now GLib.source_remove(self._input_callback_tag) @@ -641,6 +634,8 @@ def stop(self): self.main_loop.quit() + log.removeHandler(self.loghandler) + log.propagate = self.orig_log_propagate def process_input(self, source, condition): keys = self.screen.get_input() @@ -679,26 +674,25 @@ if not self.log_visible: self.log_visible = True self.rebuild() - self.log_message_raw(("bold", - " ". - join(("q: Quit", - "?: Help", - "l: Log window toggle", - "TAB: Switch window", - "w: Wrap (log lines)", - "v: Toggle verbose log", - )))) - self.log_message_raw(("bold", - " " - .join(("Clients:", - "+: Enable", - "-: Disable", - "R: Remove", - "s: Start new checker", - "S: Stop checker", - "C: Checker OK", - "a: Approve", - "d: Deny")))) + self.add_log_line(("bold", + " ".join(("q: Quit", + "?: Help", + "l: Log window toggle", + "TAB: Switch window", + "w: Wrap (log lines)", + "v: Toggle verbose log", + )))) + self.add_log_line(("bold", + " ".join(("Clients:", + "+: Enable", + "-: Disable", + "R: Remove", + "s: Start new checker", + "S: Stop checker", + "C: Checker OK", + "a: Approve", + "d: Deny", + )))) self.refresh() elif key == "tab": if self.topwidget.get_focus() is self.logbox: @@ -707,12 +701,12 @@ self.topwidget.set_focus(self.logbox) self.refresh() elif key == "v": - if self.log_level == 0: - self.log_level = 1 - self.log_message("Verbose mode: Off") + if log.level < logging.INFO: + log.setLevel(logging.INFO) + log.info("Verbose mode: Off") else: - self.log_level = 0 - self.log_message("Verbose mode: On") + log.setLevel(logging.NOTSET) + log.info("Verbose mode: On") # elif (key == "end" or key == "meta >" or key == "G" # or key == ">"): # pass # xxx end-of-buffer @@ -737,12 +731,28 @@ return True +class UILogHandler(logging.Handler): + def __init__(self, ui, *args, **kwargs): + self.ui = ui + super(UILogHandler, self).__init__(*args, **kwargs) + self.setFormatter( + logging.Formatter("%(asctime)s: %(message)s")) + def emit(self, record): + msg = self.format(record) + if record.levelno > logging.INFO: + msg = ("bold", msg) + self.ui.add_log_line(msg) + + ui = UserInterface() try: ui.run() except KeyboardInterrupt: - ui.screen.stop() -except Exception as e: - ui.log_message(str(e)) - ui.screen.stop() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "", BytesWarning) + ui.screen.stop() +except Exception: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "", BytesWarning) + ui.screen.stop() raise === modified file 'mandos.service' --- mandos.service 2017-08-20 14:14:14 +0000 +++ mandos.service 2020-02-07 20:53:34 +0000 @@ -14,7 +14,8 @@ ## If the server's D-Bus interface is disabled, the "BusName" setting ## should be removed or commented out. BusName=se.recompile.Mandos -ExecStart=/usr/sbin/mandos --foreground +EnvironmentFile=/etc/default/mandos +ExecStart=/usr/sbin/mandos --foreground $DAEMON_ARGS Restart=always KillMode=mixed ## Using socket activation won't work, because systemd always does === modified file 'plugin-runner.c' --- plugin-runner.c 2019-07-07 20:50:21 +0000 +++ plugin-runner.c 2020-02-09 03:38:33 +0000 @@ -26,8 +26,8 @@ #define _GNU_SOURCE /* TEMP_FAILURE_RETRY(), getline(), O_CLOEXEC, pipe2() */ #include /* size_t, NULL */ -#include /* malloc(), exit(), EXIT_SUCCESS, - realloc() */ +#include /* malloc(), reallocarray(), realloc(), + EXIT_SUCCESS, exit() */ #include /* bool, true, false */ #include /* fileno(), fprintf(), stderr, STDOUT_FILENO, fclose() */ @@ -179,8 +179,19 @@ /* Resize the pointed-to array to hold one more pointer */ char **new_array = NULL; do { - new_array = realloc(*array, sizeof(char *) - * (size_t) ((*len) + 2)); +#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) + new_array = reallocarray(*array, (size_t)((*len) + 2), + sizeof(char *)); +#else + if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){ + /* overflow */ + new_array = NULL; + errno = ENOMEM; + } else { + new_array = realloc(*array, (size_t)((*len) + 2) + * sizeof(char *)); + } +#endif } while(new_array == NULL and errno == EINTR); /* Malloc check */ if(new_array == NULL){ @@ -708,10 +719,21 @@ custom_argc += 1; { - char **new_argv = realloc(custom_argv, sizeof(char *) - * ((size_t)custom_argc + 1)); +#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) + char **new_argv = reallocarray(custom_argv, (size_t)custom_argc + 1, + sizeof(char *)); +#else + char **new_argv = NULL; + if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){ + /* overflow */ + errno = ENOMEM; + } else { + new_argv = realloc(custom_argv, ((size_t)custom_argc + 1) + * sizeof(char *)); + } +#endif if(new_argv == NULL){ - error(0, errno, "realloc"); + error(0, errno, "reallocarray"); exitstatus = EX_OSERR; free(new_arg); free(org_line); === modified file 'plugins.d/plymouth.c' --- plugins.d/plymouth.c 2019-07-27 10:11:45 +0000 +++ plugins.d/plymouth.c 2020-02-09 03:38:33 +0000 @@ -44,7 +44,7 @@ STDERR_FILENO, execv(), access() */ #include /* free(), EXIT_FAILURE, realloc(), EXIT_SUCCESS, malloc(), _exit(), - getenv() */ + getenv(), reallocarray() */ #include /* scandir(), alphasort() */ #include /* intmax_t, strtoumax(), SCNuMAX */ #include /* struct stat, lstat() */ @@ -204,9 +204,20 @@ char **tmp; int i = 0; for (; argv[i] != NULL; i++){ - tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2)); +#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26) + tmp = reallocarray(new_argv, ((size_t)i + 2), + sizeof(const char *)); +#else + if(((size_t)i + 2) > (SIZE_MAX / sizeof(const char *))){ + /* overflow */ + tmp = NULL; + errno = ENOMEM; + } else { + tmp = realloc(new_argv, ((size_t)i + 2) * sizeof(const char *)); + } +#endif if(tmp == NULL){ - error_plus(0, errno, "realloc"); + error_plus(0, errno, "reallocarray"); free(new_argv); _exit(EX_OSERR); }