1/*
2 * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <user_group.h>
8
9#include <ctype.h>
10#include <errno.h>
11#include <limits.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15
16#include <new>
17
18#include <errno_private.h>
19#include <launch.h>
20#include <libroot_private.h>
21#include <locks.h>
22#include <RegistrarDefs.h>
23
24#include <util/KMessage.h>
25
26
27using BPrivate::Tokenizer;
28
29
30static const char* const kUserGroupLockName = "user group";
31
32const char* BPrivate::kPasswdFile = "/etc/passwd";
33const char* BPrivate::kGroupFile = "/etc/group";
34const char* BPrivate::kShadowPwdFile = "/etc/shadow";
35
36static mutex sUserGroupLock = MUTEX_INITIALIZER(kUserGroupLockName);
37static port_id sRegistrarPort = -1;
38
39
40status_t
41BPrivate::user_group_lock()
42{
43	return mutex_lock(&sUserGroupLock);
44}
45
46
47status_t
48BPrivate::user_group_unlock()
49{
50	mutex_unlock(&sUserGroupLock);
51	return B_OK;
52}
53
54
55port_id
56BPrivate::get_registrar_authentication_port()
57{
58	if (sRegistrarPort < 0) {
59		BPrivate::KMessage data;
60		if (BPrivate::get_launch_data(B_REGISTRAR_SIGNATURE, data)
61				== B_OK) {
62			sRegistrarPort = data.GetInt32(
63				B_REGISTRAR_AUTHENTICATION_PORT_NAME "_port", -1);
64		}
65	}
66	return sRegistrarPort;
67}
68
69
70void
71BPrivate::set_registrar_authentication_port(port_id port)
72{
73	sRegistrarPort = port;
74}
75
76
77status_t
78BPrivate::send_authentication_request_to_registrar(KMessage& request,
79	KMessage& reply)
80{
81	status_t error = request.SendTo(get_registrar_authentication_port(), 0,
82		&reply);
83	if (error != B_OK)
84		return error;
85
86	return (status_t)reply.What();
87}
88
89
90class BPrivate::Tokenizer {
91public:
92	Tokenizer(char* string)
93		: fString(string)
94	{
95	}
96
97	char* NextToken(char separator)
98	{
99		if (fString == NULL)
100			return NULL;
101
102		char* token = fString;
103		fString = strchr(fString, separator);
104		if (fString != NULL) {
105			*fString = '\0';
106			fString++;
107		}
108
109		return token;
110	}
111
112	char* NextTrimmedToken(char separator)
113	{
114		char* token = NextToken(separator);
115		if (token == NULL)
116			return NULL;
117
118		// skip spaces at the beginning
119		while (*token != '\0' && isspace(*token))
120			token++;
121
122		// cut off spaces at the end
123		char* end = token + strlen(token);
124		while (end != token && isspace(end[-1]))
125			end--;
126		*end = '\0';
127
128		return token;
129	}
130
131private:
132	char*		fString;
133};
134
135
136static char*
137buffer_dup_string(const char* string, char*& buffer, size_t& bufferLen)
138{
139	if (string == NULL)
140		return NULL;
141
142	size_t size = strlen(string) + 1;
143	if (size > bufferLen)
144		return NULL;
145
146	strcpy(buffer, string);
147	char* result = buffer;
148	buffer += size;
149	bufferLen -= size;
150
151	return result;
152}
153
154
155static void*
156buffer_allocate(size_t size, size_t align, char*& buffer, size_t& bufferSize)
157{
158	// align padding
159	addr_t pad = align - (((addr_t)buffer - 1)  & (align - 1)) - 1;
160	if (pad + size > bufferSize)
161		return NULL;
162
163	char* result = buffer + pad;
164	buffer = result + size;
165	bufferSize -= pad + size;
166
167	return result;
168}
169
170
171// #pragma mark - passwd support
172
173
174status_t
175BPrivate::copy_passwd_to_buffer(const char* name, const char* password,
176	uid_t uid, gid_t gid, const char* home, const char* shell,
177	const char* realName, passwd* entry, char* buffer, size_t bufferSize)
178{
179	entry->pw_uid = uid;
180	entry->pw_gid = gid;
181
182	entry->pw_name = buffer_dup_string(name, buffer, bufferSize);
183	entry->pw_passwd = buffer_dup_string(password, buffer, bufferSize);
184	entry->pw_dir = buffer_dup_string(home, buffer, bufferSize);
185	entry->pw_shell = buffer_dup_string(shell, buffer, bufferSize);
186	entry->pw_gecos = buffer_dup_string(realName, buffer, bufferSize);
187
188	if (entry->pw_name && entry->pw_passwd && entry->pw_dir
189			&& entry->pw_shell && entry->pw_gecos) {
190		return 0;
191	}
192
193	return ERANGE;
194}
195
196
197status_t
198BPrivate::copy_passwd_to_buffer(const passwd* from, passwd* entry, char* buffer,
199	size_t bufferSize)
200{
201	return copy_passwd_to_buffer(from->pw_name, from->pw_passwd, from->pw_uid,
202		from->pw_gid, from->pw_dir, from->pw_shell, from->pw_gecos, entry,
203		buffer, bufferSize);
204}
205
206
207status_t
208BPrivate::parse_passwd_line(char* line, char*& name, char*& password,
209	uid_t& uid, gid_t& gid, char*& home, char*& shell, char*& realName)
210{
211	Tokenizer tokenizer(line);
212
213	name = tokenizer.NextTrimmedToken(':');
214	password = tokenizer.NextTrimmedToken(':');
215	char* userID = tokenizer.NextTrimmedToken(':');
216	char* groupID = tokenizer.NextTrimmedToken(':');
217	realName = tokenizer.NextTrimmedToken(':');
218	home = tokenizer.NextTrimmedToken(':');
219	shell = tokenizer.NextTrimmedToken(':');
220
221	// skip if invalid
222	size_t nameLen;
223	if (shell == NULL || (nameLen = strlen(name)) == 0
224			|| !isdigit(*userID) || !isdigit(*groupID)
225		|| nameLen >= MAX_PASSWD_NAME_LEN
226		|| strlen(password) >= MAX_PASSWD_PASSWORD_LEN
227		|| strlen(realName) >= MAX_PASSWD_REAL_NAME_LEN
228		|| strlen(home) >= MAX_PASSWD_HOME_DIR_LEN
229		|| strlen(shell) >= MAX_PASSWD_SHELL_LEN) {
230		return B_BAD_VALUE;
231	}
232
233	uid = atoi(userID);
234	gid = atoi(groupID);
235
236	return B_OK;
237}
238
239
240// #pragma mark - group support
241
242
243status_t
244BPrivate::copy_group_to_buffer(const char* name, const char* password,
245	gid_t gid, const char* const* members, int memberCount, group* entry,
246	char* buffer, size_t bufferSize)
247{
248	entry->gr_gid = gid;
249
250	// allocate member array (do that first for alignment reasons)
251	entry->gr_mem = (char**)buffer_allocate(sizeof(char*) * (memberCount + 1),
252		sizeof(char*), buffer, bufferSize);
253	if (entry->gr_mem == NULL)
254		return ERANGE;
255
256	// copy name and password
257	entry->gr_name = buffer_dup_string(name, buffer, bufferSize);
258	entry->gr_passwd = buffer_dup_string(password, buffer, bufferSize);
259	if (entry->gr_name == NULL || entry->gr_passwd == NULL)
260		return ERANGE;
261
262	// copy member array
263	for (int i = 0; i < memberCount; i++) {
264		entry->gr_mem[i] = buffer_dup_string(members[i], buffer, bufferSize);
265		if (entry->gr_mem[i] == NULL)
266			return ERANGE;
267	}
268	entry->gr_mem[memberCount] = NULL;
269
270	return 0;
271}
272
273
274status_t
275BPrivate::copy_group_to_buffer(const group* from, group* entry, char* buffer,
276	size_t bufferSize)
277{
278	int memberCount = 0;
279	while (from->gr_mem[memberCount] != NULL)
280		memberCount++;
281
282	return copy_group_to_buffer(from->gr_name, from->gr_passwd,
283		from->gr_gid, from->gr_mem, memberCount, entry, buffer, bufferSize);
284}
285
286
287status_t
288BPrivate::parse_group_line(char* line, char*& name, char*& password, gid_t& gid,
289	char** members, int& memberCount)
290{
291	Tokenizer tokenizer(line);
292
293	name = tokenizer.NextTrimmedToken(':');
294	password = tokenizer.NextTrimmedToken(':');
295	char* groupID = tokenizer.NextTrimmedToken(':');
296
297	// skip if invalid
298	size_t nameLen;
299	if (groupID == NULL || (nameLen = strlen(name)) == 0 || !isdigit(*groupID)
300		|| nameLen >= MAX_GROUP_NAME_LEN
301		|| strlen(password) >= MAX_GROUP_PASSWORD_LEN) {
302		return B_BAD_VALUE;
303	}
304
305	gid = atol(groupID);
306
307	memberCount = 0;
308
309	while (char* groupUser = tokenizer.NextTrimmedToken(',')) {
310		// ignore invalid members
311		if (*groupUser == '\0' || strlen(groupUser) >= MAX_PASSWD_NAME_LEN)
312			continue;
313
314		members[memberCount++] = groupUser;
315
316		// ignore excess members
317		if (memberCount == MAX_GROUP_MEMBER_COUNT)
318			break;
319	}
320
321	return B_OK;
322}
323
324
325// #pragma mark - shadow password support
326
327
328status_t
329BPrivate::copy_shadow_pwd_to_buffer(const char* name, const char* password,
330	int lastChanged, int min, int max, int warn, int inactive, int expiration,
331	int flags, spwd* entry, char* buffer, size_t bufferSize)
332{
333	entry->sp_lstchg = lastChanged;
334	entry->sp_min = min;
335	entry->sp_max = max;
336	entry->sp_warn = warn;
337	entry->sp_inact = inactive;
338	entry->sp_expire = expiration;
339	entry->sp_flag = flags;
340
341	entry->sp_namp = buffer_dup_string(name, buffer, bufferSize);
342	entry->sp_pwdp = buffer_dup_string(password, buffer, bufferSize);
343
344	if (entry->sp_namp && entry->sp_pwdp)
345		return 0;
346
347	return ERANGE;
348}
349
350
351status_t
352BPrivate::copy_shadow_pwd_to_buffer(const spwd* from, spwd* entry,
353	char* buffer, size_t bufferSize)
354{
355	return copy_shadow_pwd_to_buffer(from->sp_namp, from->sp_pwdp,
356		from->sp_lstchg, from->sp_min, from->sp_max, from->sp_warn,
357		from->sp_inact, from->sp_expire, from->sp_flag, entry, buffer,
358		bufferSize);
359}
360
361
362status_t
363BPrivate::parse_shadow_pwd_line(char* line, char*& name, char*& password,
364	int& lastChanged, int& min, int& max, int& warn, int& inactive,
365	int& expiration, int& flags)
366{
367	Tokenizer tokenizer(line);
368
369	name = tokenizer.NextTrimmedToken(':');
370	password = tokenizer.NextTrimmedToken(':');
371	char* lastChangedString = tokenizer.NextTrimmedToken(':');
372	char* minString = tokenizer.NextTrimmedToken(':');
373	char* maxString = tokenizer.NextTrimmedToken(':');
374	char* warnString = tokenizer.NextTrimmedToken(':');
375	char* inactiveString = tokenizer.NextTrimmedToken(':');
376	char* expirationString = tokenizer.NextTrimmedToken(':');
377	char* flagsString = tokenizer.NextTrimmedToken(':');
378
379	// skip if invalid
380	size_t nameLen;
381	if (flagsString == NULL || (nameLen = strlen(name)) == 0
382		|| nameLen >= MAX_SHADOW_PWD_NAME_LEN
383		|| strlen(password) >= MAX_SHADOW_PWD_PASSWORD_LEN) {
384		return B_BAD_VALUE;
385	}
386
387	lastChanged = atoi(lastChangedString);
388	min = minString[0] != '\0' ? atoi(minString) : -1;
389	max = maxString[0] != '\0' ? atoi(maxString) : -1;
390	warn = warnString[0] != '\0' ? atoi(warnString) : -1;
391	inactive = inactiveString[0] != '\0' ? atoi(inactiveString) : -1;
392	expiration = expirationString[0] != '\0' ? atoi(expirationString) : -1;
393	flags = atoi(flagsString);
394
395	return B_OK;
396}
397
398
399// #pragma mark -
400
401
402void
403__init_pwd_backend(void)
404{
405}
406
407
408void
409__reinit_pwd_backend_after_fork(void)
410{
411	mutex_init(&sUserGroupLock, kUserGroupLockName);
412}
413