<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5877">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Joshua Colp: Looks good to me, but someone else must approve; Verified
  George Joseph: Looks good to me, approved; Verified
  Jenkins2: Approved for Submit

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

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: Id814ee6a892f4b80d018365c8ad8d89063474f4d </div>
<div style="display:none"> Gerrit-Change-Number: 5877 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: Corey Farrell <git@cfware.com> </div>
<div style="display:none"> Gerrit-Reviewer: Corey Farrell <git@cfware.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>