[PATCH] Add a jami-daemon service.

  • Done
  • quality assurance status badge
Details
3 participants
  • Leo Prikler
  • Maxim Cournoyer
  • Maxime Devos
Owner
unassigned
Submitted by
Maxim Cournoyer
Severity
normal
M
M
Maxim Cournoyer wrote on 17 Apr 2021 22:04
[PATCH 0/1] [preview] Add a jami-daemon service.
(address . guix-patches@gnu.org)(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
20210417200414.18050-1-maxim.cournoyer@gmail.com
Hello,

This is an early version of a jami-daemon service that can be used to host
rendezvous points easily on servers, the conferencing feature of Jami.

It seems to work well in practice, but I've been struggling to fix a remaining
test failure with the stop action. It's proving difficult to pinpoint what
the problem is.

Another thing that will need to be addressed is adding the documentation.

Thanks,

Maxim

Maxim Cournoyer (1):
services: Add a service for the Jami daemon.

gnu/local.mk | 6 +-
gnu/services/telephony.scm | 283 ++++++++++++++++++-
gnu/tests/data/jami-dummy-account.dat | 391 ++++++++++++++++++++++++++
gnu/tests/telephony.scm | 202 +++++++++++++
4 files changed, 878 insertions(+), 4 deletions(-)
create mode 100644 gnu/tests/data/jami-dummy-account.dat
create mode 100644 gnu/tests/telephony.scmb

--
2.31.1
M
M
Maxim Cournoyer wrote on 17 Apr 2021 22:06
[PATCH 1/1] services: Add a service for the Jami daemon.
(address . 47849@debbugs.gnu.org)(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
20210417200617.18182-1-maxim.cournoyer@gmail.com
* gnu/services/telephony.scm (serialize-boolean)
(serialize-string-list, string-list?): New variables.
(maybe-string-list, jami-daemon-configuration): New syntax.
(%jami-daemon-accounts): New variable.
(jami-daemon-configuration->command-line-arguments): New procedure.
(jami-dbus-session-activation): Likewise.
(define-with-retries, define-send-dbus)
(jami-daemon-shepherd-services, jami-daemon-service-type): New variables.
* gnu/tests/data/jami-dummy-account.dat: New file.
* gnu/tests/telephony.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Register the new test file.
(dist_patch_DATA): Register the new data file.
---
gnu/local.mk | 6 +-
gnu/services/telephony.scm | 283 ++++++++++++++++++-
gnu/tests/data/jami-dummy-account.dat | 391 ++++++++++++++++++++++++++
gnu/tests/telephony.scm | 202 +++++++++++++
4 files changed, 878 insertions(+), 4 deletions(-)
create mode 100644 gnu/tests/data/jami-dummy-account.dat
create mode 100644 gnu/tests/telephony.scm

Toggle diff (400 lines)
diff --git a/gnu/local.mk b/gnu/local.mk
index 50b11a8ca2..4a412f5a69 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -19,7 +19,7 @@
# Copyright © 2018 Amirouche Boubekki <amirouche@hypermove.net>
# Copyright © 2018, 2019, 2020, 2021 Oleg Pykhalov <go.wigust@gmail.com>
# Copyright © 2018 Stefan Stefanovi? <stefanx2ovic@gmail.com>
-# Copyright © 2018, 2020 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+# Copyright © 2018, 2020, 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
# Copyright © 2019, 2020 Guillaume Le Vaillant <glv@posteo.net>
# Copyright © 2019, 2020 John Soo <jsoo1@asu.edu>
# Copyright © 2019 Jonathan Brielmaier <jonathan.brielmaier@web.de>
@@ -710,6 +710,7 @@ GNU_SYSTEM_MODULES = \
%D%/tests/security-token.scm \
%D%/tests/singularity.scm \
%D%/tests/ssh.scm \
+ %D%/tests/telephony.scm \
%D%/tests/version-control.scm \
%D%/tests/virtualization.scm \
%D%/tests/web.scm
@@ -1829,7 +1830,8 @@ dist_patch_DATA = \
%D%/packages/patches/ytnef-CVE-2021-3403.patch \
%D%/packages/patches/ytnef-CVE-2021-3404.patch \
%D%/packages/patches/zstd-CVE-2021-24031_CVE-2021-24032.patch \
- %D%/packages/patches/zziplib-CVE-2018-16548.patch
+ %D%/packages/patches/zziplib-CVE-2018-16548.patch \
+ %D%/tests/data/jami-dummy-account.dat
MISC_DISTRO_FILES = \
%D%/packages/ld-wrapper.in
diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm
index e1259cc2df..9e821f7286 100644
--- a/gnu/services/telephony.scm
+++ b/gnu/services/telephony.scm
@@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 nee <nee-git@hidamari.blue>
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -17,16 +18,31 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu services telephony)
- #:use-module (gnu services)
+ #:use-module ((gnu services) #:hide (delete))
+ #:use-module (gnu services configuration)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:use-module (gnu packages admin)
+ #:use-module (gnu packages glib)
+ #:use-module (gnu packages jami)
#:use-module (gnu packages telephony)
#:use-module (guix records)
+ #:use-module (guix modules)
+ #:use-module (guix packages)
#:use-module (guix gexp)
#:use-module (srfi srfi-1)
#:use-module (ice-9 match)
- #:export (murmur-configuration
+ #:export (jami-daemon-configuration
+ jami-daemon-configuration-jami-daemon
+ jami-daemon-configuration-dbus
+ jami-daemon-configuration-enable-logging?
+ jami-daemon-configuration-debug?
+ jami-daemon-configuration-auto-answer?
+ jami-daemon-configuration-account-archives
+
+ jami-daemon-service-type
+
+ murmur-configuration
make-murmur-configuration
murmur-configuration?
murmur-configuration-package
@@ -74,6 +90,269 @@
murmur-service-type))
+
+
+;;;
+;;; Jami daemon.
+;;;
+
+;;; XXX: These dummy definitions is because there's no way to disable the
+;;; serialization code from define-configuration.
+(define (serialize-boolean option value) "")
+(define (serialize-string-list field-name val) "")
+
+;;; Copied from (gnu services messaging).
+(define (string-list? val)
+ (and (list? val)
+ (and-map (lambda (x)
+ (or (computed-file? x) ;XXX: for tests
+ (and (string? x) (not (string-index x #\,)))))
+ val)))
+(define-maybe string-list)
+
+(define-configuration jami-daemon-configuration
+ (jami-daemon
+ (package libring)
+ "The Jami daemon package to use.")
+ (dbus
+ (package dbus)
+ "The D-Bus package to use to start the required D-Bus session.")
+ (enable-logging?
+ (boolean #true)
+ "Whether to enable logging to syslog.")
+ (debug?
+ (boolean #false)
+ "Whether to enable debug level messages.")
+ (auto-answer?
+ (boolean #false)
+ "Whether to force automatic answer to incoming calls.")
+ (account-archives
+ (maybe-string-list 'disabled)
+ "A list of Jami account archive (backup) file names to be (re-)provisioned
+every time the Jami daemon service starts. These Jami account backups should
+@emp{not} be encrypted and their file should be made readable only to the
+@samp{jami} user (i.e., not in the store), to guard against leaking the secret
+key material of the Jami accounts they contain. When providing this field,
+the account directories under @file{/var/lib/jami/} are recreated every time
+the service starts, ensuring a consistent state."))
+
+(define %jami-daemon-accounts
+ (list (user-group (name "jami") (system? #t))
+ (user-account
+ (name "jami")
+ (group "jami")
+ (system? #t)
+ (comment "Jami daemon user")
+ (home-directory "/var/lib/jami"))))
+
+(define (jami-daemon-configuration->command-line-arguments config)
+ "Derive the command line arguments to used to launch the Jami daemon from
+CONFIG, a <jami-daemon-configuration> object."
+ (match-record config <jami-daemon-configuration>
+ (jami-daemon dbus enable-logging? debug? auto-answer?)
+ `(,(file-append jami-daemon "/lib/ring/dring")
+ "--persistent" ;stay alive after client quits
+ ,@(if enable-logging?
+ '() ;logs go to syslog by default
+ (list "--console")) ;else stdout/stderr
+ ,@(if debug?
+ (list "--debug")
+ '())
+ ,@(if auto-answer?
+ (list "--auto-answer")
+ '()))))
+
+(define (jami-dbus-session-activation config)
+ "Create a directory to hold the Jami D-Bus session socket."
+ (with-imported-modules (source-module-closure '((gnu build activation)))
+ #~(begin
+ (use-modules (gnu build activation))
+ (let ((user (getpwnam "jami")))
+ (mkdir-p/perms "/var/run/jami" user #o700)))))
+
+;; Local definitions to expand in source form in G-exps.
+(define define-with-retries
+ '(define-syntax-rule (with-retries n delay body ...)
+ "Retry the code in BODY up to N times until it returns #t,
+else #f. A delay of DELAY seconds is inserted before each retry."
+ (let loop ((attempts 0))
+ (if (< attempts n)
+ (or (begin
+ body ...) ;return #t on success
+ (begin
+ (sleep delay) ;else wait and retry
+ (loop (+ 1 attempts))))
+ #f)))) ;maximum number of attempts reached
+
+(define define-send-dbus
+ '(define (send-dbus dbus-send interface method . arguments)
+ "Print the response and return #t on success, else #f."
+ (let* ((command `(,dbus-send
+ "--bus=unix:path=/var/run/jami/bus"
+ "--print-reply"
+ "--dest=cx.ring.Ring"
+ "/cx/ring/Ring/ConfigurationManager" ;object path
+ ,(string-append interface "." method)
+ ,@arguments))
+ (pid (fork+exec-command command
+ #:user "jami"
+ #:group "jami")))
+ (zero? (cdr (waitpid pid))))))
+
+(define (jami-daemon-shepherd-services config)
+ "Return a <shepherd-service> running the Jami daemon."
+ (let* ((jami-daemon (jami-daemon-configuration-jami-daemon config))
+ (dbus (jami-daemon-configuration-dbus config))
+ (dbus-daemon (file-append dbus "/bin/dbus-daemon"))
+ (dbus-send (file-append dbus "/bin/dbus-send"))
+ (accounts (jami-daemon-configuration-account-archives config))
+ (declarative-mode? (not (eq? 'disabled accounts))))
+
+ (list (shepherd-service
+ (documentation "Run a D-Bus session for the Jami daemon.")
+ (provision '(jami-daemon-dbus-session))
+ ;; The requirement on dbus-system is to ensure other required
+ ;; activation for D-Bus, such as a /etc/machine-id file.
+ (requirement '(dbus-system syslogd))
+ (start
+ #~(lambda args
+ #$define-with-retries
+
+ (define pid (fork+exec-command
+ (list #$dbus-daemon "--session"
+ "--address=unix:path=/var/run/jami/bus"
+ "--nofork" "--syslog-only" "--nopidfile")
+ #:user "jami"
+ #:group "jami"
+ #:environment-variables
+ ;; This is so that the cx.ring.Ring service D-Bus
+ ;; definition is found by dbus-send.
+ (list (string-append "XDG_DATA_DIRS="
+ #$jami-daemon "/share"))))
+
+ ;; XXX: This manual synchronization probably wouldn't be
+ ;; needed if we were using a PID file, but providing it via a
+ ;; customized config file with the <pidfile> would not
+ ;; override the one inherited from the base config of D-Bus.
+ (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
+ (with-retries 20 1 (catch 'system-error
+ (lambda ()
+ (connect sock AF_UNIX
+ "/var/run/jami/bus")
+ (close-port sock)
+ #t)
+ (lambda args
+ #f))))
+
+ pid))
+ (stop #~(make-kill-destructor)))
+
+ (shepherd-service
+ (documentation "Run the Jami daemon.")
+ (provision '(jami-daemon dring))
+ (requirement '(jami-daemon-dbus-session))
+ (modules `((ice-9 ftw)
+ (srfi srfi-1)
+ (srfi srfi-26)
+ ,@%default-modules))
+ (start
+ #~(lambda args
+ #$define-with-retries
+ #$define-send-dbus
+
+ (when #$declarative-mode?
+ ;; Clear the Jami configuration and accounts, to enforce the
+ ;; declared state.
+ (catch #t
+ (lambda ()
+ (delete-file-recursively "/var/lib/jami/.cache/jami")
+ (delete-file-recursively "/var/lib/jami/.config/jami")
+ (delete-file-recursively "/var/lib/jami/.local/share/jami")
+ (delete-file-recursively "/var/lib/jami/accounts"))
+ (lambda args
+ #t))
+ ;; Copy the Jami accounts from somewhere readable by root to
+ ;; a place only the jami user can read.
+ (let* ((accounts-dir "/var/lib/jami/accounts/")
+ (pwd (getpwnam "jami"))
+ (user (passwd:uid pwd))
+ (group (passwd:gid pwd)))
+ (mkdir-p accounts-dir)
+ (chown accounts-dir user group)
+ (for-each (lambda (f)
+ (let ((dest (string-append accounts-dir
+ (basename f))))
+ (copy-file f dest)
+ (chown dest user group)))
+ '#$accounts)))
+
+ ;; Start the daemon.
+ (define daemon-pid
+ (fork+exec-command
+ '#$(jami-daemon-configuration->command-line-arguments config)
+ #:user "jami"
+ #:group "jami"
+ #:environment-variables
+ (list (string-append "DBUS_SESSION_BUS_ADDRESS="
+ "unix:path=/var/run/jami/bus"))))
+
+ ;; Wait until it's reachable via D-Bus.
+ (with-retries 20 1 (send-dbus #$dbus-send
+ "org.freedesktop.DBus.Peer"
+ "Ping"))
+
+ ;; Provision the accounts.
+ (when #$declarative-mode?
+ (or (every identity
+ (map (lambda (archive)
+ (send-dbus
+ #$dbus-send
+ "cx.ring.Ring.ConfigurationManager"
+ "addAccount"
+ (string-append
+ "dict:string:string:Account.archivePath,"
+ archive
+ ",Account.type,RING")))
+ (map (cut string-append
+ "/var/lib/jami/accounts/" <>)
+ (scandir "/var/lib/jami/accounts/"
+ (lambda (f)
+ (not (member f '("." ".."))))))))
+ (format (current-error-port)
+ "error: failed provisioning the jami accounts")))
+
+ ;; Finally, return the PID of the dring process.
+ daemon-pid))
+ (stop
+ #~(lambda (pid . args)
+ (kill pid SIGTERM)
+ ;; Wait for the process to exit; this prevents overlapping
+ ;; processes when issuing 'herd restart'.
+ (waitpid pid)
+ #f))))))
+
+(define jami-daemon-service-type
+ (service-type
+ (name 'jami-daemon)
+ (default-value (jami-daemon-configuration))
+ (extensions
+ (list (service-extension shepherd-root-service-type
+ jami-daemon-shepherd-services)
+ (service-extension account-service-type
+ (const %jami-daemon-accounts))
+ (service-extension activation-service-type
+ jami-dbus-session-activation)))
+ (description "Run the Jami daemon (@command{dring}). This service is
+geared toward the use case of hosting Jami rendezvous points over a headless
+server. If you use Jami on your local machine, you may prefer to setup a user
+Shepherd service for it instead; this way, the daemon will be shared via your
+normal user D-Bus session bus.")))
+
+
+;;;
+;;; Murmur.
+;;;
+
;; https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini
(define-record-type* <murmur-configuration> murmur-configuration
diff --git a/gnu/tests/data/jami-dummy-account.dat b/gnu/tests/data/jami-dummy-account.dat
new file mode 100644
index 0000000000..026890052d
--- /dev/null
+++ b/gnu/tests/data/jami-dummy-account.dat
@@ -0,0 +1,391 @@
+;;; JSON extracted from an actual Jami account and processed with
+;;; Emacs/guile-json.
+(define %jami-account-content-sexp
+ '(("RINGCAKEY" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3F\
+oa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzBxWUozSkYvTzhQRGEKRnUwRnpRcHBCaD\
+gybGJMdURrNTlVU0I0MUJSaS9kdDZGV1BRN29YOVpsY25vNGZzM2dmUHQ0dU1hRVBkVFBGKwowbGN2Q\
+jc2cytQTEFlcjlOZGpVQzQ2ZXp0UnNiNE9aQXc4ZUk1M3EwSU04QWJFd0o0ZjllLzBmQUFueHgrK3Qw\
+CmZDeGV1YTBUaVBqVHBpZVJMNmpwQkd5UGI2Qk5pU2ViTkZCNzJOMTBnbzI4REVQYlhkNE9CNkN1blZ\
+5RGZKSU0KOC9PRy8rMndNamI4WkRwT3JrYy93U2ZHbnQyZXA3U0xKSkgwOFIzV1FKWklsSndrcGdLTH\
+FRakVwWFNpclN4dAozSkdtdXdBdE9LaXFFTXh1R043elV3ZlNINEtHdkRaUFNkZklZVXJ3eEp4aDJZZ\
+3lobG5RRC9SSnhRN3d4YlJBCjFhMUZVV0FzbDhLODk5cEtESk1GL09VOWZMRUx6QlViblpaRDRmSlg1\
+NmcyTlluUnJobS94NG9FbFk0MFNYMUUKcHYzb01hNnZrVGN5RjJnUFhKL2FkUVJoS0dFaGRjaHBpeDl\
+5UVphaDFCUFBGYW5jNzBMcjhOaDZJeHFNQ1hiMQozMG9vWHpWZmZNMVFOd28rL3hzRnBlRkRqUTAxQ0\
+9pdWZocitKREcyc0txb0o0V0JwYVhubWI1YXVrVWUvV1RKCjAxVmRyaEkvSVExd3V4QzNMMnpac3dVU\
+1NTaDk0aXg1M0hpU3pWbkI5UkxmaVhZUUVCcFEyNHVoRTdiYlo0bm0KZTczZC9zenpPTXMzYUt3OWtW\
+a2VLMTVtYWhSVWZjdEdhSVQxTkhGWUNYYXByaWExakdNdVpmSk1pSUtZUzNidQpMbUhZckF6dEptNDZ\
+0aHpjdnN3NHlhMnFoa2xUUlFJREFRQUJBb0lDQVFDaHZaUm85KzZ5aFhFTHZ6U3FXZHcxCkZGOERibG\
+hIMmhVWkNuV0kxMDM5SmdyRkxMczFSU1krSzg1aFZYMk9hV1VTNk44TmNCYzUyL1hrdFltS09HUFQKM\
+WZqMnE2M3pPcDNSSFdGNWVPMXhNeExRN3JZSDhqMGZZTFFTUytKemdwb3ZRVnJLSXkrb21JSSt3aUN6\
+R1laRApGQUM0ODJzL0J5MHdtRjVjdC9JTEdIeVY3ZXNVUlo1Vi9iL0ltQzUwQ1lDUWpQR2xBb3JkeUx\
+1MHp2NjZZUXc2CkQycTg0VHAyVUg3SExEVmhFNytUbDg4Q04xWll0VGtpSkthbkNpMFVmbStPKzJFM0\
+5HM01hajk1aDl2NktqYkoKUlkxeTNDRTVmQmkyUFNLbVVzRjN3SzdhbzJDRks5MTgybmlxL2FaNm5WO\
+Xc3NmVrRjhEOWUvS1pqUE5ZT0xkaApFczBSL2laV3RpbUx2RHdXQWNWNFNnSFFjNXJvNU9yOEFUS1ZK\
+VmlzZGFuWXkvdUhmVXZWN3U5cDVLK2E4SHU2CllabW13ZTh4bnF5M3V2M0VabE9LY20zTnZvWllVMnJ\
+HUUFQQW1sWWQ4WlRsZGxPa1JCSGxxYzllMmJuSnNTQW8KNUhhS0N3aDJsWmZpalVGNXFrMXNQcm1kN3\
+BlMld2VVV3QmVuSjJnS1ZoTE5VVGtHWmtTWGhzNlV3WWRRMjVtRQppQzl6WjhXNkQ2OXBvb0lsTTVXT\
+01ySEs0Rng1ck9vT05kUHQ4NEk1bTI3cnpnbXM4QnJXVUlGLytZZjJ0bGdmClBIR0V4c3ZCK3JRQk52\
+WHU3dXoxcVdFTlJTL2YwR2E3dVF4ZW5sZ2dubHc5M1pNOW1GWXpXb1RpdWFmdnphTnAKWEsrTEVrV2F\
+RYUs1Q0VaNEhmUlhBUUtDQVFFQTUyK240OUxQODlyQkR2bFdsTkxNanJqTDdSb0xyQ3FVZGpQWQpyT1\
+hZS3ZkTkxyS2NTc1hNdkY4YW5HQng1UG5oVDZGY05ic2dzQ3BUUXowMThZYmcrbUUxQno5ZTdFNTJGZ\
+i9NCk9BbWZqSllXUnZueUtiNnB3SGlvOHlXUXlVVk1zZU1CcmpvWk1kNkpPZEZ0K2JITHBWOS9iSkdR\
+a3NTRE04WTkKbWxGQUlUL0gyNTh1K1ZKTWsrT0prU28zZmJQSk5Ja1Q2WVBKVmNaSnZTRGI5QU83WDF\
+lendCOXVRL0FEblZ6YwpSQkJOUVZaTStZS2ZNWFJBdmFuWnlmWFFwaUxCQW8rRVRPSHJCR1dDRUhtSF\
+RCaEZIMTkyamtxNlcrTStvS0R1Cm1xMitMc2hZWTVFc2NpL2hPOVZjK2FCM0hhaGliME03M092MHFNc\
+WZoTncyU1BncHdRS0NBUUVBeDlaR1gxQnQKL3MwdGtNcGV1QWhlWjFqTklnZmFEY0RRTWlmU0k4QjRx\
+WUhiL1hOREQ5NjFQME9zMDdCN2wzNE1iZ3U2QlNwcwpXdSt0Y1hjSFlqQVJUc0Qzd0pSaVRIb0RSQzR\
+YYkxEa2pHRUVCVzRKbFVqZTA1QWZrU0QrdkZSMkJwZStxQlBLCm5yb3Mwd1BWL3RXa3MzY2VFOUlBTV\
+pWWDhQeFA3RVNXbitVZDJEWkZhcVFLb0JybHZXRXhxelpYUEJSVjhoS3QKcFBqWnFkZXFQLzhUZTBtS\
+zh5MEVreXVXOWhFdGZ2Sm5HWXhMNStrSG9xd2hQVk1tODZ5YlZNVHRQYWJTdCtPUwp0WHhJTE9RMWRN\
+QkFabzRxSnNkUUZJcTJnSHA5WFYwa2ZNUms1ajdJT1Q4c2Z6TlpKVkRNK1k5VHVlSGJXSnduCnZsWld\
+VZ2NVZTlBaWhRS0NBUUVBbFJaK2h1ckUvNGdLR2dWUld5bTRrTEJHM2dTTFJHdGhuQXVtSnlzaFoveE\
+wKZ2l1Wk55bll5L2hRQWpDMjdoUnlxb04rRFRid3hjdGVPOUJ3c1poNzBZOVJROHYwOERGVExMVE43O\
+E56UG5OcApBbXY5TGhzZTYxaFBMZU1qTkNVcVZPV3hyWFRMeWk1YkpCM2Z4SnhlWGJmNU5BMUpudUpz\
+eXF1SC82TWJ0cytKCmhkY3p3WFRjMCtBZVBKOS9nOENQZXdKYkMzRFVBQ2R1VlNHWHo4ZWZxcm1xbDd\
+jbnB5ZzBpK2pJRkNpVU8rVEcKVFcxeDg3KzUvUFF2MGtSQ0Z1UUloZ2ZCNkcwWW9vcHBrUWRZdXhKZl\
+pPaHdUUldpbTVMMlF5K294WWZySGVQOQozSlltbGFCMmJiN3kxL1FoQjcvek9VMk1nTEtYdHl4Z09ve\
+EpoQlFwZ1FLQ0FRQkIwSUE4dy9CMkNuMEhRcDhQClhUSTZOelRZRUYzd1NhQkg1SFdBOE5MTWdNaERJ\
+TUxsWnlPcVFrK1pLSGFMM2llWjFxTGRNS3VmQjNESC9idWcKeXRQb2J
This message was truncated. Download the full message here.
M
M
Maxime Devos wrote on 18 Apr 2021 13:41
1e9354c0744afc2d5d11d3eeecaba31f62e59e65.camel@telenet.be
Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
+ (delete-file-recursively "/var/lib/jami/.cache/jami")
+ (delete-file-recursively "/var/lib/jami/.config/jami")
+ (delete-file-recursively "/var/lib/jami/.local/share/jami")
+ (delete-file-recursively "/var/lib/jami/accounts"))

You might want to verify whether /var/lib/jami/{.cache,.config,.local/share,.local}
aren't symbolic links. That way, if the Jami daemon is compromised (due to buffer
overflow --> arbitrary code execution or something), the attacker can't trick the
shepherd service into deleting arbitrary directories.

This attack is _not_ blocked by fs.protected_symlinks. From the sysctl documentation:
When set to "1" symlinks are permitted to be followed only when outside
a sticky world-writable directory, or [...]

/var/lib/jami is not world-writable (I'd hope).

Example scenario:
* the jami daemon has a security bug that allows arbitrary code execution
within the daemon
* the attacker exploits this
* now the attacker can modify everything under /var/lib/jami
* the attacker deletes /var/lib/jami/.config and replaces it with a symlink
to /home/ANY-USER/.config
* eventually, the system reboots
* (delete-file-recursively "/var/lib/jami/.config/jami") is run.
As "/var/lib/jami/.config" points to "/home/ANY-USER/.config",
this means "/home/ANY-USER/.config/jami" is deleted.
* thus, ANY-USER loses their jami configuration

Does that makes sense to you?

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYHwayxccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7qSCAQDJFxwHTTtk3NefZrfxoYn7wiXU
bKKWE4wsy018Jz0yPAD/c/j0Ag79AkvEljYjkUgJSS4QjNo7MRPHVgK0cUSR7wE=
=pUai
-----END PGP SIGNATURE-----


M
M
Maxime Devos wrote on 18 Apr 2021 13:47
8f42495246e684121605e7cef2462a068130d5cc.camel@telenet.be
Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
Toggle quote (10 lines)
> + ;; Start the daemon.
> + (define daemon-pid
> + (fork+exec-command
> + '#$(jami-daemon-configuration->command-line-arguments config)
> + #:user "jami"
> + #:group "jami"
> + #:environment-variables
> + (list (string-append "DBUS_SESSION_BUS_ADDRESS="
> + "unix:path=/var/run/jami/bus"))))

It would be nice if this could be run in a container
that only has access to the relevant parts of the file system
(and not, say, /run/setuid-programs). See, e.g., gnu/build/linux-container.scm.

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYHwcNBccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7hx2AQDtvj1EkLuPKUa7vjy9nYW3l80/
mtgi+KNSxLp9hPMxtgD+JkkjJ8LUfJEgkI24ezKYQyhVyVvonAVA8hPNH2byCgs=
=7Vh7
-----END PGP SIGNATURE-----


M
M
Maxim Cournoyer wrote on 19 Apr 2021 14:07
(name . Maxime Devos)(address . maximedevos@telenet.be)(address . 47849@debbugs.gnu.org)
87czuqiiki.fsf@gmail.com
Hi Maxime!

Maxime Devos <maximedevos@telenet.be> writes:

Toggle quote (11 lines)
> Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
> + (delete-file-recursively "/var/lib/jami/.cache/jami")
> + (delete-file-recursively "/var/lib/jami/.config/jami")
> + (delete-file-recursively "/var/lib/jami/.local/share/jami")
> + (delete-file-recursively "/var/lib/jami/accounts"))
>
> You might want to verify whether /var/lib/jami/{.cache,.config,.local/share,.local}
> aren't symbolic links. That way, if the Jami daemon is compromised (due to buffer
> overflow --> arbitrary code execution or something), the attacker can't trick the
> shepherd service into deleting arbitrary directories.

It would only be able to delete directories that are world writable
though, right? Seems the opportunity to cause damage is limited, but
it's a simple check to add, so I'll do it. What about if the daemon was
run in a container (your suggestion in a following email, to which I
agree would be a good thing)? It would prevent this kind of attack,
right?

Toggle quote (6 lines)
> This attack is _not_ blocked by fs.protected_symlinks. From the sysctl documentation:
> When set to "1" symlinks are permitted to be followed only when outside
> a sticky world-writable directory, or [...]
>
> /var/lib/jami is not world-writable (I'd hope).

No, it's only readable/writable by the 'jami' user of the service:

$ sudo ls -ald /var/lib/jami
drwx------ 1 jami jami 80 Apr 19 00:38 /var/lib/jami

Toggle quote (13 lines)
> Example scenario:
> * the jami daemon has a security bug that allows arbitrary code execution
> within the daemon
> * the attacker exploits this
> * now the attacker can modify everything under /var/lib/jami
> * the attacker deletes /var/lib/jami/.config and replaces it with a symlink
> to /home/ANY-USER/.config
> * eventually, the system reboots
> * (delete-file-recursively "/var/lib/jami/.config/jami") is run.
> As "/var/lib/jami/.config" points to "/home/ANY-USER/.config",
> this means "/home/ANY-USER/.config/jami" is deleted.
> * thus, ANY-USER loses their jami configuration

The cleanup code is run as the 'jami' user, so I don't think it'd be
able to touch anything under /home/ANY-OTHER-USER/, unless they manually
loosened permissions on their home directory (shooting themselves in the
foot).

Toggle quote (2 lines)
> Does that makes sense to you?

It does! Thanks for explaining.

Maxim
M
M
Maxim Cournoyer wrote on 19 Apr 2021 14:08
(name . Maxime Devos)(address . maximedevos@telenet.be)(address . 47849@debbugs.gnu.org)
878s5eiiil.fsf@gmail.com
Hi again,

Maxime Devos <maximedevos@telenet.be> writes:

Toggle quote (15 lines)
> Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
>> + ;; Start the daemon.
>> + (define daemon-pid
>> + (fork+exec-command
>> + '#$(jami-daemon-configuration->command-line-arguments config)
>> + #:user "jami"
>> + #:group "jami"
>> + #:environment-variables
>> + (list (string-append "DBUS_SESSION_BUS_ADDRESS="
>> + "unix:path=/var/run/jami/bus"))))
>
> It would be nice if this could be run in a container
> that only has access to the relevant parts of the file system
> (and not, say, /run/setuid-programs). See, e.g., gnu/build/linux-container.scm.

I agree! The opendht service is already doing so, so it should not be
too difficult I believe. I'll try looking into it, after the 1.3.0
release is done.

Thank you!

Maxim
M
M
Maxime Devos wrote on 19 Apr 2021 16:41
(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)(address . 47849@debbugs.gnu.org)
9fc5f3f454a77ccf2a8e09fa602755166c03abf3.camel@telenet.be
Maxim Cournoyer schreef op ma 19-04-2021 om 08:07 [-0400]:
Toggle quote (17 lines)
> Hi Maxime!
>
> Maxime Devos <maximedevos@telenet.be> writes:
>
> > Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
> > + [...]
> > + (delete-file-recursively "/var/lib/jami/accounts"))
> >
> > You might want to verify whether /var/lib/jami/{.cache,.config,.local/share,.local}
> > aren't symbolic links. That way, if the Jami daemon is compromised (due to buffer
> > overflow --> arbitrary code execution or something), the attacker can't trick the
> > shepherd service into deleting arbitrary directories.
>
> It would only be able to delete directories that are world writable
> though, right? Seems the opportunity to cause damage is limited, but
> it's a simple check to add, so I'll do it.

Let's step through the relevant code of the shepherd service.

(shepherd-service
(documentation "Run the Jami daemon.")
[blabla]
(start
#~(lambda args
[other stuff]
(when [blabla, and a 'catch' form]
(delete-file-recursively "/var/lib/jami/.cache/jami")
[etcetera])
(let* (([blabla])
(user (passwd:uid [blabla jami user]))
(group [likewise]))
[blabla] (chown accounts-dir user group)))
;; Start the daemon
(define daemon-pid
(fork+exec-command [blabla Jami cmdline arguments]
#:user "jami" #:group "jami" [blabla]))
[blabla]))
(stop [blabla]))

Remember that the shepherd daemon is run as root (and therefore
has read-write-execute access to everything). The 'start' procedure
(and 'stop' procedure) are run _within_ the shepherd daemon. Thus, the
'start' gexp is run as root.

As the start procedure didn't change the uid/gid from root to something
else, (delete-file-recursively "/var/lib/jami/.cache/jami") is run as
root. IIUC, root user can read/write anything, ignoring things like "user"
and "group". World-writability is not required.

Toggle quote (5 lines)
> What about if the daemon was
> run in a container (your suggestion in a following email, to which I
> agree would be a good thing)? It would prevent this kind of attack,
> right?

I don't see how that would help. It is the _shepherd daemon_ (that runs
as root) that runs (delete-file-recursively ...), not the attacker (from
within the compromised jami-daemon process). Perhaps this is cleared up
by my previous response? If not, please walk me through the attack scenario
you had in mind that would be thwarted by running the jami-daemon in a container.

Toggle quote (15 lines)
> > Example scenario:
> > * the jami daemon has a security bug that allows arbitrary code execution
> > within the daemon
> > * the attacker exploits this
> > * now the attacker can modify everything under /var/lib/jami
> > * the attacker deletes /var/lib/jami/.config and replaces it with a symlink
> > to /home/ANY-USER/.config
> > * eventually, the system reboots
> > * (delete-file-recursively "/var/lib/jami/.config/jami") is run.
> > As "/var/lib/jami/.config" points to "/home/ANY-USER/.config",
> > this means "/home/ANY-USER/.config/jami" is deleted.
> > * thus, ANY-USER loses their jami configuration
>
> The cleanup code is run as the 'jami' user,

Err, the cleanup code is run from within the shepherd service, outside the
fork+exec-command. The shepherd service is run _within_ the shepherd daemon,
which runs as *root*. Only in the fork+exec-command, code is run as the 'jami'
user; the cleanup code is run as 'root'.

Toggle quote (3 lines)
> so I don't think it'd be able to touch anything under /home/ANY-OTHER-USER/,
> unless they manually loosened permissions on their home directory (shooting
> themselves in the foot).
See my comment above.

Greetings,
Maxime.
-----BEGIN PGP SIGNATURE-----

iI0EABYKADUWIQTB8z7iDFKP233XAR9J4+4iGRcl7gUCYH2WkBccbWF4aW1lZGV2
b3NAdGVsZW5ldC5iZQAKCRBJ4+4iGRcl7mCIAP9jTTa/0eekxqBmhORKYGL12rVg
E1ZdgcCXyh36NjjXPQEA/m3pbrue/O1BDrlXO1zgfJoxFMF/6JGQcKS77cbjXwY=
=1rqa
-----END PGP SIGNATURE-----


M
M
Maxim Cournoyer wrote on 19 Apr 2021 17:42
(name . Maxime Devos)(address . maximedevos@telenet.be)(address . 47849@debbugs.gnu.org)
87r1j6gu22.fsf@gmail.com
Hi Maxime,

Maxime Devos <maximedevos@telenet.be> writes:

Toggle quote (45 lines)
> Maxim Cournoyer schreef op ma 19-04-2021 om 08:07 [-0400]:
>> Hi Maxime!
>>
>> Maxime Devos <maximedevos@telenet.be> writes:
>>
>> > Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
>> > + [...]
>> > + (delete-file-recursively "/var/lib/jami/accounts"))
>> >
>> > You might want to verify whether /var/lib/jami/{.cache,.config,.local/share,.local}
>> > aren't symbolic links. That way, if the Jami daemon is compromised (due to buffer
>> > overflow --> arbitrary code execution or something), the attacker can't trick the
>> > shepherd service into deleting arbitrary directories.
>>
>> It would only be able to delete directories that are world writable
>> though, right? Seems the opportunity to cause damage is limited, but
>> it's a simple check to add, so I'll do it.
>
> Let's step through the relevant code of the shepherd service.
>
> (shepherd-service
> (documentation "Run the Jami daemon.")
> [blabla]
> (start
> #~(lambda args
> [other stuff]
> (when [blabla, and a 'catch' form]
> (delete-file-recursively "/var/lib/jami/.cache/jami")
> [etcetera])
> (let* (([blabla])
> (user (passwd:uid [blabla jami user]))
> (group [likewise]))
> [blabla] (chown accounts-dir user group)))
> ;; Start the daemon
> (define daemon-pid
> (fork+exec-command [blabla Jami cmdline arguments]
> #:user "jami" #:group "jami" [blabla]))
> [blabla]))
> (stop [blabla]))
>
> Remember that the shepherd daemon is run as root (and therefore
> has read-write-execute access to everything). The 'start' procedure
> (and 'stop' procedure) are run _within_ the shepherd daemon. Thus, the
> 'start' gexp is run as root.

Ah yes, looking the service definition it's obvious. Sorry for missing
that earlier :-).

Toggle quote (15 lines)
> As the start procedure didn't change the uid/gid from root to something
> else, (delete-file-recursively "/var/lib/jami/.cache/jami") is run as
> root. IIUC, root user can read/write anything, ignoring things like "user"
> and "group". World-writability is not required.
>
>> What about if the daemon was
>> run in a container (your suggestion in a following email, to which I
>> agree would be a good thing)? It would prevent this kind of attack,
>> right?
>
> I don't see how that would help. It is the _shepherd daemon_ (that runs
> as root) that runs (delete-file-recursively ...), not the attacker (from
> within the compromised jami-daemon process). Perhaps this is cleared up
> by my previous response?

Indeed! Thanks for taking the time to make it clear for me!

I'll address this in a v2 patch series, hopefully resolving the issues
with the daemon starting in double the first time the system is
reconfigured (something to do with d-bus autospawning I think -- perhaps
the D-Bus ping method is enough to spawn the process if it was not yet
up and running).

It'll take me some time to get to it; probably after the v1.3.0 is
released.

Thank you!

Maxim
M
M
Maxim Cournoyer wrote on 20 May 2021 14:31
[PATCH v2] services: Add a service for the Jami daemon.
(address . 47849@debbugs.gnu.org)
20210520123110.6331-1-maxim.cournoyer@gmail.com
* gnu/services/telephony.scm (string-list?): New procedure.
(maybe-string-list?): Likewise.
(jami-daemon-configuration): New configuration.
(%jami-daemon-accounts): New variable.
(jami-daemon-configuration->command-line-arguments): New procedure.
(jami-dbus-session-activation): Likewise.
(define-with-retries, define-send-dbus)
(jami-daemon-service-type): New variables.
(jami-daemon-shepherd-services): New procedure.
* gnu/tests/data/jami-dummy-account.dat: New file.
* gnu/tests/telephony.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Register the new test file.
(dist_patch_DATA): Register the new data file.
* doc/guix.texi (Telephony Services): Document it.
---
doc/guix.texi | 77 +++++
gnu/local.mk | 4 +-
gnu/services/telephony.scm | 355 ++++++++++++++++++++++-
gnu/tests/data/jami-dummy-account.dat | 391 ++++++++++++++++++++++++++
gnu/tests/telephony.scm | 204 ++++++++++++++
5 files changed, 1028 insertions(+), 3 deletions(-)
create mode 100644 gnu/tests/data/jami-dummy-account.dat
create mode 100644 gnu/tests/telephony.scm

Toggle diff (445 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index e8b0485f78..a58fe0a44e 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -22435,6 +22435,83 @@ and Error.
@node Telephony Services
@subsection Telephony Services
+@cindex telephony, services
+The @code{(gnu services telephony)} module contains Guix service
+definitions for telephony services. Currently it provides the following
+services:
+
+@subsubheading Jami (daemon)
+
+@cindex jami-daemon, service
+
+This section describes how to configure a Jami server that can be used
+to host video (or audio) conferences. Here's an example that
+demonstrates how to specify account archives (Jami backups) to be
+provisioned automatically:
+
+@lisp
+(service jami-daemon-service-type
+ (jami-daemon-configuration
+ (account-archives
+ '("/etc/jami/unencrypted-account-1.gz"
+ "/etc/jami/unencrypted-account-2.gz"))))
+@end lisp
+
+Jami accounts and their corresponding backup archives can be generated
+using either the @code{jami-qt} or @code{jami-gnome} Jami clients. The
+accounts should not be password-protected, but it is wise to ensure
+their files are only readable by @samp{root}. The complete set of
+available configuration options are detailed below.
+
+Available @code{jami-daemon-configuration} fields are:
+
+@deftypevr {@code{jami-daemon-configuration} parameter} package jami-daemon
+The Jami daemon package to use.
+
+@end deftypevr
+
+@deftypevr {@code{jami-daemon-configuration} parameter} package dbus
+The D-Bus package to use to start the required D-Bus session.
+
+@end deftypevr
+
+@deftypevr {@code{jami-daemon-configuration} parameter} boolean enable-logging?
+Whether to enable logging to syslog.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-daemon-configuration} parameter} boolean debug?
+Whether to enable debug level messages.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-daemon-configuration} parameter} boolean auto-answer?
+Whether to force automatic answer to incoming calls.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-daemon-configuration} parameter} maybe-string-list account-archives
+A list of Jami account archive (backup) file names to be
+(re-)provisioned every time the Jami daemon service starts. These Jami
+account backups should @emph{not} be encrypted and should be made
+readable only to the @samp{root} user (i.e., not in the store), to guard
+against leaking the secret key material of the Jami accounts they
+contain. When providing this field, the account directories under
+@file{/var/lib/jami/} are recreated every time the service starts,
+ensuring a consistent state.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@subsubheading Murmur (VoIP server)
+
@cindex Murmur (VoIP server)
@cindex VoIP server
This section describes how to set up and run a Murmur server. Murmur is
diff --git a/gnu/local.mk b/gnu/local.mk
index dd68bb5957..18700f78a1 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -712,6 +712,7 @@ GNU_SYSTEM_MODULES = \
%D%/tests/security-token.scm \
%D%/tests/singularity.scm \
%D%/tests/ssh.scm \
+ %D%/tests/telephony.scm \
%D%/tests/version-control.scm \
%D%/tests/virtualization.scm \
%D%/tests/web.scm
@@ -1842,7 +1843,8 @@ dist_patch_DATA = \
%D%/packages/patches/ytnef-CVE-2021-3403.patch \
%D%/packages/patches/ytnef-CVE-2021-3404.patch \
%D%/packages/patches/zstd-CVE-2021-24031_CVE-2021-24032.patch \
- %D%/packages/patches/zziplib-CVE-2018-16548.patch
+ %D%/packages/patches/zziplib-CVE-2018-16548.patch \
+ %D%/tests/data/jami-dummy-account.dat
MISC_DISTRO_FILES = \
%D%/packages/ld-wrapper.in
diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm
index e1259cc2df..d0dc79a68a 100644
--- a/gnu/services/telephony.scm
+++ b/gnu/services/telephony.scm
@@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 nee <nee-git@hidamari.blue>
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -17,16 +18,31 @@
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu services telephony)
- #:use-module (gnu services)
+ #:use-module ((gnu services) #:hide (delete))
+ #:use-module (gnu services configuration)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:use-module (gnu packages admin)
+ #:use-module (gnu packages glib)
+ #:use-module (gnu packages jami)
#:use-module (gnu packages telephony)
#:use-module (guix records)
+ #:use-module (guix modules)
+ #:use-module (guix packages)
#:use-module (guix gexp)
#:use-module (srfi srfi-1)
#:use-module (ice-9 match)
- #:export (murmur-configuration
+ #:export (jami-daemon-configuration
+ jami-daemon-configuration-jami-daemon
+ jami-daemon-configuration-dbus
+ jami-daemon-configuration-enable-logging?
+ jami-daemon-configuration-debug?
+ jami-daemon-configuration-auto-answer?
+ jami-daemon-configuration-account-archives
+
+ jami-daemon-service-type
+
+ murmur-configuration
make-murmur-configuration
murmur-configuration?
murmur-configuration-package
@@ -74,6 +90,337 @@
murmur-service-type))
+
+
+;;;
+;;; Jami daemon.
+;;;
+
+;;; Copied from (gnu services messaging).
+(define (string-list? val)
+ (and (list? val)
+ (and-map (lambda (x)
+ (or (computed-file? x) ;XXX: for tests
+ (and (string? x) (not (string-index x #\,)))))
+ val)))
+
+(define-maybe/no-serialization string-list)
+
+(define-configuration/no-serialization jami-daemon-configuration
+ (jami-daemon
+ (package libring)
+ "The Jami daemon package to use.")
+ (dbus
+ (package dbus)
+ "The D-Bus package to use to start the required D-Bus session.")
+ (enable-logging?
+ (boolean #t)
+ "Whether to enable logging to syslog.")
+ (debug?
+ (boolean #f)
+ "Whether to enable debug level messages.")
+ (auto-answer?
+ (boolean #f)
+ "Whether to force automatic answer to incoming calls.")
+ (account-archives
+ (maybe-string-list 'disabled)
+ "A list of Jami account archive (backup) file names to be (re-)provisioned
+every time the Jami daemon service starts. These Jami account backups should
+@emph{not} be encrypted and should be made readable only to the @samp{jami}
+user (i.e., not in the store), to guard against leaking the secret key
+material of the Jami accounts they contain. When providing this field, the
+account directories under @file{/var/lib/jami/} are recreated every time the
+service starts, ensuring a consistent state."))
+
+(define %jami-daemon-accounts
+ (list (user-group (name "jami") (system? #t))
+ (user-account
+ (name "jami")
+ (group "jami")
+ (system? #t)
+ (comment "Jami daemon user")
+ (home-directory "/var/lib/jami"))))
+
+(define (jami-daemon-configuration->command-line-arguments config)
+ "Derive the command line arguments to used to launch the Jami daemon from
+CONFIG, a <jami-daemon-configuration> object."
+ (match-record config <jami-daemon-configuration>
+ (jami-daemon dbus enable-logging? debug? auto-answer?)
+ `(,(file-append jami-daemon "/lib/ring/dring")
+ "--persistent" ;stay alive after client quits
+ ,@(if enable-logging?
+ '() ;logs go to syslog by default
+ (list "--console")) ;else stdout/stderr
+ ,@(if debug?
+ (list "--debug")
+ '())
+ ,@(if auto-answer?
+ (list "--auto-answer")
+ '()))))
+
+(define (jami-dbus-session-activation config)
+ "Create a directory to hold the Jami D-Bus session socket."
+ (with-imported-modules (source-module-closure '((gnu build activation)))
+ #~(begin
+ (use-modules (gnu build activation))
+ (let ((user (getpwnam "jami")))
+ (mkdir-p/perms "/var/run/jami" user #o700)))))
+
+;; Local definitions to expand in source form in G-exps.
+(define define-with-retries
+ '(define-syntax-rule (with-retries n delay body ...)
+ "Retry the code in BODY up to N times until it returns #t,
+else #f. A delay of DELAY seconds is inserted before each retry."
+ (let loop ((attempts 0))
+ (if (< attempts n)
+ (or (catch #t
+ (lambda ()
+ body ...)
+ (lambda args
+ #f))
+ (begin
+ (sleep delay) ;else wait and retry
+ (loop (+ 1 attempts))))
+ (error "maximum number of retry attempts reached")))))
+
+(define define-send-dbus
+ '(define (send-dbus dbus-send service path interface method . arguments)
+ "Return the response of dbus-send, else #f."
+ (let* ((command `(,dbus-send
+ "--bus=unix:path=/var/run/jami/bus"
+ "--print-reply"
+ ,(string-append "--dest=" service) ;e.g., cx.ring.Ring
+ ,path ;e.g., /cx/ring/Ring/ConfigurationManager
+ ,(string-append interface "." method)
+ ,@arguments))
+ (temporary-log-file "/var/run/jami/.temporary-dbus-output.log")
+ (clear-temp-file (lambda () (false-if-exception
+ (delete-file temporary-log-file)))))
+ (catch #t
+ (lambda ()
+ (dynamic-wind
+ clear-temp-file
+ (lambda ()
+ (let ((pid (fork+exec-command command
+ #:user "jami" #:group "jami"
+ #:log-file temporary-log-file)))
+ (match (waitpid pid)
+ ((_ . status)
+ (let ((exit-status (status:exit-val status)))
+ (unless (= 0 exit-status)
+ (error "the send-dbus command exited with"
+ exit-status))
+ #t))))
+ (call-with-input-file temporary-log-file get-string-all))
+ clear-temp-file))
+ (lambda args
+ (format (current-error-port) "command ~s failed with ~a~%"
+ command args)
+ #f)))))
+
+(define (jami-daemon-shepherd-services config)
+ "Return a <shepherd-service> running the Jami daemon."
+ (let* ((jami-daemon (jami-daemon-configuration-jami-daemon config))
+ (dbus (jami-daemon-configuration-dbus config))
+ (dbus-daemon (file-append dbus "/bin/dbus-daemon"))
+ (dbus-send (file-append dbus "/bin/dbus-send"))
+ (accounts (jami-daemon-configuration-account-archives config))
+ (declarative-mode? (not (eq? 'disabled accounts))))
+
+ (with-imported-modules (source-module-closure
+ '((gnu build shepherd)
+ (gnu system file-systems)))
+ (list (shepherd-service
+ (documentation "Run a D-Bus session for the Jami daemon.")
+ (provision '(jami-daemon-dbus-session))
+ (modules `((gnu build shepherd)
+ (gnu system file-systems)
+ ,@%default-modules))
+ ;; The requirement on dbus-system is to ensure other required
+ ;; activation for D-Bus, such as a /etc/machine-id file.
+ (requirement '(dbus-system syslogd))
+ (start
+ #~(lambda args
+ #$define-with-retries
+
+ (define pid
+ ((make-forkexec-constructor/container
+ (list #$dbus-daemon "--session"
+ "--address=unix:path=/var/run/jami/bus"
+ "--nofork" "--syslog-only" "--nopidfile")
+ #:mappings (list (file-system-mapping
+ (source "/dev/log") ;for syslog
+ (target source))
+ (file-system-mapping
+ (source "/var/run/jami")
+ (target source)
+ (writable? #t)))
+ #:user "jami"
+ #:group "jami"
+ #:environment-variables
+ ;; This is so that the cx.ring.Ring service D-Bus
+ ;; definition is found by dbus-send.
+ (list (string-append "XDG_DATA_DIRS="
+ #$jami-daemon "/share")))))
+
+ ;; XXX: This manual synchronization probably wouldn't be
+ ;; needed if we were using a PID file, but providing it via a
+ ;; customized config file with <pidfile> would not override
+ ;; the one inherited from the base config of D-Bus.
+ (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
+ (with-retries 20 1 (catch 'system-error
+ (lambda ()
+ (connect sock AF_UNIX
+ "/var/run/jami/bus")
+ (close-port sock)
+ #t)
+ (lambda args
+ #f))))
+
+ pid))
+ (stop #~(make-kill-destructor)))
+
+ (shepherd-service
+ (documentation "Run the Jami daemon.")
+ (provision '(jami-daemon dring))
+ (requirement '(jami-daemon-dbus-session))
+ (modules `((ice-9 ftw)
+ (ice-9 match)
+ (rnrs io ports)
+ (srfi srfi-1)
+ (srfi srfi-26)
+ (gnu build shepherd)
+ (gnu system file-systems)
+ ,@%default-modules))
+ (start
+ #~(lambda args
+ #$define-with-retries
+ #$define-send-dbus
+
+ (define (delete-file-recursively/safe file)
+ ;; Ensure we're not deleting things outside of
+ ;; /var/lib/jami. This prevents a possible attack in case
+ ;; the daemon is compromised and an attacker gains write
+ ;; access to /var/lib/jami.
+ (let ((parent-directory (dirname file)))
+ (if (eq? 'symlink (stat:type (stat parent-directory)))
+ (error "abnormality detected; unexpected symlink found at"
+ parent-directory)
+ (delete-file-recursively file))))
+
+ (when #$declarative-mode?
+ ;; Clear the Jami configuration and accounts, to enforce the
+ ;; declared state.
+ (catch #t
+ (lambda ()
+ (for-each (cut delete-file-recursively/safe <>)
+ '("/var/lib/jami/.cache/jami"
+ "/var/lib/jami/.config/jami"
+ "/var/lib/jami/.local/share/jami"
+ "/var/lib/jami/accounts")))
+ (lambda args
+ #t))
+ ;; Copy the Jami accounts from somewhere readable by root to
+ ;; a place only the jami user can read.
+ (let* ((accounts-dir "/var/lib/jami/accounts/")
+ (pwd (getpwnam "jami"))
+ (user (passwd:uid pwd))
+ (group (passwd:gid pwd)))
+ (mkdir-p accounts-dir)
+ (chown accounts-dir user group)
+ (for-each (lambda (f)
+ (let ((dest (string-append accounts-dir
+ (basename f))))
+ (copy-file f dest)
+ (chown dest user group)))
+ '#$accounts)))
+
+ ;; Start the daemon.
+ (define daemon-pid
+ ((make-forkexec-constructor/container
+ '#$(jami-daemon-configuration->command-line-arguments config)
+ #:mappings (list (file-system-mapping
+ (source "/dev/log") ;for syslog
+ (target source))
+ (file-system-mapping
+ (source "/var/lib/jami")
+ (target source)
+ (writable? #t))
+ (file-system-mapping
+ (source "/var/run/jami")
+ (target source)
+ (writable? #t)))
+ #:user "jami"
+ #:group "jami"
+ #:environment-variables
+ (list (string-append "DBUS_SESSION_BUS_ADDRESS="
+ "unix:path=/var/run/jami/bus")))))
+
+ ;; Wait until the service name has been acquired by D-Bus
+ ;; (this does *not* trigger automatic D-Bus service
+ ;; activation, which is what we want).
+ (with-retries 20 1
+ (let ((output (send-dbus #$dbus-send "org.freedesktop.DBus"
+ "/org/freedesktop/DBus"
+ "org.freedesktop.DBus"
+ "ListNames")))
+ (string-contains output "cx.ring.Ring")))
+
+ ;; Provision the accounts, by means of D-Bus commands sent to
+ ;; the daemon.
+ (when #$declarative-mode?
+ (or (every
+ identity
+ (map (lambda (archive)
+ (send-dbus
+ #$dbus-send
+ "cx.ring.Ring"
+ "/cx/ring/Ring/Configurat
This message was truncated. Download the full message here.
M
M
Maxim Cournoyer wrote on 20 May 2021 14:37
Re: [bug#47849] [PATCH 1/1] services: Add a service for the Jami daemon.
(name . Maxime Devos)(address . maximedevos@telenet.be)(address . 47849@debbugs.gnu.org)
87cztl1szk.fsf@gmail.com
Hello Maxime.

Maxime Devos <maximedevos@telenet.be> writes:

Toggle quote (15 lines)
> Maxim Cournoyer schreef op za 17-04-2021 om 16:06 [-0400]:
>> + ;; Start the daemon.
>> + (define daemon-pid
>> + (fork+exec-command
>> + '#$(jami-daemon-configuration->command-line-arguments config)
>> + #:user "jami"
>> + #:group "jami"
>> + #:environment-variables
>> + (list (string-append "DBUS_SESSION_BUS_ADDRESS="
>> + "unix:path=/var/run/jami/bus"))))
>
> It would be nice if this could be run in a container
> that only has access to the relevant parts of the file system
> (and not, say, /run/setuid-programs). See, e.g., gnu/build/linux-container.scm.

That's now the case in the just-sent v2, both for the D-Bus session
service as well as the Jami process itself :-). I figured out I could
simply call make-forkexec+constructor/container and execute apply the
resulting procedure.

I've also manage to (with much difficulty!) have the service properly
start, stop or restart without races. The new tests proved really
useful for the lengthy trial and error process that I had to go through.

Thanks for your patience!

Maxim
M
M
Maxim Cournoyer wrote on 20 May 2021 16:49
control message for bug #47849
(address . control@debbugs.gnu.org)
87a6opzchm.fsf@gmail.com
retitle 47849 [PATCH] Add a jami-daemon service.
quit
M
M
Maxim Cournoyer wrote on 1 Aug 2021 08:58
[PATCH v3] services: Add a service for the Jami daemon.
(address . 47849@debbugs.gnu.org)(name . Maxime Devos)(address . maximedevos@telenet.be)
20210801065857.27657-1-maxim.cournoyer@gmail.com
Hello!

This is a much more featureful/refined version of the previously sent v2 Jami
service. I think it's ready!

Thank you,

Maxim

[PATCH v3 1/3] .dir-locals.el: Specify indentation rule for
[PATCH v3 2/3] build: shepherd: Use autoload to lazily bind Shepherd
[PATCH v3 3/3] services: Add a service for Jami.
M
M
Maxim Cournoyer wrote on 1 Aug 2021 08:58
[PATCH v3 1/3] .dir-locals.el: Specify indentation rule for with-shepherd-action.
(address . 47849@debbugs.gnu.org)
20210801065857.27657-2-maxim.cournoyer@gmail.com
* .dir-locals.el (scheme-mode) <with-shepherd-action>: New indentation rule.
---
.dir-locals.el | 2 ++
1 file changed, 2 insertions(+)

Toggle diff (15 lines)
diff --git a/.dir-locals.el b/.dir-locals.el
index a4fcbfe7ca..aaa48ab552 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -141,6 +141,8 @@
(eval . (put 'with-paginated-output-port 'scheme-indent-function 1))
+ (eval . (put 'with-shepherd-action 'scheme-indent-function 3))
+
;; This notably allows '(' in Paredit to not insert a space when the
;; preceding symbol is one of these.
(eval . (modify-syntax-entry ?~ "'"))
--
2.32.0
M
M
Maxim Cournoyer wrote on 1 Aug 2021 08:58
[PATCH v3 2/3] build: shepherd: Use autoload to lazily bind Shepherd modules.
(address . 47849@debbugs.gnu.org)
20210801065857.27657-3-maxim.cournoyer@gmail.com
Instead of imperative module-autoload! directives.

* gnu/build/shepherd.scm: Replace module-autoload! directives by autoload
arguments for define-module.
---
gnu/build/shepherd.scm | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)

Toggle diff (34 lines)
diff --git a/gnu/build/shepherd.scm b/gnu/build/shepherd.scm
index d7b858dea4..778e3fc627 100644
--- a/gnu/build/shepherd.scm
+++ b/gnu/build/shepherd.scm
@@ -24,6 +24,12 @@
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:use-module (ice-9 match)
+ ;; XXX: Lazy-bind the Shepherd to avoid a compile-time dependency.
+ #:autoload (shepherd service) (fork+exec-command
+ read-pid-file
+ exec-command
+ %precious-signals)
+ #:autoload (shepherd system) (unblock-signals)
#:export (make-forkexec-constructor/container
fork+exec-command/container))
@@ -92,14 +98,6 @@
(file-exists? (file-system-mapping-source mapping)))
mappings)))))
-;; XXX: Lazy-bind the Shepherd to avoid a compile-time dependency.
-(module-autoload! (current-module)
- '(shepherd service)
- '(fork+exec-command read-pid-file exec-command
- %precious-signals))
-(module-autoload! (current-module)
- '(shepherd system) '(unblock-signals))
-
(define* (read-pid-file/container pid pid-file #:key (max-delay 5))
"Read PID-FILE in the container namespaces of PID, which exists in a
separate mount and PID name space. Return the \"outer\" PID. "
--
2.32.0
M
M
Maxim Cournoyer wrote on 1 Aug 2021 08:58
[PATCH v3 3/3] services: Add a service for Jami.
(address . 47849@debbugs.gnu.org)
20210801065857.27657-4-maxim.cournoyer@gmail.com
* gnu/services/telephony.scm (string-or-computed-file?)
(string-list?, account-fingerprint-list?): New procedures.
(maybe-string-list, maybe-account-fingerprint-list)
(maybe-boolean, maybe-string, jami-account-list): New configuration field
types.
(serialize-string-list, serialize-boolean, serialize-string)
(jami-account, jami-account->alist, jami-configuration)
(jami-account-list?, jami-account-list-maybe): New procedures.
(%jami-accounts): New variable.
(jami-configuration->command-line-arguments): New procedure.
(jami-dbus-session-activation, jami-shepherd-services): New procedures.
(jami-service-type): New variable.
* gnu/build/jami-service.scm: New file.
* gnu/tests/data/jami-dummy-account.dat: Likewise.
* gnu/tests/telephony.scm: Likewise.
* gnu/local.mk (GNU_SYSTEM_MODULES): Register them.
* Makefile.am (SCM_TESTS): Register the test file.
(dist_patch_DATA): Register the new data file.
* doc/guix.texi (Telephony Services): Document it.
---
Makefile.am | 1 +
doc/guix.texi | 264 ++++++++++
gnu/build/jami-service.scm | 587 ++++++++++++++++++++++
gnu/local.mk | 5 +-
gnu/services/telephony.scm | 684 +++++++++++++++++++++++++-
gnu/tests/data/jami-dummy-account.dat | 392 +++++++++++++++
gnu/tests/telephony.scm | 366 ++++++++++++++
tests/services/telephony.scm | 446 +++++++++++++++++
8 files changed, 2742 insertions(+), 3 deletions(-)
create mode 100644 gnu/build/jami-service.scm
create mode 100644 gnu/tests/data/jami-dummy-account.dat
create mode 100644 gnu/tests/telephony.scm
create mode 100644 tests/services/telephony.scm

Toggle diff (517 lines)
diff --git a/Makefile.am b/Makefile.am
index d5ec909213..5542aa1c56 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -489,6 +489,7 @@ SCM_TESTS = \
tests/services/file-sharing.scm \
tests/services/configuration.scm \
tests/services/linux.scm \
+ tests/services/telephony.scm \
tests/sets.scm \
tests/size.scm \
tests/status.scm \
diff --git a/doc/guix.texi b/doc/guix.texi
index 2298d512a1..c931cbeb93 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -22526,6 +22526,270 @@ and Error.
@node Telephony Services
@subsection Telephony Services
+@cindex telephony, services
+The @code{(gnu services telephony)} module contains Guix service
+definitions for telephony services. Currently it provides the following
+services:
+
+@subsubheading Jami
+
+@cindex jami, service
+
+This section describes how to configure a Jami server that can be used
+to host video (or audio) conferences, among other uses. The following
+example demonstrates how to specify Jami account archives (backups) to
+be provisioned automatically:
+
+@lisp
+(service jami-service-type
+ (jami-configuration
+ (accounts
+ (list (jami-account
+ (archive "/etc/jami/unencrypted-account-1.gz"))
+ (jami-account
+ (archive "/etc/jami/unencrypted-account-2.gz"))))))
+@end lisp
+
+When the accounts field is specified, the Jami account files of the
+service found under @file{/var/lib/jami} are recreated every time the
+service starts.
+
+Jami accounts and their corresponding backup archives can be generated
+using either the @code{jami-qt} or @code{jami-gnome} Jami clients. The
+accounts should not be password-protected, but it is wise to ensure
+their files are only readable by @samp{root}.
+
+The next example shows how to declare that only some contacts should be
+allowed to communicate with a given account:
+
+@lisp
+(service jami-service-type
+ (jami-configuration
+ (accounts
+ (list (jami-account
+ (archive "/etc/jami/unencrypted-account-1.gz")
+ (peer-discovery? #t)
+ (rendezvous-point? #t)
+ (allowed-contacts
+ '("1dbcb0f5f37324228235564b79f2b9737e9a008f"
+ "2dbcb0f5f37324228235564b79f2b9737e9a008f")))))))
+@end lisp
+
+In this mode, only the declared @code{allowed-contacts} can initiate
+communication with the Jami account. This can be used, for example,
+with rendezvous point accounts to create a private video conferencing
+space.
+
+To put the system administrator in full control of the conferences
+hosted on their system, the Jami service supports the following actions:
+
+@example sh
+herd doc jami list-actions jami
+(list-accounts
+ list-account-details
+ list-banned-contacts
+ list-contacts
+ list-moderators
+ add-moderator
+ ban-contact
+ enable-account
+ disable-account)
+@end example
+
+The above actions aim to provide the most valuable actions for
+moderation purposes, not to cover the whole Jami API. Users wanting to
+interact with the Jami daemon from Guile may be interested in
+experimenting with the @code{(gnu build jami-service)} module, which
+powers the above Shepherd actions.
+
+@c TODO: This should be auto-generated from the doc already defined on
+@c the shepherd-actions themselves in (gnu services telephony).
+The @code{add-moderator} and @code{ban-contact} actions accept a contact
+@emph{fingerprint} (40 characters long hash) as first argument and an
+account fingerprint or username as second argument:
+
+@example sh
+# herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f \
+ f3345f2775ddfe07a4b0d95daea111d15fbc1199
+
+# herd list-moderators jami
+Moderators for account f3345f2775ddfe07a4b0d95daea111d15fbc1199:
+ - 1dbcb0f5f37324228235564b79f2b9737e9a008f
+
+@end example
+
+In the case of @code{ban-contact}, the second username argument is
+optional; when omitted, the account is banned from all Jami accounts:
+
+@example sh
+# herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f
+
+# herd list-banned-contacts jami
+Banned contacts for account f3345f2775ddfe07a4b0d95daea111d15fbc1199:
+ - 1dbcb0f5f37324228235564b79f2b9737e9a008f
+
+@end example
+
+Banned contacts are also stripped from their moderation privileges.
+
+The @code{disable-account} action allows to completely disconnect an
+account from the network, making it unreachable, while
+@code{enable-account} does the inverse. They accept a single account
+username or fingerprint as first argument:
+
+@example sh
+# herd disable-account jami f3345f2775ddfe07a4b0d95daea111d15fbc1199
+
+# herd list-accounts jami
+The following Jami accounts are available:
+ - f3345f2775ddfe07a4b0d95daea111d15fbc1199 (dummy) [disabled]
+
+@end example
+
+The @code{list-account-details} action prints the detailed parameters of
+each accounts in the Recutils format, which means the @command{recsel}
+command can be used to select accounts of interest (@pxref{Selection
+Expressions,,,recutils, GNU recutils manual}). Note that period
+characters (@samp{.}) found in the account parameter keys are mapped to
+underscores (@samp{_}) in the output, to meet the requirements of the
+Recutils format. The following example shows how to print the account
+fingerprints for all accounts operating in the rendezvous point mode:
+
+@example sh
+# herd list-account-details jami | \
+ recsel -p Account.username -e 'Account.rendezVous ~ "true"'
+Account_username: f3345f2775ddfe07a4b0d95daea111d15fbc1199
+@end example
+
+The remaining actions should be self-explanatory.
+
+The complete set of available configuration options is detailed below.
+
+@c TODO: Ideally, the following fragments would be auto-generated at
+@c build time, so that they needn't be manually duplicated.
+@c Auto-generated via (configuration->documentation 'jami-configuration)
+Available @code{jami-configuration} fields are:
+
+@deftypevr {@code{jami-configuration} parameter} package jamid
+The Jami daemon package to use.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} package dbus
+The D-Bus package to use to start the required D-Bus session.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} package nss-certs
+The nss-certs package to use to provide TLS certificates.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} boolean enable-logging?
+Whether to enable logging to syslog.
+
+Defaults to @samp{#t}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} boolean debug?
+Whether to enable debug level messages.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} boolean auto-answer?
+Whether to force automatic answer to incoming calls.
+
+Defaults to @samp{#f}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-configuration} parameter} maybe-jami-account-list accounts
+A list of Jami accounts to be (re-)provisioned every time the Jami
+daemon service starts. When providing this field, the account
+directories under @file{/var/lib/jami/} are recreated every time the
+service starts, ensuring a consistent state.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@c Auto-generated via (configuration->documentation 'jami-account)
+Available @code{jami-account} fields are:
+
+@deftypevr {@code{jami-account} parameter} string-or-computed-file archive
+The account archive (backup) file name of the account. This is used to
+provision the account when the service starts. The account archive
+should @emph{not} be encrypted. It is highly recommended to make it
+readable only to the @samp{root} user (i.e., not in the store), to guard
+against leaking the secret key material of the Jami account it contains.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-account-fingerprint-list allowed-contacts
+The list of allowed contacts for the account, entered as their 40
+characters long fingerprint. Messages or calls from accounts not in
+that list will be rejected. When unspecified, the configuration of the
+account archive is used as-is with respect to contacts and public
+inbound calls/messaging allowance, which typically defaults to allow any
+contact to communicate with the account.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-account-fingerprint-list moderators
+The list of contacts that should have moderation privileges (to ban,
+mute, etc. other users) in rendezvous conferences, entered as their 40
+characters long fingerprint. When unspecified, the configuration of the
+account archive is used as-is with respect to moderation, which
+typically defaults to allow anyone to moderate.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-boolean rendezvous-point?
+Whether the account should operate in the rendezvous mode. In this
+mode, all the incoming audio/video calls are mixed into a conference.
+When left unspecified, the value from the account archive prevails.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-boolean peer-discovery?
+Whether peer discovery should be enabled. Peer discovery is used to
+discover other OpenDHT nodes on the local network, which can be useful
+to maintain communication between devices on such network even when the
+connection to the the Internet has been lost. When left unspecified,
+the value from the account archive prevails.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-string-list bootstrap-hostnames
+A list of hostnames or IPs pointing to OpenDHT nodes, that should be
+used to initially join the OpenDHT network. When left unspecified, the
+value from the account archive prevails.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@deftypevr {@code{jami-account} parameter} maybe-string name-server-uri
+The URI of the name server to use, that can be used to retrieve the
+account fingerprint for a registered username.
+
+Defaults to @samp{disabled}.
+
+@end deftypevr
+
+@subsubheading Murmur (VoIP server)
+
@cindex Murmur (VoIP server)
@cindex VoIP server
This section describes how to set up and run a Murmur server. Murmur is
diff --git a/gnu/build/jami-service.scm b/gnu/build/jami-service.scm
new file mode 100644
index 0000000000..bb43b578f9
--- /dev/null
+++ b/gnu/build/jami-service.scm
@@ -0,0 +1,587 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;;
+;;; This module contains helpers used as part of the jami-service-type
+;;; definition.
+;;;
+;;; Code:
+
+(define-module (gnu build jami-service)
+ #:use-module (ice-9 format)
+ #:use-module (ice-9 match)
+ #:use-module (ice-9 peg)
+ #:use-module (ice-9 rdelim)
+ #:use-module (ice-9 regex)
+ #:use-module (rnrs io ports)
+ #:autoload (shepherd service) (fork+exec-command)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-26)
+ #:export (account-fingerprint?
+ account-details->recutil
+ get-accounts
+ get-usernames
+ set-account-details
+ add-account
+ account->username
+ username->account
+ username->contacts
+ enable-account
+ disable-account
+
+ add-contact
+ remove-contact
+
+ set-all-moderators
+ set-moderator
+ username->all-moderators?
+ username->moderators
+
+ dbus-available-services
+ dbus-service-available?
+
+ %send-dbus-binary
+ %send-dbus-bus
+ %send-dbus-user
+ %send-dbus-group
+ %send-dbus-debug
+ send-dbus
+
+ with-retries))
+
+;;;
+;;; Utilities.
+;;;
+
+(define-syntax-rule (with-retries n delay body ...)
+ "Retry the code in BODY up to N times until it doesn't raise an exception
+nor return #f, else raise an error. A delay of DELAY seconds is inserted
+before each retry."
+ (let loop ((attempts 0))
+ (catch #t
+ (lambda ()
+ (let ((result (begin body ...)))
+ (if (not result)
+ (error "failed attempt" attempts)
+ result)))
+ (lambda args
+ (if (< attempts n)
+ (begin
+ (sleep delay) ;else wait and retry
+ (loop (+ 1 attempts)))
+ (error "maximum number of retry attempts reached"
+ body ... args))))))
+
+(define (alist->list alist)
+ "Flatten ALIST into a list."
+ (append-map (match-lambda
+ (() '())
+ ((key . value)
+ (list key value)))
+ alist))
+
+(define account-fingerprint-rx (make-regexp "[0-9A-f]{40}"))
+
+(define (account-fingerprint? val)
+ "A Jami account fingerprint is 40 characters long and only contains
+hexadecimal characters."
+ (and (string? val)
+ (regexp-exec account-fingerprint-rx val)))
+
+
+;;;
+;;; D-Bus reply parser.
+;;;
+
+(define (parse-dbus-reply reply)
+ "Return the parse tree of REPLY, a string returned by the 'dbus-send'
+command."
+ ;; Refer to 'man 1 dbus-send' for the grammar reference. Note that the
+ ;; format of the replies doesn't match the format of the input, which is the
+ ;; one documented, but it gives an idea. For an even better reference, see
+ ;; the `print_iter' procedure of the 'dbus-print-message.c' file from the
+ ;; 'dbus' package sources.
+ (define-peg-string-patterns
+ "contents <- header (item / container (item / container*)?)
+ item <-- WS type WS value NL
+ container <- array / dict / variant
+ array <-- array-start (item / container)* array-end
+ dict <-- array-start dict-entry* array-end
+ dict-entry <-- dict-entry-start item item dict-entry-end
+ variant <-- variant-start item
+ type <-- 'string' / 'int16' / 'uint16' / 'int32' / 'uint32' / 'int64' /
+ 'uint64' / 'double' / 'byte' / 'boolean' / 'objpath'
+ value <-- (!NL .)* NL
+ header < (!NL .)* NL
+ variant-start < WS 'variant'
+ array-start < WS 'array [' NL
+ array-end < WS ']' NL
+ dict-entry-start < WS 'dict entry(' NL
+ dict-entry-end < WS ')' NL
+ DQ < '\"'
+ WS < ' '*
+ NL < '\n'*")
+
+ (peg:tree (match-pattern contents reply)))
+
+(define (strip-quotes text)
+ "Strip the leading and trailing double quotes (\") characters from TEXT."
+ (let* ((text* (if (string-prefix? "\"" text)
+ (string-drop text 1)
+ text))
+ (text** (if (string-suffix? "\"" text*)
+ (string-drop-right text* 1)
+ text*)))
+ text**))
+
+(define (deserialize-item item)
+ "Return the value described by the ITEM parse tree as a Guile object."
+ ;; Strings are printed wrapped in double quotes (see the print_iter
+ ;; procedure in dbus-print-message.c).
+ (match item
+ (('item ('type "string") ('value value))
+ (strip-quotes value))
+ (('item ('type "boolean") ('value value))
+ (if (string=? "true" value)
+ #t
+ #f))
+ (('item _ ('value value))
+ value)))
+
+(define (serialize-boolean bool)
+ "Return the serialized format expected by dbus-send for BOOL."
+ (format #f "boolean:~:[false~;true~]" bool))
+
+(define (dict->alist dict-parse-tree)
+ "Translate a dict parse tree to an alist."
+ (define (tuples->alist tuples)
+ (map (lambda (x) (apply cons x)) tuples))
+
+ (match dict-parse-tree
+ ('dict
+ '())
+ (('dict ('dict-entry keys values) ...)
+ (let ((keys* (map deserialize-item keys))
+ (values* (map deserialize-item values)))
+ (tuples->alist (zip keys* values*))))))
+
+(define (array->list array-parse-tree)
+ "Translate an array parse tree to a list."
+ (match array-parse-tree
+ ('array
+ '())
+ (('array items ...)
+ (map deserialize-item items))))
+
+
+;;;
+;;; Low-level, D-Bus-related procedures.
+;;;
+
+;;; The following parameters are used in the jami-service-type service
+;;; definition to conveniently customize the behavior of the send-dbus helper,
+;;; even when called indirectly.
+(define %send-dbus-binary (make-parameter "dbus-send"))
+(define %send-dbus-bus (make-parameter #f))
+(define %send-dbus-user (make-parameter #f))
+(define %send-dbus-group (make-parameter #f))
+(define %send-dbus-debug (make-parameter #f))
+
+(define* (send-dbus #:key service path interface method
+ bus
+ dbus-send
+ user group
+ timeout
+ arguments)
+ "Return the response of DBUS-SEND, else raise an error. Unless explicitly
+provided, DBUS-SEND takes the value of the %SEND-DBUS-BINARY parameter. BUS
+can be used to specify the bus address, such as 'unix:path=/var/run/jami/bus'.
+Alternatively, the %SEND-DBUS-BUS parameter can be used. ARGUMENTS can be
+used to pass input values to a D-Bus method call. TIMEOUT is the amount of
+time to wait for a reply in milliseconds before giving up with an error. USER
+and GROUP allow choosing under which user/group the DBUS-SEND command is
+executed. Alternatively, the %SEND-DBUS-USER and %SEND-DBUS-GROUP parameters
+can be used instead."
+ (let* ((command `(,@(if dbus-send
+ (list dbus-send)
+ (list (%send-dbus-binary)))
+ ,@(if (or bus (%send-dbus-
This message was truncated. Download the full message here.
L
L
Leo Prikler wrote on 1 Aug 2021 09:58
(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
a2aa7761985c5a9b4c2f90a9292723bc79a0dd2a.camel@student.tugraz.at
Hi,

Am Sonntag, den 01.08.2021, 02:58 -0400 schrieb Maxim Cournoyer:
Toggle quote (5 lines)
> [...]

> + (let* ((command `(,@(if dbus-send
> + (list dbus-send)
> + (list (%send-dbus-binary)))
You can use non-splicing comma notation for things that are never
supposed to be lists.
Toggle quote (5 lines)
> + ,@(if (or bus (%send-dbus-bus))
> + (list (string-append "--bus="
> + (or bus (%send-dbus-
> bus))))
> + '())
You use this style here, but
Toggle quote (4 lines)
> + (let* ((uid (or (and=> (or user (%send-dbus-user))
> + (compose passwd:uid getpwnam)) -1))
> + (gid (or (and=> (or group (%send-dbus-group))
> + (compose group:gid getgrnam)) -1)))
this style here. I personally think using and=> everywhere would make
things a little clearer.

More importantly, though
Toggle quote (6 lines)
> +(define* (send-dbus #:key service path interface method
> + bus
> + dbus-send
> + user group
> + timeout
> + arguments)
If you e.g. write (bus (%send-dbus-bus)), I think the argument should
already be correctly passed upon the function call, no?

Toggle quote (17 lines)
> + (chown temp-port uid gid)))
> + (lambda ()
> + (let ((pid (fork+exec-command command
> + #:user (or user (%send-dbus-
> user))
> + #:group (or group (%send-dbus-
> group))
> + #:log-file temp-file)))
> + (match (waitpid pid)
> + ((_ . status)
> + (let ((exit-status (status:exit-val status))
> + (output (call-with-port temp-port get-string-
> all)))
> + (if (= 0 exit-status)
> + output
> + (error "the send-dbus command exited with: "
> + command exit-status output)))))))
Since output is unused in the error case, I think you can move the get-
string-all there. It would make a difference if you were to e.g. close
temp-port before that.

I did not look at the rest of this patch, but 1+2 LGTM.

Regards,
M
M
Maxim Cournoyer wrote on 2 Aug 2021 21:17
Re: bug#47849: [PATCH] Add a jami-daemon service.
(name . Leo Prikler)(address . leo.prikler@student.tugraz.at)
878s1j8xmq.fsf_-_@gmail.com
Hi Leo,

Leo Prikler <leo.prikler@student.tugraz.at> writes:

Toggle quote (11 lines)
> Hi,
>
> Am Sonntag, den 01.08.2021, 02:58 -0400 schrieb Maxim Cournoyer:
>> [...]
>
>> + (let* ((command `(,@(if dbus-send
>> + (list dbus-send)
>> + (list (%send-dbus-binary)))
> You can use non-splicing comma notation for things that are never
> supposed to be lists.

Good catch!

Toggle quote (13 lines)
>> + ,@(if (or bus (%send-dbus-bus))
>> + (list (string-append "--bus="
>> + (or bus (%send-dbus-
>> bus))))
>> + '())
> You use this style here, but
>> + (let* ((uid (or (and=> (or user (%send-dbus-user))
>> + (compose passwd:uid getpwnam)) -1))
>> + (gid (or (and=> (or group (%send-dbus-group))
>> + (compose group:gid getgrnam)) -1)))
> this style here. I personally think using and=> everywhere would make
> things a little clearer.

I tried, but it seemed it had a bigger cognitive load to follow (or,
and=>, compose, list, cut all in the same expression), so ended up
keeping it as is.

Toggle quote (10 lines)
> More importantly, though
>> +(define* (send-dbus #:key service path interface method
>> + bus
>> + dbus-send
>> + user group
>> + timeout
>> + arguments)
> If you e.g. write (bus (%send-dbus-bus)), I think the argument should
> already be correctly passed upon the function call, no?

the %send-dbus-bus is a parameter; if we were to provide it as a default
value, it would be evaluated at the time the module is read (IIUC), but
I want its value to be read at run time, when the procedure is called
instead.

Toggle quote (21 lines)
>> + (chown temp-port uid gid)))
>> + (lambda ()
>> + (let ((pid (fork+exec-command command
>> + #:user (or user (%send-dbus-
>> user))
>> + #:group (or group (%send-dbus-
>> group))
>> + #:log-file temp-file)))
>> + (match (waitpid pid)
>> + ((_ . status)
>> + (let ((exit-status (status:exit-val status))
>> + (output (call-with-port temp-port get-string-
>> all)))
>> + (if (= 0 exit-status)
>> + output
>> + (error "the send-dbus command exited with: "
>> + command exit-status output)))))))
> Since output is unused in the error case, I think you can move the get-
> string-all there. It would make a difference if you were to e.g. close
> temp-port before that.

It's actually used as the last argument of the 'error' call :-). I
added it as it was helpful to debug stderr messages from dbus-send.

Toggle quote (4 lines)
> I did not look at the rest of this patch, but 1+2 LGTM.
>
> Regards,

Thanks a lot for having a look! It's been long in the making.

Pushed as commit 69dcc24c9f.

Closing.

Maxim
Closed
L
L
Leo Prikler wrote on 3 Aug 2021 09:35
(name . Maxim Cournoyer)(address . maxim.cournoyer@gmail.com)
2a559e87966b2383d39ccfddbb667d67b9d21632.camel@student.tugraz.at
Hi Maxim,

Am Montag, den 02.08.2021, 15:17 -0400 schrieb Maxim Cournoyer:
Toggle quote (20 lines)
> > > + ,@(if (or bus (%send-dbus-bus))
> > > + (list (string-append "--bus="
> > > + (or bus (%send-
> > > dbus-
> > > bus))))
> > > + '())
> > You use this style here, but
> > > + (let* ((uid (or (and=> (or user (%send-dbus-user))
> > > + (compose passwd:uid getpwnam))
> > > -1))
> > > + (gid (or (and=> (or group (%send-dbus-group))
> > > + (compose group:gid getgrnam))
> > > -1)))
> > this style here. I personally think using and=> everywhere would
> > make
> > things a little clearer.
>
> I tried, but it seemed it had a bigger cognitive load to follow (or,
> and=>, compose, list, cut all in the same expression), so ended up
> keeping it as is.
I think the cognitive burden would in both cases be lessened if we
didn't need to write (or stuff (%send-dbus-stuff)) everywhere, which
see below.

Toggle quote (17 lines)
> > More importantly, though
> > > +(define* (send-dbus #:key service path interface method
> > > + bus
> > > + dbus-send
> > > + user group
> > > + timeout
> > > + arguments)
> > If you e.g. write (bus (%send-dbus-bus)), I think the argument
> > should
> > already be correctly passed upon the function call, no?
>
> the %send-dbus-bus is a parameter; if we were to provide it as a
> default
> value, it would be evaluated at the time the module is read (IIUC),
> but
> I want its value to be read at run time, when the procedure is called
> instead.
That's how define* works though, in my experience:

Toggle snippet (8 lines)
;; foo.scm
(define-module (foo)
#:export (*rand* rand))

(define *rand* (make-parameter 4))
(define* (rand #:optional (value (*rand*))) value)

Toggle snippet (10 lines)
scheme@(guile-user)> ,use (foo) ;; alternatively (load-compiled
"foo.go")
scheme@(guile-user)> (rand)
$1 = 4
scheme@(guile-user)> (parameterize ((*rand* 5)) (rand))
$2 = 5
scheme@(guile-user)> (rand 6)
$3 = 6

Toggle quote (27 lines)
> > > + (chown temp-port uid gid)))
> > > + (lambda ()
> > > + (let ((pid (fork+exec-command command
> > > + #:user (or user (%send-
> > > dbus-
> > > user))
> > > + #:group (or group (%send-
> > > dbus-
> > > group))
> > > + #:log-file temp-file)))
> > > + (match (waitpid pid)
> > > + ((_ . status)
> > > + (let ((exit-status (status:exit-val status))
> > > + (output (call-with-port temp-port get-string-
> > > all)))
> > > + (if (= 0 exit-status)
> > > + output
> > > + (error "the send-dbus command exited with: "
> > > + command exit-status output)))))))
> > Since output is unused in the error case, I think you can move the
> > get-
> > string-all there. It would make a difference if you were to e.g.
> > close
> > temp-port before that.
>
> It's actually used as the last argument of the 'error' call :-). I
> added it as it was helpful to debug stderr messages from dbus-send.
My bad, nvm then.

Toggle quote (7 lines)
> > I did not look at the rest of this patch, but 1+2 LGTM.
> >
> > Regards,
>
> Thanks a lot for having a look! It's been long in the making.
>
> Pushed as commit 69dcc24c9f.
As I said, I only looked at parts of it, so this push was perhaps a bit
hasty. I recently also hastily pushed something and had to fix it up
later. Let's try to keep a cool head and not let the heat get to us :)

Regards,
M
M
Maxim Cournoyer wrote on 3 Aug 2021 16:36
(name . Leo Prikler)(address . leo.prikler@student.tugraz.at)
87eeba7fya.fsf@gmail.com
Hi Leo,

[...]

Toggle quote (12 lines)
>> > I did not look at the rest of this patch, but 1+2 LGTM.
>> >
>> > Regards,
>>
>> Thanks a lot for having a look! It's been long in the making.
>>
>> Pushed as commit 69dcc24c9f.
> As I said, I only looked at parts of it, so this push was perhaps a bit
> hasty. I recently also hastily pushed something and had to fix it up
> later. Let's try to keep a cool head and not let the heat get to us
> :)

Point taken; there was an adjustment to be made in (guix self) that I
had overlooked; the push ended up breaking 'guix pull' (I quickly
reverted and fixed it with the help of Chris Baines -- thank you!).
Letting some more time would have perhaps allowed someone to catch that
issue (or running 'make as-derivation').

Thanks again,

Maxim
?