diff options
author | Jenkins2 <jenkins2@gerrit.asterisk.org> | 2017-07-26 09:17:40 -0500 |
---|---|---|
committer | Gerrit Code Review <gerrit2@gerrit.digium.api> | 2017-07-26 09:17:40 -0500 |
commit | 1bec535df2e8a7968a810cbef594fa17f7b642bc (patch) | |
tree | f3df3f11f0d958494683973a95e656d5982a806d | |
parent | b610295b62abfdcac068b2fe1406ba74df8a00b5 (diff) | |
parent | 70d2ccb9daa1926788b1296c6ccb2611341302d0 (diff) |
Merge "Core: Add support for systemd socket activation."
-rw-r--r-- | contrib/systemd/README.txt | 119 | ||||
-rw-r--r-- | contrib/systemd/asterisk-ami.socket | 10 | ||||
-rw-r--r-- | contrib/systemd/asterisk-amis.socket | 10 | ||||
-rw-r--r-- | contrib/systemd/asterisk-cli.socket | 13 | ||||
-rw-r--r-- | contrib/systemd/asterisk-http.socket | 11 | ||||
-rw-r--r-- | contrib/systemd/asterisk-https.socket | 11 | ||||
-rw-r--r-- | contrib/systemd/asterisk.service | 27 | ||||
-rw-r--r-- | contrib/systemd/asterisk.socket | 26 | ||||
-rw-r--r-- | include/asterisk/io.h | 24 | ||||
-rw-r--r-- | include/asterisk/netsock2.h | 16 | ||||
-rw-r--r-- | main/asterisk.c | 29 | ||||
-rw-r--r-- | main/io.c | 74 | ||||
-rw-r--r-- | main/tcptls.c | 17 |
13 files changed, 384 insertions, 3 deletions
diff --git a/contrib/systemd/README.txt b/contrib/systemd/README.txt new file mode 100644 index 000000000..3225641f4 --- /dev/null +++ b/contrib/systemd/README.txt @@ -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 index 000000000..1fd45e4cb --- /dev/null +++ b/contrib/systemd/asterisk-ami.socket @@ -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 index 000000000..c17cee3e2 --- /dev/null +++ b/contrib/systemd/asterisk-amis.socket @@ -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 index 000000000..9161a7be4 --- /dev/null +++ b/contrib/systemd/asterisk-cli.socket @@ -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 index 000000000..e6862b5b9 --- /dev/null +++ b/contrib/systemd/asterisk-http.socket @@ -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 index 000000000..d9240dd91 --- /dev/null +++ b/contrib/systemd/asterisk-https.socket @@ -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 index 000000000..c3d46483c --- /dev/null +++ b/contrib/systemd/asterisk.service @@ -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 index 000000000..afdca0df7 --- /dev/null +++ b/contrib/systemd/asterisk.socket @@ -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 diff --git a/include/asterisk/io.h b/include/asterisk/io.h index 6ee8450bd..f103cf556 100644 --- a/include/asterisk/io.h +++ b/include/asterisk/io.h @@ -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) } diff --git a/include/asterisk/netsock2.h b/include/asterisk/netsock2.h index 3ede99087..6c0dd633b 100644 --- a/include/asterisk/netsock2.h +++ b/include/asterisk/netsock2.h @@ -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 diff --git a/main/asterisk.c b/main/asterisk.c index 04298385a..a302836a4 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -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(<hread, 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); } @@ -4319,7 +4337,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; @@ -36,6 +36,10 @@ #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; +} diff --git a/main/tcptls.c b/main/tcptls.c index a3c7dfa72..85859a343 100644 --- a/main/tcptls.c +++ b/main/tcptls.c @@ -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)) { |