fd_device: allow fd to be passed through a unix socket
authorpacien <pacien.trangirard@pacien.net>
Mon, 20 Jan 2020 12:58:13 +0000 (13:58 +0100)
committerpacien <pacien.trangirard@pacien.net>
Mon, 20 Jan 2020 18:28:33 +0000 (19:28 +0100)
New restrictions on the Android OS forbid direct leaking of file descriptors.
This patch allows the tinc daemon to have an fd and the associated
permissions transferred to it through a Unix domain socket.

doc/tinc.conf.5.in
doc/tinc.texi
src/fd_device.c

index a907e5f..acdce0f 100644 (file)
@@ -235,7 +235,8 @@ Do NOT connect multiple
 daemons to the same multicast address, this will very likely cause routing loops.
 Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured.
 .It fd
-Use a file descriptor.
+Use a file descriptor, given directly as an integer or passed through a unix domain socket.
+On Linux, an abstract socket address can be specified by using "@" as a prefix.
 All packets are read from this interface.
 Packets received for the local node are written to it.
 .It uml Pq not compiled in by default
index 65011bd..9c45965 100644 (file)
@@ -941,7 +941,8 @@ Also note that this can cause decrypted VPN packets to be sent out on a real net
 
 @cindex fd
 @item fd
-Use a file descriptor.
+Use a file descriptor, given directly as an integer or passed through a unix domain socket.
+On Linux, an abstract socket address can be specified by using "@" as a prefix.
 All packets are read from this interface.
 Packets received for the local node are written to it.
 
index afe59bc..8429556 100644 (file)
@@ -3,7 +3,7 @@
     Copyright (C)   2001-2005   Ivo Timmermans,
                     2001-2016   Guus Sliepen <guus@tinc-vpn.org>
                     2009        Grzegorz Dymarek <gregd72002@googlemail.com>
-                    2016        Pacien TRAN-GIRARD <pacien@pacien.net>
+                    2016-2020   Pacien TRAN-GIRARD <pacien@pacien.net>
 
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -20,6 +20,8 @@
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
+#include <sys/un.h>
+
 #include "system.h"
 #include "conf.h"
 #include "device.h"
 #include "route.h"
 #include "utils.h"
 
-static inline bool check_config(void) {
-       if(routing_mode == RMODE_SWITCH) {
-               logger(DEBUG_ALWAYS, LOG_ERR, "Switch mode not supported (requires unsupported TAP device)!");
-               return false;
+struct unix_socket_addr {
+       size_t size;
+       struct sockaddr_un addr;
+};
+
+static int read_fd(int socket) {
+       char iobuf;
+       struct iovec iov = {0};
+       char cmsgbuf[CMSG_SPACE(sizeof(device_fd))];
+       struct msghdr msg = {0};
+       int ret;
+       struct cmsghdr *cmsgptr;
+
+       iov.iov_base = &iobuf;
+       iov.iov_len = 1;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = cmsgbuf;
+       msg.msg_controllen = sizeof(cmsgbuf);
+
+       if((ret = recvmsg(socket, &msg, 0)) < 1) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Could not read from unix socket (error %d)!", ret);
+               return -1;
+       }
+       if(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Error while receiving message (flags %d)!", msg.msg_flags);
+               return -1;
        }
 
-       if(!get_config_int(lookup_config(config_tree, "Device"), &device_fd)) {
-               logger(DEBUG_ALWAYS, LOG_ERR, "Could not read fd from configuration!");
-               return false;
+       cmsgptr = CMSG_FIRSTHDR(&msg);
+       if(cmsgptr->cmsg_level != SOL_SOCKET) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG level: %d, expected %d!",
+                       cmsgptr->cmsg_level, SOL_SOCKET);
+               return -1;
+       }
+       if(cmsgptr->cmsg_type != SCM_RIGHTS) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG type: %d, expected %d!",
+                       cmsgptr->cmsg_type, SCM_RIGHTS);
+               return -1;
+       }
+       if(cmsgptr->cmsg_len != CMSG_LEN(sizeof(device_fd))) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG data length: %lu, expected %lu!",
+                       cmsgptr->cmsg_len, CMSG_LEN(sizeof(device_fd)));
+               return -1;
        }
 
-       return true;
+       return *(int *) CMSG_DATA(cmsgptr);
+}
+
+static int receive_fd(struct unix_socket_addr socket_addr) {
+       int socketfd;
+       int ret;
+       int result;
+
+       if((socketfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Could not open stream socket (error %d)!", socketfd);
+               return -1;
+       }
+
+       if((ret = connect(socketfd, (struct sockaddr *) &socket_addr.addr, socket_addr.size)) < 0) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Could not connect to Unix socket (error %d)!", ret);
+               result = -1;
+               goto end;
+       }
+
+       result = read_fd(socketfd);
+
+end:
+       close(socketfd);
+       return result;
+}
+
+static struct unix_socket_addr parse_socket_addr(const char *path) {
+       struct sockaddr_un socket_addr;
+       size_t path_length;
+
+       if(strlen(path) >= sizeof(socket_addr.sun_path)) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Unix socket path too long!");
+               return (struct unix_socket_addr) {0};
+       }
+
+       socket_addr.sun_family = AF_UNIX;
+       strncpy(socket_addr.sun_path, path, sizeof(socket_addr.sun_path));
+
+       if(path[0] == '@') {
+               /* abstract namespace socket */
+               socket_addr.sun_path[0] = '\0';
+               path_length = strlen(path);
+       } else {
+               /* filesystem path with NUL terminator */
+               path_length = strlen(path) + 1;
+       }
+
+       return (struct unix_socket_addr) {
+               .size = offsetof(struct sockaddr_un, sun_path) + path_length,
+               .addr = socket_addr
+       };
 }
 
 static bool setup_device(void) {
-       if(!check_config()) {
+       if(routing_mode == RMODE_SWITCH) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Switch mode not supported (requires unsupported TAP device)!");
+               return false;
+       }
+
+       if(!get_config_string(lookup_config(config_tree, "Device"), &device)) {
+               logger(DEBUG_ALWAYS, LOG_ERR, "Could not read device from configuration!");
                return false;
        }
 
+       /* device is either directly a file descriptor or an unix socket to read it from */
+       if(sscanf(device, "%d", &device_fd) != 1) {
+               logger(DEBUG_ALWAYS, LOG_INFO, "Receiving fd from Unix socket at %s.", device);
+               device_fd = receive_fd(parse_socket_addr(device));
+       }
+
        if(device_fd < 0) {
                logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s!", device, strerror(errno));
                return false;