Core: Add support for systemd socket activation.
authorCorey Farrell <git@cfware.com>
Mon, 19 Jun 2017 00:24:04 +0000 (20:24 -0400)
committerCorey Farrell <git@cfware.com>
Mon, 19 Jun 2017 17:33:48 +0000 (13:33 -0400)
This change adds support for socket activation of certain SOCK_STREAM
listeners in Asterisk:
* AMI / AMI over TLS
* CLI
* HTTP / HTTPS

Example systemd units are provided.  This support extends to any socket
which is initialized using ast_tcptls_server_start, so any unknown
modules using this function will support socket activation.

Asterisk continues to function as normal if socket activation is not
enabled or if systemd development headers are not available during
build.

ASTERISK-27063 #close

Change-Id: Id814ee6a892f4b80d018365c8ad8d89063474f4d

13 files changed:
contrib/systemd/README.txt [new file with mode: 0644]
contrib/systemd/asterisk-ami.socket [new file with mode: 0644]
contrib/systemd/asterisk-amis.socket [new file with mode: 0644]
contrib/systemd/asterisk-cli.socket [new file with mode: 0644]
contrib/systemd/asterisk-http.socket [new file with mode: 0644]
contrib/systemd/asterisk-https.socket [new file with mode: 0644]
contrib/systemd/asterisk.service [new file with mode: 0644]
contrib/systemd/asterisk.socket [new file with mode: 0644]
include/asterisk/io.h
include/asterisk/netsock2.h
main/asterisk.c
main/io.c
main/tcptls.c

