diff options
author | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2021-08-02 15:48:59 -0400 |
---|---|---|
committer | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2021-08-02 15:48:59 -0400 |
commit | 4673f817938d9d2b1b40a072ab2e0c44a32ccc97 (patch) | |
tree | d3f12240903c4b2631849b2eff660ba0aa55dfec /gnu | |
parent | 69dcc24c9f0cdfea674eb690e7755d26a25ced2b (diff) | |
download | guix-4673f817938d9d2b1b40a072ab2e0c44a32ccc97.tar.gz |
Revert "services: Add a service for Jami."
This reverts commit 69dcc24c9f0cdfea674eb690e7755d26a25ced2b. It broke 'guix pull'.
Diffstat (limited to 'gnu')
-rw-r--r-- | gnu/build/jami-service.scm | 587 | ||||
-rw-r--r-- | gnu/local.mk | 5 | ||||
-rw-r--r-- | gnu/services/telephony.scm | 684 | ||||
-rw-r--r-- | gnu/tests/data/jami-dummy-account.dat | 392 | ||||
-rw-r--r-- | gnu/tests/telephony.scm | 366 |
5 files changed, 3 insertions, 2031 deletions
diff --git a/gnu/build/jami-service.scm b/gnu/build/jami-service.scm deleted file mode 100644 index d44e87387d..0000000000 --- a/gnu/build/jami-service.scm +++ /dev/null @@ -1,587 +0,0 @@ -;;; 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 - dbus-send - (%send-dbus-binary)) - ,@(if (or bus (%send-dbus-bus)) - (list (string-append "--bus=" - (or bus (%send-dbus-bus)))) - '()) - "--print-reply" - ,@(if timeout - (list (format #f "--reply-timeout=~d" timeout)) - '()) - ,(string-append "--dest=" service) ;e.g., cx.ring.Ring - ,path ;e.g., /cx/ring/Ring/ConfigurationManager - ,(string-append interface "." method) - ,@(or arguments '()))) - (temp-port (mkstemp! (string-copy "/tmp/dbus-send-output-XXXXXXX"))) - (temp-file (port-filename temp-port))) - (dynamic-wind - (lambda () - (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))) - (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))))))) - (lambda () - (false-if-exception (delete-file temp-file)))))) - -(define (parse-account-ids reply) - "Return the Jami account IDs from REPLY, which is assumed to be the output -of the Jami D-Bus `getAccountList' method." - (array->list (parse-dbus-reply reply))) - -(define (parse-account-details reply) - "Parse REPLY, which is assumed to be the output of the Jami D-Bus -`getAccountDetails' method, and return its content as an alist." - (dict->alist (parse-dbus-reply reply))) - -(define (parse-contacts reply) - "Parse REPLY, which is assumed to be the output of the Jamid D-Bus -`getContacts' method, and return its content as an alist." - (match (parse-dbus-reply reply) - ('array - '()) - (('array dicts ...) - (map dict->alist dicts)))) - - -;;; -;;; Higher-level, D-Bus-related procedures. -;;; - -(define (validate-fingerprint fingerprint) - "Validate that fingerprint is 40 characters long." - (unless (account-fingerprint? fingerprint) - (error "Account fingerprint is not valid:" fingerprint))) - -(define (dbus-available-services) - "Return the list of available (acquired) D-Bus services." - (let ((reply (parse-dbus-reply - (send-dbus #:service "org.freedesktop.DBus" - #:path "/org/freedesktop/DBus" - #:interface "org.freedesktop.DBus" - #:method "ListNames")))) - ;; Remove entries such as ":1.7". - (remove (cut string-prefix? ":" <>) - (array->list reply)))) - -(define (dbus-service-available? service) - "Predicate to check for the D-Bus SERVICE availability." - (member service (dbus-available-services))) - -(define* (send-dbus/configuration-manager #:key method arguments timeout) - "Query the Jami D-Bus ConfigurationManager service." - (send-dbus #:service "cx.ring.Ring" - #:path "/cx/ring/Ring/ConfigurationManager" - #:interface "cx.ring.Ring.ConfigurationManager" - #:method method - #:arguments arguments - #:timeout timeout)) - -;;; The following methods are for internal use; they make use of the account -;;; ID, an implementation detail of Jami the user should not need to be -;;; concerned with. -(define (get-account-ids) - "Return the available Jami account identifiers (IDs). Account IDs are an -implementation detail used to identify the accounts in Jami." - (parse-account-ids - (send-dbus/configuration-manager #:method "getAccountList"))) - -(define (id->account-details id) - "Retrieve the account data associated with the given account ID." - (parse-account-details - (send-dbus/configuration-manager - #:method "getAccountDetails" - #:arguments (list (string-append "string:" id))))) - -(define (id->volatile-account-details id) - "Retrieve the account data associated with the given account ID." - (parse-account-details - (send-dbus/configuration-manager - #:method "getVolatileAccountDetails" - #:arguments (list (string-append "string:" id))))) - -(define (id->account id) - "Retrieve the complete account data associated with the given account ID." - (append (id->volatile-account-details id) - (id->account-details id))) - -(define %username-to-id-cache #f) - -(define (invalidate-username-to-id-cache!) - (set! %username-to-id-cache #f)) - -(define (username->id username) - "Return the first account ID corresponding to USERNAME." - (unless (assoc-ref %username-to-id-cache username) - (set! %username-to-id-cache - (append-map - (lambda (id) - (let* ((account (id->account id)) - (username (assoc-ref account "Account.username")) - (registered-name (assoc-ref account - "Account.registeredName"))) - `(,@(if username - (list (cons username id)) - '()) - ,@(if registered-name - (list (cons registered-name id)) - '())))) - (get-account-ids)))) - (or (assoc-ref %username-to-id-cache username) - (let ((message (format #f "Could not retrieve a local account ID\ - for ~:[username~;fingerprint~]" (account-fingerprint? username)))) - (error message username)))) - -(define (account->username account) - "Return USERNAME, the registered username associated with ACCOUNT, else its -public key fingerprint." - (or (assoc-ref account "Account.registeredName") - (assoc-ref account "Account.username"))) - -(define (id->username id) - "Return USERNAME, the registered username associated with ID, else its -public key fingerprint, else #f." - (account->username (id->account id))) - -(define (get-accounts) - "Return the list of all accounts, as a list of alists." - (map id->account (get-account-ids))) - -(define (get-usernames) - "Return the list of the usernames associated with the present accounts." - (map account->username (get-accounts))) - -(define (username->account username) - "Return the first account associated with USERNAME, else #f. -USERNAME can be either the account 40 characters public key fingerprint or a -registered username." - (find (lambda (account) - (member username - (list (assoc-ref account "Account.username") - (assoc-ref account "Account.registeredName")))) - (get-accounts))) - -(define (add-account archive) - "Import the Jami account ARCHIVE and return its account ID. The archive -should *not* be encrypted with a password. Return the username associated -with the account." - (invalidate-username-to-id-cache!) - (let ((reply (send-dbus/configuration-manager - #:method "addAccount" - #:arguments (list (string-append - "dict:string:string:Account.archivePath," - archive - ",Account.type,RING"))))) - ;; The account information takes some time to be populated. - (let ((id (deserialize-item (parse-dbus-reply reply)))) - (with-retries 20 1 - (let ((username (id->username id))) - (if (string-null? username) - #f - username)))))) - -(define (remove-account username) - "Delete the Jami account associated with USERNAME, the account 40 characters -fingerprint or a registered username." - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "removeAccount" - #:arguments (list (string-append "string:" id)))) - (invalidate-username-to-id-cache!)) - -(define* (username->contacts username) - "Return the contacts associated with the account of USERNAME as two values; -the first one being the regular contacts and the second one the banned -contacts. USERNAME can be either the account 40 characters public key -fingerprint or a registered username. The contacts returned are represented -using their 40 characters fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "getContacts" - #:arguments (list (string-append "string:" id)))) - (all-contacts (parse-contacts reply)) - (banned? (lambda (contact) - (and=> (assoc-ref contact "banned") - (cut string=? "true" <>)))) - (banned (filter banned? all-contacts)) - (not-banned (filter (negate banned?) all-contacts)) - (fingerprint (cut assoc-ref <> "id"))) - (values (map fingerprint not-banned) - (map fingerprint banned)))) - -(define* (remove-contact contact username #:key ban?) - "Remove CONTACT, the 40 characters public key fingerprint of a contact, from -the account associated with USERNAME (either a fingerprint or a registered -username). When BAN? is true, also mark the contact as banned." - (validate-fingerprint contact) - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "removeContact" - #:arguments (list (string-append "string:" id) - (string-append "string:" contact) - (serialize-boolean ban?))))) - -(define (add-contact contact username) - "Add CONTACT, the 40 characters public key fingerprint of a contact, to the -account of USERNAME (either a fingerprint or a registered username)." - (validate-fingerprint contact) - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "addContact" - #:arguments (list (string-append "string:" id) - (string-append "string:" contact))))) - -(define* (set-account-details details username #:key timeout) - "Set DETAILS, an alist containing the key value pairs to set for the account -of USERNAME, a registered username or account fingerprint. The value of the -parameters not provided are unchanged. TIMEOUT is a value in milliseconds to -pass to the `send-dbus/configuration-manager' procedure." - (let* ((id (username->id username)) - (current-details (id->account-details id)) - (updated-details (map (match-lambda - ((key . value) - (or (and=> (assoc-ref details key) - (cut cons key <>)) - (cons key value)))) - current-details)) - ;; dbus-send does not permit sending null strings (it throws a - ;; "malformed dictionary" error). Luckily they seem to have the - ;; semantic of "default account value" in Jami; so simply drop them. - (updated-details* (remove (match-lambda - ((_ . value) - (string-null? value))) - updated-details))) - (send-dbus/configuration-manager - #:timeout timeout - #:method "setAccountDetails" - #:arguments - (list (string-append "string:" id) - (string-append "dict:string:string:" - (string-join (alist->list updated-details*) - ",")))))) - -(define (set-all-moderators enabled? username) - "Set the 'AllModerators' property to enabled? for the account of USERNAME, a -registered username or account fingerprint." - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "setAllModerators" - #:arguments - (list (string-append "string:" id) - (serialize-boolean enabled?))))) - -(define (username->all-moderators? username) - "Return the 'AllModerators' property for the account of USERNAME, a -registered username or account fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "isAllModerators" - #:arguments - (list (string-append "string:" id))))) - (deserialize-item (parse-dbus-reply reply)))) - -(define (username->moderators username) - "Return the moderators for the account of USERNAME, a registered username or -account fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "getDefaultModerators" - #:arguments - (list (string-append "string:" id))))) - (array->list (parse-dbus-reply reply)))) - -(define (set-moderator contact enabled? username) - "Set the moderator flag to ENABLED? for CONTACT, the 40 characters public -key fingerprint of a contact for the account of USERNAME, a registered -username or account fingerprint." - (validate-fingerprint contact) - (let* ((id (username->id username))) - (send-dbus/configuration-manager #:method "setDefaultModerator" - #:arguments - (list (string-append "string:" id) - (string-append "string:" contact) - (serialize-boolean enabled?))))) - -(define (disable-account username) - "Disable the account known by USERNAME, a registered username or account -fingerprint." - (set-account-details '(("Account.enable" . "false")) username - ;; Waiting for the reply on this command takes a very - ;; long time that trips the default D-Bus timeout value - ;; (25 s), for some reason. - #:timeout 60000)) - -(define (enable-account username) - "Enable the account known by USERNAME, a registered username or account -fingerprint." - (set-account-details '(("Account.enable" . "true")) username)) - - -;;; -;;; Presentation procedures. -;;; - -(define (.->_ text) - "Map each period character to underscore characters." - (string-map (match-lambda - (#\. #\_) - (c c)) - text)) - -(define (account-details->recutil account-details) - "Serialize the account-details alist into a recutil string. Period -characters in the keys are normalized to underscore to meet Recutils' format -requirements." - (define (pair->recutil-property pair) - (match pair - ((key . value) - (string-append (.->_ key) ": " value)))) - - (define sorted-account-details - ;; Have the account username, display name and alias appear first, for - ;; convenience. - (let ((first-items '("Account.username" - "Account.displayName" - "Account.alias"))) - (append (map (cut assoc <> account-details) first-items) - (fold alist-delete account-details first-items)))) - - (string-join (map pair->recutil-property sorted-account-details) "\n")) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: diff --git a/gnu/local.mk b/gnu/local.mk index 66a2ba52bc..9e8f2d702d 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -685,7 +685,6 @@ GNU_SYSTEM_MODULES = \ %D%/build/chromium-extension.scm \ %D%/build/cross-toolchain.scm \ %D%/build/image.scm \ - %D%/build/jami-service.scm \ %D%/build/file-systems.scm \ %D%/build/hurd-boot.scm \ %D%/build/install.scm \ @@ -723,7 +722,6 @@ 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 @@ -1883,8 +1881,7 @@ 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%/tests/data/jami-dummy-account.dat + %D%/packages/patches/zziplib-CVE-2018-16548.patch MISC_DISTRO_FILES = \ %D%/packages/ld-wrapper.in diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm index fd90840324..e1259cc2df 100644 --- a/gnu/services/telephony.scm +++ b/gnu/services/telephony.scm @@ -1,6 +1,5 @@ ;;; 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. ;;; @@ -18,45 +17,16 @@ ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. (define-module (gnu services telephony) - #:use-module ((gnu build jami-service) #:select (account-fingerprint?)) - #:use-module ((gnu services) #:hide (delete)) - #:use-module (gnu services configuration) + #:use-module (gnu services) #:use-module (gnu services shepherd) #:use-module (gnu system shadow) #:use-module (gnu packages admin) - #:use-module (gnu packages certs) - #: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 (srfi srfi-2) - #:use-module (srfi srfi-26) - #:use-module (ice-9 format) #:use-module (ice-9 match) - #:export (jami-account - jami-account-archive - jami-account-allowed-contacts - jami-account-moderators - jami-account-rendezvous-point? - jami-account-discovery? - jami-account-bootstrap-uri - jami-account-name-server-uri - - jami-configuration - jami-configuration-jamid - jami-configuration-dbus - jami-configuration-enable-logging? - jami-configuration-debug? - jami-configuration-auto-answer? - jami-configuration-accounts - - jami-service-type - - murmur-configuration + #:export (murmur-configuration make-murmur-configuration murmur-configuration? murmur-configuration-package @@ -104,652 +74,6 @@ murmur-service-type)) - -;;; -;;; Jami daemon. -;;; - -;;; XXX: Passing a computed-file object as the account is used for tests. -(define (string-or-computed-file? val) - (or (string? val) - (computed-file? val))) - -(define (string-list? val) - (and (list? val) - (and-map string? val))) - -(define (account-fingerprint-list? val) - (and (list? val) - (and-map account-fingerprint? val))) - -(define-maybe string-list) - -(define-maybe/no-serialization account-fingerprint-list) - -(define-maybe boolean) - -(define-maybe string) - -;;; The following serializers are used to derive an account details alist from -;;; a <jami-account> record. -(define (serialize-string-list _ val) - (string-join val ";")) - -(define (serialize-boolean _ val) - (format #f "~:[false~;true~]" val)) - -(define (serialize-string _ val) - val) - -;;; Note: Serialization is used to produce an account details alist that can -;;; be passed to the SET-ACCOUNT-DETAILS procedure. Fields that do not map to -;;; a Jami account 'detail' should have their serialization disabled via the -;;; 'empty-serializer' procedure. -(define-configuration jami-account - (archive - (string-or-computed-file) - "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." - empty-serializer) - (allowed-contacts - (maybe-account-fingerprint-list 'disabled) - "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." - empty-serializer) - (moderators - (maybe-account-fingerprint-list 'disabled) - "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." - empty-serializer) - ;; The serializable fields below are to be set with set-account-details. - (rendezvous-point? - (maybe-boolean 'disabled) - "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.") - (peer-discovery? - (maybe-boolean 'disabled) - "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.") - (bootstrap-hostnames - (maybe-string-list 'disabled) - "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.") - (name-server-uri - (maybe-string 'disabled) - "The URI of the name server to use, that can be used to retrieve the -account fingerprint for a registered username.")) - -(define (jami-account->alist jami-account-object) - "Serialize the JAMI-ACCOUNT object as an alist suitable to be passed to -SET-ACCOUNT-DETAILS." - (define (field-name->account-detail name) - (match name - ('rendezvous-point? "Account.rendezVous") - ('peer-discovery? "Account.peerDiscovery") - ('bootstrap-hostnames "Account.hostname") - ('name-server-uri "RingNS.uri") - (_ #f))) - - (filter-map (lambda (field) - (and-let* ((name (field-name->account-detail - (configuration-field-name field))) - (value ((configuration-field-serializer field) - name ((configuration-field-getter field) - jami-account-object))) - ;; The define-maybe default serializer produces an - ;; empty string for the 'disabled value. - (value* (if (string-null? value) - #f - value))) - (cons name value*))) - jami-account-fields)) - -(define (jami-account-list? val) - (and (list? val) - (and-map jami-account? val))) - -(define-maybe/no-serialization jami-account-list) - -(define-configuration/no-serialization jami-configuration - (jamid - (package libring) - "The Jami daemon package to use.") - (dbus - (package dbus) - "The D-Bus package to use to start the required D-Bus session.") - (nss-certs - (package nss-certs) - "The nss-certs package to use to provide TLS certificates.") - (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.") - (accounts - (maybe-jami-account-list 'disabled) - "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.")) - -(define %jami-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-configuration->command-line-arguments config) - "Derive the command line arguments to used to launch the Jami daemon from -CONFIG, a <jami-configuration> object." - (match-record config <jami-configuration> - (jamid dbus enable-logging? debug? auto-answer?) - `(,(file-append jamid "/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))))) - -(define (jami-shepherd-services config) - "Return a <shepherd-service> running the Jami daemon." - (let* ((jamid (jami-configuration-jamid config)) - (nss-certs (jami-configuration-nss-certs config)) - (dbus (jami-configuration-dbus config)) - (dbus-daemon (file-append dbus "/bin/dbus-daemon")) - (dbus-send (file-append dbus "/bin/dbus-send")) - (accounts (jami-configuration-accounts config)) - (declarative-mode? (not (eq? 'disabled accounts)))) - - (with-imported-modules (source-module-closure - '((gnu build jami-service) - (gnu build shepherd) - (gnu system file-systems))) - - (define list-accounts-action - (shepherd-action - (name 'list-accounts) - (documentation "List the available Jami accounts. Return the account -details alists keyed by their account username.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - ;; Print the accounts summary or long listing, according to - ;; user-provided option. - (let* ((usernames (get-usernames)) - (accounts (map-in-order username->account usernames))) - (match accounts - (() ;empty list - (format #t "There is no Jami account available.~%")) - ((one two ...) - (format #t "The following Jami accounts are available:~%") - (for-each - (lambda (account) - (define fingerprint (assoc-ref account - "Account.username")) - (define human-friendly-name - (or (assoc-ref account - "Account.registeredName") - (assoc-ref account - "Account.displayName") - (assoc-ref account - "Account.alias"))) - (define disabled? - (and=> (assoc-ref account "Account.enable") - (cut string=? "false" <>))) - - (format #t " - ~a~@[ (~a)~] ~:[~;[disabled]~]~%" - fingerprint human-friendly-name disabled?)) - accounts) - (display "\n"))) - ;; Return the account-details-list alist. - (map cons usernames accounts))))))) - - (define list-account-details-action - (shepherd-action - (name 'list-account-details) - (documentation "Display the account details of the available Jami -accounts in the @code{recutils} format. Return the account details alists -keyed by their account username.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (accounts (map-in-order username->account usernames))) - (for-each (lambda (account) - (display (account-details->recutil account)) - (display "\n\n")) - accounts) - (map cons usernames accounts))))))) - - (define list-contacts-action - (shepherd-action - (name 'list-contacts) - (documentation "Display the contacts for each Jami account. Return -an alist containing the contacts keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (contacts (map-in-order username->contacts usernames))) - (for-each (lambda (username contacts) - (format #t "Contacts for account ~a:~%" - username) - (format #t "~{ - ~a~%~}~%" contacts)) - usernames contacts) - (map cons usernames contacts))))))) - - (define list-moderators-action - (shepherd-action - (name 'list-moderators) - (documentation "Display the moderators for each Jami account. Return -an alist containing the moderators keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (moderators (map-in-order username->moderators - usernames))) - (for-each - (lambda (username moderators) - (if (username->all-moderators? username) - (format #t "Anyone can moderate for account ~a~%" - username) - (begin - (format #t "Moderators for account ~a:~%" username) - (format #t "~{ - ~a~%~}~%" moderators)))) - usernames moderators) - (map cons usernames moderators))))))) - - (define add-moderator-action - (shepherd-action - (name 'add-moderator) - (documentation "Add a moderator for a given Jami account. The -MODERATOR contact must be given as its 40 characters fingerprint, while the -Jami account can be provided as its registered USERNAME or fingerprint. - -@example -herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f username -@end example - -Return the moderators for the account known by USERNAME.") - (procedure - #~(lambda (_ moderator username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (set-all-moderators #f username) - (add-contact moderator username) - (set-moderator moderator #t username) - (username->moderators username)))))) - - (define ban-contact-action - (shepherd-action - (name 'ban-contact) - (documentation "Ban a contact for a given or all Jami accounts, and -clear their moderator flag. The CONTACT must be given as its 40 characters -fingerprint, while the Jami account can be provided as its registered USERNAME -or fingerprint, or omitted. When the account is omitted, CONTACT is banned -from all accounts. - -@example -herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f [username] -@end example") - (procedure - #~(lambda* (_ contact #:optional username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let ((usernames (or (and=> username list) - (get-usernames)))) - (for-each (lambda (username) - (set-moderator contact #f username) - (remove-contact contact username #:ban? #t)) - usernames))))))) - - (define list-banned-contacts-action - (shepherd-action - (name 'list-banned-contacts) - (documentation "List the banned contacts for each accounts. Return -an alist of the banned contacts, keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - - (define banned-contacts - (let ((usernames (get-usernames))) - (map cons usernames - (map-in-order (lambda (x) - (receive (_ banned) - (username->contacts x) - banned)) - usernames)))) - - (for-each (match-lambda - ((username . banned) - (unless (null? banned) - (format #t "Banned contacts for account ~a:~%" - username) - (format #t "~{ - ~a~%~}~%" banned)))) - banned-contacts) - banned-contacts))))) - - (define enable-account-action - (shepherd-action - (name 'enable-account) - (documentation "Enable an account. It takes USERNAME as an argument, -either a registered username or the fingerprint of the account.") - (procedure - #~(lambda (_ username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (enable-account username)))))) - - (define disable-account-action - (shepherd-action - (name 'disable-account) - (documentation "Disable an account. It takes USERNAME as an -argument, either a registered username or the fingerprint of the account.") - (procedure - #~(lambda (_ username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (disable-account username)))))) - - (list (shepherd-service - (documentation "Run a D-Bus session for the Jami daemon.") - (provision '(jami-dbus-session)) - (modules `((gnu build shepherd) - (gnu build jami-service) - (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 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=" - #$jamid "/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)) - (actions (list list-accounts-action - list-account-details-action - list-contacts-action - list-moderators-action - add-moderator-action - ban-contact-action - list-banned-contacts-action - enable-account-action - disable-account-action)) - (requirement '(jami-dbus-session)) - (modules `((ice-9 format) - (ice-9 ftw) - (ice-9 match) - (ice-9 receive) - (srfi srfi-1) - (srfi srfi-26) - (gnu build jami-service) - (gnu build shepherd) - (gnu system file-systems) - ,@%default-modules)) - (start - #~(lambda args - (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 account archives 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))) - '#$(and declarative-mode? - (map jami-account-archive accounts))))) - - ;; Start the daemon. - (define daemon-pid - ((make-forkexec-constructor/container - '#$(jami-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)) - ;; Expose TLS certificates for GnuTLS. - (file-system-mapping - (source #$(file-append nss-certs "/etc/ssl/certs")) - (target "/etc/ssl/certs"))) - #:user "jami" - #:group "jami" - #:environment-variables - (list (string-append "DBUS_SESSION_BUS_ADDRESS=" - "unix:path=/var/run/jami/bus") - ;; Expose TLS certificates for OpenSSL. - "SSL_CERT_DIR=/etc/ssl/certs")))) - - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - - ;; Wait until the service name has been acquired by D-Bus. - (with-retries 20 1 - (dbus-service-available? "cx.ring.Ring")) - - (when #$declarative-mode? - ;; Provision the accounts via the D-Bus API of the daemon. - (let* ((jami-account-archives - (map (cut string-append - "/var/lib/jami/accounts/" <>) - (scandir "/var/lib/jami/accounts/" - (lambda (f) - (not (member f '("." ".."))))))) - (usernames (map-in-order (cut add-account <>) - jami-account-archives))) - - (define (archive-name->username archive) - (list-ref - usernames - (list-index (lambda (f) - (string-suffix? (basename archive) f)) - jami-account-archives))) - - (for-each - (lambda (archive allowed-contacts moderators - account-details) - (let ((username (archive-name->username - archive))) - (when (not (eq? 'disabled allowed-contacts)) - ;; Reject calls from unknown contacts. - (set-account-details - '(("DHT.PublicInCalls" . "false")) username) - ;; Remove all contacts. - (for-each (cut remove-contact <> username) - (username->contacts username)) - ;; Add allowed ones. - (for-each (cut add-contact <> username) - allowed-contacts)) - (when (not (eq? 'disabled moderators)) - ;; Disable the 'AllModerators' property. - (set-all-moderators #f username) - ;; Remove all moderators. - (for-each (cut set-moderator <> #f username) - (username->moderators username)) - ;; Add declared moderators. - (for-each (cut set-moderator <> #t username) - moderators)) - ;; Set the various account parameters. - (set-account-details account-details username))) - '#$(and declarative-mode? - (map-in-order (cut jami-account-archive <>) - accounts)) - '#$(and declarative-mode? - (map-in-order - (cut jami-account-allowed-contacts <>) - accounts)) - '#$(and declarative-mode? - (map-in-order (cut jami-account-moderators <>) - accounts)) - '#$(and declarative-mode? - (map-in-order jami-account->alist accounts)))))) - - ;; Finally, return the PID of the daemon process. - daemon-pid)) - (stop - #~(lambda (pid . args) - (kill pid SIGKILL) - ;; Wait for the process to exit; this prevents overlapping - ;; processes when issuing 'herd restart'. - (waitpid pid) - #f))))))) - -(define jami-service-type - (service-type - (name 'jami) - (default-value (jami-configuration)) - (extensions - (list (service-extension shepherd-root-service-type - jami-shepherd-services) - (service-extension account-service-type - (const %jami-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 @@ -981,7 +305,3 @@ suite.") (service-extension account-service-type murmur-accounts))) (default-value (murmur-configuration)))) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: diff --git a/gnu/tests/data/jami-dummy-account.dat b/gnu/tests/data/jami-dummy-account.dat deleted file mode 100644 index 0e908396ca..0000000000 --- a/gnu/tests/data/jami-dummy-account.dat +++ /dev/null @@ -1,392 +0,0 @@ -;;; -*- mode: scheme; -*- -;;; 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\ -TUxsWnlPcVFrK1pLSGFMM2llWjFxTGRNS3VmQjNESC9idWcKeXRQb2JBVXNsN0lJSGVjVmZWaVpvMml\ -pRXhHUCtEMlB2UUFtRFVGWU90V3FrT2FPSlV2VmJ5ODhOM1NyeW9lZgo5aHpZUGxMWmxFQWNGR055S3\ -FibjJXOENHaU5LSWhXYW1Zd21UclY3T1pkeUcrTi9GZk40Vms1NkZyc1pCTDQ5CmRYU2xGZ045TTBaZ\ -WNleTEvZEpPRE9lSHNuME5VK0gvNFZEUk1hR1NmelpwSkxJOXE4T2FiSWpVM0ttb24wQTcKdzFWeWNU\ -L1FwYlBxRUFVckt5dytvMzV3MlAyaUZ1czZiMlBvUUxFTGFTRVl6K3R6UEw5UTM1ejNRdGdMQytuagp\ -IUmxCQW9JQkFES0Q4NGhrYkphczlIQ00zanNNSU9kb213ZEMvcktxVUxKNHFWZU5QR0xDY2VpNEdocn\ -dlQnNICnNoN0hibFlZSDN2U2Y3RW1iZEVCb2xlWVJsaUx5ZzJDQU1ZOGpNK0lpUEwydk1yM2Y3SzZPT\ -mU0UkVPUFJSZkcKWlJDcTh4a0ZPQlg1SUJUbkVCV3QzdVdyb1NGY2x4RTdUa2I4VkUyVzExTG9ZNlkw\ -TUNPaHdxN21xYXRUVnNrawpTRDNySmkrTFR6a2Y4OEx1bjZZNjdiaFNOTWpKZkFaUXNQc0FTRkJBUTJ\ -rQnE5alRLZGVuaU4yYTJIbm0xNCtrCnJDeU9ZVE14Q2hQbWNpS25pVy9MWnFUL0U1dlNRUGdBVzc0dT\ -VLazJoSjRBajNjRW9NVEwxSytZbStWYWh2U0cKTi8xOFdYQ1JRQkg1d0p2eXJYczBtT29GQlRnTWg4d\ -z0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") - ("ringAccountKey" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTk\ -Jna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRDNCdDRnOUVUdk9EVnYKM3hWV0ZlS\ -1Nnbk5uVEF3S2dYa3IrQ1FhdU8vTGZWM01RenNSNHliL3hoaWhhb1Z2c2FtZ3ZRU1haL0M1R3I5QQpE\ -YlAxbHNHclRCK1pXMC9uMXVEb3hmVWdZRWY3SGtVanJtdVFjUGlFWGlUVkNiY002U0NzdVNrMnRxdE1\ -BNjBNClRacVo2LzAvbkEvblR5TnFNaUJNSmFRN0NUb2xOTTh2Z2tvd0tBRW14OGpJUG9YZEttMnd6MD\ -Z6SnhwU1d4WjUKc0FBTkdSSHU0b2ZXNWJiMERvamtnRzFBYUJ6Nm9uSmdKK1JFSWV1UkpNQWFHYmtzW\ -TQ4Z1Z1b21BVTU0UFNvUApFb3psVGdHd1k0cnhJTGE0V1Y0RVZMVExrR240ZTYrRUYrSjIvRURBUFdN\ -bXREdFpUeURCc2t5bStLaDRUT2xPCjdnN3JlUUhLdDd1R21YU0RnbzVKZ2hOOHNVRFUxL1Z2YmFFcUZ\ -tTDJrZWNGOVlVZmNsUWRGY1ZncmIrMkh5R3AKRVc0b3RkZjlYYzhOMWxrbGk1dFBqRGZuQ1U3OHB6QT\ -BxQmV1SWhZTnF1VjhGSm1NemhXeVFDbE1MUEFYbXFVdwpWYlY0MWduM0NkektuUlVhZXFONXlzOG5KQ\ -mRJNDBleWlYRUlvU0VKcFpyT1Z5ck1icnNCQzltaVFpZUFhSnlBClFvcjFaRGlwMkpZNUFza2phUGQ0\ -NGk0MGNkeFpob1RhNnpzako5UjZScllFYjhWTmZQemFLaElwSXJSd2NpbCsKWjFrbUUwSE1kY21ITXR\ -hbWZhK0l6WUR4dDV6OGVOZys1RGpVVG1MRkdyQUVWRW5hNDcxdllxYnk5UCs1T1cvNQpSdkxtUER2Tj\ -dlUURpTlZENlBYRFk5OTY4bTZaaHdJREFRQUJBb0lDQURjOEkrTCtlNE41OEFqcHV0MmEyeVNqCllxY\ -VFUSWowMW1GTWhOWXMwQUdTTUswQncyMkdleXZwNFl3R1EzdnNIOSsvSkEydXdoYkJzazNpUW9FQmlx\ -Q0EKenZmOWdPcDRFNlk0elV6RitwSmQvRnUwSG4wWHBab0Rhdnp2eFN4djNFeUN3b0puYWZuL1FHeGw\ -xZEhoQUtsKwpmZGZjekRCc3NPZ1Y2cGtBd1MyY2wwOHFOT2g3cVhaQWFkYk1sQ1lWM0owU1hhaVZiNz\ -lHZXNvTzNwUVBMUUZiClNjQjFjT2sxYnNxWkpOU244d0xmMis5QVBEdzMwWEtNNHg5eTdRTE42Q3oxQ\ -WpvcFJLQ0NIS3R1SEc4UmVETTIKcnRTbjJmTnltQ0VqeDZGVTB6MHFldDV3Y01UbU5weEZuYXdEMU5s\ -dFpnZXBsSllwTjVKZXNEUmo2cFlnWXBPKwo5UDU2cEdtZVNTVzVxcHRoWFFLQmFsdy8wWXp2YUlYdHp\ -hTnZyNUJzRFNrWkU3cldzODIvQmFzUE1RckFmOGpLClZFMU9pSzcrVllUVTRFVWVoZ0FZOXdzNjFqYk\ -lSSEpQbW9VQXpkWHcyL0d0Vk9JUTFwUFJnOXNYN0JaMmUyV1YKdmd5aThPUEJxRWtwblBiMkU5Z0d1U\ -25rY01OeWFVbUl0c3pFandadW14dnZrUW5HanlTb3pjY2R3dnNQNnBJagpoN0g5VUNQTHdOM0N5N1lp\ -UmliSlZBWlNjZkF6QzhubXNLODQrTzJUZHBzTXk0SEZDMmM4dlZiclpteTVkWC9qCk1ESnBzS25JWlZ\ -JMmpXSzRpRS82aUdIWVdoY3JvWnYvVEJ0YW1SQUxTWDVOYkhhWTI0bXVRSG5yMldtaytld3EKbHRGbC\ -90bXgyVkpWUitMZ0JCUGhBb0lCQVFENEI0MjQyVTgvbkJ4d2RzelhCdWxBOVFTa0I5Zktud0RlRkV3S\ -wp3Nks0eU14YVdRU04ycjRxRDhLcW93OVZVMzRYdkRWbFh0RUlDaVh3Q0hZdW5IL3g3cXNSdEVzbHJM\ -aWg4UHRPClpDSU8zUml4RmlIQXFlQUh2YXF0NVhXdndaREx6WnV2THRJOTdINkZ1QjYxck5qMnhxdlR\ -IN05pUmp2S0R0WXgKR1VtNURoQ094cm9tR0NkWHRnWHJGaS9WRU1TSmpQMkM3OTNrN1pTNmNZL0Nkc1\ -RqWEsrZ1UzeWM3OC9kN1pYbwpKMGg2WFlSdmhlQW9Bd2dkVTl4MWtYL2ZmY2tZK1hUcFRwV2xXWmtlU\ -ytsejBpQkRnUlJzMm45OFZDeTZDRmRZCldsZXZaZy9SWXZ6dzlKdWFVcXArOHpHbHNXR2xuOEhtZW5X\ -Q1luSHJnNFBxQnRkL0FvSUJBUUQrOXhDL1N5ZGIKVWZxMUJHYy95YktZc3RLb3A1azB0d3M0SlUwTzN\ -aT1U3MlZ5YTdLT2lTemdPSzVnL2QzckhMMXR2dHViTDBWNAo1dEF4a1AzSkVYbmxZZU5IVkpROTc2RH\ -NGS3Ztc0FGL2JJdVBsdFBRT1dyM3g1eW1RU3lCOTBUczV0dFdWMTVQCktYYVNnMTZidDhwNS9MeERkZ\ -ng2c1YzN1o5RDFRd21EQllreVFIcWQ4clljTm9ad1M5ZnI3UTZhN1ZNSDVtT3IKbEF5dzBCYVdZQk9k\ -bjFGd0pVV3NlRlpmTy9vNUVqZk9Hd0xMR1hiOEVmQ1hqdlRYcUNHLzNrT1JvN1NkOWY1eQowVjIxMmt\ -YVVNINHNDbFB0SmwzeHpaMWJxQ2RMVDNITWNLUTk5UGFFVFppQnNXQ1lOcXg1c0Q0RGhoMzdZQ2hKCm\ -hlN3VUM1E0MElINUFvSUJBREN1VXR1b0UweloyQjhld2grbUpKdnlPMEh5cENFSnlrTE1Xd3gxejNkV\ -E9nQzEKbmhZMWk4TjNxbTZSYUk0SHdDVHFkTlI3b3ExZ1NJZnZNVHIrem9IdXBUYnBXeUorM3hJeDJU\ -Rk9wL3lnMnByUApURHFqWE94SUJycnc0WU5vaTRIa3poeTVKTnl3a1RpdnBaOWsySVMvQTdTQmNWVGx\ -raENianVDK0pPRWthSTJOClpiWGFZY1p1WElVQ3FzcTM2c3RRbCtWZUxRQWt2VjlHc0wrclRnT09Dbz\ -UrTkdRZEVZQnVoRkMzZlJzL1JhSVoKOWFBRTBFL3BTTWp1a05tTnQ2Mm1NSk1tTUdydXhnWFRRblBRR\ -npNSW43aXB2Z0hxQjRsUDM4emdsbnMvbmZVcgo1NWRuZXk3ejhMRFFETHVIc0RHd3hINzNKQjgrTVR2\ -WGFVbkNwQU1DZ2dFQVNBSGxBL0dvdXR6TFRvWmcxcDRUClI1YnhjZHBycFh5d3VYbW5hclJmY3VldG9\ -nUVNtTGpiS0xRNVk0RXZSTENJTzA5MDNENGNnOG5FTU10L01XTXoKSnZwZll3emJGU2J4THR1anRQSX\ -VhaHR3eXV2UkJIVEM1aG5FL3h0WEE1bWZLTDBHWXpzbmtubm1WL2lzSnBSZwpwZFVnSW5sWEJodkRyR\ -FlreUsvWEp0N1FZWlhlUzI5NXlUd0krZndoamlzVVBlTWEyUmRUUE9rQ01JbUVaNUhZCjJHSmZjS25H\ -SkxDVHpDKzNPcGtQazdFRE4vTUlMS2F3YVUxaGp1cVlKWVVUVmpXQzFEM2VUL1ViWHptM0VQNHMKVEN\ -uYWpCYVMzN0N2YVd4ek5JektXZS9TSXdGbEFmYWNSTHlneUR4Z3Q3bHp1akVObEtvU2xya3h3ckpEND\ -Z2WAptUUtDQVFBcTVQWWxSQjgvNnFiWWt1OTA0NUZRdXk2QWtlYXBaMW0raS9SQzZtbFRvUXB6NDlPU\ -Gs4ZGx5YjVtCndvSVhpaEo2V05jN1RsWlRYMnpQTTRBS0M5VFNBUWJrWDg5bjEyU2VDSUlHbXVINnk0\ -TjZiY2lxZjVVcSsvc0IKcHJKeFRNYlRSUFpqS0VVd1N0SFg1MUQ1bi9sQnZERGY3Y2VEZytsYlE0RjR\ -KMTlPd09oZ1lGcjFheGQvNXd2VgpURjNoVlQwbFZGN2RyRC9iMHZOcmxnbUNjbEk4UDg1a2dkRUhZbG\ -ZtTFoxeXJIMkNXVy9SS0lsWk9ZdFVuNFNpCkp5a2VlNDROWElXU3ovalRBdFRta3VQTzRvUjF5d3dRc\ -jdhUTF5a3hRVm9rVm5vY2xqU0tyQlk4R294a0I0eDIKUDNrb3F1UnkvcUd3QzBnN1o4ZjBTQjNQZVZt\ -eQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==") - ("ringAccountCert" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ\ -0F3SUJBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNt\ -RnRhU0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwp\ -PRFF6TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERT\ -BNVGN6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aa\ -k16TkRWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3\ -RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHB\ -LQ2MyZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTn\ -MvV1d3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd\ -0RyUXhObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBU\ -ck1uR2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3h\ -qanlCVzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WX\ -lhME8xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV\ -1l2YVI1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1E\ -U29GNjRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0Y\ -wamoKUjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzam\ -lMalJ4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxc\ -Vo5cjRqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0\ -NUFPSTFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZ\ -TdzJWMnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSE\ -JnQXdEUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2R\ -Wp1V3dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMy\ -V0UwdDlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUd\ -QZUJjWi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYV\ -VNeGt5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNV\ -GhDREdBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJi\ -TAp2M0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1E\ -xcVBvYU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUH\ -FnQ0NuWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5M\ -WFoUjgyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8x\ -elRzVWRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJ\ -Ra3Z6eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU0\ -5ybTJMY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBV\ -EUtLS0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3\ -VERFUU1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlR\ -WaVkyTXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk\ -1UY3pNakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ\ -3dOZ1lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdO\ -ak15TVRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0l\ -CQUxTcGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVn\ -llamgremVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqb\ -mVyUWd6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1\ -czBVSHZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQo\ -zWjZudElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2Tl\ -RCOUlmCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya\ -29Na3dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStS\ -TnpJWGFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2l\ -oZk5WOTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OV\ -pNblRWVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2R\ -VR0dHRuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01Z\ -eTVsOGt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjB\ -HQTFVZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQV\ -FIL01BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluY\ -m9yWmRhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2\ -VnFBU2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN\ -3dTUKTnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRG\ -NocE9SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLU\ -jd1TDdrWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2\ -bXhaSnlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUx\ -ZTzAwR3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObT\ -B5aklqbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1d\ -EU1MllzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdF\ -OFY2cWM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDR\ -KTklTM2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRT\ -RuT0FyS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tL\ -S0K") - ("ethKey" . "fN8cOT1lYNziaW0+pjBIgZ8r6+zMMhHsukkWBNPDsFo=") - ("TURN.username" . "ring") - ("TURN.server" . "turn.jami.net") - ("TURN.realm" . "ring") - ("TURN.password" . "ring") - ("TURN.enable" . "true") - ("TLS.verifyServer" . "true") - ("TLS.verifyClient" . "true") - ("TLS.serverName" . "") - ("TLS.requireClientCertificate" . "true") - ("TLS.privateKeyFile" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQU\ -RBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzM5b1Z0cXNtUGdaSUgKcHpTV\ -GtlT3BlWC9CSEx2KzFTYnJPSFpVRHEwNFZCUU5BNmJmSFNSWTJpbHE1WEVheXNVSmwzQmsvM0txZEhS\ -cQpEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXhQZ29WODZSUVBub1dCRTdhWVVEZTlJZXlxMmllZXpDK1l\ -YSnBtWTljCk5tblpaMFlHOHJGMEVpWFA0SHpVWGphZklTKzdKTTJ5ZTZyUlpINXBvdHBQNmV4NXhqVU\ -VuNEFFdWhuWGJ6U0EKNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXV0ZWaE5ySVBCVFJNZ2RaTWRGRTh0W\ -VFyaUNkNTV4dUhrYncrQmY1VQpmVENqN25tVEcybDJNbGcrSXBHVkFXUFRWNkl0NFNiS3VadW5MZmRD\ -S2FrSU03SnA3V2dJWjZNRHdkaFFQWjJNClJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcnRmeHdldlliQk1\ -yOEdRV2JQYlRKYk9tZHZnUGRGcXNTb1F4QnRUcWQKSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpuZlVtMl\ -BQcDBIK1g5Sk96d0R1QWI5K0c0cWZtK3Z6bDd1N0o3anJ0TQpHQlJlSlREdGFNd2RHa0lmcUNRZGREZ\ -HFpM1hzVHVVOVNubVN4NkdxT2V6ZU04bk55dmtZWjR2Uk9vODFzU1JjCjQ4L3pZZjJ1OFBJUm4vVy9x\ -Zk9hbjVIbkcxeFNBUDZkcHAzQ1Y0YTgwUFduZyszcXQ4NW5Ha2k2Nk00NUlnRjgKNHZwRGUyM2YyZnp\ -rTk5OOTk1VTBvUStKVGg5eG1HaWJmenlCaXk5d3RUVHVYWDB3Y3gwTm9wNjdYaFFDVVBGSApybVd6Wj\ -V5bXhEdkRhcUN1U3NwcG0vTkRVRWQyc1FJREFRQUJBb0lDQUYyWXo4bzhXdERvMjZPSkx2Ym1BeTcyC\ -jRra2VsWWZTYXpyQ1AzSUZCWnpqS2xCMHl6STVZWVRUZXI4b2ZhTmtCMXdaOE5WeUlxVVhHeVBhSzls\ -MU1BcmwKU1pFRW5iQlEyVXpCbVh6VVU4MVhhUUpxeHpMc2ZqSWR5U09teG1QaFVobFFGRHpJMTYxaXM\ -4MzI0V1A3WjJXaApsU2U1RkFQdjg1TVpYREVhY1c2R0N5SUVTYVMvdkpHQ2loQ2VzL0pCSmpoejdtNT\ -VRU3liSjl0dnJxUE5KSDhJCmhDTm1BUWhEU3NPYVJXNlpBdGV6UEdUb0FQUHNHMXhLMGdwUlVDM1YyR\ -C90bGRRa0VlSjhxaW5TaUN6ZjZIc3cKTnpncjVUbTMzTm96R3Rjc2Z4ZFl0cVB1UzRPRG40bktLSFpE\ -MTBLTng2Qi9HakdQTHIra21jUUVSMUV4U2s2QwpSemtzSTRzRml6VnVLYTFNK2ZqaVNVc3RTdHV1cTJ\ -QQTJJNzFNTTZlMHJmU3ZKM3VESEJYOWY5T1RFaGxZUFBkCnB1VWM2ek1pdEJKRTg2SFo1M08wR0MzdG\ -p1M1BnSnhmWnJyMHBCU20rSzQ3ajVTUFNkaytiVGh3UDZDZDhzWGUKNmljS3YvMXI0SjVqd1pFTmRna\ -2hKeHVuMkE1WVA0c0NBU21JSFFXWmU1cGdFQ0ljUXBHZjNBVllVdVpGeXZ6cApLL3VBRTc0L3NMK0VU\ -UnhDUlhkN0ZJM2cwZFVySTA2WUFIV1FkY1JpdWpIUU12ZXB3V2RrdUdkaE9wV3VRTy9oCjc0MGgvRlZ\ -0dFdOMjc3ajhhc3ptSDA3T2lRWFIwTU1mN2FEaldMaElMYkd2YkV4TzN0TnRBYXZ6cGxmMUNSNk8KUm\ -1nNDdhVTZpR3lON1MwTE85RUJBb0lCQVFEWWMwelh2UmJHTWxkVVZnOFFTT1ZHKzV4NXVocTVyQ2xtQ\ -lp2ZApvYitWS1hkMXBONThraWkxTFBiYUJmZG9JcWVUamdOK0E0YnVpT3RGNjdCWUdISkp0WTk4ZWR5\ -RkpZREtCSzIyCmthRml2eWVuV01UWG5jaHMvUmNXMnVGS1M3cFAyNDRXYkF2clQxZGxmTTF3L1JzbTZ\ -QVjF1dndPSmNJcEdLSzgKWWgyN2hiaThUbEF6bnJCSS9TbGZwNlpxV3Y0MFRLanNNdmVGVFNWRU5yd1\ -F2MVl4TjcxbjA1UWRVUkIrT0lJSQpPenpNMWpNcm43cjNmS3RKTEx0RllEdUhJZzJDL2E0cUZORmpjM\ -296V1laQkhlcDkrWlEyOTgxdy96VTJIZ0oxCkxiajZjMy9qQU5EakI3MnRkcWMxVUkvMWhwVUh2WDJz\ -emY2czM0L3VSY3RlaFgxWEFvSUJBUURaazVaT01RdVMKNzlVQkhCeTRFbFR2cVkvUUxjMUd1aG9LVXE\ -0dzhONEtJZlpMSDk5TTcwek9JWjNoaEdaYWJDREZHN3RqSDl4Vwo3UnhXVWt5cFRDK1h5TTc1YjF0YW\ -9jRVVyZ1hnUnE4R1JFRXg3OUtwWUE3ei95TVc1OHJCTWhHTWlGZ1JTM00zCmNQSzg2dHBvaTFDN2crd\ -lUxcjJwcDEzN0habzZiUHpKTFRPNXA1OG1tTFRPQVpKd1VSZ3pzZWJnc1dsWXZlR0wKZWNBZ3lYbUtt\ -WmVSeGRNUFlCK0NmMno1TWZ5Ujk4MHRVSDJPaitGQjlJNExzRTExREphZ2wwS1lHMWxKRTBFagpodEV\ -1cTFOTG9DcVkzYXVKYjhtRUpqNlA2Ylc2SytCZTBlUi9CS1MraGxxb3VOYWEySnlhSDc4Qis2U1VZWE\ -RuCnd6MityWUhVZEI4M0FvSUJBRFlXc2ZnaloyS0Z4KzdxUm45aVIvRXlCUXNpSjNXSWdSdmVnUEdrY\ -nRTZWRSeXYKNDIwcnRRSjVSd0o2aFRXL216S3pSVW9qSlgvTU5VYld1ODEzNW05bThJRkJqb3F6TVhq\ -S0xJSzM1NlZlY1ZGUApUSGs1RTVHd3VTbGI3dnA2N0FieXJaSUswL3VzYXdHUWEySTF6YWd1aE5BenR\ -yTHVXcE9jZFdZditwQVd2WEJJCi9aKzRvd0xLU0tGL3FvVmZVYkRPQzFSaTlCbWFpcHArTndiVVdYeV\ -pHanFzMDVGejVYUTFPTUZIMUV5M3BqZmIKaFlRODRpeTZBZDQzU3dqY3lKV1lRUUtCQzBZWDRFeWVyW\ -Dd1TTkvaEUxbWRHUGlJdmNwVk8zWCt3Ly9LQndZNQorUGtTd1NKc3lTSDRqTkRsSGE2K2VuNUpSNy81\ -YWVVNENiY0lFcWNDZ2dFQkFLelMvNnhDWnZnalN5V2poK2hxCm4wN3plQW1icUJmTEVZNHJtTFBGVUF\ -ucWFqSElNbDV4SXFnRnFkd05pQ1BCQ2RLbnNaUU9KYjVpZjRUTndKa2wKckJRNzdMUFRVVlJQY2dnVU\ -p4Uzc4S0Rnckl5Vys5V1FPTEIxZEJEb3MzUDhhbFlmb3h5eHV1Wko4SFpCY3BWaQpQQkdHdTFnSDd3V\ -0lyUzBmbVhkWlJQNGp5cGRvM3hFUWNXWEZkK1dCZE9EektmcEcwZkFzZTdDSFdDWnpBdmttCkFYQklH\ -OXQxdGZHNWQvMEZTS05GbTVPb0FPT3h3L0xZNTgrL0RmZXd0U0VBcFdRZkxTL1BmSWxVdUdvQ3FwcEM\ -Kc2pOVXVNSGxxc011Z2JsY29mNHNoZityWjMzQldYOEJSNWdIb21mRE1ibDNDQWp5TXd1dHpybzVxcD\ -BBUTBWWApxOGNDZ2dFQkFLakFXVVRYY1F2TE8yYkxOZmJBTUlSVXk1T2lTZmJCbDNYRThSZnNzaUt2V\ -VBnTFcwSlV1V3FLCjdGdUFxTlJPRHhrS0pMSTdyQlo2YVNqNitFWHpUMnJwY2dFWktnSjFOUEFRdFNs\ -UjNJUkcyU3JJdjBFK3UzbkUKK1laa3pOa2Q0MUJqTkRjRm1HV21lZk5ROUJmaVIrZlZFSkZmcE5oSkl\ -mNUloSWU0RUtZUE5VUXNua0tSVTlxUApzWi9idXBXc2w4bWVFcko3bllJQ05ucHpnSHRpNXdSMlliVF\ -VXT01odmRFUldxMnhTV3BBYmtNMElhZDBUc05kCmUrYVRQVmJOMXFibFZLMm1qUTl2YS9JSkVuSE51V\ -E9TREtJeUpvcVArQkxiRTVjQU5acXQ2OFFadWdOc2RxNHkKV2FoeStydU5LS1F3Mk5MYzQzZUtsNmxv\ -bXdtRlFZOD0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") - ("TLS.password" . "") - ("TLS.negotiationTimeoutSec" . "-1") - ("TLS.method" . "Automatic") - ("TLS.ciphers" . "") - ("TLS.certificateFile" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQ\ -XdHZ0F3SUJBZ0lJU1pUdlZPQnh3akF3RFFZSktvWklodmNOQVFFTUJRQXdTVEVOTUFzR0ExVUUKQXhN\ -RVNtRnRhVEU0TURZR0NnbVNKb21UOGl4a0FRRVRLR1l6TXpRMVpqSTNOelZrWkdabE1EZGhOR0l3WkR\ -rMQpaR0ZsWVRFeE1XUXhOV1ppWXpFeE9Ua3dIaGNOTWpFd05ERTJNVGN6TWpFd1doY05NekV3TkRFME\ -1UY3pNakV3CldqQlFNUlF3RWdZRFZRUURFd3RLWVcxcElHUmxkbWxqWlRFNE1EWUdDZ21TSm9tVDhpe\ -GtBUUVUS0dFM09XTmwKTURVNE16VmhPRFV5WlRsbU5qWmxOelF3TURjeU5EUXdPVFZpTVdaa1kyVXdO\ -bVV3Z2dJaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUMzOW9WdHFzbVBnWkl\ -IcHpTVGtlT3BlWC9CSEx2KzFTYnJPSFpVCkRxMDRWQlFOQTZiZkhTUlkyaWxxNVhFYXlzVUpsM0JrLz\ -NLcWRIUnFEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXgKUGdvVjg2UlFQbm9XQkU3YVlVRGU5SWV5cTJpZ\ -WV6QytZWEpwbVk5Y05tblpaMFlHOHJGMEVpWFA0SHpVWGphZgpJUys3Sk0yeWU2clJaSDVwb3RwUDZl\ -eDV4alVFbjRBRXVoblhielNBNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXCldGVmhOcklQQlRSTWdkWk1\ -kRkU4dFlRcmlDZDU1eHVIa2J3K0JmNVVmVENqN25tVEcybDJNbGcrSXBHVkFXUFQKVjZJdDRTYkt1Wn\ -VuTGZkQ0tha0lNN0pwN1dnSVo2TUR3ZGhRUFoyTVJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcgp0Znh3Z\ -XZZYkJNcjhHUVdiUGJUSmJPbWR2Z1BkRnFzU29ReEJ0VHFkSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpu\ -CmZVbTJQUHAwSCtYOUpPendEdUFiOStHNHFmbSt2emw3dTdKN2pydE1HQlJlSlREdGFNd2RHa0lmcUN\ -RZGREZHEKaTNYc1R1VTlTbm1TeDZHcU9lemVNOG5OeXZrWVo0dlJPbzgxc1NSYzQ4L3pZZjJ1OFBJUm\ -4vVy9xZk9hbjVIbgpHMXhTQVA2ZHBwM0NWNGE4MFBXbmcrM3F0ODVuR2tpNjZNNDVJZ0Y4NHZwRGUyM\ -2YyZnprTk5OOTk1VTBvUStKClRoOXhtR2liZnp5Qml5OXd0VFR1WFgwd2N4ME5vcDY3WGhRQ1VQRkhy\ -bVd6WjV5bXhEdkRhcUN1U3NwcG0vTkQKVUVkMnNRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkRBVUFBNEl\ -DQVFCZmRCc0p4RVVWeFRBeG5vNll1bEFoR1ZvUAplWG8xMHIwOE5DcDZRZlJxeGJlTkZ5aXEvKzEwSE\ -NpL1RoZk41OVdLNlpudFF4NlFNZUxEUVZTb0NjNzlaaWQ4ClE2RUdsWkp1c2RTTmg0VjVteXRCQVZHZ\ -2J2aXJFWU1Wcm5jWWg3bHR2LzVuSGRsbyt2WXV3Vzh0aEhHTk1TUkIKQmhJN2xydmpqY3NkbVl6L1Bp\ -NFNZdkg3c3RaVWpRYmUzNzh3UE90b3lBMTNybEtQUTF4QjJFbUxISDYya21WTQowa25wL1hQQ2o2alR\ -zakpFWko1NzZNMmloYy9DTzkvREVlWWZ4RFlJb004NEF5dTc0UU9UUVN1cnVHRjF5T1RKCmlxRkltTH\ -hMcWRxNk1Zdmx1QjFmTzlKaU9QaS84UEJFTTVpeGYzajlGOFVMQTZUTU1NSklFR1ROeUNzOWpzQloKR\ -UNiWVgweTY4WWtGYnYzQmNWaGk2K1VVWVhBVUd0akhwQ3Z2VThYaHowL0RVZFVaTkpqejRJeWl1ZE5N\ -dnFjQgppSEJDdkxFS1B4VFd1ZlFZaGM5ZkVXTm1valc1WSsvalBVeDcrQWxPM1RXZm1OclBpWDJuSEd\ -5UG5tYjZIR2hWCmp3UU0wT1h4cGxnZ2dQMWpVZ0VYWWRucStjYzdRMy94RFIvdi9Fd244T1d4OVB3eV\ -Y0WFZwZXY5QnpmeWwyQTMKdFhWVkFKMzJtcTlPQmVadXczdWlWU2E2TWdCVG80eXcrYnBjU2VIU2xXO\ -XNRc1NYY3dUNjhvOUdhR2hMNjdXMApwQWx4R2VxSWRtbTZ0MkxIUCtQQnBtL3BweTMvTmh1NFIwMTNv\ -Znp5V2Y0bEcrVnpWVGE0eXVqU1J3UlluYmZyCjlSWER0L0I5VEg5TnNsamdBQT09Ci0tLS0tRU5EIEN\ -FUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SU\ -JBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNtRnRhU\ -0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwpPRFF6\ -TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERTBNVGN\ -6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aak16Tk\ -RWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3RFFZS\ -ktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHBLQ2My\ -ZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTnMvV1d\ -3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd0RyUX\ -hObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBUck1uR\ -2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3hqanlC\ -VzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WXlhME8\ -xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV1l2YV\ -I1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1EU29GN\ -jRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0YwamoK\ -UjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzamlMalJ\ -4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxcVo5cj\ -RqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0NUFPS\ -TFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZTdzJW\ -MnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSEJnQXd\ -EUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2RWp1V3\ -dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMyV0Uwd\ -DlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUdQZUJj\ -Wi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYVVNeGt\ -5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNVGhDRE\ -dBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJiTAp2M\ -0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1ExcVBv\ -YU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUHFnQ0N\ -uWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5MWFoUj\ -gyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8xelRzV\ -WRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJRa3Z6\ -eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU05ybTJ\ -MY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS\ -0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3VERFU\ -U1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlRWaVky\ -TXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk1UY3p\ -NakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ3dOZ1\ -lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdOak15T\ -VRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUxT\ -cGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVnllamg\ -remVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqbmVyUW\ -d6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1czBVS\ -HZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQozWjZu\ -dElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2TlRCOUl\ -mCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya29Na3\ -dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStSTnpJW\ -GFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2loZk5W\ -OTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OVpNblR\ -WVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2RVR0dH\ -RuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01ZeTVsO\ -Gt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjBHQTFV\ -ZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQVFIL01\ -BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluYm9yWm\ -RhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2VnFBU\ -2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN3dTUK\ -TnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRGNocE9\ -SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLUjd1TD\ -drWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2bXhaS\ -nlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUxZTzAw\ -R3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObTB5akl\ -qbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1dEU1Ml\ -lzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdFOFY2c\ -WM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDRKTklT\ -M2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRTRuT0F\ -yS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K") - ("STUN.server" . "") - ("STUN.enable" . "false") - ("SRTP.rtpFallback" . "false") - ("SRTP.keyExchange" . "sdes") - ("SRTP.enable" . "true") - ("RingNS.uri" . "") - ("RingNS.account" . "0790738ce15fa05933b49dd77034312787da86c3") - ("DHT.PublicInCalls" . "true") - ("Account.videoPortMin" . "49152") - ("Account.videoPortMax" . "65534") - ("Account.videoEnabled" . "true") - ("Account.username" . "f3345f2775ddfe07a4b0d95daea111d15fbc1199") - ("Account.useragent" . "") - ("Account.upnpEnabled" . "true") - ("Account.type" . "RING") - ("Account.ringtoneEnabled" . "true") - ("Account.rendezVous" . "true") - ("Account.publishedSameAsLocal" . "true") - ("Account.publishedPort" . "5060") - ("Account.publishedAddress" . "") - ("Account.presenceSubscribeSupported" . "true") - ("Account.peerDiscovery" . "false") - ("Account.managerUsername" . "") - ("Account.managerUri" . "") - ("Account.mailbox" . "") - ("Account.localModeratorsEnabled" . "true") - ("Account.localInterface" . "default") - ("Account.hostname" . "bootstrap.jami.net") - ("Account.hasCustomUserAgent" . "false") - ("Account.enable" . "true") - ("Account.dtmfType" . "overrtp") - ("Account.displayName" . "dummy") - ("Account.defaultModerators" . "") - ("Account.audioPortMin" . "16384") - ("Account.audioPortMax" . "32766") - ("Account.archiveHasPassword" . "false") - ("Account.allowCertFromTrusted" . "true") - ("Account.allowCertFromHistory" . "true") - ("Account.allowCertFromContact" . "true") - ("Account.allModeratorEnabled" . "true") - ("Account.alias" . "dummy") - ("Account.activeCallLimit" . "-1") - ("Account.accountPublish" . "false") - ("Account.accountDiscovery" . "false"))) diff --git a/gnu/tests/telephony.scm b/gnu/tests/telephony.scm deleted file mode 100644 index 1155a9dbc2..0000000000 --- a/gnu/tests/telephony.scm +++ /dev/null @@ -1,366 +0,0 @@ -;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gnu.org>. -;;; -;;; 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/>. - -(define-module (gnu tests telephony) - #:use-module (gnu) - #:use-module (gnu packages) - #:use-module (gnu packages guile) - #:use-module (gnu tests) - #:use-module (gnu system vm) - #:use-module (gnu services) - #:use-module (gnu services dbus) - #:use-module (gnu services networking) - #:use-module (gnu services ssh) - #:use-module (gnu services telephony) - #:use-module (guix gexp) - #:use-module (guix modules) - #:export (%test-jami - %test-jami-provisioning)) - -;;; -;;; Jami daemon. -;;; - -(include "data/jami-dummy-account.dat") ;defines %jami-account-content-sexp - -(define %dummy-jami-account-archive - ;; A Jami account archive is a gzipped JSON file. - (computed-file - "dummy-jami-account.gz" - (with-extensions (list guile-json-4 guile-zlib) - #~(begin - (use-modules (json) (zlib)) - (let ((port (open-output-file #$output))) - (call-with-gzip-output-port port - (lambda (port) - (scm->json '#$%jami-account-content-sexp port)))))))) - -(define %allowed-contacts '("1dbcb0f5f37324228235564b79f2b9737e9a008f" - "2dbcb0f5f37324228235564b79f2b9737e9a008f")) - -(define %moderators '("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) - -(define %dummy-jami-account (jami-account - (archive %dummy-jami-account-archive) - (allowed-contacts %allowed-contacts) - (moderators %moderators) - (rendezvous-point? #t) - (peer-discovery? #f) - (bootstrap-hostnames '("bootstrap.me" - "fallback.another.host")) - (name-server-uri "https://my.name.server"))) - -(define* (make-jami-os #:key provisioning?) - (operating-system - (host-name "jami") - (timezone "America/Montreal") - (locale "en_US.UTF-8") - - (bootloader (bootloader-configuration - (bootloader grub-bootloader) - (target "/dev/sdX"))) - (file-systems (cons (file-system - (device (file-system-label "my-root")) - (mount-point "/") - (type "ext4")) - %base-file-systems)) - (firmware '()) - - (services (cons* (service jami-service-type - (if provisioning? - (jami-configuration - (debug? #t) - (accounts (list %dummy-jami-account))) - (jami-configuration - (debug? #t)))) - (service dbus-root-service-type) - ;; The following services/packages are added for - ;; debugging purposes. - (service dhcp-client-service-type) - (service openssh-service-type - (openssh-configuration - (permit-root-login #t) - (allow-empty-passwords? #t))) - %base-services)) - (packages (cons* (specification->package "recutils") - (specification->package "strace") - %base-packages)))) - -(define %jami-os - (make-jami-os)) - -(define %jami-os-provisioning - (make-jami-os #:provisioning? #t)) - -(define* (run-jami-test #:key provisioning?) - "Run tests in %JAMI-OS. When PROVISIONING? is true, test the -accounts provisioning feature of the service." - (define os (marionette-operating-system - (if provisioning? - %jami-os-provisioning - %jami-os) - #:imported-modules '((gnu services herd) - (guix combinators)))) - (define vm (virtual-machine - (operating-system os) - (memory-size 512))) - - (define username (assoc-ref %jami-account-content-sexp - "Account.username")) - - (define test - (with-imported-modules (source-module-closure - '((gnu build marionette) - (gnu build jami-service))) - #~(begin - (use-modules (rnrs base) - (srfi srfi-11) - (srfi srfi-64) - (gnu build marionette) - (gnu build jami-service)) - - (define marionette - (make-marionette (list #$vm))) - - (mkdir #$output) - (chdir #$output) - - (test-begin "jami") - - (test-assert "service is running" - (marionette-eval - '(begin - (use-modules (gnu services herd)) - (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) (number? pid)))))) - marionette)) - - (test-assert "service can be stopped" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - (setenv "PATH" "/run/current-system/profile/bin") - (let ((pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid)))))) - - (assert (number? pid)) - - (match (stop-service 'jami) - (services ;a list of service symbols - (member 'jami services))) - ;; Sometimes, the process still appear in pgrep, even - ;; though we are using waitpid after sending it SIGTERM - ;; in the service; use retries. - (with-retries 20 1 - (not (zero? (status:exit-val - (system* "pgrep" "dring"))))))) - marionette)) - - (test-assert "service can be restarted" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - ;; Start and retrieve the current PID. - (define pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid))))) - (assert (number? pid)) - - ;; Restart the service. - (restart-service 'jami) - - (define new-pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid))))) - (assert (number? new-pid)) - - (not (eq? pid new-pid))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, account present" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - ;; Accounts take some time to appear after being added. - (with-retries 20 1 - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string=? #$username - (assoc-ref account - "Account.username"))))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, allowed-contacts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Public mode is disabled. - (with-shepherd-action 'jami ('list-account-details) - results - (let ((account (assoc-ref (car results) #$username))) - (assert (string=? "false" - (assoc-ref account - "DHT.PublicInCalls"))))) - - ;; Allowed contacts match those declared in the configuration. - (with-shepherd-action 'jami ('list-contacts) results - (let ((contacts (assoc-ref (car results) #$username))) - (assert (lset= string-ci=? contacts '#$%allowed-contacts))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, moderators" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Moderators match those declared in the configuration. - (with-shepherd-action 'jami ('list-moderators) results - (let ((moderators (assoc-ref (car results) #$username))) - (assert (lset= string-ci=? moderators '#$%moderators)))) - - ;; Moderators can be added via the Shepherd action. - (with-shepherd-action 'jami - ('add-moderator "cccccccccccccccccccccccccccccccccccccccc" - #$username) results - (let ((moderators (car results))) - (assert (lset= string-ci=? moderators - (cons "cccccccccccccccccccccccccccccccccccccccc" - '#$%moderators)))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami service actions, ban/unban contacts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Globally ban a contact. - (with-shepherd-action 'jami - ('ban-contact "1dbcb0f5f37324228235564b79f2b9737e9a008f") _ - (with-shepherd-action 'jami ('list-banned-contacts) results - (every (match-lambda - ((username . banned-contacts) - (member "1dbcb0f5f37324228235564b79f2b9737e9a008f" - banned-contacts))) - (car results)))) - - ;; Ban a contact for a single account. - (with-shepherd-action 'jami - ('ban-contact "dddddddddddddddddddddddddddddddddddddddd" - #$username) _ - (with-shepherd-action 'jami ('list-banned-contacts) results - (every (match-lambda - ((username . banned-contacts) - (let ((found? (member "dddddddddddddddddddddddddddddddddddddddd" - banned-contacts))) - (if (string=? #$username username) - found? - (not found?))))) - (car results))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami service actions, enable/disable accounts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - - (with-shepherd-action 'jami - ('disable-account #$username) _ - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string= "false" - (assoc-ref account "Account.enable")))))) - - (with-shepherd-action 'jami - ('enable-account #$username) _ - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string= "true" - (assoc-ref account "Account.enable"))))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami account parameters" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - (with-shepherd-action 'jami ('list-account-details) results - (let ((account-details (assoc-ref (car results) - #$username))) - (assert (lset<= - equal? - '(("Account.hostname" . - "bootstrap.me;fallback.another.host") - ("Account.peerDiscovery" . "false") - ("Account.rendezVous" . "true") - ("RingNS.uri" . "https://my.name.server")) - account-details))))) - marionette)) - - (test-end) - (exit (= (test-runner-fail-count (test-runner-current)) 0))))) - - (gexp->derivation (if provisioning? - "jami-provisioning-test" - "jami-test") - test)) - -(define %test-jami - (system-test - (name "jami") - (description "Basic tests for the jami service.") - (value (run-jami-test)))) - -(define %test-jami-provisioning - (system-test - (name "jami-provisioning") - (description "Provisioning test for the jami service.") - (value (run-jami-test #:provisioning? #t)))) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: |