summary refs log tree commit diff
path: root/nix/nix-daemon/nix-daemon.cc
diff options
context:
space:
mode:
authorLudovic Courtès <ludovic.courtes@inria.fr>2017-06-19 17:39:24 +0200
committerLudovic Courtès <ludo@gnu.org>2017-06-22 10:59:07 +0200
commit1071f781d97509347144754b3248581cf7c6c1d5 (patch)
tree59565eacafc47841647596a5cf84c4e0311af39a /nix/nix-daemon/nix-daemon.cc
parent5df1395a8d4bb83e002e1aab5d930edd2b49d27e (diff)
downloadguix-1071f781d97509347144754b3248581cf7c6c1d5.tar.gz
daemon: '--listen' can be passed several times, can specify TCP endpoints.
* nix/nix-daemon/guix-daemon.cc (DEFAULT_GUIX_PORT): New macro.
(listen_options): New variable.
(parse_opt): Push back '--listen' options to LISTEN_OPTIONS.
(open_unix_domain_socket, open_inet_socket)
(listening_sockets): New functions.
(main): Use it.  Pass SOCKETS to 'run'.
* nix/nix-daemon/nix-daemon.cc (matchUser): Remove.
(SD_LISTEN_FDS_START): Remove.
(acceptConnection): New function.
(daemonLoop): Rewrite to take a vector of file descriptors, to select(2)
on them, and to call 'acceptConnection'.
(run): Change to take a vector of file descriptors.
* tests/guix-daemon.sh: Add test.
Diffstat (limited to 'nix/nix-daemon/nix-daemon.cc')
-rw-r--r--nix/nix-daemon/nix-daemon.cc283
1 files changed, 127 insertions, 156 deletions
diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc
index 79580ffb48..3d8e909901 100644
--- a/nix/nix-daemon/nix-daemon.cc
+++ b/nix/nix-daemon/nix-daemon.cc
@@ -18,6 +18,7 @@
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <arpa/inet.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <pwd.h>
@@ -809,151 +810,87 @@ static void setSigChldAction(bool autoReap)
 }
 
 