diff --git a/contrib/systemd/README.txt b/contrib/systemd/README.txt
new file mode 100644 (file)
index 0000000..3225641
--- /dev/null
@@ -0,0 +1,119 @@
+SystemD Socket Activation for Asterisk
+======================================
+
+This folder contains sample unit files which can be used as the basis of a
+socket activated Asterisk deployment.  Socket activation support currently
+extends to the following listeners:
+
+* Asterisk Command-line Interface
+* Asterisk Manager Interface (clear text and TLS)
+* Builtin HTTP / HTTPS server
+
+The primary use case of this feature is to allow Asterisk to be started by
+other services through use of AMI, CLI or REST API.
+
+
+Security
+========
+
+Care must be take if enabling socket activation on any IP:PORT that is not
+protected by a firewall.  Any user that can reach any socket activation
+port can start Asterisk, even if they do not have valid credentials to sign
+into the service in question.  Enabling HTTP socket activation on a system
+which provides SIP over websockets would allow remote users to start Asterisk
+any time the HTTP socket is running.
+
+This functionality bypasses the normal restriction where only 'root' can start
+a service.  Enabling AMI socket activation allows any user on the local server
+to start Asterisk by running 'telnet localhost 5038'.
+
+CLI activation is secured by the combination of SocketUser, SocketGroup and
+SocketMode settings in the systemd socket.  Only local users with access will
+be able to start asterisk by using CLI.
+
+
+Separate .socket units or a single unit
+=======================================
+
+Asterisk is a complex system with many components which can be enabled or
+disabled individually.  Using socket activation requires deciding to use
+a single socket file or multiple separate socket files.
+
+The remainder of this README assumes separate socket units are used for each
+listener.
+
+
+Service and Socket files
+========================
+
+All .socket and .service examples in this folder use "reasonable" default
+paths for Linux.  Depending on your distribution and ./configure options
+you may need to modify these before installing.  The files are meant to
+be examples rather than files to be blindly installed.
+
+
+Installing and enabling socket units
+====================================
+
+Modify socket files as desired.  Install them to a location where systemd
+will find them.  pkg-config can be used to determine an appropriate location.
+
+For socket files to be managed directly by the local administrator:
+    pkg-config systemd --variable systemdsystemconfdir
+
+For socket files to be deployed by package manager:
+    pkg-config systemd --variable systemdsystemunitdir
+
+
+After installing socket files you must run 'systemctl daemon-reload' for
+systemd to read the added/modified units.  After this you can enable the
+desired sockets, for example to enable AMI:
+    systemctl enable asterisk-ami.socket
+
+
+Socket Selection
+================
+
+Asterisk configuration is unchanged by use of socket activation.  When a
+component that supports socket activation starts a listener in Asterisk,
+any sockets provided by systemd are iterated.  The systemd socket is used
+when the bound address configured by Asterisk is an exact match with the
+address given by the ListenStream setting in the systemd socket.
+
+
+Command-line Interface
+======================
+
+Symbolic links do not appear to be resolved when checking the CLI listener.
+This may be of concern since /var/run is often a symbolic link to /run. Both
+Asterisk and systemd must use /var/run, or both must use /run.  Mismatching
+will result in service startup failure.
+
+When socket activation is used for Asterisk CLI some asterisk.conf options
+are ignored.  The following options from the [files] section are ignored
+and must instead be set by the systemd socket file.
+* astctlowner - use SocketUser
+* astctlgroup - use SocketGroup
+* astctlpermissions - use SocketMode
+
+See asterisk-cli.socket for an example of these settings.
+
+
+Stopping Asterisk
+=================
+
+Some existing asterisk.service files use CLI 'core stop now' for the ExecStop
+command.  It is not recommended to use CLI to stop Asterisk on systems where
+CLI socket activation is enabled.  If Asterisk fails to start systemd still
+tries running the ExecStop command.  This can result in an loop where ExecStop
+causes CLI socket activation to start Asterisk again.  A better way to deal
+with shutdown is to use Type=notify and do not specify an ExecStop command.
+See the example asterisk.service.
+
+
+Unused Sockets
+==============
+
+Asterisk makes no attempt to check for sockets provided by systemd that are not
+used.  It is the users responsibility to only provide sockets which Asterisk is
+configured to use.
diff --git a/contrib/systemd/asterisk-ami.socket b/contrib/systemd/asterisk-ami.socket
new file mode 100644 (file)
index 0000000..1fd45e4
--- /dev/null
@@ -0,0 +1,10 @@
+[Unit]
+Description=Asterisk Manager Interface Socket
+
+[Socket]
+Service=asterisk.service
+ListenStream=0.0.0.0:5038
+
+[Install]
+WantedBy=sockets.target
+RequiredBy=asterisk.service
diff --git a/contrib/systemd/asterisk-amis.socket b/contrib/systemd/asterisk-amis.socket
new file mode 100644 (file)
index 0000000..c17cee3
--- /dev/null
@@ -0,0 +1,10 @@
+[Unit]
+Description=Asterisk Manager Interface TLS Socket
+
+[Socket]
+Service=asterisk.service
+ListenStream=0.0.0.0:5039
+
+[Install]
+WantedBy=sockets.target
+RequiredBy=asterisk.service
diff --git a/contrib/systemd/asterisk-cli.socket b/contrib/systemd/asterisk-cli.socket
new file mode 100644 (file)
index 0000000..9161a7b
--- /dev/null
@@ -0,0 +1,13 @@
+[Unit]
+Description=Asterisk Command-line Interface Socket
+
+[Socket]
+Service=asterisk.service
+ListenStream=/var/run/asterisk/asterisk.ctl
+SocketUser=asterisk
+SocketGroup=asterisk
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
+RequiredBy=asterisk.service
diff --git a/contrib/systemd/asterisk-http.socket b/contrib/systemd/asterisk-http.socket
new file mode 100644 (file)
index 0000000..e6862b5
--- /dev/null
@@ -0,0 +1,11 @@
+[Unit]
+Description=Asterisk HTTP Socket
+
+[Socket]
+Service=asterisk.service
+FreeBind=true
+ListenStream=127.0.0.1:8088
+
+[Install]
+WantedBy=sockets.target
+RequiredBy=asterisk.service
diff --git a/contrib/systemd/asterisk-https.socket b/contrib/systemd/asterisk-https.socket
new file mode 100644 (file)
index 0000000..d9240dd
--- /dev/null
@@ -0,0 +1,11 @@
+[Unit]
+Description=Asterisk HTTPS Socket
+
+[Socket]
+Service=asterisk.service
+FreeBind=true
+ListenStream=127.0.0.1:8089
+
+[Install]
+WantedBy=sockets.target
+RequiredBy=asterisk.service
diff --git a/contrib/systemd/asterisk.service b/contrib/systemd/asterisk.service
new file mode 100644 (file)
index 0000000..c3d4648
--- /dev/null
@@ -0,0 +1,27 @@
+[Unit]
+Description=Asterisk PBX and telephony daemon.
+After=network.target
+
+[Service]
+Type=notify
+Environment=HOME=/var/lib/asterisk
+WorkingDirectory=/var/lib/asterisk
+User=asterisk
+Group=asterisk
+ExecStart=/usr/sbin/asterisk -mqf -C /etc/asterisk/asterisk.conf
+ExecReload=/usr/sbin/asterisk -rx 'core reload'
+
+#Nice=0
+#UMask=0002
+LimitCORE=infinity
+#LimitNOFILE=
+Restart=always
+RestartSec=4
+
+# Prevent duplication of logs with color codes to /var/log/messages
+StandardOutput=null
+
+PrivateTmp=true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/contrib/systemd/asterisk.socket b/contrib/systemd/asterisk.socket
new file mode 100644 (file)
index 0000000..afdca0d
--- /dev/null
@@ -0,0 +1,26 @@
+[Unit]
+Description=Asterisk Sockets
+
+[Socket]
+FreeBind=true
+SocketUser=asterisk
+SocketGroup=asterisk
+SocketMode=0660
+
+# CLI
+ListenStream=/var/run/asterisk/asterisk.ctl
+# AMI
+ListenStream=0.0.0.0:5038
+# AMIS
+ListenStream=0.0.0.0:5039
+# HTTP
+ListenStream=127.0.0.1:8088
+# HTTPS
+ListenStream=127.0.0.1:8089
+# chan_sip TCP
+ListenStream=0.0.0.0:5060
+# chan_sip TLS
+ListenStream=0.0.0.0:5061
+
+[Install]
+WantedBy=sockets.target
index 6ee8450..f103cf5 100644 (file)
@@ -24,6 +24,7 @@
 #define _ASTERISK_IO_H
 
 #include "asterisk/poll-compat.h"
