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