1 #include "shared.hh"
2 #include "local-store.hh"
3 #include "util.hh"
4 #include "serialise.hh"
5 #include "worker-protocol.hh"
6 #include "archive.hh"
7 #include "affinity.hh"
8 #include "globals.hh"
9 #include "monitor-fd.hh"
10 #include "derivations.hh"
11 #include "finally.hh"
12 #include "legacy.hh"
13
14 #include <algorithm>
15
16 #include <cstring>
17 #include <unistd.h>
18 #include <signal.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <sys/stat.h>
22 #include <sys/socket.h>
23 #include <sys/un.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <grp.h>
27 #include <fcntl.h>
28 #include <limits.h>
29
30 #if __APPLE__ || __FreeBSD__
31 #include <sys/ucred.h>
32 #endif
33
34 using namespace nix;
35
36 #ifndef __linux__
37 #define SPLICE_F_MOVE 0
splice(int fd_in,void * off_in,int fd_out,void * off_out,size_t len,unsigned int flags)38 static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
39 {
40 /* We ignore most parameters, we just have them for conformance with the linux syscall */
41 std::vector<char> buf(8192);
42 auto read_count = read(fd_in, buf.data(), buf.size());
43 if (read_count == -1)
44 return read_count;
45 auto write_count = decltype(read_count)(0);
46 while (write_count < read_count) {
47 auto res = write(fd_out, buf.data() + write_count, read_count - write_count);
48 if (res == -1)
49 return res;
50 write_count += res;
51 }
52 return read_count;
53 }
54 #endif
55
56 static FdSource from(STDIN_FILENO);
57 static FdSink to(STDOUT_FILENO);
58
59
operator <<(Sink & sink,const Logger::Fields & fields)60 Sink & operator << (Sink & sink, const Logger::Fields & fields)
61 {
62 sink << fields.size();
63 for (auto & f : fields) {
64 sink << f.type;
65 if (f.type == Logger::Field::tInt)
66 sink << f.i;
67 else if (f.type == Logger::Field::tString)
68 sink << f.s;
69 else abort();
70 }
71 return sink;
72 }
73
74
75 /* Logger that forwards log messages to the client, *if* we're in a
76 state where the protocol allows it (i.e., when canSendStderr is
77 true). */
78 struct TunnelLogger : public Logger
79 {
80 struct State
81 {
82 bool canSendStderr = false;
83 std::vector<std::string> pendingMsgs;
84 };
85
86 Sync<State> state_;
87
88 unsigned int clientVersion;
89
TunnelLoggerTunnelLogger90 TunnelLogger(unsigned int clientVersion) : clientVersion(clientVersion) { }
91
enqueueMsgTunnelLogger92 void enqueueMsg(const std::string & s)
93 {
94 auto state(state_.lock());
95
96 if (state->canSendStderr) {
97 assert(state->pendingMsgs.empty());
98 try {
99 to(s);
100 to.flush();
101 } catch (...) {
102 /* Write failed; that means that the other side is
103 gone. */
104 state->canSendStderr = false;
105 throw;
106 }
107 } else
108 state->pendingMsgs.push_back(s);
109 }
110
logTunnelLogger111 void log(Verbosity lvl, const FormatOrString & fs) override
112 {
113 if (lvl > verbosity) return;
114
115 StringSink buf;
116 buf << STDERR_NEXT << (fs.s + "\n");
117 enqueueMsg(*buf.s);
118 }
119
120 /* startWork() means that we're starting an operation for which we
121 want to send out stderr to the client. */
startWorkTunnelLogger122 void startWork()
123 {
124 auto state(state_.lock());
125 state->canSendStderr = true;
126
127 for (auto & msg : state->pendingMsgs)
128 to(msg);
129
130 state->pendingMsgs.clear();
131
132 to.flush();
133 }
134
135 /* stopWork() means that we're done; stop sending stderr to the
136 client. */
stopWorkTunnelLogger137 void stopWork(bool success = true, const string & msg = "", unsigned int status = 0)
138 {
139 auto state(state_.lock());
140
141 state->canSendStderr = false;
142
143 if (success)
144 to << STDERR_LAST;
145 else {
146 to << STDERR_ERROR << msg;
147 if (status != 0) to << status;
148 }
149 }
150
startActivityTunnelLogger151 void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
152 const std::string & s, const Fields & fields, ActivityId parent) override
153 {
154 if (GET_PROTOCOL_MINOR(clientVersion) < 20) {
155 if (!s.empty())
156 log(lvl, s + "...");
157 return;
158 }
159
160 StringSink buf;
161 buf << STDERR_START_ACTIVITY << act << lvl << type << s << fields << parent;
162 enqueueMsg(*buf.s);
163 }
164
stopActivityTunnelLogger165 void stopActivity(ActivityId act) override
166 {
167 if (GET_PROTOCOL_MINOR(clientVersion) < 20) return;
168 StringSink buf;
169 buf << STDERR_STOP_ACTIVITY << act;
170 enqueueMsg(*buf.s);
171 }
172
resultTunnelLogger173 void result(ActivityId act, ResultType type, const Fields & fields) override
174 {
175 if (GET_PROTOCOL_MINOR(clientVersion) < 20) return;
176 StringSink buf;
177 buf << STDERR_RESULT << act << type << fields;
178 enqueueMsg(*buf.s);
179 }
180 };
181
182
183 struct TunnelSink : Sink
184 {
185 Sink & to;
TunnelSinkTunnelSink186 TunnelSink(Sink & to) : to(to) { }
operator ()TunnelSink187 virtual void operator () (const unsigned char * data, size_t len)
188 {
189 to << STDERR_WRITE;
190 writeString(data, len, to);
191 }
192 };
193
194
195 struct TunnelSource : BufferedSource
196 {
197 Source & from;
TunnelSourceTunnelSource198 TunnelSource(Source & from) : from(from) { }
199 protected:
readUnbufferedTunnelSource200 size_t readUnbuffered(unsigned char * data, size_t len) override
201 {
202 to << STDERR_READ << len;
203 to.flush();
204 size_t n = readString(data, len, from);
205 if (n == 0) throw EndOfFile("unexpected end-of-file");
206 return n;
207 }
208 };
209
210
211 /* If the NAR archive contains a single file at top-level, then save
212 the contents of the file to `s'. Otherwise barf. */
213 struct RetrieveRegularNARSink : ParseSink
214 {
215 bool regular;
216 string s;
217
RetrieveRegularNARSinkRetrieveRegularNARSink218 RetrieveRegularNARSink() : regular(true) { }
219
createDirectoryRetrieveRegularNARSink220 void createDirectory(const Path & path)
221 {
222 regular = false;
223 }
224
receiveContentsRetrieveRegularNARSink225 void receiveContents(unsigned char * data, unsigned int len)
226 {
227 s.append((const char *) data, len);
228 }
229
createSymlinkRetrieveRegularNARSink230 void createSymlink(const Path & path, const string & target)
231 {
232 regular = false;
233 }
234 };
235
236
performOp(TunnelLogger * logger,ref<Store> store,bool trusted,unsigned int clientVersion,Source & from,Sink & to,unsigned int op)237 static void performOp(TunnelLogger * logger, ref<Store> store,
238 bool trusted, unsigned int clientVersion,
239 Source & from, Sink & to, unsigned int op)
240 {
241 switch (op) {
242
243 case wopIsValidPath: {
244 /* 'readStorePath' could raise an error leading to the connection
245 being closed. To be able to recover from an invalid path error,
246 call 'startWork' early, and do 'assertStorePath' afterwards so
247 that the 'Error' exception handler doesn't close the
248 connection. */
249 Path path = readString(from);
250 logger->startWork();
251 store->assertStorePath(path);
252 bool result = store->isValidPath(path);
253 logger->stopWork();
254 to << result;
255 break;
256 }
257
258 case wopQueryValidPaths: {
259 PathSet paths = readStorePaths<PathSet>(*store, from);
260 logger->startWork();
261 PathSet res = store->queryValidPaths(paths);
262 logger->stopWork();
263 to << res;
264 break;
265 }
266
267 case wopHasSubstitutes: {
268 Path path = readStorePath(*store, from);
269 logger->startWork();
270 PathSet res = store->querySubstitutablePaths({path});
271 logger->stopWork();
272 to << (res.find(path) != res.end());
273 break;
274 }
275
276 case wopQuerySubstitutablePaths: {
277 PathSet paths = readStorePaths<PathSet>(*store, from);
278 logger->startWork();
279 PathSet res = store->querySubstitutablePaths(paths);
280 logger->stopWork();
281 to << res;
282 break;
283 }
284
285 case wopQueryPathHash: {
286 Path path = readStorePath(*store, from);
287 logger->startWork();
288 auto hash = store->queryPathInfo(path)->narHash;
289 logger->stopWork();
290 to << hash.to_string(Base16, false);
291 break;
292 }
293
294 case wopQueryReferences:
295 case wopQueryReferrers:
296 case wopQueryValidDerivers:
297 case wopQueryDerivationOutputs: {
298 Path path = readStorePath(*store, from);
299 logger->startWork();
300 PathSet paths;
301 if (op == wopQueryReferences)
302 paths = store->queryPathInfo(path)->references;
303 else if (op == wopQueryReferrers)
304 store->queryReferrers(path, paths);
305 else if (op == wopQueryValidDerivers)
306 paths = store->queryValidDerivers(path);
307 else paths = store->queryDerivationOutputs(path);
308 logger->stopWork();
309 to << paths;
310 break;
311 }
312
313 case wopQueryDerivationOutputNames: {
314 Path path = readStorePath(*store, from);
315 logger->startWork();
316 StringSet names;
317 names = store->queryDerivationOutputNames(path);
318 logger->stopWork();
319 to << names;
320 break;
321 }
322
323 case wopQueryDeriver: {
324 Path path = readStorePath(*store, from);
325 logger->startWork();
326 auto deriver = store->queryPathInfo(path)->deriver;
327 logger->stopWork();
328 to << deriver;
329 break;
330 }
331
332 case wopQueryPathFromHashPart: {
333 string hashPart = readString(from);
334 logger->startWork();
335 Path path = store->queryPathFromHashPart(hashPart);
336 logger->stopWork();
337 to << path;
338 break;
339 }
340
341 case wopAddToStore: {
342 bool fixed, recursive;
343 std::string s, baseName;
344 from >> baseName >> fixed /* obsolete */ >> recursive >> s;
345 /* Compatibility hack. */
346 if (!fixed) {
347 s = "sha256";
348 recursive = true;
349 }
350 HashType hashAlgo = parseHashType(s);
351
352 TeeSource savedNAR(from);
353 RetrieveRegularNARSink savedRegular;
354
355 if (recursive) {
356 /* Get the entire NAR dump from the client and save it to
357 a string so that we can pass it to
358 addToStoreFromDump(). */
359 ParseSink sink; /* null sink; just parse the NAR */
360 parseDump(sink, savedNAR);
361 } else
362 parseDump(savedRegular, from);
363
364 logger->startWork();
365 if (!savedRegular.regular) throw Error("regular file expected");
366
367 auto store2 = store.dynamic_pointer_cast<LocalStore>();
368 if (!store2) throw Error("operation is only supported by LocalStore");
369
370 Path path = store2->addToStoreFromDump(recursive ? *savedNAR.data : savedRegular.s, baseName, recursive, hashAlgo);
371 logger->stopWork();
372
373 to << path;
374 break;
375 }
376
377 case wopAddTextToStore: {
378 string suffix = readString(from);
379 string s = readString(from);
380 PathSet refs = readStorePaths<PathSet>(*store, from);
381 logger->startWork();
382 Path path = store->addTextToStore(suffix, s, refs, NoRepair);
383 logger->stopWork();
384 to << path;
385 break;
386 }
387
388 case wopExportPath: {
389 Path path = readStorePath(*store, from);
390 readInt(from); // obsolete
391 logger->startWork();
392 TunnelSink sink(to);
393 store->exportPath(path, sink);
394 logger->stopWork();
395 to << 1;
396 break;
397 }
398
399 case wopImportPaths: {
400 logger->startWork();
401 TunnelSource source(from);
402 Paths paths = store->importPaths(source, nullptr,
403 trusted ? NoCheckSigs : CheckSigs);
404 logger->stopWork();
405 to << paths;
406 break;
407 }
408
409 case wopBuildPaths: {
410 PathSet drvs = readStorePaths<PathSet>(*store, from);
411 BuildMode mode = bmNormal;
412 if (GET_PROTOCOL_MINOR(clientVersion) >= 15) {
413 mode = (BuildMode) readInt(from);
414
415 /* Repairing is not atomic, so disallowed for "untrusted"
416 clients. */
417 if (mode == bmRepair && !trusted)
418 throw Error("repairing is not allowed because you are not in 'trusted-users'");
419 }
420 logger->startWork();
421 store->buildPaths(drvs, mode);
422 logger->stopWork();
423 to << 1;
424 break;
425 }
426
427 case wopBuildDerivation: {
428 Path drvPath = readStorePath(*store, from);
429 BasicDerivation drv;
430 readDerivation(from, *store, drv);
431 BuildMode buildMode = (BuildMode) readInt(from);
432 logger->startWork();
433 if (!trusted)
434 throw Error("you are not privileged to build derivations");
435 auto res = store->buildDerivation(drvPath, drv, buildMode);
436 logger->stopWork();
437 to << res.status << res.errorMsg;
438 break;
439 }
440
441 case wopEnsurePath: {
442 Path path = readStorePath(*store, from);
443 logger->startWork();
444 store->ensurePath(path);
445 logger->stopWork();
446 to << 1;
447 break;
448 }
449
450 case wopAddTempRoot: {
451 Path path = readStorePath(*store, from);
452 logger->startWork();
453 store->addTempRoot(path);
454 logger->stopWork();
455 to << 1;
456 break;
457 }
458
459 case wopAddIndirectRoot: {
460 Path path = absPath(readString(from));
461 logger->startWork();
462 store->addIndirectRoot(path);
463 logger->stopWork();
464 to << 1;
465 break;
466 }
467
468 case wopSyncWithGC: {
469 logger->startWork();
470 store->syncWithGC();
471 logger->stopWork();
472 to << 1;
473 break;
474 }
475
476 case wopFindRoots: {
477 logger->startWork();
478 Roots roots = store->findRoots(!trusted);
479 logger->stopWork();
480
481 size_t size = 0;
482 for (auto & i : roots)
483 size += i.second.size();
484
485 to << size;
486
487 for (auto & [target, links] : roots)
488 for (auto & link : links)
489 to << link << target;
490
491 break;
492 }
493
494 case wopCollectGarbage: {
495 GCOptions options;
496 options.action = (GCOptions::GCAction) readInt(from);
497 options.pathsToDelete = readStorePaths<PathSet>(*store, from);
498 from >> options.ignoreLiveness >> options.maxFreed;
499 // obsolete fields
500 readInt(from);
501 readInt(from);
502 readInt(from);
503
504 GCResults results;
505
506 logger->startWork();
507 if (options.ignoreLiveness)
508 throw Error("you are not allowed to ignore liveness");
509 store->collectGarbage(options, results);
510 logger->stopWork();
511
512 to << results.paths << results.bytesFreed << 0 /* obsolete */;
513
514 break;
515 }
516
517 case wopSetOptions: {
518 settings.keepFailed = readInt(from);
519 settings.keepGoing = readInt(from);
520 settings.tryFallback = readInt(from);
521 verbosity = (Verbosity) readInt(from);
522 settings.maxBuildJobs.assign(readInt(from));
523 settings.maxSilentTime = readInt(from);
524 readInt(from); // obsolete useBuildHook
525 settings.verboseBuild = lvlError == (Verbosity) readInt(from);
526 readInt(from); // obsolete logType
527 readInt(from); // obsolete printBuildTrace
528 settings.buildCores = readInt(from);
529 settings.useSubstitutes = readInt(from);
530
531 StringMap overrides;
532 if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
533 unsigned int n = readInt(from);
534 for (unsigned int i = 0; i < n; i++) {
535 string name = readString(from);
536 string value = readString(from);
537 overrides.emplace(name, value);
538 }
539 }
540
541 logger->startWork();
542
543 for (auto & i : overrides) {
544 auto & name(i.first);
545 auto & value(i.second);
546
547 auto setSubstituters = [&](Setting<Strings> & res) {
548 if (name != res.name && res.aliases.count(name) == 0)
549 return false;
550 StringSet trusted = settings.trustedSubstituters;
551 for (auto & s : settings.substituters.get())
552 trusted.insert(s);
553 Strings subs;
554 auto ss = tokenizeString<Strings>(value);
555 for (auto & s : ss)
556 if (trusted.count(s))
557 subs.push_back(s);
558 else
559 warn("ignoring untrusted substituter '%s'", s);
560 res = subs;
561 return true;
562 };
563
564 try {
565 if (name == "ssh-auth-sock") // obsolete
566 ;
567 else if (trusted
568 || name == settings.buildTimeout.name
569 || name == "connect-timeout"
570 || (name == "builders" && value == ""))
571 settings.set(name, value);
572 else if (setSubstituters(settings.substituters))
573 ;
574 else if (setSubstituters(settings.extraSubstituters))
575 ;
576 else
577 warn("ignoring the user-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
578 } catch (UsageError & e) {
579 warn(e.what());
580 }
581 }
582
583 logger->stopWork();
584 break;
585 }
586
587 case wopQuerySubstitutablePathInfo: {
588 Path path = absPath(readString(from));
589 logger->startWork();
590 SubstitutablePathInfos infos;
591 store->querySubstitutablePathInfos({path}, infos);
592 logger->stopWork();
593 SubstitutablePathInfos::iterator i = infos.find(path);
594 if (i == infos.end())
595 to << 0;
596 else {
597 to << 1 << i->second.deriver << i->second.references << i->second.downloadSize << i->second.narSize;
598 }
599 break;
600 }
601
602 case wopQuerySubstitutablePathInfos: {
603 PathSet paths = readStorePaths<PathSet>(*store, from);
604 logger->startWork();
605 SubstitutablePathInfos infos;
606 store->querySubstitutablePathInfos(paths, infos);
607 logger->stopWork();
608 to << infos.size();
609 for (auto & i : infos) {
610 to << i.first << i.second.deriver << i.second.references
611 << i.second.downloadSize << i.second.narSize;
612 }
613 break;
614 }
615
616 case wopQueryAllValidPaths: {
617 logger->startWork();
618 PathSet paths = store->queryAllValidPaths();
619 logger->stopWork();
620 to << paths;
621 break;
622 }
623
624 case wopQueryPathInfo: {
625 Path path = readStorePath(*store, from);
626 std::shared_ptr<const ValidPathInfo> info;
627 logger->startWork();
628 try {
629 info = store->queryPathInfo(path);
630 } catch (InvalidPath &) {
631 if (GET_PROTOCOL_MINOR(clientVersion) < 17) throw;
632 }
633 logger->stopWork();
634 if (info) {
635 if (GET_PROTOCOL_MINOR(clientVersion) >= 17)
636 to << 1;
637 to << info->deriver << info->narHash.to_string(Base16, false) << info->references
638 << info->registrationTime << info->narSize;
639 if (GET_PROTOCOL_MINOR(clientVersion) >= 16) {
640 to << info->ultimate
641 << info->sigs
642 << info->ca;
643 }
644 } else {
645 assert(GET_PROTOCOL_MINOR(clientVersion) >= 17);
646 to << 0;
647 }
648 break;
649 }
650
651 case wopOptimiseStore:
652 logger->startWork();
653 store->optimiseStore();
654 logger->stopWork();
655 to << 1;
656 break;
657
658 case wopVerifyStore: {
659 bool checkContents, repair;
660 from >> checkContents >> repair;
661 logger->startWork();
662 if (repair && !trusted)
663 throw Error("you are not privileged to repair paths");
664 bool errors = store->verifyStore(checkContents, (RepairFlag) repair);
665 logger->stopWork();
666 to << errors;
667 break;
668 }
669
670 case wopAddSignatures: {
671 Path path = readStorePath(*store, from);
672 StringSet sigs = readStrings<StringSet>(from);
673 logger->startWork();
674 if (!trusted)
675 throw Error("you are not privileged to add signatures");
676 store->addSignatures(path, sigs);
677 logger->stopWork();
678 to << 1;
679 break;
680 }
681
682 case wopNarFromPath: {
683 auto path = readStorePath(*store, from);
684 logger->startWork();
685 logger->stopWork();
686 dumpPath(path, to);
687 break;
688 }
689
690 case wopAddToStoreNar: {
691 bool repair, dontCheckSigs;
692 ValidPathInfo info;
693 info.path = readStorePath(*store, from);
694 from >> info.deriver;
695 if (!info.deriver.empty())
696 store->assertStorePath(info.deriver);
697 info.narHash = Hash(readString(from), htSHA256);
698 info.references = readStorePaths<PathSet>(*store, from);
699 from >> info.registrationTime >> info.narSize >> info.ultimate;
700 info.sigs = readStrings<StringSet>(from);
701 from >> info.ca >> repair >> dontCheckSigs;
702 if (!trusted && dontCheckSigs)
703 dontCheckSigs = false;
704 if (!trusted)
705 info.ultimate = false;
706
707 std::string saved;
708 std::unique_ptr<Source> source;
709 if (GET_PROTOCOL_MINOR(clientVersion) >= 21)
710 source = std::make_unique<TunnelSource>(from);
711 else {
712 TeeSink tee(from);
713 parseDump(tee, tee.source);
714 saved = std::move(*tee.source.data);
715 source = std::make_unique<StringSource>(saved);
716 }
717
718 logger->startWork();
719
720 // FIXME: race if addToStore doesn't read source?
721 store->addToStore(info, *source, (RepairFlag) repair,
722 dontCheckSigs ? NoCheckSigs : CheckSigs, nullptr);
723
724 logger->stopWork();
725 break;
726 }
727
728 case wopQueryMissing: {
729 PathSet targets = readStorePaths<PathSet>(*store, from);
730 logger->startWork();
731 PathSet willBuild, willSubstitute, unknown;
732 unsigned long long downloadSize, narSize;
733 store->queryMissing(targets, willBuild, willSubstitute, unknown, downloadSize, narSize);
734 logger->stopWork();
735 to << willBuild << willSubstitute << unknown << downloadSize << narSize;
736 break;
737 }
738
739 default:
740 throw Error(format("invalid operation %1%") % op);
741 }
742 }
743
744
processConnection(bool trusted,const std::string & userName,uid_t userId)745 static void processConnection(bool trusted,
746 const std::string & userName, uid_t userId)
747 {
748 MonitorFdHup monitor(from.fd);
749
750 /* Exchange the greeting. */
751 unsigned int magic = readInt(from);
752 if (magic != WORKER_MAGIC_1) throw Error("protocol mismatch");
753 to << WORKER_MAGIC_2 << PROTOCOL_VERSION;
754 to.flush();
755 unsigned int clientVersion = readInt(from);
756
757 if (clientVersion < 0x10a)
758 throw Error("the Nix client version is too old");
759
760 auto tunnelLogger = new TunnelLogger(clientVersion);
761 auto prevLogger = nix::logger;
762 logger = tunnelLogger;
763
764 unsigned int opCount = 0;
765
766 Finally finally([&]() {
767 _isInterrupted = false;
768 prevLogger->log(lvlDebug, fmt("%d operations", opCount));
769 });
770
771 if (GET_PROTOCOL_MINOR(clientVersion) >= 14 && readInt(from))
772 setAffinityTo(readInt(from));
773
774 readInt(from); // obsolete reserveSpace
775
776 /* Send startup error messages to the client. */
777 tunnelLogger->startWork();
778
779 try {
780
781 /* If we can't accept clientVersion, then throw an error
782 *here* (not above). */
783
784 #if 0
785 /* Prevent users from doing something very dangerous. */
786 if (geteuid() == 0 &&
787 querySetting("build-users-group", "") == "")
788 throw Error("if you run 'nix-daemon' as root, then you MUST set 'build-users-group'!");
789 #endif
790
791 /* Open the store. */
792 Store::Params params; // FIXME: get params from somewhere
793 // Disable caching since the client already does that.
794 params["path-info-cache-size"] = "0";
795 auto store = openStore(settings.storeUri, params);
796
797 store->createUser(userName, userId);
798
799 tunnelLogger->stopWork();
800 to.flush();
801
802 /* Process client requests. */
803 while (true) {
804 WorkerOp op;
805 try {
806 op = (WorkerOp) readInt(from);
807 } catch (Interrupted & e) {
808 break;
809 } catch (EndOfFile & e) {
810 break;
811 }
812
813 opCount++;
814
815 try {
816 performOp(tunnelLogger, store, trusted, clientVersion, from, to, op);
817 } catch (Error & e) {
818 /* If we're not in a state where we can send replies, then
819 something went wrong processing the input of the
820 client. This can happen especially if I/O errors occur
821 during addTextToStore() / importPath(). If that
822 happens, just send the error message and exit. */
823 bool errorAllowed = tunnelLogger->state_.lock()->canSendStderr;
824 tunnelLogger->stopWork(false, e.msg(), e.status);
825 if (!errorAllowed) throw;
826 } catch (std::bad_alloc & e) {
827 tunnelLogger->stopWork(false, "Nix daemon out of memory", 1);
828 throw;
829 }
830
831 to.flush();
832
833 assert(!tunnelLogger->state_.lock()->canSendStderr);
834 };
835
836 } catch (std::exception & e) {
837 tunnelLogger->stopWork(false, e.what(), 1);
838 to.flush();
839 return;
840 }
841 }
842
843
sigChldHandler(int sigNo)844 static void sigChldHandler(int sigNo)
845 {
846 // Ensure we don't modify errno of whatever we've interrupted
847 auto saved_errno = errno;
848 /* Reap all dead children. */
849 while (waitpid(-1, 0, WNOHANG) > 0) ;
850 errno = saved_errno;
851 }
852
853
setSigChldAction(bool autoReap)854 static void setSigChldAction(bool autoReap)
855 {
856 struct sigaction act, oact;
857 act.sa_handler = autoReap ? sigChldHandler : SIG_DFL;
858 sigfillset(&act.sa_mask);
859 act.sa_flags = 0;
860 if (sigaction(SIGCHLD, &act, &oact))
861 throw SysError("setting SIGCHLD handler");
862 }
863
864
matchUser(const string & user,const string & group,const Strings & users)865 bool matchUser(const string & user, const string & group, const Strings & users)
866 {
867 if (find(users.begin(), users.end(), "*") != users.end())
868 return true;
869
870 if (find(users.begin(), users.end(), user) != users.end())
871 return true;
872
873 for (auto & i : users)
874 if (string(i, 0, 1) == "@") {
875 if (group == string(i, 1)) return true;
876 struct group * gr = getgrnam(i.c_str() + 1);
877 if (!gr) continue;
878 for (char * * mem = gr->gr_mem; *mem; mem++)
879 if (user == string(*mem)) return true;
880 }
881
882 return false;
883 }
884
885
886 struct PeerInfo
887 {
888 bool pidKnown;
889 pid_t pid;
890 bool uidKnown;
891 uid_t uid;
892 bool gidKnown;
893 gid_t gid;
894 };
895
896
897 /* Get the identity of the caller, if possible. */
getPeerInfo(int remote)898 static PeerInfo getPeerInfo(int remote)
899 {
900 PeerInfo peer = { false, 0, false, 0, false, 0 };
901
902 #if defined(SO_PEERCRED)
903
904 ucred cred;
905 socklen_t credLen = sizeof(cred);
906 if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == -1)
907 throw SysError("getting peer credentials");
908 peer = { true, cred.pid, true, cred.uid, true, cred.gid };
909
910 #elif defined(LOCAL_PEERCRED)
911
912 #if !defined(SOL_LOCAL)
913 #define SOL_LOCAL 0
914 #endif
915
916 xucred cred;
917 socklen_t credLen = sizeof(cred);
918 if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == -1)
919 throw SysError("getting peer credentials");
920 peer = { false, 0, true, cred.cr_uid, false, 0 };
921
922 #endif
923
924 return peer;
925 }
926
927
928 #define SD_LISTEN_FDS_START 3
929
930
daemonLoop(char ** argv)931 static void daemonLoop(char * * argv)
932 {
933 if (chdir("/") == -1)
934 throw SysError("cannot change current directory");
935
936 /* Get rid of children automatically; don't let them become
937 zombies. */
938 setSigChldAction(true);
939
940 AutoCloseFD fdSocket;
941
942 /* Handle socket-based activation by systemd. */
943 if (getEnv("LISTEN_FDS") != "") {
944 if (getEnv("LISTEN_PID") != std::to_string(getpid()) || getEnv("LISTEN_FDS") != "1")
945 throw Error("unexpected systemd environment variables");
946 fdSocket = SD_LISTEN_FDS_START;
947 }
948
949 /* Otherwise, create and bind to a Unix domain socket. */
950 else {
951
952 /* Create and bind to a Unix domain socket. */
953 fdSocket = socket(PF_UNIX, SOCK_STREAM, 0);
954 if (!fdSocket)
955 throw SysError("cannot create Unix domain socket");
956
957 string socketPath = settings.nixDaemonSocketFile;
958
959 createDirs(dirOf(socketPath));
960
961 /* Urgh, sockaddr_un allows path names of only 108 characters.
962 So chdir to the socket directory so that we can pass a
963 relative path name. */
964 if (chdir(dirOf(socketPath).c_str()) == -1)
965 throw SysError("cannot change current directory");
966 Path socketPathRel = "./" + baseNameOf(socketPath);
967
968 struct sockaddr_un addr;
969 addr.sun_family = AF_UNIX;
970 if (socketPathRel.size() + 1 >= sizeof(addr.sun_path))
971 throw Error(format("socket path '%1%' is too long") % socketPathRel);
972 strcpy(addr.sun_path, socketPathRel.c_str());
973
974 unlink(socketPath.c_str());
975
976 /* Make sure that the socket is created with 0666 permission
977 (everybody can connect --- provided they have access to the
978 directory containing the socket). */
979 mode_t oldMode = umask(0111);
980 int res = bind(fdSocket.get(), (struct sockaddr *) &addr, sizeof(addr));
981 umask(oldMode);
982 if (res == -1)
983 throw SysError(format("cannot bind to socket '%1%'") % socketPath);
984
985 if (chdir("/") == -1) /* back to the root */
986 throw SysError("cannot change current directory");
987
988 if (listen(fdSocket.get(), 5) == -1)
989 throw SysError(format("cannot listen on socket '%1%'") % socketPath);
990 }
991
992 closeOnExec(fdSocket.get());
993
994 /* Loop accepting connections. */
995 while (1) {
996
997 try {
998 /* Accept a connection. */
999 struct sockaddr_un remoteAddr;
1000 socklen_t remoteAddrLen = sizeof(remoteAddr);
1001
1002 AutoCloseFD remote = accept(fdSocket.get(),
1003 (struct sockaddr *) &remoteAddr, &remoteAddrLen);
1004 checkInterrupt();
1005 if (!remote) {
1006 if (errno == EINTR) continue;
1007 throw SysError("accepting connection");
1008 }
1009
1010 closeOnExec(remote.get());
1011
1012 bool trusted = false;
1013 PeerInfo peer = getPeerInfo(remote.get());
1014
1015 struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
1016 string user = pw ? pw->pw_name : std::to_string(peer.uid);
1017
1018 struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
1019 string group = gr ? gr->gr_name : std::to_string(peer.gid);
1020
1021 Strings trustedUsers = settings.trustedUsers;
1022 Strings allowedUsers = settings.allowedUsers;
1023
1024 if (matchUser(user, group, trustedUsers))
1025 trusted = true;
1026
1027 if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
1028 throw Error(format("user '%1%' is not allowed to connect to the Nix daemon") % user);
1029
1030 printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""))
1031 % (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>")
1032 % (peer.uidKnown ? user : "<unknown>"));
1033
1034 /* Fork a child to handle the connection. */
1035 ProcessOptions options;
1036 options.errorPrefix = "unexpected Nix daemon error: ";
1037 options.dieWithParent = false;
1038 options.runExitHandlers = true;
1039 options.allowVfork = false;
1040 startProcess([&]() {
1041 fdSocket = -1;
1042
1043 /* Background the daemon. */
1044 if (setsid() == -1)
1045 throw SysError(format("creating a new session"));
1046
1047 /* Restore normal handling of SIGCHLD. */
1048 setSigChldAction(false);
1049
1050 /* For debugging, stuff the pid into argv[1]. */
1051 if (peer.pidKnown && argv[1]) {
1052 string processName = std::to_string(peer.pid);
1053 strncpy(argv[1], processName.c_str(), strlen(argv[1]));
1054 }
1055
1056 /* Handle the connection. */
1057 from.fd = remote.get();
1058 to.fd = remote.get();
1059 processConnection(trusted, user, peer.uid);
1060
1061 exit(0);
1062 }, options);
1063
1064 } catch (Interrupted & e) {
1065 return;
1066 } catch (Error & e) {
1067 printError(format("error processing connection: %1%") % e.msg());
1068 }
1069 }
1070 }
1071
1072
_main(int argc,char ** argv)1073 static int _main(int argc, char * * argv)
1074 {
1075 {
1076 auto stdio = false;
1077
1078 parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
1079 if (*arg == "--daemon")
1080 ; /* ignored for backwards compatibility */
1081 else if (*arg == "--help")
1082 showManPage("nix-daemon");
1083 else if (*arg == "--version")
1084 printVersion("nix-daemon");
1085 else if (*arg == "--stdio")
1086 stdio = true;
1087 else return false;
1088 return true;
1089 });
1090
1091 initPlugins();
1092
1093 if (stdio) {
1094 if (getStoreType() == tDaemon) {
1095 /* Forward on this connection to the real daemon */
1096 auto socketPath = settings.nixDaemonSocketFile;
1097 auto s = socket(PF_UNIX, SOCK_STREAM, 0);
1098 if (s == -1)
1099 throw SysError("creating Unix domain socket");
1100
1101 auto socketDir = dirOf(socketPath);
1102 if (chdir(socketDir.c_str()) == -1)
1103 throw SysError(format("changing to socket directory '%1%'") % socketDir);
1104
1105 auto socketName = baseNameOf(socketPath);
1106 auto addr = sockaddr_un{};
1107 addr.sun_family = AF_UNIX;
1108 if (socketName.size() + 1 >= sizeof(addr.sun_path))
1109 throw Error(format("socket name %1% is too long") % socketName);
1110 strcpy(addr.sun_path, socketName.c_str());
1111
1112 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) == -1)
1113 throw SysError(format("cannot connect to daemon at %1%") % socketPath);
1114
1115 auto nfds = (s > STDIN_FILENO ? s : STDIN_FILENO) + 1;
1116 while (true) {
1117 fd_set fds;
1118 FD_ZERO(&fds);
1119 FD_SET(s, &fds);
1120 FD_SET(STDIN_FILENO, &fds);
1121 if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
1122 throw SysError("waiting for data from client or server");
1123 if (FD_ISSET(s, &fds)) {
1124 auto res = splice(s, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
1125 if (res == -1)
1126 throw SysError("splicing data from daemon socket to stdout");
1127 else if (res == 0)
1128 throw EndOfFile("unexpected EOF from daemon socket");
1129 }
1130 if (FD_ISSET(STDIN_FILENO, &fds)) {
1131 auto res = splice(STDIN_FILENO, nullptr, s, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
1132 if (res == -1)
1133 throw SysError("splicing data from stdin to daemon socket");
1134 else if (res == 0)
1135 return 0;
1136 }
1137 }
1138 } else {
1139 processConnection(true, "root", 0);
1140 }
1141 } else {
1142 daemonLoop(argv);
1143 }
1144
1145 return 0;
1146 }
1147 }
1148
1149 static RegisterLegacyCommand s1("nix-daemon", _main);
1150