+#include "asterisk/netsock2.h"
 
 #if defined(__cplusplus) || defined(c_plusplus)
 extern "C" {
@@ -148,6 +149,29 @@ int ast_get_termcols(int fd);
  */
 int ast_sd_notify(const char *state);
 
+/*!
+ * \brief Find a listening file descriptor provided by socket activation.
+ * \param type SOCK_STREAM or SOCK_DGRAM
+ * \param addr The socket address of the bound listener.
+ * \retval <0 No match.
+ * \retval >0 File Descriptor matching sockaddr.
+ *
+ * \note This function returns -1 if systemd's development headers were not
+ * detected on the system.
+ */
+int ast_sd_get_fd(int type, const struct ast_sockaddr *addr);
+
+/*!
+ * \brief Find a listening AF_LOCAL file descriptor provided by socket activation.
+ * \param type SOCK_STREAM or SOCK_DGRAM
+ * \param path The path of the listener.
+ * \retval <0 No match.
+ * \retval >0 File Descriptor matching path.
+ *
+ * \note This function returns -1 if systemd's development headers were not
+ * detected on the system.
+ */
+int ast_sd_get_fd_un(int type, const char *path);
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
index 3ede990..6c0dd63 100644 (file)
@@ -129,6 +129,22 @@ static inline void ast_sockaddr_setnull(struct ast_sockaddr *addr)
 }
 
 /*!
+ * \brief
+ * Copies the data from a sockaddr to an ast_sockaddr
+ *
+ * \param dst The destination ast_sockaddr
+ * \param src The source sockaddr
+ * \param len Length of the value stored in sockaddr
+ * \retval void
+ */
+static inline void ast_sockaddr_copy_sockaddr(struct ast_sockaddr *dst,
+               struct sockaddr *src, socklen_t len)
+{
+       memcpy(dst, src, len);
+       dst->len = len;
+}
+
+/*!
  * \since 1.8
  *
  * \brief
index 16313ea..4424022 100644 (file)
@@ -350,6 +350,7 @@ struct ast_eid ast_eid_default;
 char record_cache_dir[AST_CACHE_DIR_LEN] = DEFAULT_TMP_DIR;
 
 static int ast_socket = -1;            /*!< UNIX Socket for allowing remote control */
+static int ast_socket_is_sd = 0; /*!< Is socket activation responsible for ast_socket? */
 static int ast_consock = -1;           /*!< UNIX Socket for controlling another asterisk */
 pid_t ast_mainpid;
 struct console {
@@ -1576,8 +1577,16 @@ static int ast_makesocket(void)
        uid_t uid = -1;
        gid_t gid = -1;
 
-       for (x = 0; x < AST_MAX_CONNECTS; x++)
+       for (x = 0; x < AST_MAX_CONNECTS; x++) {
                consoles[x].fd = -1;
+       }
+
+       if (ast_socket_is_sd) {
+               ast_socket = ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET);
+
+               goto start_lthread;
+       }
+
        unlink(ast_config_AST_SOCKET);
        ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0);
        if (ast_socket < 0) {
@@ -1602,12 +1611,19 @@ static int ast_makesocket(void)
                return -1;
        }
 
+start_lthread:
        if (ast_pthread_create_background(&lthread, NULL, listener, NULL)) {
                ast_log(LOG_WARNING, "Unable to create listener thread.\n");
                close(ast_socket);
                return -1;
        }
 
+       if (ast_socket_is_sd) {
+               /* owner/group/permissions are set by systemd, we might not even have access
+                * to socket file so leave it alone */
+               return 0;
+       }
+
        if (!ast_strlen_zero(ast_config_AST_CTL_OWNER)) {
                struct passwd *pw;
                if ((pw = getpwnam(ast_config_AST_CTL_OWNER)) == NULL)
@@ -2075,7 +2091,9 @@ static void really_quit(int num, shutdown_nice_t niceness, int restart)
                pthread_cancel(lthread);
                close(ast_socket);
                ast_socket = -1;
-               unlink(ast_config_AST_SOCKET);
+               if (!ast_socket_is_sd) {
+                       unlink(ast_config_AST_SOCKET);
+               }
                pthread_kill(lthread, SIGURG);
                pthread_join(lthread, NULL);
        }
