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
 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
 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
 
 @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.
 
 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>
     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
 
     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.
 */
 
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
+#include <sys/un.h>
+
 #include "system.h"
 #include "conf.h"
 #include "device.h"
 #include "system.h"
 #include "conf.h"
 #include "device.h"
 #include "route.h"
 #include "utils.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) {
 }
 
 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;
        }
 
                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;
        if(device_fd < 0) {
                logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s!", device, strerror(errno));
                return false;