xref: /haiku/src/servers/launch/LaunchDaemon.cpp (revision e51a20af4f59f029df31fe7e82a167a6daa423f5)
1 /*
2  * Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <map>
8 #include <set>
9 
10 #include <errno.h>
11 #include <grp.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 
16 #include <Directory.h>
17 #include <driver_settings.h>
18 #include <Entry.h>
19 #include <File.h>
20 #include <ObjectList.h>
21 #include <Path.h>
22 #include <PathFinder.h>
23 #include <Server.h>
24 
25 #include <AppMisc.h>
26 #include <DriverSettingsMessageAdapter.h>
27 #include <LaunchDaemonDefs.h>
28 #include <LaunchRosterPrivate.h>
29 #include <RosterPrivate.h>
30 #include <syscalls.h>
31 
32 #include "multiuser_utils.h"
33 
34 #include "InitRealTimeClockJob.h"
35 #include "InitSharedMemoryDirectoryJob.h"
36 #include "InitTemporaryDirectoryJob.h"
37 #include "Job.h"
38 #include "Target.h"
39 #include "Worker.h"
40 
41 
42 using namespace ::BPrivate;
43 using namespace BSupportKit;
44 using BSupportKit::BPrivate::JobQueue;
45 
46 
47 static const char* kLaunchDirectory = "launch";
48 
49 
50 const static settings_template kPortTemplate[] = {
51 	{B_STRING_TYPE, "name", NULL, true},
52 	{B_INT32_TYPE, "capacity", NULL},
53 };
54 
55 const static settings_template kJobTemplate[] = {
56 	{B_STRING_TYPE, "name", NULL, true},
57 	{B_BOOL_TYPE, "disabled", NULL},
58 	{B_STRING_TYPE, "launch", NULL},
59 	{B_STRING_TYPE, "requires", NULL},
60 	{B_BOOL_TYPE, "legacy", NULL},
61 	{B_MESSAGE_TYPE, "port", kPortTemplate},
62 	{B_BOOL_TYPE, "no_safemode", NULL},
63 	{0, NULL, NULL}
64 };
65 
66 const static settings_template kTargetTemplate[] = {
67 	{B_STRING_TYPE, "name", NULL, true},
68 	{B_BOOL_TYPE, "reset", NULL},
69 	{B_MESSAGE_TYPE, "job", kJobTemplate},
70 	{B_MESSAGE_TYPE, "service", kJobTemplate},
71 	{0, NULL, NULL}
72 };
73 
74 const static settings_template kSettingsTemplate[] = {
75 	{B_MESSAGE_TYPE, "target", kTargetTemplate},
76 	{B_MESSAGE_TYPE, "job", kJobTemplate},
77 	{B_MESSAGE_TYPE, "service", kJobTemplate},
78 	{0, NULL, NULL}
79 };
80 
81 
82 class Session {
83 public:
84 								Session(uid_t user, const BMessenger& target);
85 
86 			uid_t				User() const
87 									{ return fUser; }
88 			const BMessenger&	Daemon() const
89 									{ return fDaemon; }
90 
91 private:
92 			uid_t				fUser;
93 			BMessenger			fDaemon;
94 };
95 
96 
97 typedef std::map<BString, Job*> JobMap;
98 typedef std::map<uid_t, Session*> SessionMap;
99 typedef std::map<BString, Target*> TargetMap;
100 
101 
102 class LaunchDaemon : public BServer, public Finder {
103 public:
104 								LaunchDaemon(bool userMode, status_t& error);
105 	virtual						~LaunchDaemon();
106 
107 	virtual	Job*				FindJob(const char* name) const;
108 	virtual	Target*				FindTarget(const char* name) const;
109 			Session*			FindSession(uid_t user) const;
110 
111 	virtual	void				ReadyToRun();
112 	virtual	void				MessageReceived(BMessage* message);
113 
114 private:
115 			uid_t				_GetUserID(BMessage* message);
116 
117 			void				_ReadPaths(const BStringList& paths);
118 			void				_ReadEntry(const char* context, BEntry& entry);
119 			void				_ReadDirectory(const char* context,
120 									BEntry& directory);
121 			status_t			_ReadFile(const char* context, BEntry& entry);
122 
123 			void				_AddJobs(Target* target, BMessage& message);
124 			void				_AddJob(Target* target, bool service,
125 									BMessage& message);
126 			void				_InitJobs();
127 			void				_LaunchJobs(Target* target);
128 			void				_AddLaunchJob(Job* job);
129 			void				_AddTarget(Target* target);
130 
131 			status_t			_StartSession(const char* login,
132 									const char* password);
133 
134 			void				_RetrieveKernelOptions();
135 			void				_SetupEnvironment();
136 			void				_InitSystem();
137 			void				_AddInitJob(BJob* job);
138 
139 			bool				_IsSafeMode() const;
140 
141 private:
142 			JobMap				fJobs;
143 			TargetMap			fTargets;
144 			JobQueue			fJobQueue;
145 			SessionMap			fSessions;
146 			MainWorker*			fMainWorker;
147 			Target*				fInitTarget;
148 			bool				fSafeMode;
149 			bool				fUserMode;
150 };
151 
152 
153 static const char*
154 get_leaf(const char* signature)
155 {
156 	const char* separator = strrchr(signature, '/');
157 	if (separator != NULL)
158 		return separator + 1;
159 
160 	return signature;
161 }
162 
163 
164 // #pragma mark -
165 
166 
167 Session::Session(uid_t user, const BMessenger& daemon)
168 	:
169 	fUser(user),
170 	fDaemon(daemon)
171 {
172 }
173 
174 
175 // #pragma mark -
176 
177 
178 LaunchDaemon::LaunchDaemon(bool userMode, status_t& error)
179 	:
180 	BServer(kLaunchDaemonSignature, NULL,
181 		create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
182 			userMode ? "AppPort" : B_LAUNCH_DAEMON_PORT_NAME), false, &error),
183 	fInitTarget(userMode ? NULL : new Target("init")),
184 	fUserMode(userMode)
185 {
186 	fMainWorker = new MainWorker(fJobQueue);
187 	if (fInitTarget != NULL)
188 		_AddTarget(fInitTarget);
189 
190 	// We may not be able to talk to the registrar
191 	if (!fUserMode)
192 		BRoster::Private().SetWithoutRegistrar(true);
193 }
194 
195 
196 LaunchDaemon::~LaunchDaemon()
197 {
198 }
199 
200 
201 Job*
202 LaunchDaemon::FindJob(const char* name) const
203 {
204 	if (name == NULL)
205 		return NULL;
206 
207 	JobMap::const_iterator found = fJobs.find(BString(name).ToLower());
208 	if (found != fJobs.end())
209 		return found->second;
210 
211 	return NULL;
212 }
213 
214 
215 Target*
216 LaunchDaemon::FindTarget(const char* name) const
217 {
218 	if (name == NULL)
219 		return NULL;
220 
221 	TargetMap::const_iterator found = fTargets.find(BString(name).ToLower());
222 	if (found != fTargets.end())
223 		return found->second;
224 
225 	return NULL;
226 }
227 
228 
229 Session*
230 LaunchDaemon::FindSession(uid_t user) const
231 {
232 	SessionMap::const_iterator found = fSessions.find(user);
233 	if (found != fSessions.end())
234 		return found->second;
235 
236 	return NULL;
237 }
238 
239 
240 void
241 LaunchDaemon::ReadyToRun()
242 {
243 	_RetrieveKernelOptions();
244 	_SetupEnvironment();
245 	if (fUserMode) {
246 		BLaunchRoster roster;
247 		BLaunchRoster::Private(roster).RegisterSessionDaemon(this);
248 	} else
249 		_InitSystem();
250 
251 	BStringList paths;
252 	BPathFinder::FindPaths(B_FIND_PATH_DATA_DIRECTORY, kLaunchDirectory,
253 		fUserMode ? B_FIND_PATHS_USER_ONLY : B_FIND_PATHS_SYSTEM_ONLY, paths);
254 	_ReadPaths(paths);
255 
256 	BPathFinder::FindPaths(B_FIND_PATH_SETTINGS_DIRECTORY, kLaunchDirectory,
257 		fUserMode ? B_FIND_PATHS_USER_ONLY : B_FIND_PATHS_SYSTEM_ONLY, paths);
258 	_ReadPaths(paths);
259 
260 	_InitJobs();
261 	_LaunchJobs(NULL);
262 }
263 
264 
265 void
266 LaunchDaemon::MessageReceived(BMessage* message)
267 {
268 	switch (message->what) {
269 		case B_GET_LAUNCH_DATA:
270 		{
271 			uid_t user = _GetUserID(message);
272 			if (user < 0)
273 				return;
274 
275 			BMessage reply((uint32)B_OK);
276 			Job* job = FindJob(get_leaf(message->GetString("name")));
277 			if (job == NULL) {
278 				Session* session = FindSession(user);
279 				if (session != NULL) {
280 					// Forward request to user launch_daemon
281 					if (session->Daemon().SendMessage(message) == B_OK)
282 						break;
283 				}
284 				reply.what = B_NAME_NOT_FOUND;
285 			} else {
286 				// If the job has not been launched yet, we'll pass on our
287 				// team here. The rationale behind this is that this team
288 				// will temporarily own the synchronous reply ports.
289 				reply.AddInt32("team", job->Team() < 0
290 					? current_team() : job->Team());
291 
292 				PortMap::const_iterator iterator = job->Ports().begin();
293 				for (; iterator != job->Ports().end(); iterator++) {
294 					BString name;
295 					if (iterator->second.HasString("name"))
296 						name << iterator->second.GetString("name") << "_";
297 					name << "port";
298 
299 					reply.AddInt32(name.String(),
300 						iterator->second.GetInt32("port", -1));
301 				}
302 
303 				_AddLaunchJob(job);
304 			}
305 			message->SendReply(&reply);
306 			break;
307 		}
308 
309 		case B_LAUNCH_TARGET:
310 		{
311 			uid_t user = _GetUserID(message);
312 			if (user < 0)
313 				break;
314 
315 			const char* name = message->GetString("target");
316 			const char* baseName = message->GetString("base target");
317 
318 			Target* target = FindTarget(name);
319 			if (target == NULL) {
320 				Target* baseTarget = FindTarget(baseName);
321 				if (baseTarget != NULL) {
322 					target = new Target(name);
323 
324 					// Copy all jobs with the base target into the new target
325 					for (JobMap::iterator iterator = fJobs.begin();
326 							iterator != fJobs.end();) {
327 						Job* job = iterator->second;
328 						iterator++;
329 
330 						if (job->Target() == baseTarget) {
331 							Job* copy = new Job(*job);
332 							copy->SetTarget(target);
333 
334 							fJobs.insert(std::make_pair(copy->Name(), copy));
335 						}
336 					}
337 				}
338 			}
339 			if (target == NULL) {
340 				Session* session = FindSession(user);
341 				if (session != NULL) {
342 					// Forward request to user launch_daemon
343 					if (session->Daemon().SendMessage(message) == B_OK)
344 						break;
345 				}
346 
347 				BMessage reply(B_NAME_NOT_FOUND);
348 				message->SendReply(&reply);
349 				break;
350 			}
351 
352 			BMessage data;
353 			if (message->FindMessage("data", &data) == B_OK)
354 				target->AddData(data.GetString("name"), data);
355 
356 			_LaunchJobs(target);
357 			break;
358 		}
359 
360 		case B_LAUNCH_SESSION:
361 		{
362 			uid_t user = _GetUserID(message);
363 			if (user < 0)
364 				break;
365 
366 			status_t status = B_OK;
367 			const char* login = message->GetString("login");
368 			const char* password = message->GetString("password");
369 			if (login == NULL || password == NULL)
370 				status = B_BAD_VALUE;
371 			if (status == B_OK && user != 0) {
372 				// Only the root user can start sessions
373 				status = B_PERMISSION_DENIED;
374 			}
375 			if (status == B_OK)
376 				status = _StartSession(login, password);
377 
378 			BMessage reply((uint32)status);
379 			message->SendReply(&reply);
380 			break;
381 		}
382 
383 		case B_REGISTER_SESSION_DAEMON:
384 		{
385 			uid_t user = _GetUserID(message);
386 			if (user < 0)
387 				break;
388 
389 			status_t status = B_OK;
390 
391 			BMessenger target;
392 			if (message->FindMessenger("target", &target) != B_OK)
393 				status = B_BAD_VALUE;
394 
395 			if (status == B_OK) {
396 				Session* session = new (std::nothrow) Session(user, target);
397 				if (session != NULL)
398 					fSessions.insert(std::pair<uid_t, Session*>(user, session));
399 				else
400 					status = B_NO_MEMORY;
401 			}
402 
403 			BMessage reply((uint32)status);
404 			message->SendReply(&reply);
405 			break;
406 		}
407 
408 		default:
409 			BServer::MessageReceived(message);
410 			break;
411 	}
412 }
413 
414 
415 uid_t
416 LaunchDaemon::_GetUserID(BMessage* message)
417 {
418 	uid_t user = (uid_t)message->GetInt32("user", -1);
419 	if (user < 0) {
420 		BMessage reply((uint32)B_BAD_VALUE);
421 		message->SendReply(&reply);
422 	}
423 	return user;
424 }
425 
426 
427 void
428 LaunchDaemon::_ReadPaths(const BStringList& paths)
429 {
430 	for (int32 i = 0; i < paths.CountStrings(); i++) {
431 		BEntry entry(paths.StringAt(i));
432 		if (entry.InitCheck() != B_OK || !entry.Exists())
433 			continue;
434 
435 		_ReadDirectory(NULL, entry);
436 	}
437 }
438 
439 
440 void
441 LaunchDaemon::_ReadEntry(const char* context, BEntry& entry)
442 {
443 	if (entry.IsDirectory())
444 		_ReadDirectory(context, entry);
445 	else
446 		_ReadFile(context, entry);
447 }
448 
449 
450 void
451 LaunchDaemon::_ReadDirectory(const char* context, BEntry& directoryEntry)
452 {
453 	BDirectory directory(&directoryEntry);
454 
455 	BEntry entry;
456 	while (directory.GetNextEntry(&entry) == B_OK) {
457 		_ReadEntry(context, entry);
458 	}
459 }
460 
461 
462 status_t
463 LaunchDaemon::_ReadFile(const char* context, BEntry& entry)
464 {
465 	DriverSettingsMessageAdapter adapter;
466 
467 	BPath path;
468 	status_t status = path.SetTo(&entry);
469 	if (status != B_OK)
470 		return status;
471 
472 	BMessage message;
473 	status = adapter.ConvertFromDriverSettings(path.Path(), kSettingsTemplate,
474 			message);
475 	if (status == B_OK) {
476 		_AddJobs(NULL, message);
477 
478 		BMessage targetMessage;
479 		for (int32 index = 0; message.FindMessage("target", index,
480 				&targetMessage) == B_OK; index++) {
481 			const char* name = targetMessage.GetString("name");
482 			if (name == NULL) {
483 				// TODO: log error
484 				debug_printf("Target has no name, ignoring it!\n");
485 				continue;
486 			}
487 
488 			Target* target = FindTarget(name);
489 			if (target == NULL) {
490 				target = new Target(name);
491 				_AddTarget(target);
492 			} else if (targetMessage.GetBool("reset")) {
493 				// Remove all jobs from this target
494 				for (JobMap::iterator iterator = fJobs.begin();
495 						iterator != fJobs.end();) {
496 					Job* job = iterator->second;
497 					JobMap::iterator remove = iterator++;
498 
499 					if (job->Target() == target) {
500 						fJobs.erase(remove);
501 						delete job;
502 					}
503 				}
504 			}
505 
506 			_AddJobs(target, targetMessage);
507 		}
508 	}
509 
510 	return status;
511 }
512 
513 
514 void
515 LaunchDaemon::_AddJobs(Target* target, BMessage& message)
516 {
517 	BMessage job;
518 	for (int32 index = 0; message.FindMessage("service", index,
519 			&job) == B_OK; index++) {
520 		_AddJob(target, true, job);
521 	}
522 
523 	for (int32 index = 0; message.FindMessage("job", index, &job) == B_OK;
524 			index++) {
525 		_AddJob(target, false, job);
526 	}
527 }
528 
529 
530 void
531 LaunchDaemon::_AddJob(Target* target, bool service, BMessage& message)
532 {
533 	BString name = message.GetString("name");
534 	if (name.IsEmpty()) {
535 		// Invalid job description
536 		return;
537 	}
538 	name.ToLower();
539 
540 	Job* job = FindJob(name);
541 	if (job == NULL)
542 		job = new Job(name);
543 
544 	job->SetEnabled(!message.GetBool("disabled", !job->IsEnabled()));
545 	job->SetService(service);
546 	job->SetCreateDefaultPort(!message.GetBool("legacy", !service));
547 	job->SetLaunchInSafeMode(
548 		!message.GetBool("no_safemode", !job->LaunchInSafeMode()));
549 	job->SetTarget(target);
550 
551 	BMessage portMessage;
552 	for (int32 index = 0;
553 			message.FindMessage("port", index, &portMessage) == B_OK; index++) {
554 		job->AddPort(portMessage);
555 	}
556 
557 	const char* argument;
558 	for (int32 index = 0;
559 			message.FindString("launch", index, &argument) == B_OK; index++) {
560 		job->AddArgument(argument);
561 	}
562 
563 	const char* requirement;
564 	for (int32 index = 0;
565 			message.FindString("requires", index, &requirement) == B_OK;
566 			index++) {
567 		job->AddRequirement(requirement);
568 	}
569 	if (fInitTarget != NULL)
570 		job->AddRequirement(fInitTarget->Name());
571 
572 	fJobs.insert(std::pair<BString, Job*>(job->Name(), job));
573 }
574 
575 
576 void
577 LaunchDaemon::_InitJobs()
578 {
579 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();) {
580 		Job* job = iterator->second;
581 		JobMap::iterator remove = iterator++;
582 
583 		status_t status = B_NO_INIT;
584 		if (job->IsEnabled() && (!_IsSafeMode() || job->LaunchInSafeMode())) {
585 			std::set<BString> dependencies;
586 			status = job->Init(*this, dependencies);
587 		}
588 
589 		if (status != B_OK) {
590 			if (status != B_NO_INIT) {
591 				// TODO: log error
592 				debug_printf("Init \"%s\" failed: %s\n", job->Name(),
593 					strerror(status));
594 			}
595 
596 			// Remove jobs that won't be used later on
597 			fJobs.erase(remove);
598 			delete job;
599 		}
600 	}
601 }
602 
603 
604 void
605 LaunchDaemon::_LaunchJobs(Target* target)
606 {
607 	for (JobMap::iterator iterator = fJobs.begin(); iterator != fJobs.end();
608 			iterator++) {
609 		Job* job = iterator->second;
610 		if (job->Target() == target)
611 			_AddLaunchJob(job);
612 	}
613 }
614 
615 
616 void
617 LaunchDaemon::_AddLaunchJob(Job* job)
618 {
619 	if (!job->IsLaunched())
620 		fJobQueue.AddJob(job);
621 }
622 
623 
624 void
625 LaunchDaemon::_AddTarget(Target* target)
626 {
627 	fTargets.insert(std::make_pair(target->Title(), target));
628 }
629 
630 
631 status_t
632 LaunchDaemon::_StartSession(const char* login, const char* password)
633 {
634 	Unlock();
635 
636 	// TODO: enable user/group code and password authentication
637 	// The launch_daemon currently cannot talk to the registrar, though
638 /*
639 	struct passwd* passwd = getpwnam(login);
640 	if (passwd == NULL)
641 		return B_NAME_NOT_FOUND;
642 	if (strcmp(passwd->pw_name, login) != 0)
643 		return B_NAME_NOT_FOUND;
644 
645 	// TODO: check for auto-login, and ignore password then
646 	if (!verify_password(passwd, getspnam(login), password))
647 		return B_PERMISSION_DENIED;
648 
649 	// Check if there is a user session running already
650 	uid_t user = passwd->pw_uid;
651 	gid_t group = passwd->pw_gid;
652 */
653 
654 	if (fork() == 0) {
655 		if (setsid() < 0)
656 			exit(EXIT_FAILURE);
657 
658 /*
659 debug_printf("session leader...\n");
660 		if (initgroups(login, group) == -1) {
661 debug_printf("1.ouch: %s\n", strerror(errno));
662 			exit(EXIT_FAILURE);
663 		}
664 		//endgrent();
665 		if (setgid(group) != 0) {
666 debug_printf("2.ouch: %s\n", strerror(errno));
667 			exit(EXIT_FAILURE);
668 		}
669 		if (setuid(user) != 0) {
670 debug_printf("3.ouch: %s\n", strerror(errno));
671 			exit(EXIT_FAILURE);
672 		}
673 */
674 
675 		// TODO: This leaks the parent application
676 		be_app = NULL;
677 
678 		// TODO: take over system jobs, and reserve their names
679 		status_t status;
680 		LaunchDaemon* daemon = new LaunchDaemon(true, status);
681 		if (status == B_OK)
682 			daemon->Run();
683 
684 		delete daemon;
685 		exit(EXIT_SUCCESS);
686 	}
687 	Lock();
688 	return B_OK;
689 }
690 
691 
692 void
693 LaunchDaemon::_RetrieveKernelOptions()
694 {
695 	char buffer[32];
696 	size_t size = sizeof(buffer);
697 	status_t status = _kern_get_safemode_option(B_SAFEMODE_SAFE_MODE, buffer,
698 		&size);
699 	if (status == B_OK) {
700 		fSafeMode = !strncasecmp(buffer, "true", size)
701 			|| !strncasecmp(buffer, "yes", size)
702 			|| !strncasecmp(buffer, "on", size)
703 			|| !strncasecmp(buffer, "enabled", size);
704 	} else
705 		fSafeMode = false;
706 }
707 
708 
709 void
710 LaunchDaemon::_SetupEnvironment()
711 {
712 	// Determine safemode kernel option
713 	BString safemode = "SAFEMODE=";
714 	safemode << (_IsSafeMode() ? "yes" : "no");
715 
716 	putenv(safemode.String());
717 }
718 
719 
720 /*!	Basic system initialization that must happen before any jobs are launched.
721 */
722 void
723 LaunchDaemon::_InitSystem()
724 {
725 	_AddInitJob(new InitRealTimeClockJob());
726 	_AddInitJob(new InitSharedMemoryDirectoryJob());
727 	_AddInitJob(new InitTemporaryDirectoryJob());
728 
729 	fJobQueue.AddJob(fInitTarget);
730 }
731 
732 
733 void
734 LaunchDaemon::_AddInitJob(BJob* job)
735 {
736 	fInitTarget->AddDependency(job);
737 	fJobQueue.AddJob(job);
738 }
739 
740 
741 bool
742 LaunchDaemon::_IsSafeMode() const
743 {
744 	return fSafeMode;
745 }
746 
747 
748 // #pragma mark -
749 
750 
751 int
752 main()
753 {
754 	status_t status;
755 	LaunchDaemon* daemon = new LaunchDaemon(false, status);
756 	if (status == B_OK)
757 		daemon->Run();
758 
759 	delete daemon;
760 	return status == B_OK ? EXIT_SUCCESS : EXIT_FAILURE;
761 }
762