Subject: [bug#28960] [PATCH] services: Add murmur.

From: nee <[email-masked]>
Date: Mon, 23 Oct 2017 23:34:22 +0200

Hello, this patch adds a murmur service.
Murmur is the biggest implementation of a mumble voice chat server. The
murmur executable is already packaged in the mumble package.

I added most of the available options to the configuration.
I consciously did not include the following settings:
-settings for changing the .ini at runtime through "ZeroC Ice" or "dbus"
-settings for different databases, because the wiki mentions problems
with other databases and strongly recommends using the default sqlite¹.

1) https://wiki.mumble.info/wiki/Murmur.ini (ctrl-f sqlite)
From 74618e5a39198077327f14362d8d98538f4d39ab Mon Sep 17 00:00:00 2001
From: nee <[email-masked]>
Date: Sat, 14 Oct 2017 11:27:50 +0200
Subject: [PATCH] services: Add murmur.

* gnu/services/telephony.scm: New file.
* gnu/local.mk: Add it.
* doc/guix.texi: Document it.
---
 doc/guix.texi              | 161 ++++++++++++++++++++-
 gnu/local.mk               |   1 +
 gnu/services/telephony.scm | 344 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 505 insertions(+), 1 deletion(-)
 create mode 100644 gnu/services/telephony.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index 7b5b71179..c06e596aa 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -221,6 +221,7 @@ Services
 * Database Services::           SQL databases, key-value stores, etc.
 * Mail Services::               IMAP, POP3, SMTP, and all that.
 * Messaging Services::          Messaging services.
+* Telephony Services::          Telephony services.
 * Monitoring Services::         Monitoring services.
 * Kerberos Services::           Kerberos services.
 * Web Services::                Web servers.
@@ -9245,6 +9246,7 @@ declaration.
 * Database Services::           SQL databases, key-value stores, etc.
 * Mail Services::               IMAP, POP3, SMTP, and all that.
 * Messaging Services::          Messaging services.
+* Telephony Services::          Telephony services.
 * Monitoring Services::         Monitoring services.
 * Kerberos Services::           Kerberos services.
 * Web Services::                Web servers.
@@ -14025,6 +14027,164 @@ string, you could instantiate a prosody service like this:
           (prosody.cfg.lua "")))
 @end example
 
