libpwdgrp: fix a memory leak in getXXnam (we did not save address of string buf)
[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.
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 or trailing whitespace in fields is stripped.
19  *      c) some fields are not allowed to be empty (e.g. username, uid/gid,
20  *         homedir, shell) and in this case NULL is returned and errno is
21  *         set to EINVAL. This behaviour could be easily changed by
22  *         modifying PW_DEF, GR_DEF, SP_DEF strings (uppercase
23  *         makes a field mandatory).
24  *      d) the string representing uid/gid must be convertible by strtoXX
25  *         functions, or errno is set to EINVAL.
26  *      e) leading or trailing whitespace in group member names are stripped.
27  * 3) the internal function for getgrouplist uses dynamically allocated buffer.
28  * 4) at the moment only the functions really used by busybox code are
29  *    implemented, if you need a particular missing function it should be
30  *    easy to write it by using the internal common code.
31  */
32
33 #include "libbb.h"
34
35 struct const_passdb {
36         const char *filename;
37         const char def[10];
38         const uint8_t off[9];
39         uint8_t numfields;
40 };
41 struct passdb {
42         const char *filename;
43         const char def[10];
44         const uint8_t off[9];
45         uint8_t numfields;
46         FILE *fp;
47         char *malloced;
48         char struct_result[0
49                                 | sizeof(struct passwd)
50                                 | sizeof(struct group)
51         IF_USE_BB_SHADOW(       | sizeof(struct spwd)  )
52         /* bitwise OR above is poor man's max(a,b,c) */
53         ];
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
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
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
102 };
103 #endif
104
105 /* We avoid having big global data. */
106 struct statics {
107         /* It's ok to use same buffer (db[0].struct_result) 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 };
115
116 static struct statics *ptr_to_statics;
117 #define S     (*ptr_to_statics)
118 #define has_S (ptr_to_statics)
119
120 static struct statics *get_S(void)
121 {
122         if (!ptr_to_statics) {
123                 ptr_to_statics = xzalloc(sizeof(S));
124                 memcpy(&S.db[0], &const_pw_db, sizeof(const_pw_db));
125                 memcpy(&S.db[1], &const_gr_db, sizeof(const_gr_db));
126 #if ENABLE_USE_BB_SHADOW
127                 memcpy(&S.db[2], &const_sp_db, sizeof(const_sp_db));
128 #endif
129         }
130         return ptr_to_statics;
131 }
132
133 /* Internal functions */
134
135 /* Divide the passwd/group/shadow record in fields
136  * by substituting the given delimeter
137  * e.g. ':' or ',' with '\0'.
138  * Returns the number of fields found.
139  * Strips leading and trailing whitespace in fields.
140  */
141 static int tokenize(char *buffer, int ch)
142 {
143         char *p = buffer;
144         char *s = p;
145         int num_fields = 0;
146
147         for (;;) {
148                 if (isblank(*s)) {
149                         overlapping_strcpy(s, skip_whitespace(s));
150                 }
151                 if (*p == ch || *p == '\0') {
152                         char *end = p;
153                         while (p != s && isblank(p[-1]))
154                                 p--;
155                         if (p != end)
156                                 overlapping_strcpy(p, end);
157                         num_fields++;
158                         if (*end == '\0') {
159                                 S.tokenize_end = p + 1;
160                                 return num_fields;
161                         }
162                         *p = '\0';
163                         s = p + 1;
164                 }
165                 p++;
166         }
167 }
168
169 /* Returns !NULL on success and matching line broken up in fields by '\0' in buf.
170  * We require the expected number of fields to be found.
171  */
172 static char *parse_common(FILE *fp, const char *filename,
173                 int n_fields,
174                 const char *key, int field_pos)
175 {
176         int count = 0;
177         char *buf;
178
179         while ((buf = xmalloc_fgetline(fp)) != NULL) {
180                 count++;
181                 /* Skip empty lines, comment lines */
182                 if (buf[0] == '\0' || buf[0] == '#')
183                         goto free_and_next;
184                 if (tokenize(buf, ':') != n_fields) {
185                         /* number of fields is wrong */
186                         bb_error_msg("bad record at %s:%u", filename, count);
187                         goto free_and_next;
188                 }
189
190 /* Ugly hack: group db requires aqdditional buffer space
191  * for members[] array. If there is only one group, we need space
192  * for 3 pointers: alignment padding, group name, NULL.
193  * +1 for every additional group.
194  */
195                 if (n_fields == sizeof(GR_DEF)-1) { /* if we read group file */
196                         int resize = 3;
197                         char *p = buf;
198                         while (*p)
199                                 if (*p++ == ',')
200                                         resize++;
201                         resize *= sizeof(char**);
202                         resize += S.tokenize_end - buf;
203                         buf = xrealloc(buf, resize);
204                 }
205
206                 if (!key) {
207                         /* no key specified: sequential read, return a record */
208                         break;
209                 }
210                 if (strcmp(key, nth_string(buf, field_pos)) == 0) {
211                         /* record found */
212                         break;
213                 }
214  free_and_next:
215                 free(buf);
216         }
217
218         return buf;
219 }
220
221 static char *parse_file(const char *filename,
222                 int n_fields,
223                 const char *key, int field_pos)
224 {
225         char *buf = NULL;
226         FILE *fp = fopen_for_read(filename);
227
228         if (fp) {
229                 buf = parse_common(fp, filename, n_fields, key, field_pos);
230                 fclose(fp);
231         }
232         return buf;
233 }
234
235 /* Convert passwd/group/shadow file record in buffer to a struct */
236 static void *convert_to_struct(struct passdb *db,
237                 char *buffer, void *result)
238 {
239         const char *def = db->def;
240         const uint8_t *off = db->off;
241
242 /* TODO? for consistency, zero out all fields */
243 /*      memset(result, 0, size_of_result);*/
244
245         for (;;) {
246                 void *member = (char*)result + (*off++);
247
248                 if ((*def | 0x20) == 's') { /* s or S */
249                         *(char **)member = (char*)buffer;
250                         if (!buffer[0] && (*def == 'S')) {
251                                 errno = EINVAL;
252                         }
253                 }
254                 if (*def == 'I') {
255                         *(int *)member = bb_strtou(buffer, NULL, 10);
256                 }
257 #if ENABLE_USE_BB_SHADOW
258                 if (*def == 'l') {
259                         long n = -1;
260                         if (buffer[0])
261                                 n = bb_strtol(buffer, NULL, 10);
262                         *(long *)member = n;
263                 }
264 #endif
265                 if (*def == 'm') {
266                         char **members;
267                         int i = tokenize(buffer, ',');
268
269                         /* Store members[] after buffer's end.
270                          * This is safe ONLY because there is a hack
271                          * in parse_common() which allocates additional space
272                          * at the end of malloced buffer!
273                          */
274                         members = (char **)
275                                 ( ((intptr_t)S.tokenize_end + sizeof(char**))
276                                 & -(intptr_t)sizeof(char**)
277                                 );
278
279                         ((struct group *)result)->gr_mem = members;
280                         while (--i >= 0) {
281                                 *members++ = buffer;
282                                 buffer += strlen(buffer) + 1;
283                         }
284                         *members = NULL;
285                 }
286                 /* def "r" does nothing */
287
288                 def++;
289                 if (*def == '\0')
290                         break;
291                 buffer += strlen(buffer) + 1;
292         }
293
294         if (errno)
295                 result = NULL;
296         return result;
297 }
298
299 /****** getXXnam/id_r */
300
301 static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos,
302                 char *buffer, size_t buflen,
303                 void *result)
304 {
305         void *struct_buf = *(void**)result;
306         char *buf;
307         struct passdb *db;
308         get_S();
309         db = &S.db[db_and_field_pos >> 2];
310
311         *(void**)result = NULL;
312         buf = parse_file(db->filename, db->numfields, name, db_and_field_pos & 3);
313         if (buf) {
314                 size_t size = S.tokenize_end - buf;
315                 if (size > buflen) {
316                         errno = ERANGE;
317                 } else {
318                         memcpy(buffer, buf, size);
319                         *(void**)result = convert_to_struct(db, buffer, struct_buf);
320                 }
321                 free(buf);
322         }
323         /* "The reentrant functions return zero on success.
324          * In case of error, an error number is returned."
325          * NB: not finding the record is also a "success" here:
326          */
327         return errno;
328 }
329
330 int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf,
331                 char *buffer, size_t buflen,
332                 struct passwd **result)
333 {
334         /* Why the "store buffer address in result" trick?
335          * This way, getXXnam_r has the same ABI signature as getpwnam_r,
336          * hopefully compiler can optimize tail call better in this case.
337          */
338         *result = struct_buf;
339         return getXXnam_r(name, (0 << 2) + 0, buffer, buflen, result);
340 }
341 #if ENABLE_USE_BB_SHADOW
342 int FAST_FUNC getspnam_r(const char *name, struct spwd *struct_buf, char *buffer, size_t buflen,
343                 struct spwd **result)
344 {
345         *result = struct_buf;
346         return getXXnam_r(name, (2 << 2) + 0, buffer, buflen, result);
347 }
348 #endif
349
350 /****** getXXent_r */
351
352 static int FAST_FUNC getXXent_r(void *struct_buf, char *buffer, size_t buflen,
353                 void *result,
354                 unsigned db_idx)
355 {
356         char *buf;
357         struct passdb *db;
358         get_S();
359         db = &S.db[db_idx];
360
361         *(void**)result = NULL;
362
363         if (!db->fp) {
364                 db->fp = fopen_for_read(db->filename);
365                 if (!db->fp) {
366                         return errno;
367                 }
368                 close_on_exec_on(fileno(db->fp));
369         }
370
371         buf = parse_common(db->fp, db->filename, db->numfields, /*no search key:*/ NULL, 0);
372         if (buf) {
373                 size_t size = S.tokenize_end - buf;
374                 if (size > buflen) {
375                         errno = ERANGE;
376                 } else {
377                         memcpy(buffer, buf, size);
378                         *(void**)result = convert_to_struct(db, buffer, struct_buf);
379                 }
380                 free(buf);
381         }
382         /* "The reentrant functions return zero on success.
383          * In case of error, an error number is returned."
384          * NB: not finding the record is also a "success" here:
385          */
386         return errno;
387 }
388
389 int FAST_FUNC getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen, struct passwd **result)
390 {
391         return getXXent_r(struct_buf, buffer, buflen, result, 0);
392 }
393
394 /****** getXXnam/id */
395
396 static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos)
397 {
398         char *buf;
399         void *result;
400         struct passdb *db;
401         get_S();
402         db = &S.db[db_and_field_pos >> 2];
403
404         result = NULL;
405
406         if (!db->fp) {
407                 db->fp = fopen_for_read(db->filename);
408                 if (!db->fp) {
409                         return NULL;
410                 }
411                 close_on_exec_on(fileno(db->fp));
412         }
413
414         buf = parse_common(db->fp, db->filename, db->numfields, name, db_and_field_pos & 3);
415         if (buf) {
416                 free(db->malloced);
417                 db->malloced = buf;
418                 result = convert_to_struct(db, buf, db->struct_result);
419         }
420         return result;
421 }
422
423 struct passwd* FAST_FUNC getpwnam(const char *name)
424 {
425         return getXXnam(name, (0 << 2) + 0);
426 }
427 struct group* FAST_FUNC getgrnam(const char *name)
428 {
429         return getXXnam(name, (1 << 2) + 0);
430 }
431 struct passwd* FAST_FUNC getpwuid(uid_t id)
432 {
433         return getXXnam(utoa(id), (0 << 2) + 2);
434 }
435 struct group* FAST_FUNC getgrgid(gid_t id)
436 {
437         return getXXnam(utoa(id), (1 << 2) + 2);
438 }
439
440 /****** end/setXXend */
441
442 void FAST_FUNC endpwent(void)
443 {
444         if (has_S && S.db[0].fp) {
445                 fclose(S.db[0].fp);
446                 S.db[0].fp = NULL;
447         }
448 }
449 void FAST_FUNC setpwent(void)
450 {
451         if (has_S && S.db[0].fp) {
452                 rewind(S.db[0].fp);
453         }
454 }
455 void FAST_FUNC endgrent(void)
456 {
457         if (has_S && S.db[1].fp) {
458                 fclose(S.db[1].fp);
459                 S.db[1].fp = NULL;
460         }
461 }
462
463 /****** initgroups and getgrouplist */
464
465 static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr,
466                 const char *user, gid_t gid)
467 {
468         FILE *fp;
469         gid_t *group_list;
470         int ngroups;
471
472         get_S();
473
474         /* We alloc space for 8 gids at a time. */
475         group_list = xzalloc(8 * sizeof(group_list[0]));
476         group_list[0] = gid;
477         ngroups = 1;
478
479         fp = fopen_for_read(_PATH_GROUP);
480         if (fp) {
481                 char *buf;
482                 while ((buf = parse_common(fp, _PATH_GROUP, sizeof(GR_DEF)-1, NULL, 0)) != NULL) {
483                         char **m;
484                         struct group group;
485                         if (!convert_to_struct(&S.db[1], buf, &group))
486                                 goto next;
487                         if (group.gr_gid == gid)
488                                 goto next;
489                         for (m = group.gr_mem; *m; m++) {
490                                 if (strcmp(*m, user) != 0)
491                                         continue;
492                                 group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups);
493                                 group_list[ngroups++] = group.gr_gid;
494                                 break;
495                         }
496  next:
497                         free(buf);
498                 }
499                 fclose(fp);
500         }
501         *ngroups_ptr = ngroups;
502         return group_list;
503 }
504
505 int FAST_FUNC initgroups(const char *user, gid_t gid)
506 {
507         int ngroups;
508         gid_t *group_list = getgrouplist_internal(&ngroups, user, gid);
509
510         ngroups = setgroups(ngroups, group_list);
511         free(group_list);
512         return ngroups;
513 }
514
515 int FAST_FUNC getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups)
516 {
517         int ngroups_old = *ngroups;
518         gid_t *group_list = getgrouplist_internal(ngroups, user, gid);
519
520         if (*ngroups <= ngroups_old) {
521                 ngroups_old = *ngroups;
522                 memcpy(groups, group_list, ngroups_old * sizeof(groups[0]));
523         } else {
524                 ngroups_old = -1;
525         }
526         free(group_list);
527         return ngroups_old;
528 }