-bool matchUser(const string & user, const string & group, const Strings & users)
+/* Accept a connection on FDSOCKET and fork a server process to process the
+   new connection.  */
+static void acceptConnection(int fdSocket)
 {
-    if (find(users.begin(), users.end(), "*") != users.end())
-        return true;
-
-    if (find(users.begin(), users.end(), user) != users.end())
-        return true;
-
-    for (auto & i : users)
-        if (string(i, 0, 1) == "@") {
-            if (group == string(i, 1)) return true;
-            struct group * gr = getgrnam(i.c_str() + 1);
-            if (!gr) continue;
-            for (char * * mem = gr->gr_mem; *mem; mem++)
-                if (user == string(*mem)) return true;
-        }
-
-    return false;
-}
-
-
-#define SD_LISTEN_FDS_START 3
-
-
-static void daemonLoop()
-{
-    if (chdir("/") == -1)
-        throw SysError("cannot change current directory");
-
-    /* Get rid of children automatically; don't let them become
-       zombies. */
-    setSigChldAction(true);
-
-    AutoCloseFD fdSocket;
-
-    /* Handle socket-based activation by systemd. */
-    if (getEnv("LISTEN_FDS") != "") {
-        if (getEnv("LISTEN_PID") != std::to_string(getpid()) || getEnv("LISTEN_FDS") != "1")
-            throw Error("unexpected systemd environment variables");
-        fdSocket = SD_LISTEN_FDS_START;
-    }
-
-    /* Otherwise, create and bind to a Unix domain socket. */
-    else {
-
-        /* Create and bind to a Unix domain socket. */
-        fdSocket = socket(PF_UNIX, SOCK_STREAM, 0);
-        if (fdSocket == -1)
-            throw SysError("cannot create Unix domain socket");
-
-        string socketPath = settings.nixDaemonSocketFile;
-
-        createDirs(dirOf(socketPath));
-
-        /* Urgh, sockaddr_un allows path names of only 108 characters.
-           So chdir to the socket directory so that we can pass a
-           relative path name. */
-        if (chdir(dirOf(socketPath).c_str()) == -1)
-            throw SysError("cannot change current directory");
-        Path socketPathRel = "./" + baseNameOf(socketPath);
-
-        struct sockaddr_un addr;
-        addr.sun_family = AF_UNIX;
-        if (socketPathRel.size() >= sizeof(addr.sun_path))
-            throw Error(format("socket path `%1%' is too long") % socketPathRel);
-        strcpy(addr.sun_path, socketPathRel.c_str());
-
-        unlink(socketPath.c_str());
-
-        /* Make sure that the socket is created with 0666 permission
-           (everybody can connect --- provided they have access to the
-           directory containing the socket). */
-        mode_t oldMode = umask(0111);
-        int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr));
-        umask(oldMode);
-        if (res == -1)
-            throw SysError(format("cannot bind to socket `%1%'") % socketPath);
-
-        if (chdir("/") == -1) /* back to the root */
-            throw SysError("cannot change current directory");
-
-        if (listen(fdSocket, 5) == -1)
-            throw SysError(format("cannot listen on socket `%1%'") % socketPath);
-    }
-
-    closeOnExec(fdSocket);
-
-    /* Loop accepting connections. */
-    while (1) {
-
-        try {
-            /* Important: the server process *cannot* open the SQLite
-               database, because it doesn't like forks very much. */
-            assert(!store);
-
-            /* Accept a connection. */
-            struct sockaddr_un remoteAddr;
-            socklen_t remoteAddrLen = sizeof(remoteAddr);
-
-            AutoCloseFD remote = accept(fdSocket,
-                (struct sockaddr *) &remoteAddr, &remoteAddrLen);
-            checkInterrupt();
-            if (remote == -1) {
-                if (errno == EINTR)
-                    continue;
-                else
-                    throw SysError("accepting connection");
-            }
-
-            closeOnExec(remote);
-
-            bool trusted = false;
-            pid_t clientPid = -1;
+    uid_t clientUid = (uid_t) -1;
+    gid_t clientGid = (gid_t) -1;
 
+    try {
+	/* Important: the server process *cannot* open the SQLite
+	   database, because it doesn't like forks very much. */
+	assert(!store);
+
+	/* Accept a connection. */
+	struct sockaddr_storage remoteAddr;
+	socklen_t remoteAddrLen = sizeof(remoteAddr);
+
+    try_again:
+	AutoCloseFD remote = accept(fdSocket,
+				    (struct sockaddr *) &remoteAddr, &remoteAddrLen);
+	checkInterrupt();
+	if (remote == -1) {
+	    if (errno == EINTR)
+		goto try_again;
+	    else
+		throw SysError("accepting connection");
+	}
+
+	closeOnExec(remote);
+
+	pid_t clientPid = -1;
+	bool trusted = false;
+
+	/* Get the identity of the caller, if possible. */
+	if (remoteAddr.ss_family == AF_UNIX) {
 #if defined(SO_PEERCRED)
-            /* Get the identity of the caller, if possible. */
-            ucred cred;
-            socklen_t credLen = sizeof(cred);
-            if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
-                throw SysError("getting peer credentials");
+	    ucred cred;
+	    socklen_t credLen = sizeof(cred);
+	    if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED,
+			   &cred, &credLen) == -1)
+		throw SysError("getting peer credentials");
 
-            clientPid = cred.pid;
+	    clientPid = cred.pid;
+	    clientUid = cred.uid;
+	    clientGid = cred.gid;
+	    trusted = clientUid == 0;
 
             struct passwd * pw = getpwuid(cred.uid);
             string user = pw ? pw->pw_name : std::to_string(cred.uid);
 
-            struct group * gr = getgrgid(cred.gid);
-            string group = gr ? gr->gr_name : std::to_string(cred.gid);
-
-            Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
-            Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
-
-            if (matchUser(user, group, trustedUsers))
-                trusted = true;
-
-            if (!trusted && !matchUser(user, group, allowedUsers))
-                throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user);
-
-            printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%"
-                    + (trusted ? " (trusted)" : "")) % clientPid % user);
+	    printMsg(lvlInfo,
+		     format((string) "accepted connection from pid %1%, user %2%")
+		     % clientPid % user);
 #endif
