[prev in list] [next in list] [prev in thread] [next in thread] 

List:       busybox
Subject:    [PATCH] malloced getpw/grxxx functions for bb
From:       tito <farmatito () tiscali ! it>
Date:       2014-12-29 21:19:51
Message-ID: 201412292219.51659.farmatito () tiscali ! it
[Download RAW message or body]

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


["pwd_grp.patch" (text/x-patch)]

New malloced getpw/grxxx functions for bb.

Signed-off-by: Tito Ragusa <farmatito@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 <farmatito@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;
-}

["pwd_grp.c" (text/x-csrc)]

/* 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 <farmatito@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

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic