summaryrefslogtreecommitdiff
path: root/nix
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
parent5df1395a8d4bb83e002e1aab5d930edd2b49d27e (diff)
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')
-rw-r--r--nix/nix-daemon/guix-daemon.cc152
-rw-r--r--nix/nix-daemon/nix-daemon.cc283
2 files changed, 266 insertions, 169 deletions
diff --git a/nix/nix-daemon/guix-daemon.cc b/nix/nix-daemon/guix-daemon.cc
index 0d9c33d1d2e..7963358202d 100644
--- a/nix/nix-daemon/guix-daemon.cc
+++ b/nix/nix-daemon/guix-daemon.cc
@@ -1,5 +1,6 @@
1/* GNU Guix --- Functional package management for GNU 1/* GNU Guix --- Functional package management for GNU
2 Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org> 2 Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
3 Copyright (C) 2006, 2010, 2012, 2014 Eelco Dolstra <e.dolstra@tudelft.nl>
3 4
4 This file is part of GNU Guix. 5 This file is part of GNU Guix.
5 6
@@ -30,8 +31,12 @@
30#include <unistd.h> 31#include <unistd.h>
31#include <sys/types.h> 32#include <sys/types.h>
32#include <sys/stat.h> 33#include <sys/stat.h>
34#include <sys/socket.h>
35#include <sys/un.h>
36#include <netdb.h>
33#include <strings.h> 37#include <strings.h>
34#include <exception> 38#include <exception>
39#include <iostream>
35 40
36#include <libintl.h> 41#include <libintl.h>
37#include <locale.h> 42#include <locale.h>
@@ -43,7 +48,7 @@ char **argvSaved;
43using namespace nix; 48using namespace nix;
44 49
45/* Entry point in `nix-daemon.cc'. */ 50/* Entry point in `nix-daemon.cc'. */
46extern void run (Strings args); 51extern void run (const std::vector<int> &);
47 52
48 53
49/* Command-line options. */ 54/* Command-line options. */
@@ -149,6 +154,12 @@ to live outputs") },
149 }; 154 };
150 155
151 156
157/* Default port for '--listen' on TCP/IP. */
158#define DEFAULT_GUIX_PORT "44146"
159
160/* List of '--listen' options. */
161static std::list<std::string> listen_options;
162
152/* Convert ARG to a Boolean value, or throw an error if it does not denote a 163/* Convert ARG to a Boolean value, or throw an error if it does not denote a
153 Boolean. */ 164 Boolean. */
154static bool 165static bool
@@ -217,15 +228,7 @@ parse_opt (int key, char *arg, struct argp_state *state)
217 settings.keepLog = false; 228 settings.keepLog = false;
218 break; 229 break;
219 case GUIX_OPT_LISTEN: 230 case GUIX_OPT_LISTEN:
220 try 231 listen_options.push_back (arg);
221 {
222 settings.nixDaemonSocketFile = canonPath (arg);
223 }
224 catch (std::exception &e)
225 {
226 fprintf (stderr, _("error: %s\n"), e.what ());
227 exit (EXIT_FAILURE);
228 }
229 break; 232 break;
230 case GUIX_OPT_SUBSTITUTE_URLS: 233 case GUIX_OPT_SUBSTITUTE_URLS:
231 settings.set ("substitute-urls", arg); 234 settings.set ("substitute-urls", arg);
@@ -276,13 +279,134 @@ static const struct argp argp =
276 guix_textdomain 279 guix_textdomain
277 }; 280 };
278 281
282
283static int
284open_unix_domain_socket (const char *file)
285{
286 /* Create and bind to a Unix domain socket. */
287 AutoCloseFD fdSocket = socket (PF_UNIX, SOCK_STREAM, 0);
288 if (fdSocket == -1)
289 throw SysError (_("cannot create Unix domain socket"));
290
291 createDirs (dirOf (file));
292
293 /* Urgh, sockaddr_un allows path names of only 108 characters.
294 So chdir to the socket directory so that we can pass a
295 relative path name. */
296 if (chdir (dirOf (file).c_str ()) == -1)
297 throw SysError (_("cannot change current directory"));
298 Path fileRel = "./" + baseNameOf (file);
299
300 struct sockaddr_un addr;
301 addr.sun_family = AF_UNIX;
302 if (fileRel.size () >= sizeof (addr.sun_path))
303 throw Error (format (_("socket file name '%1%' is too long")) % fileRel);
304 strcpy (addr.sun_path, fileRel.c_str ());
305
306 unlink (file);
307
308 /* Make sure that the socket is created with 0666 permission
309 (everybody can connect --- provided they have access to the
310 directory containing the socket). */
311 mode_t oldMode = umask (0111);
312 int res = bind (fdSocket, (struct sockaddr *) &addr, sizeof addr);
313 umask (oldMode);
314 if (res == -1)
315 throw SysError (format (_("cannot bind to socket '%1%'")) % file);
316
317 if (chdir ("/") == -1) /* back to the root */
318 throw SysError (_("cannot change current directory"));
319
320 if (listen (fdSocket, 5) == -1)
321 throw SysError (format (_("cannot listen on socket '%1%'")) % file);
322
323 return fdSocket.borrow ();
324}
325
326/* Return a listening socket for ADDRESS, which has the given LENGTH. */
327static int
328open_inet_socket (const struct sockaddr *address, socklen_t length)
329{
330 AutoCloseFD fd = socket (address->sa_family, SOCK_STREAM, 0);
331 if (fd == -1)
332 throw SysError (_("cannot create TCP socket"));
333
334 int res = bind (fd, address, length);
335 if (res == -1)
336 throw SysError (_("cannot bind TCP socket"));
337
338 if (listen (fd, 5) == -1)
339 throw SysError (format (_("cannot listen on TCP socket")));
340
341 return fd.borrow ();
342}
343
344/* Return a list of file descriptors of listening sockets. */
345static std::vector<int>
346listening_sockets (const std::list<std::string> &options)
347{
348 std::vector<int> result;
349
350 if (options.empty ())
351 {
352 /* Open the default Unix-domain socket. */
353 auto fd = open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
354 result.push_back (fd);
355 return result;
356 }
357
358 /* Open the user-specified sockets. */
359 for (const std::string& option: options)
360 {
361 if (option[0] == '/')
362 {
363 /* Assume OPTION is the file name of a Unix-domain socket. */
364 settings.nixDaemonSocketFile = canonPath (option);
365 int fd =
366 open_unix_domain_socket (settings.nixDaemonSocketFile.c_str ());
367 result.push_back (fd);
368 }
369 else
370 {
371 /* Assume OPTIONS has the form "HOST" or "HOST:PORT". */
372 auto colon = option.find_last_of (":");
373 auto host = colon == std::string::npos
374 ? option : option.substr (0, colon);
375 auto port = colon == std::string::npos
376 ? DEFAULT_GUIX_PORT
377 : option.substr (colon + 1, option.size () - colon - 1);
378
379 struct addrinfo *res, hints;
380
381 memset (&hints, '\0', sizeof hints);
382 hints.ai_socktype = SOCK_STREAM;
383 hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
384
385 int err = getaddrinfo (host.c_str(), port.c_str (),
386 &hints, &res);
387
388 if (err != 0)
389 throw Error(format ("failed to look up '%1%': %2%")
390 % option % gai_strerror (err));
391
392 printMsg (lvlDebug, format ("listening on '%1%', port '%2%'")
393 % host % port);
394
395 /* XXX: Pick the first result, RES. */
396 result.push_back (open_inet_socket (res->ai_addr,
397 res->ai_addrlen));
398
399 freeaddrinfo (res);
400 }
401 }
402
403 return result;
404}
279 405
280 406
281int 407int
282main (int argc, char *argv[]) 408main (int argc, char *argv[])
283{ 409{
284 static const Strings nothing;
285
286 setlocale (LC_ALL, ""); 410 setlocale (LC_ALL, "");
287 bindtextdomain (guix_textdomain, LOCALEDIR); 411 bindtextdomain (guix_textdomain, LOCALEDIR);
288 textdomain (guix_textdomain); 412 textdomain (guix_textdomain);
@@ -359,6 +483,8 @@ main (int argc, char *argv[])
359 483
360 argp_parse (&argp, argc, argv, 0, 0, 0); 484 argp_parse (&argp, argc, argv, 0, 0, 0);
361 485
486 auto sockets = listening_sockets (listen_options);
487
362 /* Effect all the changes made via 'settings.set'. */ 488 /* Effect all the changes made via 'settings.set'. */
363 settings.update (); 489 settings.update ();
364 490
@@ -402,7 +528,7 @@ using `--build-users-group' is highly recommended\n"));
402 printMsg (lvlDebug, 528 printMsg (lvlDebug,
403 format ("listening on `%1%'") % settings.nixDaemonSocketFile); 529 format ("listening on `%1%'") % settings.nixDaemonSocketFile);
404 530
405 run (nothing); 531 run (sockets);
406 } 532 }
407 catch (std::exception &e) 533 catch (std::exception &e)
408 { 534 {
diff --git a/nix/nix-daemon/nix-daemon.cc b/nix/nix-daemon/nix-daemon.cc
index 79580ffb48c..3d8e909901c 100644
--- a/nix/nix-daemon/nix-daemon.cc
+++ b/nix/nix-daemon/nix-daemon.cc
@@ -18,6 +18,7 @@
18#include <sys/stat.h> 18#include <sys/stat.h>
19#include <sys/socket.h> 19#include <sys/socket.h>
20#include <sys/un.h> 20#include <sys/un.h>
21#include <arpa/inet.h>
21#include <fcntl.h> 22#include <fcntl.h>
22#include <errno.h> 23#include <errno.h>
23#include <pwd.h> 24#include <pwd.h>
@@ -809,151 +810,87 @@ static void setSigChldAction(bool autoReap)
809} 810}
810 811
811 812
812bool matchUser(const string & user, const string & group, const Strings & users) 813/* Accept a connection on FDSOCKET and fork a server process to process the
814 new connection. */
815static void acceptConnection(int fdSocket)
813{ 816{
814 if (find(users.begin(), users.end(), "*") != users.end()) 817 uid_t clientUid = (uid_t) -1;
815 return true; 818 gid_t clientGid = (gid_t) -1;
816
817 if (find(users.begin(), users.end(), user) != users.end())
818 return true;
819
820 for (auto & i : users)
821 if (string(i, 0, 1) == "@") {
822 if (group == string(i, 1)) return true;
823 struct group * gr = getgrnam(i.c_str() + 1);
824 if (!gr) continue;
825 for (char * * mem = gr->gr_mem; *mem; mem++)
826 if (user == string(*mem)) return true;
827 }
828
829 return false;
830}
831
832
833#define SD_LISTEN_FDS_START 3
834
835
836static void daemonLoop()
837{
838 if (chdir("/") == -1)
839 throw SysError("cannot change current directory");
840
841 /* Get rid of children automatically; don't let them become
842 zombies. */
843 setSigChldAction(true);
844
845 AutoCloseFD fdSocket;
846
847 /* Handle socket-based activation by systemd. */
848 if (getEnv("LISTEN_FDS") != "") {
849 if (getEnv("LISTEN_PID") != std::to_string(getpid()) || getEnv("LISTEN_FDS") != "1")
850 throw Error("unexpected systemd environment variables");
851 fdSocket = SD_LISTEN_FDS_START;
852 }
853
854 /* Otherwise, create and bind to a Unix domain socket. */
855 else {
856
857 /* Create and bind to a Unix domain socket. */
858 fdSocket = socket(PF_UNIX, SOCK_STREAM, 0);
859 if (fdSocket == -1)
860 throw SysError("cannot create Unix domain socket");
861
862 string socketPath = settings.nixDaemonSocketFile;
863
864 createDirs(dirOf(socketPath));
865
866 /* Urgh, sockaddr_un allows path names of only 108 characters.
867 So chdir to the socket directory so that we can pass a
868 relative path name. */
869 if (chdir(dirOf(socketPath).c_str()) == -1)
870 throw SysError("cannot change current directory");
871 Path socketPathRel = "./" + baseNameOf(socketPath);
872
873 struct sockaddr_un addr;
874 addr.sun_family = AF_UNIX;
875 if (socketPathRel.size() >= sizeof(addr.sun_path))
876 throw Error(format("socket path `%1%' is too long") % socketPathRel);
877 strcpy(addr.sun_path, socketPathRel.c_str());
878
879 unlink(socketPath.c_str());
880
881 /* Make sure that the socket is created with 0666 permission
882 (everybody can connect --- provided they have access to the
883 directory containing the socket). */
884 mode_t oldMode = umask(0111);
885 int res = bind(fdSocket, (struct sockaddr *) &addr, sizeof(addr));
886 umask(oldMode);
887 if (res == -1)
888 throw SysError(format("cannot bind to socket `%1%'") % socketPath);
889
890 if (chdir("/") == -1) /* back to the root */
891 throw SysError("cannot change current directory");
892
893 if (listen(fdSocket, 5) == -1)
894 throw SysError(format("cannot listen on socket `%1%'") % socketPath);
895 }
896
897 closeOnExec(fdSocket);
898
899 /* Loop accepting connections. */
900 while (1) {
901
902 try {
903 /* Important: the server process *cannot* open the SQLite
904 database, because it doesn't like forks very much. */
905 assert(!store);
906
907 /* Accept a connection. */
908 struct sockaddr_un remoteAddr;
909 socklen_t remoteAddrLen = sizeof(remoteAddr);
910
911 AutoCloseFD remote = accept(fdSocket,
912 (struct sockaddr *) &remoteAddr, &remoteAddrLen);
913 checkInterrupt();
914 if (remote == -1) {
915 if (errno == EINTR)
916 continue;
917 else
918 throw SysError("accepting connection");
919 }
920
921 closeOnExec(remote);
922
923 bool trusted = false;
924 pid_t clientPid = -1;
925 819
820 try {
821 /* Important: the server process *cannot* open the SQLite
822 database, because it doesn't like forks very much. */
823 assert(!store);
824
825 /* Accept a connection. */
826 struct sockaddr_storage remoteAddr;
827 socklen_t remoteAddrLen = sizeof(remoteAddr);
828
829 try_again:
830 AutoCloseFD remote = accept(fdSocket,
831 (struct sockaddr *) &remoteAddr, &remoteAddrLen);
832 checkInterrupt();
833 if (remote == -1) {
834 if (errno == EINTR)
835 goto try_again;
836 else
837 throw SysError("accepting connection");
838 }
839
840 closeOnExec(remote);
841
842 pid_t clientPid = -1;
843 bool trusted = false;
844
845 /* Get the identity of the caller, if possible. */
846 if (remoteAddr.ss_family == AF_UNIX) {
926#if defined(SO_PEERCRED) 847#if defined(SO_PEERCRED)
927 /* Get the identity of the caller, if possible. */ 848 ucred cred;
928 ucred cred; 849 socklen_t credLen = sizeof(cred);
929 socklen_t credLen = sizeof(cred); 850 if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED,
930 if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1) 851 &cred, &credLen) == -1)
931 throw SysError("getting peer credentials"); 852 throw SysError("getting peer credentials");
932 853
933 clientPid = cred.pid; 854 clientPid = cred.pid;
855 clientUid = cred.uid;
856 clientGid = cred.gid;
857 trusted = clientUid == 0;
934 858
935 struct passwd * pw = getpwuid(cred.uid); 859 struct passwd * pw = getpwuid(cred.uid);
936 string user = pw ? pw->pw_name : std::to_string(cred.uid); 860 string user = pw ? pw->pw_name : std::to_string(cred.uid);
937 861
938 struct group * gr = getgrgid(cred.gid); 862 printMsg(lvlInfo,
939 string group = gr ? gr->gr_name : std::to_string(cred.gid); 863 format((string) "accepted connection from pid %1%, user %2%")
940 864 % clientPid % user);
941 Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
942 Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
943
944 if (matchUser(user, group, trustedUsers))
945 trusted = true;
946
947 if (!trusted && !matchUser(user, group, allowedUsers))
948 throw Error(format("user `%1%' is not allowed to connect to the Nix daemon") % user);
949
950 printMsg(lvlInfo, format((string) "accepted connection from pid %1%, user %2%"
951 + (trusted ? " (trusted)" : "")) % clientPid % user);
952#endif 865#endif
953 866 } else {
954 /* Fork a child to handle the connection. */ 867 char address_str[128];
955 startProcess([&]() { 868 const char *result;
956 fdSocket.close(); 869
870 if (remoteAddr.ss_family == AF_INET) {
871 struct sockaddr_in *addr = (struct sockaddr_in *) &remoteAddr;
872 struct in_addr inaddr = { addr->sin_addr };
873 result = inet_ntop(AF_INET, &inaddr,
874 address_str, sizeof address_str);
875 } else if (remoteAddr.ss_family == AF_INET6) {
876 struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &remoteAddr;
877 struct in6_addr inaddr = { addr->sin6_addr };
878 result = inet_ntop(AF_INET6, &inaddr,
879 address_str, sizeof address_str);
880 } else {
881 result = NULL;
882 }
883
884 if (result != NULL) {
885 printMsg(lvlInfo,
886 format("accepted connection from %1%")
887 % address_str);
888 }
889 }
890
891 /* Fork a child to handle the connection. */
892 startProcess([&]() {
893 close(fdSocket);
957 894
958 /* Background the daemon. */ 895 /* Background the daemon. */
959 if (setsid() == -1) 896 if (setsid() == -1)
@@ -968,17 +905,11 @@ static void daemonLoop()
968 strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1])); 905 strncpy(argvSaved[1], processName.c_str(), strlen(argvSaved[1]));
969 } 906 }
970 907
971#if defined(SO_PEERCRED)
972 /* Store the client's user and group for this connection. This 908 /* Store the client's user and group for this connection. This
973 has to be done in the forked process since it is per 909 has to be done in the forked process since it is per
974 connection. */ 910 connection. Setting these to -1 means: do not change. */
975 settings.clientUid = cred.uid; 911 settings.clientUid = clientUid;
976 settings.clientGid = cred.gid; 912 settings.clientGid = clientGid;
977#else
978 /* Setting these to -1 means: do not change */
979 settings.clientUid = (uid_t) -1;
980 settings.clientGid = (gid_t) -1;
981#endif
982 913
983 /* Handle the connection. */ 914 /* Handle the connection. */
984 from.fd = remote; 915 from.fd = remote;
@@ -988,23 +919,63 @@ static void daemonLoop()
988 exit(0); 919 exit(0);
989 }, false, "unexpected Nix daemon error: ", true); 920 }, false, "unexpected Nix daemon error: ", true);
990 921
991 } catch (Interrupted & e) { 922 } catch (Interrupted & e) {
992 throw; 923 throw;
993 } catch (Error & e) { 924 } catch (Error & e) {
994 printMsg(lvlError, format("error processing connection: %1%") % e.msg()); 925 printMsg(lvlError, format("error processing connection: %1%") % e.msg());
995 }
996 } 926 }
997} 927}
998 928
999 929static void daemonLoop(const std::vector<int>& sockets)
1000void run(Strings args)
1001{ 930{
1002 for (Strings::iterator i = args.begin(); i != args.end(); ) { 931 if (chdir("/") == -1)
1003 string arg = *i++; 932 throw SysError("cannot change current directory");
1004 if (arg == "--daemon") /* ignored for backwards compatibility */; 933
934 /* Get rid of children automatically; don't let them become
935 zombies. */
936 setSigChldAction(true);
937
938 /* Mark sockets as close-on-exec. */
939 for(int fd: sockets) {
940 closeOnExec(fd);
1005 } 941 }
1006 942
1007 daemonLoop(); 943 /* Prepare the FD set corresponding to SOCKETS. */
944 auto initializeFDSet = [&](fd_set *set) {
945 FD_ZERO(set);
946 for (int fd: sockets) {
947 FD_SET(fd, set);
948 }
949 };
950
951 /* Loop accepting connections. */
952 while (1) {
953 fd_set readfds;
954
955 initializeFDSet(&readfds);
956 int count =
957 select(*std::max_element(sockets.begin(), sockets.end()) + 1,
958 &readfds, NULL, NULL,
959 NULL);
960 if (count < 0) {
961 int err = errno;
962 if (err == EINTR)
963 continue;
964 throw SysError(format("select error: %1%") % strerror(err));
965 }
966
967 for (unsigned int i = 0; i < sockets.size(); i++) {
968 if (FD_ISSET(sockets[i], &readfds)) {
969 acceptConnection(sockets[i]);
970 }
971 }
972 }
973}
974
975
976void run(const std::vector<int>& sockets)
977{
978 daemonLoop(sockets);
1008} 979}
1009 980
1010 981