diff --git a/contrib/systemd/README.txt b/contrib/systemd/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..3225641f49612d41026bfe7fde7baeecf86acc07 --- /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 0000000000000000000000000000000000000000..1fd45e4cb871a68f956f3dec2e84ad61bf97837b --- /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 0000000000000000000000000000000000000000..c17cee3e268ad2ea0ba4b7fa4b2ca66f64f3a342 --- /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 0000000000000000000000000000000000000000..9161a7be466d355e49bfadee84855269799dcb30 --- /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 0000000000000000000000000000000000000000..e6862b5b9228e7a49745cb1360b61342a49e5104 --- /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 0000000000000000000000000000000000000000..d9240dd910633ac89086e6427e4a9e323f25d486 --- /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 0000000000000000000000000000000000000000..c3d46483c731a7a17f12be5baf8d21cc3bc27311 --- /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 0000000000000000000000000000000000000000..afdca0df78a635229da8e1b1ca75429cc75ecfef --- /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 6ee8450bd7007d4a05f2b14c0b0cbb6d043fdf07..f103cf556bab7dcf999475ebfcfecebebbc0c161 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 3ede99087788a242e0c33c8cdfe02e51e821a913..6c0dd633b03c140f3e2d1617c441131a427ba841 100644 --- a/include/asterisk/netsock2.h +++ b/include/asterisk/netsock2.h @@ -128,6 +128,22 @@ static inline void ast_sockaddr_setnull(struct ast_sockaddr *addr) addr->len = 0; } +/*! + * \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 * diff --git a/main/asterisk.c b/main/asterisk.c index 16313ea685312f017cba277d09f89c835e45bde3..4424022c94a5e55830e52352885a9d85301afdae 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); } @@ -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; diff --git a/main/io.c b/main/io.c index b063c22396c481b81d2bc8130d7fd282e51a4a0d..ed455df977fceee34fc6ebcc76ee41844df7adc1 100644 --- a/main/io.c +++ b/main/io.c @@ -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 a3c7dfa7202d1f41386ac8a1e24fc75b7f21cdf9..85859a3435ca03fe8f736888f80dc1c3ef23f5ad 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)) {