On Saturday 06 December 2014 15:24:01 tito wrote: > On Sunday 09 November 2014 20:56:07 tito wrote: > > On Sunday 28 September 2014 15:24:50 tito wrote: > > > On Saturday 27 September 2014 14:58:08 tito wrote: > > > > On Wednesday 24 September 2014 23:02:55 tito wrote: > > > > > On Saturday 20 September 2014 16:32:01 tito wrote: > > > > > > Hi, > > > > > > One more fix of a return value. > > > > > > > > > > Hi, > > > > > more return value and errno fixes. > > > > > > > > > Hi, > > > > make the tokenize function more robust. > > > > > > > Hi, > > > more minor errno fixes and code cleanups. > > > > > Hi, > > is there anybody bold out there that wants to review > > this stuff? > > > Hi, > make the parse_common function more robust. > Hi, after putting more work at this malloced libpwdgrp functions I think they now are pretty robust, with no memory leaks and able to handle more or less gracefully problematic passwd/group/shadow files. The main features of this implemantation are:
* 1) the buffer for getpwuid, getgrgid, getpwnam, getgrnam is dynamically * allocated and reused by later calls. if ERANGE error pops up it is * reallocated to the size of the longest line found so far in the * passwd/group files and reused for later calls. * If ENABLE_FEATURE_CLEAN_UP is set the buffers are freed at program * exit using the atexit function to make valgrind happy. * 2) the passwd/group files: * a) must contain the expected number of fields (as per count of field * delimeters ":") or we will complain with a error message. * b) leading or trailing whitespace in fields is allowed and handled. * c) some fields are not allowed to be empty (e.g. username, uid/gid, * homedir, shell) and in this case NULL is returned and errno is * set to EINVAL. This behaviour could be easily changed by * modifying PW_DEF, GR_DEF, SP_DEF strings (uppercase * makes a field mandatory). * d) the string representing uid/gid must be convertible by strtoXX * functions or NULL is returned and errno is set to EINVAL. * e) leading or trailing whitespaces in member names and empty members * are allowed and handled. * 3) the internal function for getgrouplist uses a dynamically allocated * buffer and retries with a bigger one in case it is to small; * 4) the _r functions use the user supplied buffers that are never reallocated * but use mostly the same common code as the other functions. * 5) at the moment only the functions really used by busybox code are * implemented, if you need a particular missing function it should be * easy to write it by using the internal common code. I've done some testing and everything seems to work as expected nonetheless some more testing by the list members and a good code review by more experienced programmers is needed as this lib calls stuff is very new for me and maybe I overlooked something obvious. Attached you will find a patch and a drop in replacement for pwd_grp.c for easier review and testing. Size increase is about 120 bytes due to the increased feature set, but the bloat-o-meter output looks somewhat suspicious: ./scripts/bloat-o-meter busybox_unstripped_original busybox_unstripped function old new delta convert_to_struct - 351 +351 parse_common - 214 +214 tokenize - 136 +136 getgrouplist_internal 185 309 +124 getpw_common - 96 +96 getgr_common - 96 +96 getpw_common_malloc - 78 +78 getgr_common_malloc - 78 +78 parse_file - 75 +75 .rodata 142136 142185 +49 bb_internal_getpwent_r 102 147 +45 my_pwd - 28 +28 my_grp - 16 +16 pwd_buffer_size - 4 +4 pwd_buffer - 4 +4 my_pw - 4 +4 my_gr - 4 +4 grp_buffer_size - 4 +4 grp_buffer - 4 +4 gr_off 3 4 +1 sp_off 9 8 -1 ptr_to_statics 4 - -4 bb_internal_getpwuid 37 19 -18 bb_internal_getspnam_r 119 97 -22 bb_internal_getgrgid 43 19 -24 bb_internal_getpwnam 37 11 -26 get_S 30 - -30 bb_internal_getgrnam 43 11 -32 bb_internal_getpwuid_r 111 - -111 bb_internal_getgrgid_r 111 - -111 bb__parsepwent 112 - -112 bb_internal_getpwnam_r 119 - -119 bb_internal_getgrnam_r 119 - -119 bb__parsespent 124 - -124 bb__pgsreader 194 - -194 bb__parsegrent 246 - -246 ------------------------------------------------------------------------------ (add/remove: 16/10 grow/shrink: 4/6 up/down: 1411/-1293) Total: 118 bytes Critics, hints, improvements are welcome. Ciao, Tito
New malloced getpw/grxxx functions for bb. Signed-off-by: Tito Ragusa <farmat...@tiscali.it> --- libpwdgrp/pwd_grp.c.original 2014-12-29 21:58:19.059565297 +0100 +++ libpwdgrp/pwd_grp.c 2014-12-29 22:00:18.000000000 +0100 @@ -17,592 +17,579 @@ * large group member lists will cause error returns. */ -#include "libbb.h" -#include <assert.h> - -/**********************************************************************/ -/* Sizes for statically allocated buffers. */ - -#define PWD_BUFFER_SIZE 256 -#define GRP_BUFFER_SIZE 256 - -/**********************************************************************/ -/* Prototypes for internal functions. */ - -static int bb__pgsreader( - int FAST_FUNC (*parserfunc)(void *d, char *line), - void *data, - char *__restrict line_buff, - size_t buflen, - FILE *f); +/* Copyright (C) 2014 Tito Ragusa <farmat...@tiscali.it> + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ +/* This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY!! + * + * Rewrite of some parts. Main differences are: + * + * 1) the buffer for getpwuid, getgrgid, getpwnam, getgrnam is dynamically + * allocated and reused by later calls. if ERANGE error pops up it is + * reallocated to the size of the longest line found so far in the + * passwd/group files and reused for later calls. + * If ENABLE_FEATURE_CLEAN_UP is set the buffers are freed at program + * exit using the atexit function to make valgrind happy. + * 2) the passwd/group files: + * a) must contain the expected number of fields (as per count of field + * delimeters ":") or we will complain with a error message. + * b) leading or trailing whitespace in fields is allowed and handled. + * c) some fields are not allowed to be empty (e.g. username, uid/gid, + * homedir, shell) and in this case NULL is returned and errno is + * set to EINVAL. This behaviour could be easily changed by + * modifying PW_DEF, GR_DEF, SP_DEF strings (uppercase + * makes a field mandatory). + * d) the string representing uid/gid must be convertible by strtoXX + * functions or NULL is returned and errno is set to EINVAL. + * e) leading or trailing whitespaces in member names and empty members + * are allowed and handled. + * 3) the internal function for getgrouplist uses a dynamically allocated + * buffer and retries with a bigger one in case it is to small; + * 4) the _r functions use the user supplied buffers that are never reallocated + * but use mostly the same common code as the other functions. + * 5) at the moment only the functions really used by busybox code are + * implemented, if you need a particular missing function it should be + * easy to write it by using the internal common code. + */ -static int FAST_FUNC bb__parsepwent(void *pw, char *line); -static int FAST_FUNC bb__parsegrent(void *gr, char *line); +#include "libbb.h" +/* S = string not empty, s = string maybe empty, */ +/* I = uid,gid, l = long maybe empty, m = members,*/ +/* r = reserved */ +#define PW_DEF "SsIIsSS" +static const char *pw_def = PW_DEF; +#define _PW_FIELDS sizeof(PW_DEF)-1 +#define GR_DEF "SsIm" +static const char *gr_def = GR_DEF; +#define _GR_FIELDS sizeof(GR_DEF)-1 #if ENABLE_USE_BB_SHADOW -static int FAST_FUNC bb__parsespent(void *sp, char *line); -#endif +#define SP_DEF "Ssllllllr" +static const char *sp_def = SP_DEF; +#define _SP_FIELDS sizeof(SP_DEF)-1 +#endif + +/* Initial buffer size */ +#define PWD_GRP_BUFSIZE 256 +/* for getpw_common_malloc */ +static char *pwd_buffer = NULL; +static size_t pwd_buffer_size = PWD_GRP_BUFSIZE; +struct passwd *my_pw; +struct passwd my_pwd; +/* for getgr_common_malloc */ +static char *grp_buffer = NULL; +static size_t grp_buffer_size = PWD_GRP_BUFSIZE; +struct group *my_gr; +struct group my_grp; +/* for setpwent, getpwent_r, endpwent */ +static FILE *pwf = NULL; +/* for setgrent, getgrent_r, endgrent */ +static FILE *grf = NULL; + +/* We do no file locking */ +#define LOCK ((void) 0) +#define UNLOCK ((void) 0) /**********************************************************************/ -/* We avoid having big global data. */ - -struct statics { - /* Smaller things first */ - /* It's ok to use one buffer for getpwuid and getpwnam. Manpage says: - * "The return value may point to a static area, and may be overwritten - * by subsequent calls to getpwent(), getpwnam(), or getpwuid()." - */ - struct passwd getpw_resultbuf; - struct group getgr_resultbuf; - - char getpw_buffer[PWD_BUFFER_SIZE]; - char getgr_buffer[GRP_BUFFER_SIZE]; -#if 0 //ENABLE_USE_BB_SHADOW - struct spwd getsp_resultbuf; - char getsp_buffer[PWD_BUFFER_SIZE]; -#endif -// Not converted - too small to bother -//pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; -//FILE *pwf /*= NULL*/; -//FILE *grf /*= NULL*/; -//FILE *spf /*= NULL*/; -}; - -static struct statics *ptr_to_statics; - -static struct statics *get_S(void) -{ - if (!ptr_to_statics) - ptr_to_statics = xzalloc(sizeof(*ptr_to_statics)); - return ptr_to_statics; -} - -/* Always use in this order, get_S() must be called first */ -#define RESULTBUF(name) &((S = get_S())->name##_resultbuf) -#define BUFFER(name) (S->name##_buffer) - -/**********************************************************************/ -/* For the various fget??ent_r funcs, return - * - * 0: success - * ENOENT: end-of-file encountered - * ERANGE: buflen too small - * other error values possible. See bb__pgsreader. - * - * Also, *result == resultbuf on success and NULL on failure. - * - * NOTE: glibc difference - For the ENOENT case, glibc also sets errno. - * We do not, as it really isn't an error if we reach the end-of-file. - * Doing so is analogous to having fgetc() set errno on EOF. - */ +/* Internal functions */ /**********************************************************************/ -int fgetpwent_r(FILE *__restrict stream, struct passwd *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct passwd **__restrict result) +/* Divide the passwd/group/shadow record in fields + * by substituting the given delimeter + * e.g. ':' or ',' with '\0'. + * Returns the number of fields found. + * Strips leading or trailing whitespace in fields. + */ +static int FAST_FUNC tokenize(char *buffer, int ch) { - int rv; + char *p = buffer; + char *s = p; + int num_fields = 1; - *result = NULL; - - rv = bb__pgsreader(bb__parsepwent, resultbuf, buffer, buflen, stream); - if (!rv) { - *result = resultbuf; + while (*p) { + while (isspace(*s)) { + memmove(s, s + 1, strlen(s + 1) + 1); + } + if (*p == ch) { + while (p != s && isspace(*(p - 1))) { + memmove(p - 1, p, strlen(p) + 1); + p = p - 1; + } + *p = '\0'; + num_fields++; + s = p + 1; + } + p++; } - - return rv; + return num_fields; } -int fgetgrent_r(FILE *__restrict stream, struct group *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct group **__restrict result) +/* Fill the buffer linebuf up to len with + * a line of a passwd/group file. + * Returns 0 on success, ENOENT on EOF and + * ERANGE if buffer is not big enough, + * in the latter case the size needed + * is set in size_t *len. + */ +static int FAST_FUNC get_record_line(FILE *file, char *linebuf, size_t *len) { - int rv; + int ch; + int idx = 0; - *result = NULL; - - rv = bb__pgsreader(bb__parsegrent, resultbuf, buffer, buflen, stream); - if (!rv) { - *result = resultbuf; + while ((ch = getc(file)) != EOF) { + if (idx < *len) { + linebuf[idx] = (char) ch; + } + idx++; + if (ch == '\n' || ch == '\0') + break; } - - return rv; -} - -#if ENABLE_USE_BB_SHADOW -#ifdef UNUSED_FOR_NOW -int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct spwd **__restrict result) + if (idx > *len) { + *len = idx; + return ERANGE; + } + /* return ENOENT only on EOF and no data */ + if (idx > 0) { + /* terminate string and remove trailing '\n' if any.*/ + linebuf[idx - 1] = '\0'; + return 0; + } + return ENOENT; +} + +/* Returns 0 on success and matching line broken up in fields by '\0' in buf or + * error. We require the expected number of fields to be found. + * size_t *len is used to check that the buffer provided is big enough, if not + * we reuse size_t *len to pass the needed length to the caller. + */ +static int FAST_FUNC parse_common(FILE *f, const char *key, int field_pos, + char *buf, size_t *len, const char *filename, int n_fields) { - int rv; - - *result = NULL; - - rv = bb__pgsreader(bb__parsespent, resultbuf, buffer, buflen, stream); - if (!rv) { - *result = resultbuf; + int count = 0; + int error; + const char *s; + + while ((error = get_record_line(f, buf, len)) == 0) { + count++; + /* Skip empty lines, comment lines */ + if (buf[0] != '\0' && buf[0] != '#') { + if (tokenize(buf, ':') == n_fields) { + if (field_pos >= 0) { + if (*(s = nth_string(buf, field_pos))) { + if (key && *key) { + if (strcmp(key, s) == 0) { + /* record found */ + break; + } + } /* else skip: caller passed bad key */ + } /* else skip: field is empty */ + } /*else skip: the field we look for doesn't exist */ + if (field_pos < 0) { + /* No field specified: sequential read, return a record */ + break; + } + } else { + /* number of fields is wrong */ + bb_error_msg("%s: bad record at line %d", filename, count); + } + } } - - return rv; + return error; } -#endif -#endif -/**********************************************************************/ -/* For the various fget??ent funcs, return NULL on failure and a - * pointer to the appropriate struct (statically allocated) on success. - * TODO: audit & stop using these in bbox, they pull in static buffers */ -/**********************************************************************/ - -#ifdef UNUSED_SINCE_WE_AVOID_STATIC_BUFS -struct passwd *fgetpwent(FILE *stream) +static int FAST_FUNC parse_file(const char *filename, int n_fields, + const char *key, int field_pos, char *buf, size_t *len) { - struct statics *S; - struct passwd *resultbuf = RESULTBUF(getpw); - char *buffer = BUFFER(getpw); - struct passwd *result; + FILE *f = fopen_for_read(filename); + int ret = errno; - fgetpwent_r(stream, resultbuf, buffer, sizeof(BUFFER(getpw)), &result); - return result; + if (f) { + ret = parse_common(f, key, field_pos, buf, len, filename, n_fields); + fclose(f); + } + return ret; } -struct group *fgetgrent(FILE *stream) +/* Convert passwd/group/shadow file record in buffer to a struct */ +static void * FAST_FUNC convert_to_struct(const char *def, + const unsigned char *off, char *buffer, + void *result, int *error) { - struct statics *S; - struct group *resultbuf = RESULTBUF(getgr); - char *buffer = BUFFER(getgr); - struct group *result; - - fgetgrent_r(stream, resultbuf, buffer, sizeof(BUFFER(getgr)), &result); - return result; -} -#endif + int idx; + long ret; + char *m; + char **members = NULL; + for (idx = 0; def[idx] != '\0'; idx++) { + if (def[idx] == 'S' || def[idx] == 's') { + m = (char *) nth_string(buffer, idx); + *(char **)((char *)result + off[idx]) = m; + if (!*m && (def[idx] == 'S')) { + *error = EINVAL; + } + } + if (def[idx] == 'I') { + m = (char *) nth_string(buffer, idx); + *(int *)((int) result + off[idx]) = bb_strtou(m, NULL, 10); + if (errno) { + *error = EINVAL; + } + } #if ENABLE_USE_BB_SHADOW -#ifdef UNUSED_SINCE_WE_AVOID_STATIC_BUFS -struct spwd *fgetspent(FILE *stream) -{ - struct statics *S; - struct spwd *resultbuf = RESULTBUF(getsp); - char *buffer = BUFFER(getsp); - struct spwd *result; - - fgetspent_r(stream, resultbuf, buffer, sizeof(BUFFER(getsp)), &result); - return result; -} -#endif + if (def[idx] == 'l') { + char *endptr; -#ifdef UNUSED_FOR_NOW -int sgetspent_r(const char *string, struct spwd *result_buf, - char *buffer, size_t buflen, struct spwd **result) -{ - int rv = ERANGE; - - *result = NULL; - - if (buflen < PWD_BUFFER_SIZE) { - DO_ERANGE: - errno = rv; - goto DONE; - } + m = (char *) nth_string(buffer, idx); + ret = bb_strtol(m, &endptr, 10); + if (endptr == m) { + ret = -1L; + } else if (errno) { + *error = EINVAL; + } + *(long *)((long) result + off[idx]) = ret; + } +#endif + if (def[idx] == 'm') { + char *s = (char *) nth_string(buffer, idx); + int i = tokenize(s, ','); + char *p = (char *) nth_string(s, i); + /* Now align (p+1), rounding up. */ + /* Assumes sizeof(char **) is a power of 2. */ + members = (char **)((((intptr_t) p) + sizeof(char **)) + & ~((intptr_t)(sizeof(char **) - 1))); - if (string != buffer) { - if (strlen(string) >= buflen) { - goto DO_ERANGE; + ((struct group *) result)->gr_mem = members; + /* Pointing to char prior to first member. */ + p = s - 1; + while (1) { + if (*(p + 1)) + *members++ = ++p; + if (!--i) + break; + while (*++p) + continue; + } + *members = NULL; } - strcpy(buffer, string); } - - rv = bb__parsespent(result_buf, buffer); - if (!rv) { - *result = result_buf; + if (*error) { + result = NULL; + errno = *error; } - - DONE: - return rv; + return result; } -#endif -#endif /* ENABLE_USE_BB_SHADOW */ - -/**********************************************************************/ - -#define GETXXKEY_R_FUNC getpwnam_r -#define GETXXKEY_R_PARSER bb__parsepwent -#define GETXXKEY_R_ENTTYPE struct passwd -#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->pw_name, key)) -#define GETXXKEY_R_KEYTYPE const char *__restrict -#define GETXXKEY_R_PATHNAME _PATH_PASSWD -#include "pwd_grp_internal.c" - -#define GETXXKEY_R_FUNC getgrnam_r -#define GETXXKEY_R_PARSER bb__parsegrent -#define GETXXKEY_R_ENTTYPE struct group -#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->gr_name, key)) -#define GETXXKEY_R_KEYTYPE const char *__restrict -#define GETXXKEY_R_PATHNAME _PATH_GROUP -#include "pwd_grp_internal.c" #if ENABLE_USE_BB_SHADOW -#define GETXXKEY_R_FUNC getspnam_r -#define GETXXKEY_R_PARSER bb__parsespent -#define GETXXKEY_R_ENTTYPE struct spwd -#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->sp_namp, key)) -#define GETXXKEY_R_KEYTYPE const char *__restrict -#define GETXXKEY_R_PATHNAME _PATH_SHADOW -#include "pwd_grp_internal.c" -#endif - -#define GETXXKEY_R_FUNC getpwuid_r -#define GETXXKEY_R_PARSER bb__parsepwent -#define GETXXKEY_R_ENTTYPE struct passwd -#define GETXXKEY_R_TEST(ENT) ((ENT)->pw_uid == key) -#define GETXXKEY_R_KEYTYPE uid_t -#define GETXXKEY_R_PATHNAME _PATH_PASSWD -#include "pwd_grp_internal.c" - -#define GETXXKEY_R_FUNC getgrgid_r -#define GETXXKEY_R_PARSER bb__parsegrent -#define GETXXKEY_R_ENTTYPE struct group -#define GETXXKEY_R_TEST(ENT) ((ENT)->gr_gid == key) -#define GETXXKEY_R_KEYTYPE gid_t -#define GETXXKEY_R_PATHNAME _PATH_GROUP -#include "pwd_grp_internal.c" - -/**********************************************************************/ -/* TODO: audit & stop using these in bbox, they pull in static buffers */ +static const unsigned char sp_off[] ALIGN1 = { + offsetof(struct spwd, sp_namp), /* 1 - Login name */ + offsetof(struct spwd, sp_pwdp), /* 2 - Encrypted password */ + offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ + offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ + offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ + offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ + offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ + offsetof(struct spwd, sp_expire) /* 7 - not a char ptr */ +// ,offsetof(struct spwd, sp_flag) /* 8 - Reserved */ +}; -/* This one has many users */ -struct passwd *getpwuid(uid_t uid) +int getspnam_r(const char *name, struct spwd *spbuf, char *buf, size_t buflen, + struct spwd **spbufp) { - struct statics *S; - struct passwd *resultbuf = RESULTBUF(getpw); - char *buffer = BUFFER(getpw); - struct passwd *result; - - getpwuid_r(uid, resultbuf, buffer, sizeof(BUFFER(getpw)), &result); - return result; + int error; + size_t len = buflen; + + *spbufp = NULL; + error = parse_file(_PATH_SHADOW, _SP_FIELDS, name, 0, buf, &len); + if (error == 0) { + *spbufp = convert_to_struct(sp_def, sp_off, buf, spbuf, &error); + } + return error; } +#endif /* ENABLE_USE_BB_SHADOW */ -/* This one has many users */ -struct group *getgrgid(gid_t gid) -{ - struct statics *S; - struct group *resultbuf = RESULTBUF(getgr); - char *buffer = BUFFER(getgr); - struct group *result; +static const unsigned char pw_off[] ALIGN1 = { + offsetof(struct passwd, pw_name), /* 0 */ + offsetof(struct passwd, pw_passwd), /* 1 */ + offsetof(struct passwd, pw_uid), /* 2 - not a char ptr */ + offsetof(struct passwd, pw_gid), /* 3 - not a char ptr */ + offsetof(struct passwd, pw_gecos), /* 4 */ + offsetof(struct passwd, pw_dir), /* 5 */ + offsetof(struct passwd, pw_shell) /* 6 */ +}; - getgrgid_r(gid, resultbuf, buffer, sizeof(BUFFER(getgr)), &result); - return result; -} +static const unsigned char gr_off[] ALIGN1 = { + offsetof(struct group, gr_name), /* 0 */ + offsetof(struct group, gr_passwd), /* 1 */ + offsetof(struct group, gr_gid), /* 2 - not a char ptr */ + offsetof(struct group, gr_mem) /* 3 - char ** ptr */ +}; -#if 0 //ENABLE_USE_BB_SHADOW -/* This function is non-standard and is currently not built. It seems - * to have been created as a reentrant version of the non-standard - * functions getspuid. Why getspuid was added, I do not know. */ -int getspuid_r(uid_t uid, struct spwd *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct spwd **__restrict result) -{ - int rv; - struct passwd *pp; - struct passwd password; - char pwd_buff[PWD_BUFFER_SIZE]; +/* Common code for getpwxxx/_r functions. */ +static int FAST_FUNC getpw_common(const char *name, int field, + struct passwd *pwd, char *buf, size_t *buflen, + struct passwd **result) +{ + int error; *result = NULL; - rv = getpwuid_r(uid, &password, pwd_buff, sizeof(pwd_buff), &pp); - if (!rv) { - rv = getspnam_r(password.pw_name, resultbuf, buffer, buflen, result); + error = parse_file(_PATH_PASSWD, _PW_FIELDS, name, field, buf, buflen); + if (error == 0) { + *result = convert_to_struct(pw_def, pw_off, buf, pwd, &error); } - - return rv; + return (error == ENOENT) ? 0 : error; } -/* This function is non-standard and is currently not built. - * Why it was added, I do not know. */ -struct spwd *getspuid(uid_t uid) +/* Common code for getgrxxx/_r functions. */ +static int FAST_FUNC getgr_common(const char *name, int field, + struct group *grp, char *buf, size_t *buflen, + struct group **result) { - struct statics *S; - struct spwd *resultbuf = RESULTBUF(getsp); - char *buffer = BUFFER(getsp); - struct spwd *result; + int error; - getspuid_r(uid, resultbuf, buffer, sizeof(BUFFER(getsp)), &result); - return result; + *result = NULL; + error = parse_file(_PATH_GROUP, _GR_FIELDS, name, field, buf, buflen); + if (error == 0) { + *result = convert_to_struct(gr_def, gr_off, buf, grp, &error); + } + return (error == ENOENT) ? 0 : error; } -#endif -/* This one has many users */ -struct passwd *getpwnam(const char *name) +#if ENABLE_FEATURE_CLEAN_UP +static void FAST_FUNC free_pwd(void) { - struct statics *S; - struct passwd *resultbuf = RESULTBUF(getpw); - char *buffer = BUFFER(getpw); - struct passwd *result; - - getpwnam_r(name, resultbuf, buffer, sizeof(BUFFER(getpw)), &result); - return result; + free(pwd_buffer); } - -/* This one has many users */ -struct group *getgrnam(const char *name) +#endif +/* Common code for getpwxxx functions. */ +static struct passwd * FAST_FUNC getpw_common_malloc(const char *name, int field) { - struct statics *S; - struct group *resultbuf = RESULTBUF(getgr); - char *buffer = BUFFER(getgr); - struct group *result; - - getgrnam_r(name, resultbuf, buffer, sizeof(BUFFER(getgr)), &result); - return result; + int error = 0; +#if ENABLE_FEATURE_CLEAN_UP + if (pwd_buffer == NULL) { + atexit(free_pwd); + } +#endif +retry: + pwd_buffer = xrealloc(pwd_buffer, pwd_buffer_size * sizeof(char)); + error = getpw_common(name, field, &my_pwd, pwd_buffer, &pwd_buffer_size, &my_pw); + if (error == ERANGE) { + errno = 0; + goto retry; + } + return my_pw; } -#if 0 //ENABLE_USE_BB_SHADOW -struct spwd *getspnam(const char *name) +#if ENABLE_FEATURE_CLEAN_UP +static void FAST_FUNC free_grp(void) { - struct statics *S; - struct spwd *resultbuf = RESULTBUF(getsp); - char *buffer = BUFFER(getsp); - struct spwd *result; - - getspnam_r(name, resultbuf, buffer, sizeof(BUFFER(getsp)), &result); - return result; + free(grp_buffer); } #endif -/**********************************************************************/ - -/* FIXME: we don't have such CONFIG_xx - ?! */ - -#if defined CONFIG_USE_BB_THREADSAFE_SHADOW && defined PTHREAD_MUTEX_INITIALIZER -static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; -# define LOCK pthread_mutex_lock(&mylock) -# define UNLOCK pthread_mutex_unlock(&mylock); -#else -# define LOCK ((void) 0) -# define UNLOCK ((void) 0) -#endif - -static FILE *pwf /*= NULL*/; -void setpwent(void) +/* Common code for getgrxxx functions. */ +static struct group * FAST_FUNC getgr_common_malloc(const char *name, int field) { - LOCK; - if (pwf) { - rewind(pwf); + int error = 0; +#if ENABLE_FEATURE_CLEAN_UP + if (grp_buffer == NULL) { + atexit(free_grp); } - UNLOCK; -} - -void endpwent(void) -{ - LOCK; - if (pwf) { - fclose(pwf); - pwf = NULL; +#endif +retry: + grp_buffer = xrealloc(grp_buffer, grp_buffer_size * sizeof(char)); + error = getgr_common(name, field, &my_grp, grp_buffer, &grp_buffer_size, &my_gr); + if (error == ERANGE) { + errno = 0; + goto retry; } - UNLOCK; + return my_gr; } +/**********************************************************************/ -int getpwent_r(struct passwd *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct passwd **__restrict result) +int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { - int rv; + int error; + size_t len = buflen; LOCK; - *result = NULL; /* In case of error... */ - + *pwbufp = NULL; if (!pwf) { pwf = fopen_for_read(_PATH_PASSWD); if (!pwf) { - rv = errno; - goto ERR; + return errno; } close_on_exec_on(fileno(pwf)); } - - rv = bb__pgsreader(bb__parsepwent, resultbuf, buffer, buflen, pwf); - if (!rv) { - *result = resultbuf; - } - - ERR: - UNLOCK; - return rv; -} - -static FILE *grf /*= NULL*/; -void setgrent(void) -{ - LOCK; - if (grf) { - rewind(grf); + error = parse_common(pwf, NULL, -1, buf, &len, _PATH_PASSWD, _PW_FIELDS); + if (error == 0) { + *pwbufp = convert_to_struct(pw_def, pw_off, buf, pwbuf, &error); } UNLOCK; -} -void endgrent(void) -{ - LOCK; - if (grf) { - fclose(grf); - grf = NULL; - } - UNLOCK; + return error; } -int getgrent_r(struct group *__restrict resultbuf, - char *__restrict buffer, size_t buflen, - struct group **__restrict result) +int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { - int rv; - + int error; + size_t len = buflen; + LOCK; - *result = NULL; /* In case of error... */ - + *gbufp = NULL; if (!grf) { grf = fopen_for_read(_PATH_GROUP); if (!grf) { - rv = errno; - goto ERR; + return errno; } close_on_exec_on(fileno(grf)); } - - rv = bb__pgsreader(bb__parsegrent, resultbuf, buffer, buflen, grf); - if (!rv) { - *result = resultbuf; + error = parse_common(grf, NULL, -1, buf, &len, _PATH_GROUP, _GR_FIELDS); + if (error == 0) { + *gbufp = convert_to_struct(gr_def, gr_off, buf, gbuf, &error); } - - ERR: UNLOCK; - return rv; + + return error; } -#ifdef UNUSED_FOR_NOW -#if ENABLE_USE_BB_SHADOW -static FILE *spf /*= NULL*/; -void setspent(void) +int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, + struct passwd **result) { - LOCK; - if (spf) { - rewind(spf); - } - UNLOCK; + size_t len = buflen; + + return getpw_common(name, 0, pwd, buf, &len, result); } -void endspent(void) +int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, + struct group **result) { - LOCK; - if (spf) { - fclose(spf); - spf = NULL; - } - UNLOCK; + size_t len = buflen; + + return getgr_common(name, 0, grp, buf, &len, result); } -int getspent_r(struct spwd *resultbuf, char *buffer, - size_t buflen, struct spwd **result) +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, + struct passwd **result) { - int rv; - - LOCK; - *result = NULL; /* In case of error... */ + size_t len = buflen; - if (!spf) { - spf = fopen_for_read(_PATH_SHADOW); - if (!spf) { - rv = errno; - goto ERR; - } - close_on_exec_on(fileno(spf)); - } + return getpw_common(utoa(uid), 2, pwd, buf, &len, result); +} - rv = bb__pgsreader(bb__parsespent, resultbuf, buffer, buflen, spf); - if (!rv) { - *result = resultbuf; - } +int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, + struct group **result) +{ + size_t len = buflen; - ERR: - UNLOCK; - return rv; + return getgr_common(utoa(gid), 2, grp, buf, &len, result); } -#endif -#endif /* UNUSED_FOR_NOW */ -#ifdef UNUSED_SINCE_WE_AVOID_STATIC_BUFS -struct passwd *getpwent(void) +struct passwd *getpwnam(const char *name) { - static char line_buff[PWD_BUFFER_SIZE]; - static struct passwd pwd; - struct passwd *result; + return getpw_common_malloc(name, 0); +} - getpwent_r(&pwd, line_buff, sizeof(line_buff), &result); - return result; +struct group *getgrnam(const char *name) +{ + return getgr_common_malloc(name, 0); } -struct group *getgrent(void) +struct passwd *getpwuid(uid_t uid) { - static char line_buff[GRP_BUFFER_SIZE]; - static struct group gr; - struct group *result; + return getpw_common_malloc(utoa(uid), 2); +} - getgrent_r(&gr, line_buff, sizeof(line_buff), &result); - return result; +struct group *getgrgid(gid_t gid) +{ + return getgr_common_malloc(utoa(gid), 2); } -#if ENABLE_USE_BB_SHADOW -struct spwd *getspent(void) +void endpwent(void) { - static char line_buff[PWD_BUFFER_SIZE]; - static struct spwd spwd; - struct spwd *result; + LOCK; + if (pwf) { + fclose(pwf); + pwf = NULL; + } + UNLOCK; +} - getspent_r(&spwd, line_buff, sizeof(line_buff), &result); - return result; +void setpwent(void) +{ + LOCK; + if (pwf) { + rewind(pwf); + } + UNLOCK; } -struct spwd *sgetspent(const char *string) +void endgrent(void) { - static char line_buff[PWD_BUFFER_SIZE]; - static struct spwd spwd; - struct spwd *result; + LOCK; + if (grf) { + fclose(grf); + grf = NULL; + } + UNLOCK; +} - sgetspent_r(string, &spwd, line_buff, sizeof(line_buff), &result); - return result; +void setgrent(void) +{ + LOCK; + if (grf) { + rewind(grf); + } + UNLOCK; } -#endif -#endif /* UNUSED_SINCE_WE_AVOID_STATIC_BUFS */ -static gid_t *getgrouplist_internal(int *ngroups_ptr, const char *user, gid_t gid) +static gid_t * FAST_FUNC getgrouplist_internal(int *ngroups_ptr, + const char *user, gid_t gid) { - FILE *grfile; + FILE *f; + struct group *gr; + struct group grp; gid_t *group_list; int ngroups; - struct group group; - char buff[PWD_BUFFER_SIZE]; + char * buffer = NULL; + size_t buflen = PWD_GRP_BUFSIZE; + int ret = 0; - /* We alloc space for 8 gids at a time. */ - group_list = xmalloc(8 * sizeof(group_list[0])); - group_list[0] = gid; +retry: + group_list = NULL; ngroups = 1; - - grfile = fopen_for_read(_PATH_GROUP); - if (grfile) { - while (!bb__pgsreader(bb__parsegrent, &group, buff, sizeof(buff), grfile)) { - char **m; - assert(group.gr_mem); /* Must have at least a NULL terminator. */ - if (group.gr_gid == gid) - continue; - for (m = group.gr_mem; *m; m++) { - if (strcmp(*m, user) != 0) - continue; - group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups); - group_list[ngroups++] = group.gr_gid; - break; + + f = fopen_for_read(_PATH_GROUP); + if (f) { + group_list = xrealloc(group_list, 1 * sizeof(gid_t)); + *group_list = gid; + buffer = xrealloc(buffer, buflen * sizeof( char)); + while ((ret = parse_common(f, NULL, -1, buffer, &buflen, + _PATH_GROUP, _GR_FIELDS)) == 0) { + gr = convert_to_struct(gr_def, gr_off, buffer, &grp, &ret); + if (gr != NULL) { + if (gr->gr_gid != gid) { + /* reuse int ret */ + for (/*ret = 0*/; gr->gr_mem[ret] != NULL; ret++) { + if (strcmp(gr->gr_mem[ret], user) == 0) { + group_list = xrealloc(group_list, + (ngroups + 1) * sizeof(gid_t)); + group_list[ngroups++] = gr->gr_gid; + } + } + } } } - fclose(grfile); + fclose(f); + } + if (ret == ERANGE) { + /* buffer was to small, retry */ + free(group_list); + goto retry; } + free(buffer); + *ngroups_ptr = ngroups; return group_list; } @@ -631,409 +618,3 @@ int getgrouplist(const char *user, gid_t free(group_list); return ngroups_old; } - -#ifdef UNUSED_SINCE_WE_AVOID_STATIC_BUFS -int putpwent(const struct passwd *__restrict p, FILE *__restrict f) -{ - int rv = -1; - -#if 0 - /* glibc does this check */ - if (!p || !f) { - errno = EINVAL; - return rv; - } -#endif - - /* No extra thread locking is needed above what fprintf does. */ - if (fprintf(f, "%s:%s:%lu:%lu:%s:%s:%s\n", - p->pw_name, p->pw_passwd, - (unsigned long)(p->pw_uid), - (unsigned long)(p->pw_gid), - p->pw_gecos, p->pw_dir, p->pw_shell) >= 0 - ) { - rv = 0; - } - - return rv; -} - -int putgrent(const struct group *__restrict p, FILE *__restrict f) -{ - int rv = -1; - -#if 0 - /* glibc does this check */ - if (!p || !f) { - errno = EINVAL; - return rv; - } -#endif - - if (fprintf(f, "%s:%s:%lu:", - p->gr_name, p->gr_passwd, - (unsigned long)(p->gr_gid)) >= 0 - ) { - static const char format[] ALIGN1 = ",%s"; - - char **m; - const char *fmt; - - fmt = format + 1; - - assert(p->gr_mem); - m = p->gr_mem; - - while (1) { - if (!*m) { - if (fputc('\n', f) >= 0) { - rv = 0; - } - break; - } - if (fprintf(f, fmt, *m) < 0) { - break; - } - m++; - fmt = format; - } - } - - return rv; -} -#endif - -#if ENABLE_USE_BB_SHADOW -#ifdef UNUSED_FOR_NOW -static const unsigned char put_sp_off[] ALIGN1 = { - offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ - offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ - offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ - offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ - offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ - offsetof(struct spwd, sp_expire) /* 7 - not a char ptr */ -}; - -int putspent(const struct spwd *p, FILE *stream) -{ - const char *fmt; - long x; - int i; - int rv = -1; - - /* Unlike putpwent and putgrent, glibc does not check the args. */ - if (fprintf(stream, "%s:%s:", p->sp_namp, - (p->sp_pwdp ? p->sp_pwdp : "")) < 0 - ) { - goto DO_UNLOCK; - } - - for (i = 0; i < sizeof(put_sp_off); i++) { - fmt = "%ld:"; - x = *(long *)((char *)p + put_sp_off[i]); - if (x == -1) { - fmt += 3; - } - if (fprintf(stream, fmt, x) < 0) { - goto DO_UNLOCK; - } - } - - if ((p->sp_flag != ~0UL) && (fprintf(stream, "%lu", p->sp_flag) < 0)) { - goto DO_UNLOCK; - } - - if (fputc('\n', stream) > 0) { - rv = 0; - } - - DO_UNLOCK: - return rv; -} -#endif -#endif /* USE_BB_SHADOW */ - -/**********************************************************************/ -/* Internal functions */ -/**********************************************************************/ - -static const unsigned char pw_off[] ALIGN1 = { - offsetof(struct passwd, pw_name), /* 0 */ - offsetof(struct passwd, pw_passwd), /* 1 */ - offsetof(struct passwd, pw_uid), /* 2 - not a char ptr */ - offsetof(struct passwd, pw_gid), /* 3 - not a char ptr */ - offsetof(struct passwd, pw_gecos), /* 4 */ - offsetof(struct passwd, pw_dir), /* 5 */ - offsetof(struct passwd, pw_shell) /* 6 */ -}; - -static int FAST_FUNC bb__parsepwent(void *data, char *line) -{ - char *endptr; - char *p; - int i; - - i = 0; - while (1) { - p = (char *) data + pw_off[i]; - - if (i < 2 || i > 3) { - *((char **) p) = line; - if (i == 6) { - return 0; - } - /* NOTE: glibc difference - glibc allows omission of - * ':' seperators after the gid field if all remaining - * entries are empty. We require all separators. */ - line = strchr(line, ':'); - if (!line) { - break; - } - } else { - unsigned long t = strtoul(line, &endptr, 10); - /* Make sure we had at least one digit, and that the - * failing char is the next field seperator ':'. See - * glibc difference note above. */ - /* TODO: Also check for leading whitespace? */ - if ((endptr == line) || (*endptr != ':')) { - break; - } - line = endptr; - if (i & 1) { /* i == 3 -- gid */ - *((gid_t *) p) = t; - } else { /* i == 2 -- uid */ - *((uid_t *) p) = t; - } - } - - *line++ = '\0'; - i++; - } /* while (1) */ - - return -1; -} - -/**********************************************************************/ - -static const unsigned char gr_off[] ALIGN1 = { - offsetof(struct group, gr_name), /* 0 */ - offsetof(struct group, gr_passwd), /* 1 */ - offsetof(struct group, gr_gid) /* 2 - not a char ptr */ -}; - -static int FAST_FUNC bb__parsegrent(void *data, char *line) -{ - char *endptr; - char *p; - int i; - char **members; - char *end_of_buf; - - end_of_buf = ((struct group *) data)->gr_name; /* Evil hack! */ - i = 0; - while (1) { - p = (char *) data + gr_off[i]; - - if (i < 2) { - *((char **) p) = line; - line = strchr(line, ':'); - if (!line) { - break; - } - *line++ = '\0'; - i++; - } else { - *((gid_t *) p) = strtoul(line, &endptr, 10); - - /* NOTE: glibc difference - glibc allows omission of the - * trailing colon when there is no member list. We treat - * this as an error. */ - - /* Make sure we had at least one digit, and that the - * failing char is the next field seperator ':'. See - * glibc difference note above. */ - if ((endptr == line) || (*endptr != ':')) { - break; - } - - i = 1; /* Count terminating NULL ptr. */ - p = endptr; - - if (p[1]) { /* We have a member list to process. */ - /* Overwrite the last ':' with a ',' before counting. - * This allows us to (1) test for initial ',' - * and (2) adds one ',' so that the number of commas - * equals the member count. */ - *p = ','; - do { - /* NOTE: glibc difference - glibc allows and trims leading - * (but not trailing) space. We treat this as an error. */ - /* NOTE: glibc difference - glibc allows consecutive and - * trailing commas, and ignores "empty string" users. We - * treat this as an error. */ - if (*p == ',') { - ++i; - *p = 0; /* nul-terminate each member string. */ - if (!*++p || (*p == ',') || isspace(*p)) { - goto ERR; - } - } - } while (*++p); - } - - /* Now align (p+1), rounding up. */ - /* Assumes sizeof(char **) is a power of 2. */ - members = (char **)( (((intptr_t) p) + sizeof(char **)) - & ~((intptr_t)(sizeof(char **) - 1)) ); - - if (((char *)(members + i)) > end_of_buf) { /* No space. */ - break; - } - - ((struct group *) data)->gr_mem = members; - - if (--i) { - p = endptr; /* Pointing to char prior to first member. */ - while (1) { - *members++ = ++p; - if (!--i) - break; - while (*++p) - continue; - } - } - *members = NULL; - - return 0; - } - } /* while (1) */ - - ERR: - return -1; -} - -/**********************************************************************/ - -#if ENABLE_USE_BB_SHADOW -static const unsigned char sp_off[] ALIGN1 = { - offsetof(struct spwd, sp_namp), /* 0: char* */ - offsetof(struct spwd, sp_pwdp), /* 1: char* */ - offsetof(struct spwd, sp_lstchg), /* 2: long */ - offsetof(struct spwd, sp_min), /* 3: long */ - offsetof(struct spwd, sp_max), /* 4: long */ - offsetof(struct spwd, sp_warn), /* 5: long */ - offsetof(struct spwd, sp_inact), /* 6: long */ - offsetof(struct spwd, sp_expire), /* 7: long */ - offsetof(struct spwd, sp_flag) /* 8: unsigned long */ -}; - -static int FAST_FUNC bb__parsespent(void *data, char *line) -{ - char *endptr; - char *p; - int i; - - i = 0; - while (1) { - p = (char *) data + sp_off[i]; - if (i < 2) { - *((char **) p) = line; - line = strchr(line, ':'); - if (!line) { - break; /* error */ - } - } else { - *((long *) p) = strtoul(line, &endptr, 10); - if (endptr == line) { - *((long *) p) = -1L; - } - line = endptr; - if (i == 8) { - if (*line != '\0') { - break; /* error */ - } - return 0; /* all ok */ - } - if (*line != ':') { - break; /* error */ - } - } - *line++ = '\0'; - i++; - } - - return EINVAL; -} -#endif - -/**********************************************************************/ - -/* Reads until EOF, or until it finds a line which fits in the buffer - * and for which the parser function succeeds. - * - * Returns 0 on success and ENOENT for end-of-file (glibc convention). - */ -static int bb__pgsreader( - int FAST_FUNC (*parserfunc)(void *d, char *line), - void *data, - char *__restrict line_buff, - size_t buflen, - FILE *f) -{ - int skip; - int rv = ERANGE; - - if (buflen < PWD_BUFFER_SIZE) { - errno = rv; - return rv; - } - - skip = 0; - while (1) { - if (!fgets(line_buff, buflen, f)) { - if (feof(f)) { - rv = ENOENT; - } - break; - } - - { - int line_len = strlen(line_buff) - 1; - if (line_len >= 0 && line_buff[line_len] == '\n') { - line_buff[line_len] = '\0'; - } else - if (line_len + 2 == buflen) { - /* A start (or continuation) of overlong line */ - skip = 1; - continue; - } /* else: a last line in the file, and it has no '\n' */ - } - - if (skip) { - /* This "line" is a remainder of overlong line, ignore */ - skip = 0; - continue; - } - - /* NOTE: glibc difference - glibc strips leading whitespace from - * records. We do not allow leading whitespace. */ - - /* Skip empty lines, comment lines, and lines with leading - * whitespace. */ - if (line_buff[0] != '\0' && line_buff[0] != '#' && !isspace(line_buff[0])) { - if (parserfunc == bb__parsegrent) { - /* Do evil group hack: - * The group entry parsing function needs to know where - * the end of the buffer is so that it can construct the - * group member ptr table. */ - ((struct group *) data)->gr_name = line_buff + buflen; - } - if (parserfunc(data, line_buff) == 0) { - rv = 0; - break; - } - } - } /* while (1) */ - - return rv; -}
/* vi: set sw=4 ts=4: */ /* Copyright (C) 2003 Manuel Novoa III * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ /* Nov 6, 2003 Initial version. * * NOTE: This implementation is quite strict about requiring all * field seperators. It also does not allow leading whitespace * except when processing the numeric fields. glibc is more * lenient. See the various glibc difference comments below. * * TODO: * Move to dynamic allocation of (currently statically allocated) * buffers; especially for the group-related functions since * large group member lists will cause error returns. */ /* Copyright (C) 2014 Tito Ragusa <farmat...@tiscali.it> * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ /* This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY!! * * Rewrite of some parts. Main differences are: * * 1) the buffer for getpwuid, getgrgid, getpwnam, getgrnam is dynamically * allocated and reused by later calls. if ERANGE error pops up it is * reallocated to the size of the longest line found so far in the * passwd/group files and reused for later calls. * If ENABLE_FEATURE_CLEAN_UP is set the buffers are freed at program * exit using the atexit function to make valgrind happy. * 2) the passwd/group files: * a) must contain the expected number of fields (as per count of field * delimeters ":") or we will complain with a error message. * b) leading or trailing whitespace in fields is allowed and handled. * c) some fields are not allowed to be empty (e.g. username, uid/gid, * homedir, shell) and in this case NULL is returned and errno is * set to EINVAL. This behaviour could be easily changed by * modifying PW_DEF, GR_DEF, SP_DEF strings (uppercase * makes a field mandatory). * d) the string representing uid/gid must be convertible by strtoXX * functions or NULL is returned and errno is set to EINVAL. * e) leading or trailing whitespaces in member names and empty members * are allowed and handled. * 3) the internal function for getgrouplist uses a dynamically allocated * buffer and retries with a bigger one in case it is to small; * 4) the _r functions use the user supplied buffers that are never reallocated * but use mostly the same common code as the other functions. * 5) at the moment only the functions really used by busybox code are * implemented, if you need a particular missing function it should be * easy to write it by using the internal common code. */ #include "libbb.h" /* S = string not empty, s = string maybe empty, */ /* I = uid,gid, l = long maybe empty, m = members,*/ /* r = reserved */ #define PW_DEF "SsIIsSS" static const char *pw_def = PW_DEF; #define _PW_FIELDS sizeof(PW_DEF)-1 #define GR_DEF "SsIm" static const char *gr_def = GR_DEF; #define _GR_FIELDS sizeof(GR_DEF)-1 #if ENABLE_USE_BB_SHADOW #define SP_DEF "Ssllllllr" static const char *sp_def = SP_DEF; #define _SP_FIELDS sizeof(SP_DEF)-1 #endif /* Initial buffer size */ #define PWD_GRP_BUFSIZE 256 /* for getpw_common_malloc */ static char *pwd_buffer = NULL; static size_t pwd_buffer_size = PWD_GRP_BUFSIZE; struct passwd *my_pw; struct passwd my_pwd; /* for getgr_common_malloc */ static char *grp_buffer = NULL; static size_t grp_buffer_size = PWD_GRP_BUFSIZE; struct group *my_gr; struct group my_grp; /* for setpwent, getpwent_r, endpwent */ static FILE *pwf = NULL; /* for setgrent, getgrent_r, endgrent */ static FILE *grf = NULL; /* We do no file locking */ #define LOCK ((void) 0) #define UNLOCK ((void) 0) /**********************************************************************/ /* Internal functions */ /**********************************************************************/ /* Divide the passwd/group/shadow record in fields * by substituting the given delimeter * e.g. ':' or ',' with '\0'. * Returns the number of fields found. * Strips leading or trailing whitespace in fields. */ static int FAST_FUNC tokenize(char *buffer, int ch) { char *p = buffer; char *s = p; int num_fields = 1; while (*p) { while (isspace(*s)) { memmove(s, s + 1, strlen(s + 1) + 1); } if (*p == ch) { while (p != s && isspace(*(p - 1))) { memmove(p - 1, p, strlen(p) + 1); p = p - 1; } *p = '\0'; num_fields++; s = p + 1; } p++; } return num_fields; } /* Fill the buffer linebuf up to len with * a line of a passwd/group file. * Returns 0 on success, ENOENT on EOF and * ERANGE if buffer is not big enough, * in the latter case the size needed * is set in size_t *len. */ static int FAST_FUNC get_record_line(FILE *file, char *linebuf, size_t *len) { int ch; int idx = 0; while ((ch = getc(file)) != EOF) { if (idx < *len) { linebuf[idx] = (char) ch; } idx++; if (ch == '\n' || ch == '\0') break; } if (idx > *len) { *len = idx; return ERANGE; } /* return ENOENT only on EOF and no data */ if (idx > 0) { /* terminate string and remove trailing '\n' if any.*/ linebuf[idx - 1] = '\0'; return 0; } return ENOENT; } /* Returns 0 on success and matching line broken up in fields by '\0' in buf or * error. We require the expected number of fields to be found. * size_t *len is used to check that the buffer provided is big enough, if not * we reuse size_t *len to pass the needed length to the caller. */ static int FAST_FUNC parse_common(FILE *f, const char *key, int field_pos, char *buf, size_t *len, const char *filename, int n_fields) { int count = 0; int error; const char *s; while ((error = get_record_line(f, buf, len)) == 0) { count++; /* Skip empty lines, comment lines */ if (buf[0] != '\0' && buf[0] != '#') { if (tokenize(buf, ':') == n_fields) { if (field_pos >= 0) { if (*(s = nth_string(buf, field_pos))) { if (key && *key) { if (strcmp(key, s) == 0) { /* record found */ break; } } /* else skip: caller passed bad key */ } /* else skip: field is empty */ } /*else skip: the field we look for doesn't exist */ if (field_pos < 0) { /* No field specified: sequential read, return a record */ break; } } else { /* number of fields is wrong */ bb_error_msg("%s: bad record at line %d", filename, count); } } } return error; } static int FAST_FUNC parse_file(const char *filename, int n_fields, const char *key, int field_pos, char *buf, size_t *len) { FILE *f = fopen_for_read(filename); int ret = errno; if (f) { ret = parse_common(f, key, field_pos, buf, len, filename, n_fields); fclose(f); } return ret; } /* Convert passwd/group/shadow file record in buffer to a struct */ static void * FAST_FUNC convert_to_struct(const char *def, const unsigned char *off, char *buffer, void *result, int *error) { int idx; long ret; char *m; char **members = NULL; for (idx = 0; def[idx] != '\0'; idx++) { if (def[idx] == 'S' || def[idx] == 's') { m = (char *) nth_string(buffer, idx); *(char **)((char *)result + off[idx]) = m; if (!*m && (def[idx] == 'S')) { *error = EINVAL; } } if (def[idx] == 'I') { m = (char *) nth_string(buffer, idx); *(int *)((int) result + off[idx]) = bb_strtou(m, NULL, 10); if (errno) { *error = EINVAL; } } #if ENABLE_USE_BB_SHADOW if (def[idx] == 'l') { char *endptr; m = (char *) nth_string(buffer, idx); ret = bb_strtol(m, &endptr, 10); if (endptr == m) { ret = -1L; } else if (errno) { *error = EINVAL; } *(long *)((long) result + off[idx]) = ret; } #endif if (def[idx] == 'm') { char *s = (char *) nth_string(buffer, idx); int i = tokenize(s, ','); char *p = (char *) nth_string(s, i); /* Now align (p+1), rounding up. */ /* Assumes sizeof(char **) is a power of 2. */ members = (char **)((((intptr_t) p) + sizeof(char **)) & ~((intptr_t)(sizeof(char **) - 1))); ((struct group *) result)->gr_mem = members; /* Pointing to char prior to first member. */ p = s - 1; while (1) { if (*(p + 1)) *members++ = ++p; if (!--i) break; while (*++p) continue; } *members = NULL; } } if (*error) { result = NULL; errno = *error; } return result; } #if ENABLE_USE_BB_SHADOW static const unsigned char sp_off[] ALIGN1 = { offsetof(struct spwd, sp_namp), /* 1 - Login name */ offsetof(struct spwd, sp_pwdp), /* 2 - Encrypted password */ offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ offsetof(struct spwd, sp_expire) /* 7 - not a char ptr */ // ,offsetof(struct spwd, sp_flag) /* 8 - Reserved */ }; int getspnam_r(const char *name, struct spwd *spbuf, char *buf, size_t buflen, struct spwd **spbufp) { int error; size_t len = buflen; *spbufp = NULL; error = parse_file(_PATH_SHADOW, _SP_FIELDS, name, 0, buf, &len); if (error == 0) { *spbufp = convert_to_struct(sp_def, sp_off, buf, spbuf, &error); } return error; } #endif /* ENABLE_USE_BB_SHADOW */ static const unsigned char pw_off[] ALIGN1 = { offsetof(struct passwd, pw_name), /* 0 */ offsetof(struct passwd, pw_passwd), /* 1 */ offsetof(struct passwd, pw_uid), /* 2 - not a char ptr */ offsetof(struct passwd, pw_gid), /* 3 - not a char ptr */ offsetof(struct passwd, pw_gecos), /* 4 */ offsetof(struct passwd, pw_dir), /* 5 */ offsetof(struct passwd, pw_shell) /* 6 */ }; static const unsigned char gr_off[] ALIGN1 = { offsetof(struct group, gr_name), /* 0 */ offsetof(struct group, gr_passwd), /* 1 */ offsetof(struct group, gr_gid), /* 2 - not a char ptr */ offsetof(struct group, gr_mem) /* 3 - char ** ptr */ }; /* Common code for getpwxxx/_r functions. */ static int FAST_FUNC getpw_common(const char *name, int field, struct passwd *pwd, char *buf, size_t *buflen, struct passwd **result) { int error; *result = NULL; error = parse_file(_PATH_PASSWD, _PW_FIELDS, name, field, buf, buflen); if (error == 0) { *result = convert_to_struct(pw_def, pw_off, buf, pwd, &error); } return (error == ENOENT) ? 0 : error; } /* Common code for getgrxxx/_r functions. */ static int FAST_FUNC getgr_common(const char *name, int field, struct group *grp, char *buf, size_t *buflen, struct group **result) { int error; *result = NULL; error = parse_file(_PATH_GROUP, _GR_FIELDS, name, field, buf, buflen); if (error == 0) { *result = convert_to_struct(gr_def, gr_off, buf, grp, &error); } return (error == ENOENT) ? 0 : error; } #if ENABLE_FEATURE_CLEAN_UP static void FAST_FUNC free_pwd(void) { free(pwd_buffer); } #endif /* Common code for getpwxxx functions. */ static struct passwd * FAST_FUNC getpw_common_malloc(const char *name, int field) { int error = 0; #if ENABLE_FEATURE_CLEAN_UP if (pwd_buffer == NULL) { atexit(free_pwd); } #endif retry: pwd_buffer = xrealloc(pwd_buffer, pwd_buffer_size * sizeof(char)); error = getpw_common(name, field, &my_pwd, pwd_buffer, &pwd_buffer_size, &my_pw); if (error == ERANGE) { errno = 0; goto retry; } return my_pw; } #if ENABLE_FEATURE_CLEAN_UP static void FAST_FUNC free_grp(void) { free(grp_buffer); } #endif /* Common code for getgrxxx functions. */ static struct group * FAST_FUNC getgr_common_malloc(const char *name, int field) { int error = 0; #if ENABLE_FEATURE_CLEAN_UP if (grp_buffer == NULL) { atexit(free_grp); } #endif retry: grp_buffer = xrealloc(grp_buffer, grp_buffer_size * sizeof(char)); error = getgr_common(name, field, &my_grp, grp_buffer, &grp_buffer_size, &my_gr); if (error == ERANGE) { errno = 0; goto retry; } return my_gr; } /**********************************************************************/ int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd **pwbufp) { int error; size_t len = buflen; LOCK; *pwbufp = NULL; if (!pwf) { pwf = fopen_for_read(_PATH_PASSWD); if (!pwf) { return errno; } close_on_exec_on(fileno(pwf)); } error = parse_common(pwf, NULL, -1, buf, &len, _PATH_PASSWD, _PW_FIELDS); if (error == 0) { *pwbufp = convert_to_struct(pw_def, pw_off, buf, pwbuf, &error); } UNLOCK; return error; } int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp) { int error; size_t len = buflen; LOCK; *gbufp = NULL; if (!grf) { grf = fopen_for_read(_PATH_GROUP); if (!grf) { return errno; } close_on_exec_on(fileno(grf)); } error = parse_common(grf, NULL, -1, buf, &len, _PATH_GROUP, _GR_FIELDS); if (error == 0) { *gbufp = convert_to_struct(gr_def, gr_off, buf, gbuf, &error); } UNLOCK; return error; } int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { size_t len = buflen; return getpw_common(name, 0, pwd, buf, &len, result); } int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, struct group **result) { size_t len = buflen; return getgr_common(name, 0, grp, buf, &len, result); } int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { size_t len = buflen; return getpw_common(utoa(uid), 2, pwd, buf, &len, result); } int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, struct group **result) { size_t len = buflen; return getgr_common(utoa(gid), 2, grp, buf, &len, result); } struct passwd *getpwnam(const char *name) { return getpw_common_malloc(name, 0); } struct group *getgrnam(const char *name) { return getgr_common_malloc(name, 0); } struct passwd *getpwuid(uid_t uid) { return getpw_common_malloc(utoa(uid), 2); } struct group *getgrgid(gid_t gid) { return getgr_common_malloc(utoa(gid), 2); } void endpwent(void) { LOCK; if (pwf) { fclose(pwf); pwf = NULL; } UNLOCK; } void setpwent(void) { LOCK; if (pwf) { rewind(pwf); } UNLOCK; } void endgrent(void) { LOCK; if (grf) { fclose(grf); grf = NULL; } UNLOCK; } void setgrent(void) { LOCK; if (grf) { rewind(grf); } UNLOCK; } static gid_t * FAST_FUNC getgrouplist_internal(int *ngroups_ptr, const char *user, gid_t gid) { FILE *f; struct group *gr; struct group grp; gid_t *group_list; int ngroups; char * buffer = NULL; size_t buflen = PWD_GRP_BUFSIZE; int ret = 0; retry: group_list = NULL; ngroups = 1; f = fopen_for_read(_PATH_GROUP); if (f) { group_list = xrealloc(group_list, 1 * sizeof(gid_t)); *group_list = gid; buffer = xrealloc(buffer, buflen * sizeof( char)); while ((ret = parse_common(f, NULL, -1, buffer, &buflen, _PATH_GROUP, _GR_FIELDS)) == 0) { gr = convert_to_struct(gr_def, gr_off, buffer, &grp, &ret); if (gr != NULL) { if (gr->gr_gid != gid) { /* reuse int ret */ for (/*ret = 0*/; gr->gr_mem[ret] != NULL; ret++) { if (strcmp(gr->gr_mem[ret], user) == 0) { group_list = xrealloc(group_list, (ngroups + 1) * sizeof(gid_t)); group_list[ngroups++] = gr->gr_gid; } } } } } fclose(f); } if (ret == ERANGE) { /* buffer was to small, retry */ free(group_list); goto retry; } free(buffer); *ngroups_ptr = ngroups; return group_list; } int initgroups(const char *user, gid_t gid) { int ngroups; gid_t *group_list = getgrouplist_internal(&ngroups, user, gid); ngroups = setgroups(ngroups, group_list); free(group_list); return ngroups; } int getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups) { int ngroups_old = *ngroups; gid_t *group_list = getgrouplist_internal(ngroups, user, gid); if (*ngroups <= ngroups_old) { ngroups_old = *ngroups; memcpy(groups, group_list, ngroups_old * sizeof(groups[0])); } else { ngroups_old = -1; } free(group_list); return ngroups_old; }
_______________________________________________ busybox mailing list busybox@busybox.net http://lists.busybox.net/mailman/listinfo/busybox