1 /* 2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include "user_group_common.h" 7 8 #include <ctype.h> 9 #include <errno.h> 10 #include <fcntl.h> 11 #include <limits.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 17 #include <new> 18 19 #include <libroot_lock.h> 20 #include <libroot_private.h> 21 22 23 using BPrivate::FileLineReader; 24 using BPrivate::Tokenizer; 25 using BPrivate::FileDBEntry; 26 using BPrivate::FileDBReader; 27 using BPrivate::FileDB; 28 using BPrivate::PasswdDBEntry; 29 using BPrivate::PasswdEntryHandler; 30 using BPrivate::PasswdDBReader; 31 using BPrivate::PasswdDB; 32 using BPrivate::GroupDBEntry; 33 using BPrivate::GroupEntryHandler; 34 using BPrivate::GroupDBReader; 35 using BPrivate::GroupDB; 36 37 38 const char* BPrivate::kPasswdFile = "/etc/passwd"; 39 const char* BPrivate::kGroupFile = "/etc/group"; 40 41 static benaphore sUserGroupLock; 42 43 44 status_t 45 BPrivate::user_group_lock() 46 { 47 return benaphore_lock(&sUserGroupLock); 48 } 49 50 51 status_t 52 BPrivate::user_group_unlock() 53 { 54 return benaphore_unlock(&sUserGroupLock); 55 } 56 57 58 class FileLineReader { 59 public: 60 FileLineReader(int fd) 61 : fFD(fd), 62 fSize(0), 63 fOffset(0) 64 { 65 } 66 67 char* NextLine() 68 { 69 char* eol; 70 if (fOffset >= fSize 71 || (eol = strchr(fBuffer + fOffset, '\n')) == NULL) { 72 _ReadBuffer(); 73 if (fOffset >= fSize) 74 return NULL; 75 76 eol = strchr(fBuffer + fOffset, '\n'); 77 if (eol == NULL) 78 eol = fBuffer + fSize; 79 } 80 81 char* result = fBuffer + fOffset; 82 *eol = '\0'; 83 fOffset = eol + 1 - fBuffer; 84 return result; 85 } 86 87 char* NextNonEmptyLine() 88 { 89 while (char* line = NextLine()) { 90 while (*line != '\0' && isspace(*line)) 91 line++; 92 93 if (*line != '\0' && *line != '#') 94 return line; 95 } 96 97 return NULL; 98 } 99 100 private: 101 void _ReadBuffer() 102 { 103 // catch special cases: full buffer or already done with the file 104 if (fSize == LINE_MAX || fFD < 0) 105 return; 106 107 // move buffered bytes to the beginning of the buffer 108 int leftBytes = 0; 109 if (fOffset < fSize) { 110 leftBytes = fSize - fOffset; 111 memmove(fBuffer, fBuffer + fOffset, leftBytes); 112 } 113 114 fOffset = 0; 115 fSize = leftBytes; 116 117 // read 118 ssize_t bytesRead = read(fFD, fBuffer + leftBytes, 119 LINE_MAX - leftBytes); 120 if (bytesRead > 0) 121 fSize += bytesRead; 122 else 123 fFD = -1; 124 125 // null-terminate 126 fBuffer[fSize] = '\0'; 127 } 128 129 private: 130 int fFD; 131 char fBuffer[LINE_MAX + 1]; 132 int fSize; 133 int fOffset; 134 }; 135 136 137 class Tokenizer { 138 public: 139 Tokenizer(char* string) 140 : fString(string) 141 { 142 } 143 144 char* NextToken(char separator) 145 { 146 if (fString == NULL) 147 return NULL; 148 149 char* token = fString; 150 fString = strchr(fString, separator); 151 if (fString != NULL) { 152 *fString = '\0'; 153 fString++; 154 } 155 156 return token; 157 } 158 159 char* NextTrimmedToken(char separator) 160 { 161 char* token = NextToken(separator); 162 if (token == NULL) 163 return NULL; 164 165 // skip spaces at the beginning 166 while (*token != '\0' && isspace(*token)) 167 token++; 168 169 // cut off spaces at the end 170 char* end = token + strlen(token); 171 while (end != token && isspace(end[-1])) 172 end--; 173 *end = '\0'; 174 175 return token; 176 } 177 178 private: 179 char* fString; 180 }; 181 182 183 static char* 184 buffer_dup_string(const char* string, char*& buffer, size_t& bufferLen) 185 { 186 if (string == NULL) 187 return NULL; 188 189 size_t size = strlen(string) + 1; 190 if (size > bufferLen) 191 return NULL; 192 193 strcpy(buffer, string); 194 char* result = buffer; 195 buffer += size; 196 bufferLen -= size; 197 198 return result; 199 } 200 201 202 static void* 203 buffer_allocate(size_t size, size_t align, char*& buffer, size_t& bufferSize) 204 { 205 // align padding 206 addr_t pad = align - (((addr_t)buffer - 1) & (align - 1)) - 1; 207 if (pad + size > bufferSize) 208 return NULL; 209 210 char* result = buffer + pad; 211 buffer = result + size; 212 bufferSize -= pad + size; 213 214 return result; 215 } 216 217 218 // #pragma mark - FileDBEntry 219 220 221 FileDBEntry::FileDBEntry() 222 : 223 fName(NULL), 224 fID(-1), 225 fNext(NULL) 226 { 227 } 228 229 230 FileDBEntry::~FileDBEntry() 231 { 232 } 233 234 235 // #pragma mark - FileDBReader 236 237 238 FileDBReader::FileDBReader() 239 { 240 } 241 242 243 FileDBReader::~FileDBReader() 244 { 245 } 246 247 248 status_t 249 FileDBReader::Read(const char* path) 250 { 251 // read file 252 int fd = open(path, O_RDONLY); 253 if (fd < 0) 254 return errno; 255 256 FileLineReader reader(fd); 257 258 status_t error = B_OK; 259 260 while (char* line = reader.NextNonEmptyLine()) { 261 Tokenizer tokenizer(line); 262 error = ParseEntryLine(tokenizer); 263 if (error != B_OK) 264 break; 265 } 266 267 close(fd); 268 269 return error; 270 } 271 272 273 // #pragma mark - FileDB 274 275 276 FileDB::FileDB() 277 : 278 fEntries(NULL), 279 fLastEntry(NULL) 280 { 281 } 282 283 284 FileDB::~FileDB() 285 { 286 while (FileDBEntry* entry = fEntries) { 287 fEntries = entry->Next(); 288 delete entry; 289 } 290 fLastEntry = NULL; 291 } 292 293 294 int 295 FileDB::GetNextEntry(void* entryBuffer, char* buffer, size_t bufferSize) 296 { 297 FileDBEntry* entry = NULL; 298 299 if (fLastEntry == NULL) { 300 // rewound 301 entry = fEntries; 302 } else if (fLastEntry->Next() != NULL) { 303 // get next entry 304 entry = fLastEntry->Next(); 305 } 306 307 // copy the entry, if we found one 308 if (entry != NULL) { 309 int result = entry->CopyToBuffer(entryBuffer, buffer, bufferSize); 310 if (result == 0) 311 fLastEntry = entry; 312 return result; 313 } 314 315 return ENOENT; 316 } 317 318 319 void 320 FileDB::RewindEntries() 321 { 322 fLastEntry = NULL; 323 } 324 325 326 FileDBEntry* 327 FileDB::FindEntry(const char* name) const 328 { 329 // find the entry 330 FileDBEntry* entry = fEntries; 331 while (entry != NULL && strcmp(entry->Name(), name) != 0) 332 entry = entry->Next(); 333 334 return entry; 335 } 336 337 338 FileDBEntry* 339 FileDB::FindEntry(int32 id) const 340 { 341 // find the entry 342 FileDBEntry* entry = fEntries; 343 while (entry != NULL && entry->ID() != id) 344 entry = entry->Next(); 345 346 return entry; 347 } 348 349 350 int 351 FileDB::GetEntry(const char* name, void* entryBuffer, char* buffer, 352 size_t bufferSize) const 353 { 354 FileDBEntry* entry = FindEntry(name); 355 if (entry == NULL) 356 return ENOENT; 357 358 return entry->CopyToBuffer(entryBuffer, buffer, bufferSize); 359 } 360 361 362 int 363 FileDB::GetEntry(int32 id, void* entryBuffer, char* buffer, 364 size_t bufferSize) const 365 { 366 FileDBEntry* entry = FindEntry(id); 367 if (entry == NULL) 368 return ENOENT; 369 370 return entry->CopyToBuffer(entryBuffer, buffer, bufferSize); 371 } 372 373 374 void 375 FileDB::AddEntry(FileDBEntry* entry) 376 { 377 entry->SetNext(fEntries); 378 fEntries = entry; 379 } 380 381 382 // #pragma mark - passwd support 383 384 385 status_t 386 BPrivate::copy_passwd_to_buffer(const char* name, const char* password, 387 uid_t uid, gid_t gid, const char* home, const char* shell, 388 const char* realName, passwd* entry, char* buffer, size_t bufferSize) 389 { 390 entry->pw_uid = uid; 391 entry->pw_gid = gid; 392 393 entry->pw_name = buffer_dup_string(name, buffer, bufferSize); 394 entry->pw_passwd = buffer_dup_string(password, buffer, bufferSize); 395 entry->pw_dir = buffer_dup_string(home, buffer, bufferSize); 396 entry->pw_shell = buffer_dup_string(shell, buffer, bufferSize); 397 entry->pw_gecos = buffer_dup_string(realName, buffer, bufferSize); 398 399 if (entry->pw_name && entry->pw_passwd && entry->pw_dir 400 && entry->pw_shell && entry->pw_gecos) { 401 return 0; 402 } 403 404 return ERANGE; 405 } 406 407 408 // #pragma mark - PasswdDBEntry 409 410 411 PasswdDBEntry::PasswdDBEntry() 412 : FileDBEntry(), 413 fPassword(NULL), 414 fHome(NULL), 415 fShell(NULL), 416 fRealName(NULL) 417 { 418 } 419 420 421 PasswdDBEntry::~PasswdDBEntry() 422 { 423 free(fName); 424 } 425 426 427 bool 428 PasswdDBEntry::Init(const char* name, const char* password, uid_t uid, 429 gid_t gid, const char* home, const char* shell, const char* realName) 430 { 431 size_t bufferSize = strlen(name) + 1 432 + strlen(password) + 1 433 + strlen(home) + 1 434 + strlen(shell) + 1 435 + strlen(realName) + 1; 436 437 char* buffer = (char*)malloc(bufferSize); 438 if (buffer == NULL) 439 return false; 440 441 fID = uid; 442 fGID = gid; 443 fName = buffer_dup_string(name, buffer, bufferSize); 444 fPassword = buffer_dup_string(password, buffer, bufferSize); 445 fHome = buffer_dup_string(home, buffer, bufferSize); 446 fShell = buffer_dup_string(shell, buffer, bufferSize); 447 fRealName = buffer_dup_string(realName, buffer, bufferSize); 448 449 return true; 450 } 451 452 453 int 454 PasswdDBEntry::CopyToBuffer(void* entryBuffer, char* buffer, size_t bufferSize) 455 { 456 return copy_passwd_to_buffer(fName, fPassword, fID, fGID, fHome, fShell, 457 fRealName, (passwd*)entryBuffer, buffer, bufferSize); 458 } 459 460 461 // #pragma mark - PasswdEntryHandler 462 463 464 PasswdEntryHandler::~PasswdEntryHandler() 465 { 466 } 467 468 469 // #pragma mark - PasswdDBReader 470 471 472 PasswdDBReader::PasswdDBReader(PasswdEntryHandler* handler) 473 : fHandler(handler) 474 { 475 } 476 477 478 status_t 479 PasswdDBReader::ParseEntryLine(Tokenizer& tokenizer) 480 { 481 char* name = tokenizer.NextTrimmedToken(':'); 482 char* password = tokenizer.NextTrimmedToken(':'); 483 char* userID = tokenizer.NextTrimmedToken(':'); 484 char* groupID = tokenizer.NextTrimmedToken(':'); 485 char* realName = tokenizer.NextTrimmedToken(':'); 486 char* home = tokenizer.NextTrimmedToken(':'); 487 char* shell = tokenizer.NextTrimmedToken(':'); 488 489 // skip if invalid 490 size_t nameLen; 491 if (shell == NULL || (nameLen = strlen(name)) == 0 492 || !isdigit(*userID) || !isdigit(*groupID) 493 || nameLen >= MAX_PASSWD_NAME_LEN 494 || strlen(password) >= MAX_PASSWD_PASSWORD_LEN 495 || strlen(realName) >= MAX_PASSWD_REAL_NAME_LEN 496 || strlen(home) >= MAX_PASSWD_HOME_DIR_LEN 497 || strlen(shell) >= MAX_PASSWD_SHELL_LEN) { 498 return B_OK; 499 } 500 501 gid_t uid = atoi(userID); 502 gid_t gid = atoi(groupID); 503 504 return fHandler->HandleEntry(name, password, uid, gid, home, shell, 505 realName); 506 } 507 508 509 // #pragma mark - PasswdDB 510 511 512 status_t 513 PasswdDB::Init() 514 { 515 return PasswdDBReader(this).Read(kPasswdFile); 516 } 517 518 519 status_t 520 PasswdDB::HandleEntry(const char* name, const char* password, uid_t uid, 521 gid_t gid, const char* home, const char* shell, const char* realName) 522 { 523 PasswdDBEntry* entry = new(nothrow) PasswdDBEntry(); 524 if (entry == NULL || !entry->Init(name, password, uid, gid, home, shell, 525 realName)) { 526 delete entry; 527 return B_NO_MEMORY; 528 } 529 530 AddEntry(entry); 531 return B_OK; 532 } 533 534 535 // #pragma mark - passwd support 536 537 538 status_t 539 BPrivate::copy_group_to_buffer(const char* name, const char* password, 540 gid_t gid, const char* const* members, int memberCount, group* entry, 541 char* buffer, size_t bufferSize) 542 { 543 entry->gr_gid = gid; 544 545 // allocate member array (do that first for alignment reasons) 546 entry->gr_mem = (char**)buffer_allocate(sizeof(char*) * (memberCount + 1), 547 sizeof(char*), buffer, bufferSize); 548 if (entry->gr_mem == NULL) 549 return ERANGE; 550 551 // copy name and password 552 entry->gr_name = buffer_dup_string(name, buffer, bufferSize); 553 entry->gr_passwd = buffer_dup_string(password, buffer, bufferSize); 554 if (entry->gr_name == NULL || entry->gr_passwd == NULL) 555 return ERANGE; 556 557 // copy member array 558 for (int i = 0; i < memberCount; i++) { 559 entry->gr_mem[i] = buffer_dup_string(members[i], buffer, bufferSize); 560 if (entry->gr_mem[i] == NULL) 561 return ERANGE; 562 } 563 entry->gr_mem[memberCount] = NULL; 564 565 return 0; 566 } 567 568 569 // #pragma mark - GroupDBEntry 570 571 572 GroupDBEntry::GroupDBEntry() 573 : FileDBEntry(), 574 fPassword(NULL), 575 fMembers(NULL), 576 fMemberCount(0) 577 { 578 } 579 580 581 GroupDBEntry::~GroupDBEntry() 582 { 583 free(fMembers); 584 } 585 586 587 bool 588 GroupDBEntry::Init(const char* name, const char* password, gid_t gid, 589 const char* const* members, int memberCount) 590 { 591 size_t bufferSize = sizeof(char*) * (memberCount + 1) 592 + strlen(name) + 1 593 + strlen(password) + 1; 594 595 for (int i = 0; i < memberCount; i++) 596 bufferSize += strlen(members[i]) + 1; 597 598 char* buffer = (char*)malloc(bufferSize); 599 if (buffer == NULL) 600 return false; 601 602 // allocate member array first (for alignment reasons) 603 fMembers = (char**)buffer_allocate(sizeof(char*) * (memberCount + 1), 604 sizeof(char*), buffer, bufferSize); 605 606 fID = gid; 607 fName = buffer_dup_string(name, buffer, bufferSize); 608 fPassword = buffer_dup_string(password, buffer, bufferSize); 609 610 // copy members 611 for (int i = 0; i < memberCount; i++) 612 fMembers[i] = buffer_dup_string(members[i], buffer, bufferSize); 613 fMembers[memberCount] = NULL; 614 fMemberCount = memberCount; 615 616 return true; 617 } 618 619 620 int 621 GroupDBEntry::CopyToBuffer(void* entryBuffer, char* buffer, size_t bufferSize) 622 { 623 return copy_group_to_buffer(fName, fPassword, fID, fMembers, fMemberCount, 624 (group*)entryBuffer, buffer, bufferSize); 625 } 626 627 628 // #pragma mark - GroupEntryHandler 629 630 631 GroupEntryHandler::~GroupEntryHandler() 632 { 633 } 634 635 636 // #pragma mark - GroupDBReader 637 638 639 GroupDBReader::GroupDBReader(GroupEntryHandler* handler) 640 : fHandler(handler) 641 { 642 } 643 644 645 status_t 646 GroupDBReader::ParseEntryLine(Tokenizer& tokenizer) 647 { 648 char* name = tokenizer.NextTrimmedToken(':'); 649 char* password = tokenizer.NextTrimmedToken(':'); 650 char* groupID = tokenizer.NextTrimmedToken(':'); 651 652 // skip if invalid 653 size_t nameLen; 654 if (groupID == NULL || (nameLen = strlen(name)) == 0 || !isdigit(*groupID) 655 || nameLen >= MAX_GROUP_NAME_LEN 656 || strlen(password) >= MAX_GROUP_PASSWORD_LEN) { 657 return B_OK; 658 } 659 660 gid_t gid = atol(groupID); 661 662 const char* members[MAX_GROUP_MEMBER_COUNT]; 663 int memberCount = 0; 664 665 while (char* groupUser = tokenizer.NextTrimmedToken(',')) { 666 // ignore invalid members 667 if (*groupUser == '\0' || strlen(groupUser) >= MAX_PASSWD_NAME_LEN) 668 continue; 669 670 members[memberCount++] = groupUser; 671 672 // ignore excess members 673 if (memberCount == MAX_GROUP_MEMBER_COUNT) 674 break; 675 } 676 677 return fHandler->HandleEntry(name, password, gid, members, memberCount); 678 } 679 680 681 // #pragma mark - GroupDB 682 683 684 status_t 685 GroupDB::Init() 686 { 687 return GroupDBReader(this).Read(kGroupFile); 688 } 689 690 691 status_t 692 GroupDB::HandleEntry(const char* name, const char* password, gid_t gid, 693 const char* const* members, int memberCount) 694 { 695 GroupDBEntry* entry = new(nothrow) GroupDBEntry(); 696 if (entry == NULL || !entry->Init(name, password, gid, members, 697 memberCount)) { 698 delete entry; 699 return B_NO_MEMORY; 700 } 701 702 AddEntry(entry); 703 return B_OK; 704 } 705 706 707 // #pragma mark - 708 709 710 void 711 __init_pwd_backend(void) 712 { 713 benaphore_init(&sUserGroupLock, "user group"); 714 } 715 716 717 void 718 __reinit_pwd_backend_after_fork(void) 719 { 720 benaphore_init(&sUserGroupLock, "user group"); 721 } 722