-
-            /* Fork a child to handle the connection. */
-            startProcess([&]() {
-                fdSocket.close();
+	} else {
+	    char address_str[128];
+	    const char *result;
+
+	    if (remoteAddr.ss_family == AF_INET) {
+		struct sockaddr_in *addr = (struct sockaddr_in *) &remoteAddr;
+		struct in_addr inaddr = { addr->sin_addr };
+		result = inet_ntop(AF_INET, &inaddr,
+				   address_str, sizeof address_str);
+	    } else if (remoteAddr.ss_family == AF_INET6) {
+		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &remoteAddr;
+		struct in6_addr inaddr = { addr->sin6_addr };
+		result = inet_ntop(AF_INET6, &inaddr,
+				   address_str, sizeof address_str);
+	    } else {
+		result = NULL;
+	    }
+
+	    if (result != NULL) {
+		printMsg(lvlInfo,
+			 format("accepted connection from %1%")
+			 % address_str);
+	    }
+	}
+
+	/* Fork a child to handle the connection. */
+	startProcess([&]() {
+                close(fdSocket);
 
                 /* Background the daemon. */
                 if (setsid() == -1)
@@ -968,17 +905,11 @@ static void daemonLoop()
                     strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
                 }
 
-#if defined(SO_PEERCRED)
                 /* Store the client's user and group for this connection. This
                    has to be done in the forked process since it is per
-                   connection. */
-                settings.clientUid = cred.uid;
-                settings.clientGid = cred.gid;
-#else
-                /* Setting these to -1 means: do not change */
-                settings.clientUid = (uid_t) -1;
-                settings.clientGid = (gid_t) -1;
-#endif
+                   connection.  Setting these to -1 means: do not change.  */
+                settings.clientUid = clientUid;
+		settings.clientGid = clientGid;
 
                 /* Handle the connection. */
                 from.fd = remote;
@@ -988,23 +919,63 @@ static void daemonLoop()
                 exit(0);
             }, false, "unexpected Nix daemon error: ", true);
 
-        } catch (Interrupted & e) {
-            throw;
-        } catch (Error & e) {
-            printMsg(lvlError, format("error processing connection: %1%") % e.msg());
-        }
+    } catch (Interrupted & e) {
+	throw;
+    } catch (Error & e) {
+	printMsg(lvlError, format("error processing connection: %1%") % e.msg());
     }
 }
 
-
-void run(Strings args)
+static void daemonLoop(const std::vector<int>& sockets)
 {
-    for (Strings::iterator i = args.begin(); i != args.end(); ) {
-        string arg = *i++;
-        if (arg == "--daemon") /* ignored for backwards compatibility */;
+    if (chdir("/") == -1)
+        throw SysError("cannot change current directory");
+
+    /* Get rid of children automatically; don't let them become
+       zombies. */
+    setSigChldAction(true);
+
+    /* Mark sockets as close-on-exec.  */
+    for(int fd: sockets) {
+	closeOnExec(fd);
     }
 
-    daemonLoop();
+    /* Prepare the FD set corresponding to SOCKETS.  */
+    auto initializeFDSet = [&](fd_set *set) {
+	FD_ZERO(set);
+	for (int fd: sockets) {
+	    FD_SET(fd, set);
+	}
+    };
+
+    /* Loop accepting connections. */
+    while (1) {
+	fd_set readfds;
+
+	initializeFDSet(&readfds);
+	int count =
+	    select(*std::max_element(sockets.begin(), sockets.end()) + 1,
+		   &readfds, NULL, NULL,
+		   NULL);
+	if (count < 0) {
+	    int err = errno;
+	    if (err == EINTR)
+		continue;
+	    throw SysError(format("select error: %1%") % strerror(err));
+	}
+
+	for (unsigned int i = 0; i < sockets.size(); i++) {
+	    if (FD_ISSET(sockets[i], &readfds)) {
+		acceptConnection(sockets[i]);
+	    }
+	}
+    }
+}
+
+
+void run(const std::vector<int>& sockets)
+{
+    daemonLoop(sockets);
 }