blob: cefbc8a7e17172952ad6fea3686882bcfc39ab3c
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 | |
34 | struct 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 | }; |
41 | struct 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 | |
64 | static 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 | }; |
77 | static 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 |
88 | static 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. */ |
106 | struct 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 | |
117 | static 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 |
122 | static 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 | |
133 | static 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 | */ |
157 | static 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 | */ |
188 | static 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 | |
236 | static 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 */ |
250 | static 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 | |
315 | static 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 | |
338 | static 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 | |
356 | static 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 | |
370 | int 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 |
382 | int 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 | |
393 | static 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 | |
413 | int 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 | |
423 | static 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 | |
440 | struct passwd* FAST_FUNC getpwent(void) |
441 | { |
442 | return getXXent(0); |
443 | } |
444 | |
445 | /****** getXXnam/id */ |
446 | |
447 | static 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 | |
456 | struct passwd* FAST_FUNC getpwnam(const char *name) |
457 | { |
458 | return getXXnam(name, (0 << 2) + 0); |
459 | } |
460 | struct group* FAST_FUNC getgrnam(const char *name) |
461 | { |
462 | return getXXnam(name, (1 << 2) + 0); |
463 | } |
464 | struct passwd* FAST_FUNC getpwuid(uid_t id) |
465 | { |
466 | return getXXnam(utoa(id), (0 << 2) + 2); |
467 | } |
468 | struct group* FAST_FUNC getgrgid(gid_t id) |
469 | { |
470 | return getXXnam(utoa(id), (1 << 2) + 2); |
471 | } |
472 | |
473 | /****** end/setXXend */ |
474 | |
475 | void 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 | } |
482 | void FAST_FUNC setpwent(void) |
483 | { |
484 | if (has_S && S.db[0].fp) { |
485 | rewind(S.db[0].fp); |
486 | } |
487 | } |
488 | void 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 | |
498 | static 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 | |
537 | int 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 | |
547 | int 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 | } |
561 |