+
+@node Telephony Services
+@subsubsection Telephony Services
+@cindex Murmur
+
+Murmur is the official server of the @code{mumble} voice over IP (VoIP) software.
+
+@deftp {Data Type} murmur-configuration
+The service type for the murmur server. An example configuration can look like this:
+@example
+(service murmur-service-type
+         (murmur-configuration
+	  (welcome-text "Welcome to this mumble server running on GuixSD!")
+          (cert-required #t) ; disallow text password logins
+          (ssl-cert "/etc/letsencrypt/live/mumble.example.com/fullchain.pem")
+          (ssl-key "/etc/letsencrypt/live/mumble.example.com/privkey.pem")))
+@end example
+
+After reconfiguring your system, you have to manually set the
+SuperUser password with the command that is printed during the activation phase.
+Then you can use the @code{mumble} client to
+login as new user, register, and logout.
+For the next step login with the name "SuperUser" and the SuperUser password
+you set previously, and grant your newly registered user admin/moderator rights
+and create some channels.
+
+Available @code{murmur-configuration} fields are:
+@table @asis
+@item @code{package} (default: @code{mumble})
+Package that contains @code{bin/murmurd}.
+@item @code{user} (default: @code{"murmur"})
+User who will run the murmur server.
+@item @code{group} (default: @code{"murmur"})
+Group of the user who will run the murmur server.
+@item @code{port} (default: @code{64738})
+Port on which the server will listen.
+@item @code{welcome-text} (default: @code{""})
+Welcome text sent to clients when they connect.
+@item @code{server-password} (default: @code{""})
+Password the clients have to enter in order to connect.
+@item @code{max-users} (default: @code{100})
+Maximum of users that can be connected to the server at once.
+@item @code{max-user-bandwidth} (default: @code{#f})
+Maximum voice traffic a user can send per second.
+@item @code{database-file} (default: @code{"/var/lib/murmur/db.sqlite"})
+Filepath location of the sqlite database.
+The service's user will become the owner of the directory.
+@item @code{log-file} (default: @code{"/var/log/murmur/murmur.log"})
+Filepath of the log file.
+The service's user will become the owner of the directory.
+@item @code{autoban-attempts} (default: @code{10})
+Maximum number of logins a user can make in @code{autoban-timeframe}
+without getting auto banned for @code{autoban-time}.
+@item @code{autoban-timeframe} (default: @code{120})
+Timeframe for autoban in seconds.
+@item @code{autoban-time} (default: @code{300})
+Amount of time in seconds for which a client gets banned
+when violating the autoban limits.
+@item @code{opus-threshold} (default: @code{100})
+Percentage of clients that need to support opus
+before switching over to opus audio codec.
+@item @code{channel-nesting-limit} (default: @code{10})
+How deep channels can be nested at maximum.
+@item @code{channelname-regex} (default: @code{#f})
+A string in from of a Qt regular expression that channel names must conform to.
+@item @code{username-regex} (default: @code{#f})
+A string in from of a Qt regular expression that user names must conform to.
+@item @code{text-message-length} (default: @code{5000})
+Maximum size in bytes that a user can send in one text chat message.
+@item @code{image-message-length} (default: @code{(* 128 1024)})
+Maximum size in bytes that a user can send in one image message.
+@item @code{cert-required} (default: @code{#f})
+If it is set to @code{#t} clients that use weak password authentification
+will not be accepted. Users must have completed the certificate wizard to join.
+@item @code{remember-channel} (defualt @code{#f})
+Should murmur remember the last channel each user was in when they disconnected
+and put them into the remembered channel when they rejoin.
+@item @code{allow-html} (default: @code{#f})
+Should html be allowed in text messages, user comments, and channel descriptions.
+@item @code{allow-ping} (default: @code{#f})
+Setting to true exposes the current user count, the maximum user count, and
+the server's maximum bandwidth per client to unauthenticated users. In the
+Mumble client, this information is shown in the Connect dialog.
+
+Disabling this setting will prevent public listing of the server.
+@item @code{bonjour} (default: @code{#f})
+Should the server advertise itself in the local network through the bonjour protocol.
+@item @code{send-version} (default: @code{#f})
+Should the murmur server version be exposed in ping requests.
+@item @code{log-days} (default: @code{31})
+Murmur also stores logs in the database, which are accessible via RPC.
+The default is 31 days of months, but you can set this setting to 0 to keep logs forever,
+or -1 to disable logging to the database.
+@item @code{obfuscate-ips} (default @code{#t})
+Should logged ips be obfuscated to protect the privacy of users.
+@item @code{ssl-cert} (default: @code{#f})
+Filepath to the ssl-cert used for encrypted connections.
+@example
+(ssl-cert "/etc/letsencrypt/live/example.com/fullchain.pem")
+@end example
+@item @code{ssl-key} (default: @code{#f})
+Filepath to the ssl private key used for encrypted connections.
+@example
+(ssl-key "/etc/letsencrypt/live/example.com/privkey.pem")
+@end example
+@item @code{ssl-dh-params} (default: @code{#f})
+Filepath to a PEM-encoded file with Diffie-Hellman parameters
+for the ssl encryption. Alternatively you set it to
+@code{"@@ffdhe2048"}, @code{"@@ffdhe3072"}, @code{"@@ffdhe4096"}, @code{"@@ffdhe6144"}
+or @code{"@@ffdhe8192"} to use bundled parameters from RFC 7919.
+@item @code{ssl-ciphers} (default: @code{#f})
+The @code{ssl-ciphers} option chooses the cipher suites to make available for use
+in SSL/TLS.
+
+This option is specified using
+@uref{https://www.openssl.org/docs/apps/ciphers.html#CIPHER-LIST-FORMAT, OpenSSL cipher list notation}.
+
+It is recommended that you try your cipher string using 'openssl ciphers <string>'
+before setting it here, to get a feel for which cipher suites you will get.
+After setting this option, it is recommend that you inspect your Murmur log
+to ensure that Murmur is using the cipher suites that you expected it to.
+
+Note: Changing this option may impact the backwards compatibility of your
+Murmur server, and can remove the ability for older Mumble clients to be able
+to connect to it.
+@item @code{public-registration} (default: @code{#f})
+Must be a @code{<murmur-public-registration-configuration>} record or @code{#f}.
+
+You can optionally register your server in the public server list that the
+@code{mumble} client shows on startup.
+You cannot register your server if you have set a @code{server-password},
+or set @code{allow-ping} to @code{#f}.
+
+It might take a few hours until it shows up in the public list.
+
+@item @code{file} (default: @code{#f})
+Optional alternative override for this configuration.
+@end table
+@end deftp
+
+@deftp {Data Type} murmur-public-registration-configuration
+Configuration for public registration of a murmur service.
+@table @asis
+@item @code{name}
+This is a display name for your server. Not to be confused with the hostname.
+@item @code{password}
+A password to identify your registration.
+Subsequent updates will need the same password. Don't lose your password.
+@item @code{url}
+This should be a http(s):// link to your website.
+@item @code{hostname} (default: @code{#f})
+By default your server will be listed by it's ip.
+If it is set your server will be linked by this hostname instead.
+@end table
+@end deftp
+
+
+
 @node Monitoring Services
 @subsubsection Monitoring Services
 
@@ -14135,7 +14295,6 @@ the 2nd element of the pair is the password.
 @end table
 @end deftp
 
-
 @node Kerberos Services
 @subsubsection Kerberos Services
 @cindex Kerberos
diff --git a/gnu/local.mk b/gnu/local.mk
index b71b36024..daa210a38 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -458,6 +458,7 @@ GNU_SYSTEM_MODULES =				\
   %D%/services/spice.scm				\
   %D%/services/ssh.scm				\
   %D%/services/sysctl.scm			\
+  %D%/services/telephony.scm			\
   %D%/services/version-control.scm              \
   %D%/services/vpn.scm				\
   %D%/services/web.scm				\
diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm
new file mode 100644
index 000000000..1fc5cb834
--- /dev/null
+++ b/gnu/services/telephony.scm
@@ -0,0 +1,344 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2017 nee  <[email-masked]>
+;;;
+;;; 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 services telephony)
+  #:use-module (gnu services)
+  #:use-module (gnu services shepherd)
+  #:use-module (gnu system shadow)
+  #:use-module (gnu packages admin)
+  #:use-module (gnu packages telephony)
+  #:use-module (guix records)
+  #:use-module (guix gexp)
+  #:use-module (srfi srfi-1)
+  #:use-module (ice-9 match)
+  #:export (<murmur-configuration>
+            murmur-configuration
+            make-murmur-configuration
+            murmur-configuration?
+            murmur-configuration-package
+            murmur-configuration-user
+            murmur-configuration-group
+            murmur-configuration-port
+            murmur-configuration-welcome-text
+            murmur-configuration-server-password
+            murmur-configuration-max-users
+            murmur-configuration-max-user-bandwidth
+            murmur-configuration-database-file
+            murmur-configuration-log-file
+            murmur-configuration-pid-file
+            murmur-configuration-autoban-attempts
+            murmur-configuration-autoban-timeframe
+            murmur-configuration-autoban-time
+            murmur-configuration-opus-threshold
+            murmur-configuration-channel-nesting-limit
+            murmur-configuration-channelname-regex
+            murmur-configuration-username-regex
+            murmur-configuration-text-message-length
+            murmur-configuration-image-message-length
+            murmur-configuration-cert-required
+            murmur-configuration-remember-channel
+            murmur-configuration-allow-html
+            murmur-configuration-allow-ping
+            murmur-configuration-bonjour
+            murmur-configuration-send-version
+            murmur-configuration-log-days
+            murmur-configuration-obfuscate-ips
+            murmur-configuration-ssl-cert
+            murmur-configuration-ssl-key
+            murmur-configuration-ssl-dh-params
+            murmur-configuration-ssl-ciphers
+            murmur-configuration-public-registration
+            murmur-configuration-file
+
+            <murmur-public-registration-configuration>
+            murmur-public-registration-configuration
+            make-murmur-public-registration-configuration
+            murmur-public-registration-configuration?
+            murmur-public-registration-configuration-name
+            murmur-public-registration-configuration-url
+            murmur-public-registration-configuration-password
+            murmur-public-registration-configuration-hostname
+
+            murmur-service-type))
+
+;; https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini
+
+(define-record-type* <murmur-configuration> murmur-configuration
+  make-murmur-configuration
+  murmur-configuration?
+  (package               murmur-configuration-package ;<package>
+                         (default mumble))
+  (user                  murmur-configuration-user
+                         (default "murmur"))
+  (group                 murmur-configuration-group
+                         (default "murmur"))
+  (port                  murmur-configuration-port
+                         (default 64738))
+  (welcome-text          murmur-configuration-welcome-text
+                         (default ""))
+  (server-password       murmur-configuration-server-password
+                         (default ""))
+  (max-users             murmur-configuration-max-users
+                         (default 100))
+  (max-user-bandwidth    murmur-configuration-max-user-bandwidth
+                         (default #f))
+  (database-file         murmur-configuration-database-file
+                         (default "/var/lib/murmur/db.sqlite"))
+  (log-file              murmur-configuration-log-file
+                         (default "/var/log/murmur/murmur.log"))
+  (pid-file              murmur-configuration-pid-file
+                         (default "/var/run/murmur/murmur.pid"))
+  (autoban-attempts      murmur-configuration-autoban-attempts
+                         (default 10))
+  (autoban-timeframe     murmur-configuration-autoban-timeframe
+                         (default 120))
+  (autoban-time          murmur-configuration-autoban-time
+                         (default 300))
+  (opus-threshold        murmur-configuration-opus-threshold
+                         (default 100)) ; integer percent
+  (channel-nesting-limit murmur-configuration-channel-nesting-limit
+                         (default 10))
+  (channelname-regex     murmur-configuration-channelname-regex
+                         (default #f))
+  (username-regex        murmur-configuration-username-regex
+                         (default #f))
+  (text-message-length   murmur-configuration-text-message-length
+                         (default 5000))
+  (image-message-length  murmur-configuration-image-message-length
+                         (default (* 128 1024))) ; 128 Kilobytes
+  (cert-required         murmur-configuration-cert-required
+                         (default #f))
+  (remember-channel      murmur-configuration-remember-channel
+                         (default #f))
+  (allow-html            murmur-configuration-allow-html
+                         (default #f))
+  (allow-ping            murmur-configuration-allow-ping
+                         (default #f))
+  (bonjour               murmur-configuration-bonjour
+                         (default #f))
+  (send-version          murmur-configuration-send-version
+                         (default #f))
+  (log-days              murmur-configuration-log-days
+                         (default 31))
+  (obfuscate-ips         murmur-obfuscate-ips
+                         (default #t))
+  (ssl-cert              murmur-configuration-ssl-cert
+                         (default #f))
+  (ssl-key               murmur-configuration-ssl-key
+                         (default #f))
+  (ssl-dh-params         murmur-configuration-ssl-dh-params
+                         (default #f))
+  (ssl-ciphers           murmur-configuration-ssl-ciphers
+                         (default #f))
+  (public-registration   murmur-configuration-public-registration
+                         (default #f))  ; <murmur-public-registration-configuration>
+  (file                  murmur-configuration-file
+                         (default #f)))
+
+(define-record-type* <murmur-public-registration-configuration>
+  murmur-public-registration-configuration
+  make-murmur-public-registration-configuration
+  murmur-public-registration-configuration?
+  (name         murmur-public-registration-configuration-name)
+  (password     murmur-public-registration-configuration-password)
+  (url          murmur-public-registration-configuration-url)
+  (hostname     murmur-public-registration-configuration-hostname
+                (default #f)))
+
+(define (flatten . lst)
+  "Return a list that recursively concatenates all sub-lists of LST."
+  (define (flatten1 head out)
+    (if (list? head)
+        (fold-right flatten1 out head)
+        (cons head out)))
+  (fold-right flatten1 '() lst))
+
+(define (default-murmur-config
+          package user group port welcome-text server-password
+          max-users max-user-bandwidth database-file log-file pid-file
+          autoban-attempts autoban-timeframe autoban-time
+          opus-threshold channel-nesting-limit channelname-regex username-regex
+          text-message-length image-message-length cert-required
+          remember-channel allow-html allow-ping bonjour send-version log-days
+          obfuscate-ips ssl-cert ssl-key ssl-dh-params ssl-ciphers
+          public-registration)
+  (apply mixed-text-file "murmur.ini"
+         (flatten
+          "welcometext=" welcome-text "\n"
+          "port=" (number->string port) "\n"
+          "serverpassword=" server-password "\n"
+          (if max-user-bandwidth (list "bandwidth=" (number->string max-user-bandwidth)) '())
+          "users=" (number->string max-users) "\n"
+          "uname=" user "\n"
+          "database=" database-file "\n"
+          "logfile=" log-file "\n"
+          "pidfile=" pid-file "\n"
+          (if autoban-attempts (list "autobanAttempts=" (number->string autoban-attempts) "\n") '())
+          (if autoban-timeframe (list "autobanTimeframe=" (number->string autoban-timeframe) "\n") '())
+          (if autoban-time (list "autobanTime=" (number->string autoban-time) "\n") '())
+          (if opus-threshold (list "opusthreshold=" (number->string opus-threshold) "\n") '())
+          (if channel-nesting-limit (list "channelnestinglimit=" (number->string channel-nesting-limit) "\n") '())
+          (if channelname-regex (list "channelname=" channelname-regex "\n") '())
+          (if username-regex (list "username=" username-regex "\n") '())
+          (if text-message-length (list "textmessagelength=" (number->string text-message-length) "\n") '())
+          (if image-message-length (list "imagemessagelength=" (number->string image-message-length) "\n") '())
+          (if log-days (list "logdays=" (number->string log-days) "\n") '())
+          "obfuscate=" (if obfuscate-ips "true" "false") "\n"
+          "certrequired=" (if cert-required "true" "false") "\n"
+          "rememberchannel" (if remember-channel "true" "false") "\n"
+          "allowhtml=" (if allow-html "true" "false") "\n"
+          "allowping=" (if allow-ping "true" "false") "\n"
+          "bonjour=" (if bonjour "true" "false") "\n"
+          "sendversion=" (if send-version "true" "false") "\n"
+          (cond ((and ssl-cert ssl-key)
+                 (list
+                  "sslCert=" ssl-cert "\n"
+                  "sslKey=" ssl-key "\n"))
+                ((or ssl-cert ssl-key)
+                 (error "ssl-cert and ssl-key must both be set"
+                        ssl-cert ssl-key))
+                (else '()))
+          (if ssl-dh-params (list "sslDHParams=" ssl-dh-params) '())
+          (if ssl-ciphers (list "sslCiphers=" ssl-ciphers) '())
+
+          (match public-registration
+            (#f '())
+            (($ <murmur-public-registration-configuration>
+                name password url hostname)
+             (if (and (or (not server-password) (string-null? server-password))
+                      allow-ping)
+                 (list
+                  "registerName=" name "\n"
+                  "registerPassword=" password "\n"
+                  "registerUrl=" url "\n"
+                  (if hostname
+                      (string-append "registerHostname=" hostname "\n")
+                      ""))
+                 (error "To publicly register your murmur server your server must be publicy visible
+and users must be able to join without a password. To fix this set:
+(allow-ping #t)
+(server-password "")
+Or set public-registration to #f")))))))
+
+(define murmur-activation
+  (match-lambda
+    (($ <murmur-configuration>
+        package user group port welcome-text server-password
+        max-users max-user-bandwidth database-file log-file pid-file
+        autoban-attempts autoban-timeframe autoban-time
+        opus-threshold channel-nesting-limit channelname-regex username-regex
+        text-message-length image-message-length cert-required remember-channel
+        allow-html allow-ping bonjour send-version log-days obfuscate-ips
+        ssl-cert ssl-key ssl-dh-params ssl-ciphers public-registration file)
+     #~(begin
+         (use-modules (guix build utils))
+         (let ((log-dir (dirname #$log-file))
+               (pid-dir (dirname #$pid-file))
+               (db-dir (dirname #$database-file))
+               (user (getpwnam #$user))
+               (init-dir
+                (lambda (name dir user)
+                  (format #t "creating murmur ~a directory '~a'\n" name dir)
+                  (mkdir-p dir)
+                  (chown dir (passwd:uid user) (passwd:gid user))
+                  (chmod dir #o700)))
+               (ini #$(or file
+                          (default-murmur-config
+                            package user group port welcome-text
+                            server-password max-users max-user-bandwidth
+                            database-file log-file pid-file autoban-attempts
+                            autoban-timeframe autoban-time
+                            opus-threshold channel-nesting-limit
+                            channelname-regex username-regex
+                            text-message-length image-message-length
+                            cert-required remember-channel allow-html allow-ping
+                            bonjour send-version log-days obfuscate-ips ssl-cert
+                            ssl-key ssl-dh-params ssl-ciphers
+                            public-registration))))
+           (init-dir "log" log-dir user)
+           (init-dir "pid" pid-dir user)
+           (init-dir "database" db-dir user)
+
+           (format #t "murmur: use config file: ~a~%\n" ini)
+           (format #t "murmur: to set the SuperUser password run:
+    `~a -ini ~a -readsupw`\n"
+                   #$(file-append package "/bin/murmurd") ini)
+           #t)))))
+
+(define murmur-accounts
+  (match-lambda
+    (($ <murmur-configuration> _ user group)
+     (filter identity
+             (list
+              (and (equal? group "murmur")
+                   (user-group
+                    (name "murmur")
+                    (system? #t)))
+              (and (equal? user "murmur")
+                   (user-account
+                    (name "murmur")
+                    (group group)
+                    (system? #t)
+                    (comment "Murmur Daemon")
+                    (home-directory "/var/empty")
+                    (shell (file-append shadow "/sbin/nologin")))))))))
+
+(define murmur-shepherd-service
+  (match-lambda
+    (($ <murmur-configuration>
+        package user group port welcome-text server-password
+        max-users max-user-bandwidth database-file log-file pid-file
+        autoban-attempts autoban-timeframe autoban-time
+        opus-threshold channel-nesting-limit channelname-regex username-regex
+        text-message-length image-message-length cert-required remember-channel
+        allow-html allow-ping bonjour send-version log-days obfuscate-ips
+        ssl-cert ssl-key ssl-dh-params ssl-ciphers public-registration file)
+     (list (shepherd-service
+            (provision '(murmur))
+            (documentation "Run the murmur mumble-server.")
+            (requirement '(networking))
+            (start #~(make-forkexec-constructor
+                      '(#$(file-append package "/bin/murmurd")
+                        "-ini"
+                        #$(or file
+                              (default-murmur-config
+                                package user group port welcome-text
+                                server-password max-users max-user-bandwidth
+                                database-file log-file pid-file autoban-attempts
+                                autoban-timeframe autoban-time
+                                opus-threshold channel-nesting-limit
+                                channelname-regex username-regex
+                                text-message-length image-message-length
+                                cert-required remember-channel allow-html
+                                allow-ping bonjour send-version log-days
+                                obfuscate-ips ssl-cert ssl-key ssl-dh-params
+                                ssl-ciphers public-registration)))
+                      #:pid-file #$pid-file))
+            (stop #~(make-kill-destructor)))))))
+
+(define murmur-service-type
+  (service-type (name 'murmur)
+                (description "The murmur service type.")
+                (extensions
+                 (list (service-extension shepherd-root-service-type
+                                          murmur-shepherd-service)
+                       (service-extension activation-service-type
+                                          murmur-activation)
+		       (service-extension account-service-type
+                                          murmur-accounts)))
+                (default-value (murmur-configuration))))
-- 
2.14.1