source: MondoRescue/branches/3.3/mindi-busybox/libpwdgrp/pwd_grp.c@ 3887

Last change on this file since 3887 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 14.3 KB
Line 
1/* vi: set sw=4 ts=4: */
2/* Copyright (C) 2014 Tito Ragusa <farmatito@tiscali.it>
3 *
4 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
5 */
6/* This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY!!
8 *
9 * Rewrite of some parts. Main differences are:
10 *
11 * 1) the buffer for getpwuid, getgrgid, getpwnam, getgrnam is dynamically
12 * allocated.
13 * If ENABLE_FEATURE_CLEAN_UP is set the buffers are freed at program
14 * exit using the atexit function to make valgrind happy.
15 * 2) the passwd/group files:
16 * a) must contain the expected number of fields (as per count of field
17 * delimeters ":") or we will complain with a error message.
18 * b) leading and trailing whitespace in fields is stripped.
19 * c) some fields are not allowed to be empty (e.g. username, uid/gid),
20 * and in this case NULL is returned and errno is set to EINVAL.
21 * This behaviour could be easily changed by modifying PW_DEF, GR_DEF,
22 * SP_DEF strings (uppercase makes a field mandatory).
23 * d) the string representing uid/gid must be convertible by strtoXX
24 * functions, or errno is set to EINVAL.
25 * e) leading and trailing whitespace in group member names is stripped.
26 * 3) the internal function for getgrouplist uses dynamically allocated buffer.
27 * 4) at the moment only the functions really used by busybox code are
28 * implemented, if you need a particular missing function it should be
29 * easy to write it by using the internal common code.
30 */
31
32#include "libbb.h"
33
34struct const_passdb {
35 const char *filename;
36 char def[7 + 2*ENABLE_USE_BB_SHADOW];
37 uint8_t off[7 + 2*ENABLE_USE_BB_SHADOW];
38 uint8_t numfields;
39 uint8_t size_of;
40};
41struct passdb {
42 const char *filename;
43 char def[7 + 2*ENABLE_USE_BB_SHADOW];
44 uint8_t off[7 + 2*ENABLE_USE_BB_SHADOW];
45 uint8_t numfields;
46 uint8_t size_of;
47 FILE *fp;
48 char *malloced;
49};
50/* Note: for shadow db, def[] will not contain terminating NUL,
51 * but convert_to_struct() logic detects def[] end by "less than SP?",
52 * not by "is it NUL?" condition; and off[0] happens to be zero
53 * for every db anyway, so there _is_ in fact a terminating NUL there.
54 */
55
56/* S = string not empty, s = string maybe empty,
57 * I = uid,gid, l = long maybe empty, m = members,
58 * r = reserved
59 */
60#define PW_DEF "SsIIsss"
61#define GR_DEF "SsIm"
62#define SP_DEF "Ssllllllr"
63
64static const struct const_passdb const_pw_db = {
65 _PATH_PASSWD, PW_DEF,
66 {
67 offsetof(struct passwd, pw_name), /* 0 S */
68 offsetof(struct passwd, pw_passwd), /* 1 s */
69 offsetof(struct passwd, pw_uid), /* 2 I */
70 offsetof(struct passwd, pw_gid), /* 3 I */
71 offsetof(struct passwd, pw_gecos), /* 4 s */
72 offsetof(struct passwd, pw_dir), /* 5 s */
73 offsetof(struct passwd, pw_shell) /* 6 s */
74 },
75 sizeof(PW_DEF)-1, sizeof(struct passwd)
76};
77static const struct const_passdb const_gr_db = {
78 _PATH_GROUP, GR_DEF,
79 {
80 offsetof(struct group, gr_name), /* 0 S */
81 offsetof(struct group, gr_passwd), /* 1 s */
82 offsetof(struct group, gr_gid), /* 2 I */
83 offsetof(struct group, gr_mem) /* 3 m (char **) */
84 },
85 sizeof(GR_DEF)-1, sizeof(struct group)
86};
87#if ENABLE_USE_BB_SHADOW
88static const struct const_passdb const_sp_db = {
89 _PATH_SHADOW, SP_DEF,
90 {
91 offsetof(struct spwd, sp_namp), /* 0 S Login name */
92 offsetof(struct spwd, sp_pwdp), /* 1 s Encrypted password */
93 offsetof(struct spwd, sp_lstchg), /* 2 l */
94 offsetof(struct spwd, sp_min), /* 3 l */
95 offsetof(struct spwd, sp_max), /* 4 l */
96 offsetof(struct spwd, sp_warn), /* 5 l */
97 offsetof(struct spwd, sp_inact), /* 6 l */
98 offsetof(struct spwd, sp_expire), /* 7 l */
99 offsetof(struct spwd, sp_flag) /* 8 r Reserved */
100 },
101 sizeof(SP_DEF)-1, sizeof(struct spwd)
102};
103#endif
104
105/* We avoid having big global data. */
106struct statics {
107 /* We use same buffer (db[0].malloced) for getpwuid and getpwnam.
108 * Manpage says:
109 * "The return value may point to a static area, and may be overwritten
110 * by subsequent calls to getpwent(), getpwnam(), or getpwuid()."
111 */
112 struct passdb db[2 + ENABLE_USE_BB_SHADOW];
113 char *tokenize_end;
114 unsigned string_size;
115};
116
117static struct statics *ptr_to_statics;
118#define S (*ptr_to_statics)
119#define has_S (ptr_to_statics)
120
121#if ENABLE_FEATURE_CLEAN_UP
122static void free_static(void)
123{
124 free(S.db[0].malloced);
125 free(S.db[1].malloced);
126# if ENABLE_USE_BB_SHADOW
127 free(S.db[2].malloced);
128# endif
129 free(ptr_to_statics);
130}
131#endif
132
133static struct statics *get_S(void)
134{
135 if (!ptr_to_statics) {
136 ptr_to_statics = xzalloc(sizeof(S));
137 memcpy(&S.db[0], &const_pw_db, sizeof(const_pw_db));
138 memcpy(&S.db[1], &const_gr_db, sizeof(const_gr_db));
139#if ENABLE_USE_BB_SHADOW
140 memcpy(&S.db[2], &const_sp_db, sizeof(const_sp_db));
141#endif
142#if ENABLE_FEATURE_CLEAN_UP
143 atexit(free_static);
144#endif
145 }
146 return ptr_to_statics;
147}
148
149/* Internal functions */
150
151/* Divide the passwd/group/shadow record in fields
152 * by substituting the given delimeter
153 * e.g. ':' or ',' with '\0'.
154 * Returns the number of fields found.
155 * Strips leading and trailing whitespace in fields.
156 */
157static int tokenize(char *buffer, int ch)
158{
159 char *p = buffer;
160 char *s = p;
161 int num_fields = 0;
162
163 for (;;) {
164 if (isblank(*s)) {
165 overlapping_strcpy(s, skip_whitespace(s));
166 }
167 if (*p == ch || *p == '\0') {
168 char *end = p;
169 while (p != s && isblank(p[-1]))
170 p--;
171 if (p != end)
172 overlapping_strcpy(p, end);
173 num_fields++;
174 if (*end == '\0') {
175 S.tokenize_end = p + 1;
176 return num_fields;
177 }
178 *p = '\0';
179 s = p + 1;
180 }
181 p++;
182 }
183}
184
185/* Returns !NULL on success and matching line broken up in fields by '\0' in buf.
186 * We require the expected number of fields to be found.
187 */
188static char *parse_common(FILE *fp, struct passdb *db,
189 const char *key, int field_pos)
190{
191 char *buf;
192
193 while ((buf = xmalloc_fgetline(fp)) != NULL) {
194 /* Skip empty lines, comment lines */
195 if (buf[0] == '\0' || buf[0] == '#')
196 goto free_and_next;
197 if (tokenize(buf, ':') != db->numfields) {
198 /* number of fields is wrong */
199 bb_error_msg("%s: bad record", db->filename);
200 goto free_and_next;
201 }
202
203 if (field_pos == -1) {
204 /* no key specified: sequential read, return a record */
205 break;
206 }
207 if (strcmp(key, nth_string(buf, field_pos)) == 0) {
208 /* record found */
209 break;
210 }
211 free_and_next:
212 free(buf);
213 }
214
215 S.string_size = S.tokenize_end - buf;
216/*
217 * Ugly hack: group db requires additional buffer space
218 * for members[] array. If there is only one group, we need space
219 * for 3 pointers: alignment padding, group name, NULL.
220 * +1 for every additional group.
221 */
222 if (buf && db->numfields == sizeof(GR_DEF)-1) { /* if we read group file... */
223 int cnt = 3;
224 char *p = buf;
225 while (p < S.tokenize_end)
226 if (*p++ == ',')
227 cnt++;
228 S.string_size += cnt * sizeof(char*);
229//bb_error_msg("+%d words = %u key:%s buf:'%s'", cnt, S.string_size, key, buf);
230 buf = xrealloc(buf, S.string_size);
231 }
232
233 return buf;
234}
235
236static char *parse_file(struct passdb *db,
237 const char *key, int field_pos)
238{
239 char *buf = NULL;
240 FILE *fp = fopen_for_read(db->filename);
241
242 if (fp) {
243 buf = parse_common(fp, db, key, field_pos);
244 fclose(fp);
245 }
246 return buf;
247}
248
249/* Convert passwd/group/shadow file record in buffer to a struct */
250static void *convert_to_struct(struct passdb *db,
251 char *buffer, void *result)
252{
253 const char *def = db->def;
254 const uint8_t *off = db->off;
255
256 /* For consistency, zero out all fields */
257 memset(result, 0, db->size_of);
258
259 for (;;) {
260 void *member = (char*)result + (*off++);
261
262 if ((*def | 0x20) == 's') { /* s or S */
263 *(char **)member = (char*)buffer;
264 if (!buffer[0] && (*def == 'S')) {
265 errno = EINVAL;
266 }
267 }
268 if (*def == 'I') {
269 *(int *)member = bb_strtou(buffer, NULL, 10);
270 }
271#if ENABLE_USE_BB_SHADOW
272 if (*def == 'l') {
273 long n = -1;
274 if (buffer[0])
275 n = bb_strtol(buffer, NULL, 10);
276 *(long *)member = n;
277 }
278#endif
279 if (*def == 'm') {
280 char **members;
281 int i = tokenize(buffer, ',');
282
283 /* Store members[] after buffer's end.
284 * This is safe ONLY because there is a hack
285 * in parse_common() which allocates additional space
286 * at the end of malloced buffer!
287 */
288 members = (char **)
289 ( ((intptr_t)S.tokenize_end + sizeof(members[0]))
290 & -(intptr_t)sizeof(members[0])
291 );
292 ((struct group *)result)->gr_mem = members;
293 while (--i >= 0) {
294 if (buffer[0]) {
295 *members++ = buffer;
296 // bb_error_msg("member[]='%s'", buffer);
297 }
298 buffer += strlen(buffer) + 1;
299 }
300 *members = NULL;
301 }
302 /* def "r" does nothing */
303
304 def++;
305 if ((unsigned char)*def <= (unsigned char)' ')
306 break;
307 buffer += strlen(buffer) + 1;
308 }
309
310 if (errno)
311 result = NULL;
312 return result;
313}
314
315static int massage_data_for_r_func(struct passdb *db,
316 char *buffer, size_t buflen,
317 void **result,
318 char *buf)
319{
320 void *result_buf = *result;
321 *result = NULL;
322 if (buf) {
323 if (S.string_size > buflen) {
324 errno = ERANGE;
325 } else {
326 memcpy(buffer, buf, S.string_size);
327 *result = convert_to_struct(db, buffer, result_buf);
328 }
329 free(buf);
330 }
331 /* "The reentrant functions return zero on success.
332 * In case of error, an error number is returned."
333 * NB: not finding the record is also a "success" here:
334 */
335 return errno;
336}
337
338static void* massage_data_for_non_r_func(struct passdb *db, char *buf)
339{
340 if (!buf)
341 return NULL;
342
343 free(db->malloced);
344 /* We enlarge buf and move string data up, freeing space
345 * for struct passwd/group/spwd at the beginning. This way,
346 * entire result of getXXnam is in a single malloced block.
347 * This enables easy creation of xmalloc_getpwnam() API.
348 */
349 db->malloced = buf = xrealloc(buf, db->size_of + S.string_size);
350 memmove(buf + db->size_of, buf, S.string_size);
351 return convert_to_struct(db, buf + db->size_of, buf);
352}
353
354/****** getXXnam/id_r */
355
356static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos,
357 char *buffer, size_t buflen,
358 void *result)
359{
360 char *buf;
361 struct passdb *db = &get_S()->db[db_and_field_pos >> 2];
362
363 buf = parse_file(db, name, 0 /*db_and_field_pos & 3*/);
364 /* "db_and_field_pos & 3" is commented out since so far we don't implement
365 * getXXXid_r() functions which would use that to pass 2 here */
366
367 return massage_data_for_r_func(db, buffer, buflen, result, buf);
368}
369
370int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf,
371 char *buffer, size_t buflen,
372 struct passwd **result)
373{
374 /* Why the "store buffer address in result" trick?
375 * This way, getXXnam_r has the same ABI signature as getpwnam_r,
376 * hopefully compiler can optimize tail call better in this case.
377 */
378 *result = struct_buf;
379 return getXXnam_r(name, (0 << 2) + 0, buffer, buflen, result);
380}
381#if ENABLE_USE_BB_SHADOW
382int FAST_FUNC getspnam_r(const char *name, struct spwd *struct_buf, char *buffer, size_t buflen,
383 struct spwd **result)
384{
385 *result = struct_buf;
386 return getXXnam_r(name, (2 << 2) + 0, buffer, buflen, result);
387}
388#endif
389
390#ifdef UNUSED
391/****** getXXent_r */
392
393static int FAST_FUNC getXXent_r(uintptr_t db_idx, char *buffer, size_t buflen,
394 void *result)
395{
396 char *buf;
397 struct passdb *db = &get_S()->db[db_idx];
398
399 if (!db->fp) {
400 db->fp = fopen_for_read(db->filename);
401 if (!db->fp) {
402 return errno;
403 }
404 close_on_exec_on(fileno(db->fp));
405 }
406
407 buf = parse_common(db->fp, db, /*no search key:*/ NULL, -1);
408 if (!buf && !errno)
409 errno = ENOENT;
410 return massage_data_for_r_func(db, buffer, buflen, result, buf);
411}
412
413int FAST_FUNC getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen,
414 struct passwd **result)
415{
416 *result = struct_buf;
417 return getXXent_r(0, buffer, buflen, result);
418}
419#endif
420
421/****** getXXent */
422
423static void* FAST_FUNC getXXent(uintptr_t db_idx)
424{
425 char *buf;
426 struct passdb *db = &get_S()->db[db_idx];
427
428 if (!db->fp) {
429 db->fp = fopen_for_read(db->filename);
430 if (!db->fp) {
431 return NULL;
432 }
433 close_on_exec_on(fileno(db->fp));
434 }
435
436 buf = parse_common(db->fp, db, /*no search key:*/ NULL, -1);
437 return massage_data_for_non_r_func(db, buf);
438}
439
440struct passwd* FAST_FUNC getpwent(void)
441{
442 return getXXent(0);
443}
444
445/****** getXXnam/id */
446
447static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos)
448{
449 char *buf;
450 struct passdb *db = &get_S()->db[db_and_field_pos >> 2];
451
452 buf = parse_file(db, name, db_and_field_pos & 3);
453 return massage_data_for_non_r_func(db, buf);
454}
455
456struct passwd* FAST_FUNC getpwnam(const char *name)
457{
458 return getXXnam(name, (0 << 2) + 0);
459}
460struct group* FAST_FUNC getgrnam(const char *name)
461{
462 return getXXnam(name, (1 << 2) + 0);
463}
464struct passwd* FAST_FUNC getpwuid(uid_t id)
465{
466 return getXXnam(utoa(id), (0 << 2) + 2);
467}
468struct group* FAST_FUNC getgrgid(gid_t id)
469{
470 return getXXnam(utoa(id), (1 << 2) + 2);
471}
472
473/****** end/setXXend */
474
475void FAST_FUNC endpwent(void)
476{
477 if (has_S && S.db[0].fp) {
478 fclose(S.db[0].fp);
479 S.db[0].fp = NULL;
480 }
481}
482void FAST_FUNC setpwent(void)
483{
484 if (has_S && S.db[0].fp) {
485 rewind(S.db[0].fp);
486 }
487}
488void FAST_FUNC endgrent(void)
489{
490 if (has_S && S.db[1].fp) {
491 fclose(S.db[1].fp);
492 S.db[1].fp = NULL;
493 }
494}
495
496/****** initgroups and getgrouplist */
497
498static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr,
499 const char *user, gid_t gid)
500{
501 FILE *fp;
502 gid_t *group_list;
503 int ngroups;
504
505 /* We alloc space for 8 gids at a time. */
506 group_list = xzalloc(8 * sizeof(group_list[0]));
507 group_list[0] = gid;
508 ngroups = 1;
509
510 fp = fopen_for_read(_PATH_GROUP);
511 if (fp) {
512 struct passdb *db = &get_S()->db[1];
513 char *buf;
514 while ((buf = parse_common(fp, db, NULL, -1)) != NULL) {
515 char **m;
516 struct group group;
517 if (!convert_to_struct(db, buf, &group))
518 goto next;
519 if (group.gr_gid == gid)
520 goto next;
521 for (m = group.gr_mem; *m; m++) {
522 if (strcmp(*m, user) != 0)
523 continue;
524 group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups);
525 group_list[ngroups++] = group.gr_gid;
526 goto next;
527 }
528 next:
529 free(buf);
530 }
531 fclose(fp);
532 }
533 *ngroups_ptr = ngroups;
534 return group_list;
535}
536
537int FAST_FUNC initgroups(const char *user, gid_t gid)
538{
539 int ngroups;
540 gid_t *group_list = getgrouplist_internal(&ngroups, user, gid);
541
542 ngroups = setgroups(ngroups, group_list);
543 free(group_list);
544 return ngroups;
545}
546
547int FAST_FUNC getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups)
548{
549 int ngroups_old = *ngroups;
550 gid_t *group_list = getgrouplist_internal(ngroups, user, gid);
551
552 if (*ngroups <= ngroups_old) {
553 ngroups_old = *ngroups;
554 memcpy(groups, group_list, ngroups_old * sizeof(groups[0]));
555 } else {
556 ngroups_old = -1;
557 }
558 free(group_list);
559 return ngroups_old;
560}
Note: See TracBrowser for help on using the repository browser.