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