1 /* vi: set sw=4 ts=4: */
2 /* Copyright (C) 2014 Tito Ragusa <farmatito@tiscali.it>
4 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
6 /* This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY!!
9 * Rewrite of some parts. Main differences are:
11 * 1) the buffer for getpwuid, getgrgid, getpwnam, getgrnam is dynamically
12 * allocated and reused by later calls. if ERANGE error pops up it is
13 * reallocated to the size of the longest line found so far in the
14 * passwd/group files and reused for later calls.
15 * If ENABLE_FEATURE_CLEAN_UP is set the buffers are freed at program
16 * exit using the atexit function to make valgrind happy.
17 * 2) the passwd/group files:
18 * a) must contain the expected number of fields (as per count of field
19 * delimeters ":") or we will complain with a error message.
20 * b) leading or trailing whitespace in fields is allowed and handled.
21 * c) some fields are not allowed to be empty (e.g. username, uid/gid,
22 * homedir, shell) and in this case NULL is returned and errno is
23 * set to EINVAL. This behaviour could be easily changed by
24 * modifying PW_DEF, GR_DEF, SP_DEF strings (uppercase
25 * makes a field mandatory).
26 * d) the string representing uid/gid must be convertible by strtoXX
27 * functions or NULL is returned and errno is set to EINVAL.
28 * e) leading or trailing whitespaces in member names and empty members
29 * are allowed and handled.
30 * 3) the internal function for getgrouplist uses a dynamically allocated
31 * buffer and retries with a bigger one in case it is too small;
32 * 4) the _r functions use the user supplied buffers that are never reallocated
33 * but use mostly the same common code as the other functions.
34 * 5) at the moment only the functions really used by busybox code are
35 * implemented, if you need a particular missing function it should be
36 * easy to write it by using the internal common code.
41 /* S = string not empty, s = string maybe empty, */
42 /* I = uid,gid, l = long maybe empty, m = members,*/
44 #define PW_DEF "SsIIsSS"
46 #define SP_DEF "Ssllllllr"
48 static const uint8_t pw_off[] ALIGN1 = {
49 offsetof(struct passwd, pw_name), /* 0 S */
50 offsetof(struct passwd, pw_passwd), /* 1 s */
51 offsetof(struct passwd, pw_uid), /* 2 I */
52 offsetof(struct passwd, pw_gid), /* 3 I */
53 offsetof(struct passwd, pw_gecos), /* 4 s */
54 offsetof(struct passwd, pw_dir), /* 5 S */
55 offsetof(struct passwd, pw_shell) /* 6 S */
57 static const uint8_t gr_off[] ALIGN1 = {
58 offsetof(struct group, gr_name), /* 0 S */
59 offsetof(struct group, gr_passwd), /* 1 s */
60 offsetof(struct group, gr_gid), /* 2 I */
61 offsetof(struct group, gr_mem) /* 3 m (char **) */
63 #if ENABLE_USE_BB_SHADOW
64 static const uint8_t sp_off[] ALIGN1 = {
65 offsetof(struct spwd, sp_namp), /* 0 S Login name */
66 offsetof(struct spwd, sp_pwdp), /* 1 s Encrypted password */
67 offsetof(struct spwd, sp_lstchg), /* 2 l */
68 offsetof(struct spwd, sp_min), /* 3 l */
69 offsetof(struct spwd, sp_max), /* 4 l */
70 offsetof(struct spwd, sp_warn), /* 5 l */
71 offsetof(struct spwd, sp_inact), /* 6 l */
72 offsetof(struct spwd, sp_expire), /* 7 l */
73 offsetof(struct spwd, sp_flag) /* 8 r Reserved */
94 static const struct const_passdb const_pw_db = { _PATH_PASSWD, pw_off, PW_DEF, sizeof(PW_DEF)-1, sizeof(struct passwd) };
95 static const struct const_passdb const_gr_db = { _PATH_GROUP , gr_off, GR_DEF, sizeof(GR_DEF)-1, sizeof(struct group) };
96 #if ENABLE_USE_BB_SHADOW
97 static const struct const_passdb const_sp_db = { _PATH_SHADOW, sp_off, SP_DEF, sizeof(SP_DEF)-1, sizeof(struct spwd) };
100 /* We avoid having big global data. */
102 /* It's ok to use one buffer for getpwuid and getpwnam. Manpage says:
103 * "The return value may point to a static area, and may be overwritten
104 * by subsequent calls to getpwent(), getpwnam(), or getpwuid()."
106 struct passdb db[2 + ENABLE_USE_BB_SHADOW];
110 static struct statics *ptr_to_statics;
111 #define S (*ptr_to_statics)
112 #define has_S (ptr_to_statics)
114 static struct statics *get_S(void)
116 if (!ptr_to_statics) {
117 ptr_to_statics = xzalloc(sizeof(S));
118 memcpy(&S.db[0], &const_pw_db, sizeof(const_pw_db));
119 memcpy(&S.db[1], &const_gr_db, sizeof(const_gr_db));
120 #if ENABLE_USE_BB_SHADOW
121 memcpy(&S.db[2], &const_sp_db, sizeof(const_sp_db));
124 return ptr_to_statics;
127 /**********************************************************************/
128 /* Internal functions */
129 /**********************************************************************/
131 /* Divide the passwd/group/shadow record in fields
132 * by substituting the given delimeter
133 * e.g. ':' or ',' with '\0'.
134 * Returns the number of fields found.
135 * Strips leading and trailing whitespace in fields.
137 static int tokenize(char *buffer, int ch)
145 overlapping_strcpy(s, skip_whitespace(s));
147 if (*p == ch || *p == '\0') {
149 while (p != s && isblank(p[-1]))
152 overlapping_strcpy(p, end);
155 S.tokenize_end = p + 1;
165 /* Returns !NULL on success and matching line broken up in fields by '\0' in buf.
166 * We require the expected number of fields to be found.
168 static char *parse_common(FILE *fp, const char *filename,
170 const char *key, int field_pos)
175 while ((buf = xmalloc_fgetline(fp)) != NULL) {
177 /* Skip empty lines, comment lines */
178 if (buf[0] == '\0' || buf[0] == '#')
180 if (tokenize(buf, ':') != n_fields) {
181 /* number of fields is wrong */
182 bb_error_msg("bad record at %s:%u", filename, count);
186 /* Ugly hack: group db requires aqdditional buffer space
187 * for members[] array. If there is only one group, we need space
188 * for 3 pointers: alignment padding, group name, NULL.
189 * +1 for every additional group.
191 if (n_fields == sizeof(GR_DEF)-1) { /* if we read group file */
197 resize *= sizeof(char**);
198 resize += S.tokenize_end - buf;
199 buf = xrealloc(buf, resize);
203 /* no key specified: sequential read, return a record */
206 if (strcmp(key, nth_string(buf, field_pos)) == 0) {
217 static char *parse_file(const char *filename,
219 const char *key, int field_pos)
222 FILE *fp = fopen_for_read(filename);
225 buf = parse_common(fp, filename, n_fields, key, field_pos);
231 /* Convert passwd/group/shadow file record in buffer to a struct */
232 static void *convert_to_struct(const char *def, const unsigned char *off,
233 char *buffer, void *result)
236 void *member = (char*)result + (*off++);
238 if ((*def | 0x20) == 's') { /* s or S */
239 *(char **)member = (char*)buffer;
240 if (!buffer[0] && (*def == 'S')) {
245 *(int *)member = bb_strtou(buffer, NULL, 10);
247 #if ENABLE_USE_BB_SHADOW
251 n = bb_strtol(buffer, NULL, 10);
257 int i = tokenize(buffer, ',');
259 /* Store members[] after buffer's end.
260 * This is safe ONLY because there is a hack
261 * in parse_common() which allocates additional space
262 * at the end of malloced buffer!
265 ( ((intptr_t)S.tokenize_end + sizeof(char**))
266 & -(intptr_t)sizeof(char**)
269 ((struct group *)result)->gr_mem = members;
272 buffer += strlen(buffer) + 1;
276 /* def "r" does nothing */
281 buffer += strlen(buffer) + 1;
289 /****** getXXnam/id_r */
291 static int getXXnam_r(const char *name, uintptr_t db_and_field_pos, char *buffer, size_t buflen,
294 void *struct_buf = *(void**)result;
298 db = &S.db[db_and_field_pos >> 2];
300 *(void**)result = NULL;
301 buf = parse_file(db->filename, db->numfields, name, db_and_field_pos & 3);
303 size_t size = S.tokenize_end - buf;
307 memcpy(buffer, buf, size);
308 *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf);
312 /* "The reentrant functions return zero on success.
313 * In case of error, an error number is returned."
314 * NB: not finding the record is also a "success" here:
319 int getpwnam_r(const char *name, struct passwd *struct_buf, char *buffer, size_t buflen,
320 struct passwd **result)
322 /* Why the "store buffer address in result" trick?
323 * This way, getXXnam_r has the same ABI signature as getpwnam_r,
324 * hopefully compiler can optimize tall call better in this case.
326 *result = struct_buf;
327 return getXXnam_r(name, (0 << 2) + 0, buffer, buflen, result);
329 #if ENABLE_USE_BB_SHADOW
330 int getspnam_r(const char *name, struct spwd *struct_buf, char *buffer, size_t buflen,
331 struct spwd **result)
333 *result = struct_buf;
334 return getXXnam_r(name, (2 << 2) + 0, buffer, buflen, result);
338 /****** getXXent_r */
340 static int getXXent_r(void *struct_buf, char *buffer, size_t buflen,
349 *(void**)result = NULL;
352 db->fp = fopen_for_read(db->filename);
356 close_on_exec_on(fileno(db->fp));
359 buf = parse_common(db->fp, db->filename, db->numfields, /*no search key:*/ NULL, 0);
361 size_t size = S.tokenize_end - buf;
365 memcpy(buffer, buf, size);
366 *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf);
370 /* "The reentrant functions return zero on success.
371 * In case of error, an error number is returned."
372 * NB: not finding the record is also a "success" here:
377 int getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen, struct passwd **result)
379 return getXXent_r(struct_buf, buffer, buflen, result, 0);
382 /****** getXXnam/id */
384 static void *getXXnam(const char *name, unsigned db_and_field_pos)
390 db = &S.db[db_and_field_pos >> 2];
395 db->fp = fopen_for_read(db->filename);
399 close_on_exec_on(fileno(db->fp));
404 buf = parse_common(db->fp, db->filename, db->numfields, name, db_and_field_pos & 3);
406 db->malloced = xzalloc(db->size_of);
407 result = convert_to_struct(db->def, db->off, buf, db->malloced);
412 struct passwd *getpwnam(const char *name)
414 return getXXnam(name, (0 << 2) + 0);
416 struct group *getgrnam(const char *name)
418 return getXXnam(name, (1 << 2) + 0);
420 struct passwd *getpwuid(uid_t id)
422 return getXXnam(utoa(id), (0 << 2) + 2);
424 struct group *getgrgid(gid_t id)
426 return getXXnam(utoa(id), (1 << 2) + 2);
429 /****** end/setXXend */
433 if (has_S && S.db[0].fp) {
440 if (has_S && S.db[0].fp) {
446 if (has_S && S.db[1].fp) {
452 /****** initgroups and getgrouplist */
454 static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr,
455 const char *user, gid_t gid)
463 /* We alloc space for 8 gids at a time. */
464 group_list = xmalloc(8 * sizeof(group_list[0]));
468 fp = fopen_for_read(_PATH_GROUP);
471 while ((buf = parse_common(fp, _PATH_GROUP, sizeof(GR_DEF)-1, NULL, 0)) != NULL) {
474 if (!convert_to_struct(GR_DEF, gr_off, buf, &group))
476 if (group.gr_gid == gid)
478 for (m = group.gr_mem; *m; m++) {
479 if (strcmp(*m, user) != 0)
481 group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups);
482 group_list[ngroups++] = group.gr_gid;
490 *ngroups_ptr = ngroups;
494 int initgroups(const char *user, gid_t gid)
497 gid_t *group_list = getgrouplist_internal(&ngroups, user, gid);
499 ngroups = setgroups(ngroups, group_list);
504 int getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups)
506 int ngroups_old = *ngroups;
507 gid_t *group_list = getgrouplist_internal(ngroups, user, gid);
509 if (*ngroups <= ngroups_old) {
510 ngroups_old = *ngroups;
511 memcpy(groups, group_list, ngroups_old * sizeof(groups[0]));