X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=libpwdgrp%2Fpwd_grp.c;h=c9bbc8bdadce5ce754407d0b3e4128f40ec62fde;hb=31c765081dc41f158786545fbea9294be4685bd2;hp=0d8e2bb5c400f2bf1bcf7c714a24fd9051a77d46;hpb=908b6e5dfdbc81322680ce939e5415161c637bb4;p=oweals%2Fbusybox.git diff --git a/libpwdgrp/pwd_grp.c b/libpwdgrp/pwd_grp.c index 0d8e2bb5c..c9bbc8bda 100644 --- a/libpwdgrp/pwd_grp.c +++ b/libpwdgrp/pwd_grp.c @@ -1,5 +1,5 @@ /* vi: set sw=4 ts=4: */ -/* Copyright (C) 2014 Tito Ragusa +/* Copyright (C) 2014 Tito Ragusa * * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ @@ -9,108 +9,127 @@ * 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. + * allocated. * 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). + * delimiters ":") or we will complain with a error message. + * b) leading and trailing whitespace in fields is stripped. + * c) some fields are not allowed to be empty (e.g. username, uid/gid), + * 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 too 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 + * functions, or errno is set to EINVAL. + * e) leading and trailing whitespace in group member names is stripped. + * 3) the internal function for getgrouplist uses dynamically allocated buffer. + * 4) 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" -#define GR_DEF "SsIm" -#define SP_DEF "Ssllllllr" - -static const uint8_t pw_off[] ALIGN1 = { - offsetof(struct passwd, pw_name), /* 0 S */ - offsetof(struct passwd, pw_passwd), /* 1 s */ - offsetof(struct passwd, pw_uid), /* 2 I */ - offsetof(struct passwd, pw_gid), /* 3 I */ - offsetof(struct passwd, pw_gecos), /* 4 s */ - offsetof(struct passwd, pw_dir), /* 5 S */ - offsetof(struct passwd, pw_shell) /* 6 S */ -}; -static const uint8_t gr_off[] ALIGN1 = { - offsetof(struct group, gr_name), /* 0 S */ - offsetof(struct group, gr_passwd), /* 1 s */ - offsetof(struct group, gr_gid), /* 2 I */ - offsetof(struct group, gr_mem) /* 3 m (char **) */ -}; -#if ENABLE_USE_BB_SHADOW -static const uint8_t sp_off[] ALIGN1 = { - offsetof(struct spwd, sp_namp), /* 0 S Login name */ - offsetof(struct spwd, sp_pwdp), /* 1 s Encrypted password */ - offsetof(struct spwd, sp_lstchg), /* 2 l */ - offsetof(struct spwd, sp_min), /* 3 l */ - offsetof(struct spwd, sp_max), /* 4 l */ - offsetof(struct spwd, sp_warn), /* 5 l */ - offsetof(struct spwd, sp_inact), /* 6 l */ - offsetof(struct spwd, sp_expire), /* 7 l */ - offsetof(struct spwd, sp_flag) /* 8 r Reserved */ -}; -#endif - struct const_passdb { const char *filename; - const uint8_t *off; - const char def[10]; + char def[7 + 2*ENABLE_USE_BB_SHADOW]; + uint8_t off[7 + 2*ENABLE_USE_BB_SHADOW]; uint8_t numfields; uint8_t size_of; }; struct passdb { const char *filename; - const uint8_t *off; - const char def[10]; + char def[7 + 2*ENABLE_USE_BB_SHADOW]; + uint8_t off[7 + 2*ENABLE_USE_BB_SHADOW]; uint8_t numfields; uint8_t size_of; FILE *fp; - void *malloced; + char *malloced; }; +/* Note: for shadow db, def[] will not contain terminating NUL, + * but convert_to_struct() logic detects def[] end by "less than SP?", + * not by "is it NUL?" condition; and off[0] happens to be zero + * for every db anyway, so there _is_ in fact a terminating NUL there. + */ -static const struct const_passdb const_pw_db = { _PATH_PASSWD, pw_off, PW_DEF, sizeof(PW_DEF)-1, sizeof(struct passwd) }; -static const struct const_passdb const_gr_db = { _PATH_GROUP , gr_off, GR_DEF, sizeof(GR_DEF)-1, sizeof(struct group) }; +/* S = string not empty, s = string maybe empty, + * I = uid,gid, l = long maybe empty, m = members, + * r = reserved + */ +#define PW_DEF "SsIIsss" +#define GR_DEF "SsIm" +#define SP_DEF "Ssllllllr" + +static const struct const_passdb const_pw_db = { + _PATH_PASSWD, PW_DEF, + { + offsetof(struct passwd, pw_name), /* 0 S */ + offsetof(struct passwd, pw_passwd), /* 1 s */ + offsetof(struct passwd, pw_uid), /* 2 I */ + offsetof(struct passwd, pw_gid), /* 3 I */ + offsetof(struct passwd, pw_gecos), /* 4 s */ + offsetof(struct passwd, pw_dir), /* 5 s */ + offsetof(struct passwd, pw_shell) /* 6 s */ + }, + sizeof(PW_DEF)-1, sizeof(struct passwd) +}; +static const struct const_passdb const_gr_db = { + _PATH_GROUP, GR_DEF, + { + offsetof(struct group, gr_name), /* 0 S */ + offsetof(struct group, gr_passwd), /* 1 s */ + offsetof(struct group, gr_gid), /* 2 I */ + offsetof(struct group, gr_mem) /* 3 m (char **) */ + }, + sizeof(GR_DEF)-1, sizeof(struct group) +}; #if ENABLE_USE_BB_SHADOW -static const struct const_passdb const_sp_db = { _PATH_SHADOW, sp_off, SP_DEF, sizeof(SP_DEF)-1, sizeof(struct spwd) }; +static const struct const_passdb const_sp_db = { + _PATH_SHADOW, SP_DEF, + { + offsetof(struct spwd, sp_namp), /* 0 S Login name */ + offsetof(struct spwd, sp_pwdp), /* 1 s Encrypted password */ + offsetof(struct spwd, sp_lstchg), /* 2 l */ + offsetof(struct spwd, sp_min), /* 3 l */ + offsetof(struct spwd, sp_max), /* 4 l */ + offsetof(struct spwd, sp_warn), /* 5 l */ + offsetof(struct spwd, sp_inact), /* 6 l */ + offsetof(struct spwd, sp_expire), /* 7 l */ + offsetof(struct spwd, sp_flag) /* 8 r Reserved */ + }, + sizeof(SP_DEF)-1, sizeof(struct spwd) +}; #endif /* We avoid having big global data. */ struct statics { - /* It's ok to use one buffer for getpwuid and getpwnam. Manpage says: + /* We use same buffer (db[0].malloced) 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 passdb db[2 + ENABLE_USE_BB_SHADOW]; char *tokenize_end; + unsigned string_size; }; static struct statics *ptr_to_statics; #define S (*ptr_to_statics) #define has_S (ptr_to_statics) +#if ENABLE_FEATURE_CLEAN_UP +static void free_static(void) +{ + free(S.db[0].malloced); + free(S.db[1].malloced); +# if ENABLE_USE_BB_SHADOW + free(S.db[2].malloced); +# endif + free(ptr_to_statics); +} +#endif + static struct statics *get_S(void) { if (!ptr_to_statics) { @@ -119,19 +138,20 @@ static struct statics *get_S(void) memcpy(&S.db[1], &const_gr_db, sizeof(const_gr_db)); #if ENABLE_USE_BB_SHADOW memcpy(&S.db[2], &const_sp_db, sizeof(const_sp_db)); +#endif +#if ENABLE_FEATURE_CLEAN_UP + atexit(free_static); #endif } return ptr_to_statics; } -/**********************************************************************/ -/* Internal functions */ -/**********************************************************************/ +/* Internal functions */ /* Divide the passwd/group/shadow record in fields - * by substituting the given delimeter + * by substituting the given delimiter * e.g. ':' or ',' with '\0'. - * Returns the number of fields found. + * Returns the number of fields found. * Strips leading and trailing whitespace in fields. */ static int tokenize(char *buffer, int ch) @@ -165,41 +185,22 @@ static int tokenize(char *buffer, int ch) /* Returns !NULL on success and matching line broken up in fields by '\0' in buf. * We require the expected number of fields to be found. */ -static char *parse_common(FILE *fp, const char *filename, - int n_fields, +static char *parse_common(FILE *fp, struct passdb *db, const char *key, int field_pos) { - int count = 0; char *buf; while ((buf = xmalloc_fgetline(fp)) != NULL) { - count++; /* Skip empty lines, comment lines */ if (buf[0] == '\0' || buf[0] == '#') goto free_and_next; - if (tokenize(buf, ':') != n_fields) { + if (tokenize(buf, ':') != db->numfields) { /* number of fields is wrong */ - bb_error_msg("bad record at %s:%u", filename, count); + bb_error_msg("%s: bad record", db->filename); goto free_and_next; } -/* Ugly hack: group db requires aqdditional buffer space - * for members[] array. If there is only one group, we need space - * for 3 pointers: alignment padding, group name, NULL. - * +1 for every additional group. - */ - if (n_fields == sizeof(GR_DEF)-1) { /* if we read group file */ - int resize = 3; - char *p = buf; - while (*p) - if (*p++ == ',') - resize++; - resize *= sizeof(char**); - resize += S.tokenize_end - buf; - buf = xrealloc(buf, resize); - } - - if (!key) { + if (field_pos == -1) { /* no key specified: sequential read, return a record */ break; } @@ -211,27 +212,50 @@ static char *parse_common(FILE *fp, const char *filename, free(buf); } + S.string_size = S.tokenize_end - buf; +/* + * Ugly hack: group db requires additional buffer space + * for members[] array. If there is only one group, we need space + * for 3 pointers: alignment padding, group name, NULL. + * +1 for every additional group. + */ + if (buf && db->numfields == sizeof(GR_DEF)-1) { /* if we read group file... */ + int cnt = 3; + char *p = buf; + while (p < S.tokenize_end) + if (*p++ == ',') + cnt++; + S.string_size += cnt * sizeof(char*); +//bb_error_msg("+%d words = %u key:%s buf:'%s'", cnt, S.string_size, key, buf); + buf = xrealloc(buf, S.string_size); + } + return buf; } -static char *parse_file(const char *filename, - int n_fields, +static char *parse_file(struct passdb *db, const char *key, int field_pos) { char *buf = NULL; - FILE *fp = fopen_for_read(filename); + FILE *fp = fopen_for_read(db->filename); if (fp) { - buf = parse_common(fp, filename, n_fields, key, field_pos); + buf = parse_common(fp, db, key, field_pos); fclose(fp); } return buf; } /* Convert passwd/group/shadow file record in buffer to a struct */ -static void *convert_to_struct(const char *def, const unsigned char *off, +static void *convert_to_struct(struct passdb *db, char *buffer, void *result) { + const char *def = db->def; + const uint8_t *off = db->off; + + /* For consistency, zero out all fields */ + memset(result, 0, db->size_of); + for (;;) { void *member = (char*)result + (*off++); @@ -262,13 +286,15 @@ static void *convert_to_struct(const char *def, const unsigned char *off, * at the end of malloced buffer! */ members = (char **) - ( ((intptr_t)S.tokenize_end + sizeof(char**)) - & -(intptr_t)sizeof(char**) + ( ((intptr_t)S.tokenize_end + sizeof(members[0])) + & -(intptr_t)sizeof(members[0]) ); - ((struct group *)result)->gr_mem = members; while (--i >= 0) { - *members++ = buffer; + if (buffer[0]) { + *members++ = buffer; + // bb_error_msg("member[]='%s'", buffer); + } buffer += strlen(buffer) + 1; } *members = NULL; @@ -276,7 +302,7 @@ static void *convert_to_struct(const char *def, const unsigned char *off, /* def "r" does nothing */ def++; - if (*def == '\0') + if ((unsigned char)*def <= (unsigned char)' ') break; buffer += strlen(buffer) + 1; } @@ -286,26 +312,19 @@ static void *convert_to_struct(const char *def, const unsigned char *off, return result; } -/****** getXXnam/id_r */ - -static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos, char *buffer, size_t buflen, - void *result) +static int massage_data_for_r_func(struct passdb *db, + char *buffer, size_t buflen, + void **result, + char *buf) { - void *struct_buf = *(void**)result; - char *buf; - struct passdb *db; - get_S(); - db = &S.db[db_and_field_pos >> 2]; - - *(void**)result = NULL; - buf = parse_file(db->filename, db->numfields, name, db_and_field_pos & 3); + void *result_buf = *result; + *result = NULL; if (buf) { - size_t size = S.tokenize_end - buf; - if (size > buflen) { + if (S.string_size > buflen) { errno = ERANGE; } else { - memcpy(buffer, buf, size); - *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf); + memcpy(buffer, buf, S.string_size); + *result = convert_to_struct(db, buffer, result_buf); } free(buf); } @@ -316,8 +335,41 @@ static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos, ch return errno; } -int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf, char *buffer, size_t buflen, - struct passwd **result) +static void* massage_data_for_non_r_func(struct passdb *db, char *buf) +{ + if (!buf) + return NULL; + + free(db->malloced); + /* We enlarge buf and move string data up, freeing space + * for struct passwd/group/spwd at the beginning. This way, + * entire result of getXXnam is in a single malloced block. + * This enables easy creation of xmalloc_getpwnam() API. + */ + db->malloced = buf = xrealloc(buf, db->size_of + S.string_size); + memmove(buf + db->size_of, buf, S.string_size); + return convert_to_struct(db, buf + db->size_of, buf); +} + +/****** getXXnam/id_r */ + +static int FAST_FUNC getXXnam_r(const char *name, uintptr_t db_and_field_pos, + char *buffer, size_t buflen, + void *result) +{ + char *buf; + struct passdb *db = &get_S()->db[db_and_field_pos >> 2]; + + buf = parse_file(db, name, 0 /*db_and_field_pos & 3*/); + /* "db_and_field_pos & 3" is commented out since so far we don't implement + * getXXXid_r() functions which would use that to pass 2 here */ + + return massage_data_for_r_func(db, buffer, buflen, result, buf); +} + +int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf, + char *buffer, size_t buflen, + struct passwd **result) { /* Why the "store buffer address in result" trick? * This way, getXXnam_r has the same ABI signature as getpwnam_r, @@ -328,25 +380,21 @@ int FAST_FUNC getpwnam_r(const char *name, struct passwd *struct_buf, char *buff } #if ENABLE_USE_BB_SHADOW int FAST_FUNC getspnam_r(const char *name, struct spwd *struct_buf, char *buffer, size_t buflen, - struct spwd **result) + struct spwd **result) { *result = struct_buf; return getXXnam_r(name, (2 << 2) + 0, buffer, buflen, result); } #endif +#ifdef UNUSED /****** getXXent_r */ -static int FAST_FUNC getXXent_r(void *struct_buf, char *buffer, size_t buflen, - void *result, - unsigned db_idx) +static int FAST_FUNC getXXent_r(uintptr_t db_idx, char *buffer, size_t buflen, + void *result) { char *buf; - struct passdb *db; - get_S(); - db = &S.db[db_idx]; - - *(void**)result = NULL; + struct passdb *db = &get_S()->db[db_idx]; if (!db->fp) { db->fp = fopen_for_read(db->filename); @@ -356,40 +404,26 @@ static int FAST_FUNC getXXent_r(void *struct_buf, char *buffer, size_t buflen, close_on_exec_on(fileno(db->fp)); } - buf = parse_common(db->fp, db->filename, db->numfields, /*no search key:*/ NULL, 0); - if (buf) { - size_t size = S.tokenize_end - buf; - if (size > buflen) { - errno = ERANGE; - } else { - memcpy(buffer, buf, size); - *(void**)result = convert_to_struct(db->def, db->off, buffer, struct_buf); - } - free(buf); - } - /* "The reentrant functions return zero on success. - * In case of error, an error number is returned." - * NB: not finding the record is also a "success" here: - */ - return errno; + buf = parse_common(db->fp, db, /*no search key:*/ NULL, -1); + if (!buf && !errno) + errno = ENOENT; + return massage_data_for_r_func(db, buffer, buflen, result, buf); } -int FAST_FUNC getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen, struct passwd **result) +int FAST_FUNC getpwent_r(struct passwd *struct_buf, char *buffer, size_t buflen, + struct passwd **result) { - return getXXent_r(struct_buf, buffer, buflen, result, 0); + *result = struct_buf; + return getXXent_r(0, buffer, buflen, result); } +#endif -/****** getXXnam/id */ +/****** getXXent */ -static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos) +static void* FAST_FUNC getXXent(uintptr_t db_idx) { char *buf; - void *result; - struct passdb *db; - get_S(); - db = &S.db[db_and_field_pos >> 2]; - - result = NULL; + struct passdb *db = &get_S()->db[db_idx]; if (!db->fp) { db->fp = fopen_for_read(db->filename); @@ -399,14 +433,24 @@ static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos) close_on_exec_on(fileno(db->fp)); } - free(db->malloced); - db->malloced = NULL; - buf = parse_common(db->fp, db->filename, db->numfields, name, db_and_field_pos & 3); - if (buf) { - db->malloced = xzalloc(db->size_of); - result = convert_to_struct(db->def, db->off, buf, db->malloced); - } - return result; + buf = parse_common(db->fp, db, /*no search key:*/ NULL, -1); + return massage_data_for_non_r_func(db, buf); +} + +struct passwd* FAST_FUNC getpwent(void) +{ + return getXXent(0); +} + +/****** getXXnam/id */ + +static void* FAST_FUNC getXXnam(const char *name, unsigned db_and_field_pos) +{ + char *buf; + struct passdb *db = &get_S()->db[db_and_field_pos >> 2]; + + buf = parse_file(db, name, db_and_field_pos & 3); + return massage_data_for_non_r_func(db, buf); } struct passwd* FAST_FUNC getpwnam(const char *name) @@ -458,20 +502,19 @@ static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr, gid_t *group_list; int ngroups; - get_S(); - /* We alloc space for 8 gids at a time. */ - group_list = xmalloc(8 * sizeof(group_list[0])); + group_list = xzalloc(8 * sizeof(group_list[0])); group_list[0] = gid; ngroups = 1; fp = fopen_for_read(_PATH_GROUP); if (fp) { + struct passdb *db = &get_S()->db[1]; char *buf; - while ((buf = parse_common(fp, _PATH_GROUP, sizeof(GR_DEF)-1, NULL, 0)) != NULL) { + while ((buf = parse_common(fp, db, NULL, -1)) != NULL) { char **m; struct group group; - if (!convert_to_struct(GR_DEF, gr_off, buf, &group)) + if (!convert_to_struct(db, buf, &group)) goto next; if (group.gr_gid == gid) goto next; @@ -480,7 +523,7 @@ static gid_t* FAST_FUNC getgrouplist_internal(int *ngroups_ptr, continue; group_list = xrealloc_vector(group_list, /*8=2^3:*/ 3, ngroups); group_list[ngroups++] = group.gr_gid; - break; + goto next; } next: free(buf);