xref: /haiku/src/system/libroot/posix/user_group_common.cpp (revision f8cb30712e09e13b6eeb08f225810875aea1e6f8)
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