0d8e2bb5c400f2bf1bcf7c714a24fd9051a77d46
[oweals/busybox.git] / libpwdgrp / pwd_grp.c
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 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.
37  */
38
39 #include "libbb.h"
40
41 /* S = string not empty, s = string maybe empty,  */
42 /* I = uid,gid, l = long maybe empty, m = members,*/
43 /* r = reserved */
44 #define PW_DEF "SsIIsSS"
45 #define GR_DEF "SsIm"
46 #define SP_DEF "Ssllllllr"
47
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 */
56 };
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 **) */
62 };
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 */
74 };
75 #endif
76
77 struct const_passdb {
78         const char *filename;
79         const uint8_t *off;
80         const char def[10];
81         uint8_t numfields;
82         uint8_t size_of;
83 };
84 struct passdb {
85         const char *filename;
86         const uint8_t *off;
87         const char def[10];
88         uint8_t numfields;
89         uint8_t size_of;
90         FILE *fp;
91         void *malloced;
92 };
93
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) };
98 #endif
99
100 /* We avoid having big global data. */
101 struct statics {
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()."
105          */
106         struct passdb db[2 + ENABLE_USE_BB_SHADOW];
107         char *tokenize_end;
108 };
109
110 static struct statics *ptr_to_statics;
111 #define S     (*ptr_to_statics)
112 #define has_S (ptr_to_statics)
113
114 static struct statics *get_S(void)
115 {
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));
122 #endif
123         }
124         return ptr_to_statics;
125 }
126
127 /**********************************************************************/
128 /* Internal functions                                                 */
129 /**********************************************************************/
130
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.
136  */
137 static int tokenize(char *buffer, int ch)
138 {
139         char *p = buffer;
140         char *s = p;
141         int num_fields = 0;
142
143         for (;;) {
144                 if (isblank(*s)) {
145                         overlapping_strcpy(s, skip_whitespace(s));
146                 }
147                 if (*p == ch || *p == '\0') {
148                         char *end = p;
149                         while (p != s && isblank(p[-1]))
150                                 p--;
151                         if (p != end)
152                                 overlapping_strcpy(p, end);
153                         num_fields++;
154                         if (*end == '\0') {
155                                 S.tokenize_end = p + 1;
156                                 return num_fields;
157                         }
158                         *p = '\0';
159                         s = p + 1;
160                 }
161                 p++;
162         }
163 }
164
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.
167  */
168 static char *parse_common(FILE *fp, const char *filename,
169                 int n_fields,
170                 const char *key, int field_pos)
171 {
172         int count = 0;
173         char *buf;
174
175         while ((buf = xmalloc_fgetline(fp)) != NULL) {
176                 count++;
177                 /* Skip empty lines, comment lines */
178                 if (buf[0] == '\0' || buf[0] == '#')
179                         goto free_and_next;
180                 if (tokenize(buf, ':') != n_fields) {
181                         /* number of fields is wrong */
182                         bb_error_msg("bad record at %s:%u", filename, count);
183                         goto free_and_next;
184                 }
185
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.
190  */
191                 if (n_fields == sizeof(GR_DEF)-1) { /* if we read group file */
192                         int resize = 3;
193                         char *p = buf;
194                         while (*p)
195                                 if (*p++ == ',')
196                                         resize++;
197                         resize *= sizeof(char**);
198                         resize += S.tokenize_end - buf;
199                         buf = xrealloc(buf, resize);
200                 }
201
202                 if (!key) {
203                         /* no key specified: sequential read, return a record */
204                         break;
205                 }
206                 if (strcmp(key, nth_string(buf, field_pos)) == 0) {
207                         /* record found */
208                         break;
209                 }
210  free_and_next:
211                 free(buf);
212         }
213
214         return buf;
215 }
216
217 static char *parse_file(const char *filename,
218                 int n_fields,
219                 const char *key, int field_pos)
220 {
221         char *buf = NULL;
222         FILE *fp = fopen_for_read(filename);
223
224         if (fp) {
225                 buf = parse_common(fp, filename, n_fields, key, field_pos);
226                 fclose(fp);
227         }
228         return buf;
229 }
230
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)
234 {
235         for (;;) {
236                 void *member = (char*)result + (*off++);
237
238                 if ((*def | 0x20) == 's') { /* s or S */
239                         *(char **)member = (char*)buffer;
240                         if (!buffer[0] && (*def == 'S')) {
241                                 errno = EINVAL;
242                         }
243                 }
244                 if (*def == 'I') {
245                         *(int *)member = bb_strtou(buffer, NULL, 10);
246                 }
247 #if ENABLE_USE_BB_SHADOW
248                 if (*def == 'l') {
249                         long n = -1;
250                         if (buffer[0])
251                                 n = bb_strtol(buffer, NULL, 10);
252                         *(long *)member = n;
253                 }
254 #endif
255                 if (*def == 'm') {
256                         char **members;
257                         int i = tokenize(buffer, ',');
258
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!
263                          */
264                         members = (char **)
265                                 ( ((intptr_t)S.tokenize_end + sizeof(char**))
266                                 & -(intptr_t)sizeof(char**)
267                                 );
268
269                         ((struct group *)result)->gr_mem = members;
270                         while (--i >= 0) {
271                                 *members++ = buffer;
272                                 buffer += strlen(buffer) + 1;
273                         }
274                         *members = NULL;
275                 }
276                 /* def "r" does nothing */
277
278                 def++;
279                 if (*def == '\0')
280                         break;
281                 buffer += strlen(buffer) + 1;
282         }
283
284         if (errno)
285                 result = NULL;
286         return result;
287 }
288
289 /****** getXXnam/id_r */
290
291 static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos, char *buffer, size_t buflen,
292                 void *result)
293 {
294         void *struct_buf = *(void**)result;
295         char *buf;
296         struct passdb *db;
297         get_S();
298         db = &S.db[db_and_field_pos >> 2];
299
300         *(void**)result = NULL;
301         buf = parse_file(db->filename, db->numfields, name, db_and_field_pos & 3);
302         if (buf) {
303                 size_t size = S.tokenize_end - buf;
304                 if (size > buflen) {
305                         errno = ERANGE;
306                 } else {
307                         memcpy(buffer, buf, size);
308                         *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf);
309                 }
310                 free(buf);
311         }
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:
315          */
316         return errno;
317 }
318
319 int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf, char *buffer, size_t buflen,
320                                 struct passwd **result)
321 {
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 tail call better in this case.
325          */
326         *result = struct_buf;
327         return getXXnam_r(name, (0 << 2) + 0, buffer, buflen, result);
328 }
329 #if ENABLE_USE_BB_SHADOW
330 int FAST_FUNC getspnam_r(const char *name, struct spwd *struct_buf, char *buffer, size_t buflen,
331                            struct spwd **result)
332 {
333         *result = struct_buf;
334         return getXXnam_r(name, (2 << 2) + 0, buffer, buflen, result);
335 }
336 #endif
337
338 /****** getXXent_r */
339
340 static int FAST_FUNC getXXent_r(void *struct_buf, char *buffer, size_t buflen,
341                 void *result,
342                 unsigned db_idx)
343 {
344         char *buf;
345         struct passdb *db;
346         get_S();
347         db = &S.db[db_idx];
348
349         *(void**)result = NULL;
350
351         if (!db->fp) {
352                 db->fp = fopen_for_read(db->filename);
353                 if (!db->fp) {
354                         return errno;
355                 }
356                 close_on_exec_on(fileno(db->fp));
357         }
358
359         buf = parse_common(db->fp, db->filename, db->numfields, /*no search key:*/ NULL, 0);
360         if (buf) {
361                 size_t size = S.tokenize_end - buf;
362                 if (size > buflen) {
363                         errno = ERANGE;
364                 } else {
365                         memcpy(buffer, buf, size);
366                         *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf);
367                 }
368                 free(buf);
369         }
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:
373          */
374         return errno;
375 }
376
377 int FAST_FUNC getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen, struct passwd **result)
378 {
379         return getXXent_r(struct_buf, buffer, buflen, result, 0);
380 }
381
382 /****** getXXnam/id */
383
384 static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos)
385 {
386         char *buf;
387         void *result;
388         struct passdb *db;
389         get_S();
390         db = &S.db[db_and_field_pos >> 2];
391
392         result = NULL;
393
394         if (!db->fp) {
395                 db->fp = fopen_for_read(db->filename);
396                 if (!db->fp) {
397                         return NULL;
398                 }
399                 close_on_exec_on(fileno(db->fp));
400         }
401
402         free(db->malloced);
403         db->malloced = NULL;
404         buf = parse_common(db->fp, db->filename, db->numfields, name, db_and_field_pos & 3);
405         if (buf) {
406                 db->malloced = xzalloc(db->size_of);
407                 result = convert_to_struct(db->def, db->off, buf, db->malloced);
408         }
409         return result;
410 }
411
412 struct passwd* FAST_FUNC getpwnam(const char *name)
413 {
414         return getXXnam(name, (0 << 2) + 0);
415 }
416 struct group* FAST_FUNC getgrnam(const char *name)
417 {
418         return getXXnam(name, (1 << 2) + 0);
419 }
420 struct passwd* FAST_FUNC getpwuid(uid_t id)
421 {
422         return getXXnam(utoa(id), (0 << 2) + 2);
423 }
424 struct group* FAST_FUNC getgrgid(gid_t id)
425 {
426         return getXXnam(utoa(id), (1 << 2) + 2);
427 }
428
429 /****** end/setXXend */
430
431 void FAST_FUNC endpwent(void)
432 {
433         if (has_S && S.db[0].fp) {
434                 fclose(S.db[0].fp);
435                 S.db[0].fp = NULL;
436         }
437 }
438 void FAST_FUNC setpwent(void)
439 {
440         if (has_S && S.db[0].fp) {
441                 rewind(S.db[0].fp);
442         }
443 }
444 void FAST_FUNC endgrent(void)
445 {
446         if (has_S && S.db[1].fp) {
447                 fclose(S.db[1].fp);
448                 S.db[1].fp = NULL;
449         }
450 }
451
452 /****** initgroups and getgrouplist */
453
454 static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr,
455                 const char *user, gid_t gid)
456 {
457         FILE *fp;
458         gid_t *group_list;
459         int ngroups;
460
461         get_S();
462
463         /* We alloc space for 8 gids at a time. */
464         group_list = xmalloc(8 * sizeof(group_list[0]));
465         group_list[0] = gid;
466         ngroups = 1;
467
468         fp = fopen_for_read(_PATH_GROUP);
469         if (fp) {
470                 char *buf;
471                 while ((buf = parse_common(fp, _PATH_GROUP, sizeof(GR_DEF)-1, NULL, 0)) != NULL) {
472                         char **m;
473                         struct group group;
474                         if (!convert_to_struct(GR_DEF, gr_off, buf, &group))
475                                 goto next;
476                         if (group.gr_gid == gid)
477                                 goto next;
478                         for (m = group.gr_mem; *m; m++) {
479                                 if (strcmp(*m, user) != 0)
480                                         continue;
481                                 group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups);
482                                 group_list[ngroups++] = group.gr_gid;
483                                 break;
484                         }
485  next:
486                         free(buf);
487                 }
488                 fclose(fp);
489         }
490         *ngroups_ptr = ngroups;
491         return group_list;
492 }
493
494 int FAST_FUNC initgroups(const char *user, gid_t gid)
495 {
496         int ngroups;
497         gid_t *group_list = getgrouplist_internal(&ngroups, user, gid);
498
499         ngroups = setgroups(ngroups, group_list);
500         free(group_list);
501         return ngroups;
502 }
503
504 int FAST_FUNC getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups)
505 {
506         int ngroups_old = *ngroups;
507         gid_t *group_list = getgrouplist_internal(ngroups, user, gid);
508
509         if (*ngroups <= ngroups_old) {
510                 ngroups_old = *ngroups;
511                 memcpy(groups, group_list, ngroups_old * sizeof(groups[0]));
512         } else {
513                 ngroups_old = -1;
514         }
515         free(group_list);
516         return ngroups_old;
517 }