@@ -4317,7 +4335,12 @@ int main(int argc, char *argv[])
        /* Initial value of the maximum active system verbosity level. */
        ast_verb_sys_level = option_verbose;
 
-       if (ast_tryconnect()) {
+       if (ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET) > 0) {
+               ast_socket_is_sd = 1;
+       }
+
+       /* DO NOT perform check for existing daemon if systemd has CLI socket activation */
+       if (!ast_socket_is_sd && ast_tryconnect()) {
                /* One is already running */
                if (ast_opt_remote) {
                        multi_thread_safe = 1;
index b063c22..ed455df 100644 (file)
--- a/main/io.c
+++ b/main/io.c
 #include "asterisk/utils.h"
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
+
+#ifndef SD_LISTEN_FDS_START
+#define SD_LISTEN_FDS_START 3
+#endif
 #endif
 
 #ifdef DEBUG_IO
@@ -392,3 +396,73 @@ int ast_sd_notify(const char *state) {
        return 0;
 #endif
 }
+
+/*!
+ * \internal \brief Check the type and sockaddr of a file descriptor.
+ * \param fd File Descriptor to check.
+ * \param type SOCK_STREAM or SOCK_DGRAM
+ * \param addr The socket address to match.
+ * \retval 0 if matching
+ * \retval -1 if not matching
+ */
+#ifdef HAVE_SYSTEMD
+static int ast_sd_is_socket_sockaddr(int fd, int type, const struct ast_sockaddr* addr)
+{
+       int canretry = 1;
+       struct ast_sockaddr fd_addr;
+       struct sockaddr ss;
+       socklen_t ss_len;
+
+       if (sd_is_socket(fd, AF_UNSPEC, type, 1) <= 0) {
+               return -1;
+       }
+
+doretry:
+       if (getsockname(fd, &ss, &ss_len) != 0) {
+               return -1;
+       }
+
+       if (ss.sa_family == AF_UNSPEC && canretry) {
+               /* An unknown bug can cause silent failure from
+                * the first call to getsockname. */
+               canretry = 0;
+               goto doretry;
+       }
+
+       ast_sockaddr_copy_sockaddr(&fd_addr, &ss, ss_len);
+
+       return ast_sockaddr_cmp(addr, &fd_addr);
+}
+#endif
+
+int ast_sd_get_fd(int type, const struct ast_sockaddr *addr)
+{
+#ifdef HAVE_SYSTEMD
+       int count = sd_listen_fds(0);
+       int idx;
+
+       for (idx = 0; idx < count; idx++) {
+               if (!ast_sd_is_socket_sockaddr(idx + SD_LISTEN_FDS_START, type, addr)) {
+                       return idx + SD_LISTEN_FDS_START;
+               }
+       }
+#endif
+
+       return -1;
+}
+
+int ast_sd_get_fd_un(int type, const char *path)
+{
+#ifdef HAVE_SYSTEMD
+       int count = sd_listen_fds(0);
+       int idx;
+
+       for (idx = 0; idx < count; idx++) {
+               if (sd_is_socket_unix(idx + SD_LISTEN_FDS_START, type, 1, path, 0) > 0) {
+                       return idx + SD_LISTEN_FDS_START;
+               }
+       }
+#endif
+
+       return -1;
+}
index a3c7dfa..85859a3 100644 (file)
@@ -40,6 +40,7 @@
 
 #include "asterisk/compat.h"
 #include "asterisk/tcptls.h"
+#include "asterisk/io.h"
 #include "asterisk/http.h"
 #include "asterisk/utils.h"
 #include "asterisk/strings.h"
@@ -618,6 +619,7 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
        int flags;
        int x = 1;
        int tls_changed = 0;
+       int sd_socket;
 
        if (desc->tls_cfg) {
                char hash[41];
@@ -689,6 +691,19 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
                pthread_join(desc->master, NULL);
        }
 
+       sd_socket = ast_sd_get_fd(SOCK_STREAM, &desc->local_address);
+
+       if (sd_socket != -1) {
+               if (desc->accept_fd != sd_socket) {
+                       if (desc->accept_fd != -1) {
+                               close(desc->accept_fd);
+                       }
+                       desc->accept_fd = sd_socket;
+               }
+
+               goto systemd_socket_activation;
+       }
+
        if (desc->accept_fd != -1) {
                close(desc->accept_fd);
        }
@@ -718,6 +733,8 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
                ast_log(LOG_ERROR, "Unable to listen for %s!\n", desc->name);
                goto error;
        }
+
+systemd_socket_activation:
        flags = fcntl(desc->accept_fd, F_GETFL);
        fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
        if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {