[prev in list] [next in list] [prev in thread] [next in thread]
List: busybox
Subject: Re: [RFC] malloced getpw/grxxx functions for bb
From: tito <farmatito () tiscali ! it>
Date: 2014-09-28 13:24:50
Message-ID: 201409281524.50579.farmatito () tiscali ! it
[Download RAW message or body]
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.
> > >
> > > Ciao,
> > > Tito
> > >
> >
> > Hi,
> > more return value and errno fixes.
> >
> > Ciao,
> > Tito
> >
> Hi,
> make the tokenize function more robust.
>
> Ciao,
> Tito
>
Hi,
more minor errno fixes and code cleanups.
Ciao,
Tito
["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.
*/
/* 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 in the passwd/group
* files and reused for later calls.
*
* 2) the passwd/group files:
* a) must contain the expected number of fields;
* b) some fields are not allowed to be empty (e.g. username, homedir, shell);
* c) leading or trailing spaces in fields are not allowed;
* d) the string representing uid/gid must be convertible by strtoXX functions;
* if the above explained conditions are not met a error message about bad
* record in file is printed so that the user knows about it.
* 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"
/* for the fields in passwd/group/shadow files */
#define NOT_EMPTY 0
#define MAYBE_EMPTY 1
/* Fields in passwd file starting from pos 1 */
#define _PASSWD_FIELDS 7
/* S = string not empty, s = string maybe empty, */
/* I = uid,gid, l = long maybe empty, m = members */
static const char *pw_def = "SsIIsSS";
/* Fields in group file starting from pos 1 */
#define _GROUP_FIELDS 4
static const char *gr_def = "SsIm";
/* Fields in shadow file starting from pos 1 */
#define _SHADOW_FIELDS 9
#if ENABLE_USE_BB_SHADOW
/* But we parse only 8 as last is reserved*/
static const char *sp_def = "Ssllllll";
#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;
/* TODO: We do no file locking, but we do
* in libbb/update_passwd.c, so maybe the same code
* could be reused here */
#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.
* Checks for leading or trailing spaces in fields.
*/
static int FAST_FUNC tokenize(char *buffer, int ch, int *error)
{
char *p = buffer;
char *s = p;
int num_fields = 1;
while (*p) {
if (*p == ch) {
*p = '\0';
num_fields++;
s = p + 1;
}
if (*s == ' ' || last_char_is(s, ' ') != NULL) {
*error = EINVAL;
}
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
* the length of the passwd/group record
* as negative integer if bigger than len.
* CAVEAT: if a OS has negative errno
* this will break!!!
*/
static int FAST_FUNC get_record_line(FILE *file, char *linebuf, int 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) {
/* return -size of buffer needed to hold the */
/* full line instead of ERANGE.*/
return -idx;
}
/* terminate string and remove trailing '\n' if any.*/
linebuf[idx - 1] = '\0';
/* return ENOENT only on EOF and no data */
if (idx > 0) {
return 0;
}
return errno = ENOENT;
}
static void FAST_FUNC set_errno_and_msg(int error, const char *filename)
{
errno = error;
bb_error_msg("'%s': bad record", filename);
}
/* Returns 0 on success and matching line broken up in fields by '\0' in buf or \
error.
* We require all separators ':' to be there and warn and skip the line if they are \
not.
* int n_fields is the expected number of fields of the passwd/group file starting \
from 1.
* size_t len is used to check that the buffer provided is big enough, if not we use \
the
* return value to pass the needed lenght as negative integer to the caller.
*/
static int FAST_FUNC parse_common(FILE *f, const char *key, int field,
char *buf, size_t len, const char *filename, int n_fields)
{
int error = 0;
const char *s;
while (1) {
if ((error = get_record_line(f, buf, len)) != 0) {
/* We use error to pass the needed length to the */
/* caller as negative value instead of ERANGE */
return error;
}
/* Skip empty lines, comment lines */
if (buf[0] != '\0' && buf[0] != '#') {
/* Check that there are the expected fields */
if (tokenize(buf, ':', &error) == n_fields) {
if (key) {
s = nth_string(buf, field);
/* Empty fields not allowed */
if (!*s) {
error = EINVAL;
break;
}
if (strcmp(key, s) == 0) {
/* Record found */
break;
}
} else {
/* Sequential read - return record */
break;
}
} else {
error = EINVAL;
}
}
}
if (error) {
set_errno_and_msg(error, filename);
}
return error;
}
static int FAST_FUNC parse_file(const char *filename, int n_fields, const char *key, \
int field, char *buf, size_t len) {
int ret;
FILE *f = fopen_for_read(filename);
ret = errno;
if (f) {
ret = parse_common(f, key, field, 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, const char *filename)
{
int idx;
char **members = NULL;
char *m;
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') {
*(int *)((int) result + off[idx]) = bb_strtou(nth_string(buffer, idx), NULL, 10);
if (errno) {
*error = EINVAL;
}
}
#if ENABLE_USE_BB_SHADOW
if (def[idx] == 'l') {
char *endptr;
long ret;
/* Reuse char *m */
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') {
int i = 0;
int num_members = 0;
char *s = (char *) nth_string(buffer, idx);
/* members with leading or trailing spaces set error to EINVAL */
num_members = tokenize(s, ',', error);
members = xmalloc(sizeof (char *) * (num_members + 1));
if (*s) {
/* Leaks a little memory */
while (num_members--) {
/* Reuse char *m */
m = (char *) nth_string(s, i);
if (!*m) {
*error = EINVAL;
}
members[i++] = m;
}
}
members[i] = NULL;
*(char ***)((char *)result + off[idx]) = members;
}
}
if (*error) {
free(members);
result = NULL;
set_errno_and_msg(*error, filename);
}
return result;
}
static int FAST_FUNC fix_error_code(int error)
{
if (error < 0)
error = errno = ERANGE;
return error;
}
#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 */
};
static struct spwd * FAST_FUNC convert_to_shadow(char *buffer, struct spwd *result, \
int *error) {
return convert_to_struct(sp_def, sp_off, buffer, result, error, _PATH_SHADOW);
}
int getspnam_r(const char *name, struct spwd *spbuf, char *buf, size_t buflen, struct \
spwd **spbufp) {
int error;
*spbufp = NULL;
error = parse_file(_PATH_SHADOW, _SHADOW_FIELDS, name, 0, buf, buflen);
if (error == 0) {
*spbufp = convert_to_shadow(buf, spbuf, &error);
}
return fix_error_code(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 struct passwd * FAST_FUNC convert_to_pwd(char *buffer, struct passwd *result, \
int *error) {
return convert_to_struct(pw_def, pw_off, buffer, result, error, _PATH_PASSWD);
}
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 */
};
static struct group * FAST_FUNC convert_to_grp(char *buffer, struct group *result, \
int *error) {
return convert_to_struct(gr_def, gr_off, buffer, result, error, _PATH_GROUP);
}
/* Common code for getpwxxx_r functions. */
static int FAST_FUNC getpw_common_r(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, _PASSWD_FIELDS, name, field, buf, buflen);
if (error == 0) {
*result = convert_to_pwd(buf, pwd, &error);
}
return (error == ENOENT) ? 0 : error;
}
/* Common code for getgrxxx_r functions. */
static int FAST_FUNC getgr_common_r(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, _GROUP_FIELDS, name, field, buf, buflen);
if (error == 0) {
*result = convert_to_grp(buf, grp, &error);
}
return (error == ENOENT) ? 0 : error;
}
/* Common code for getpwxxx functions. */
static struct passwd * FAST_FUNC getpw_common_malloc(const char *name, int field)
{
int error = 0;
retry:
pwd_buffer = xrealloc(pwd_buffer, pwd_buffer_size * sizeof( char));
error = getpw_common_r(name, field, &my_pwd, pwd_buffer, pwd_buffer_size, &my_pw);
if (error < 0) {
pwd_buffer_size = abs(error);
goto retry;
}
return my_pw;
}
/* Common code for getgrxxx functions. */
static struct group * FAST_FUNC getgr_common_malloc(const char *name, int field)
{
int error = 0;
retry:
grp_buffer = xrealloc(grp_buffer, grp_buffer_size * sizeof( char));
error = getgr_common_r(name, field, &my_grp, grp_buffer, grp_buffer_size, &my_gr);
if (error < 0) {
grp_buffer_size = abs(error);
goto retry;
}
return my_gr;
}
/**********************************************************************/
int getpwent_r(struct passwd *pwbuf, char *buf, size_t buflen, struct passwd \
**pwbufp) {
int error;
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, buflen, _PATH_PASSWD, _PASSWD_FIELDS);
if (error == 0) {
*pwbufp = convert_to_pwd(buf, pwbuf, &error);
}
UNLOCK;
return fix_error_code(error);
}
int getgrent_r(struct group *gbuf, char *buf, size_t buflen, struct group **gbufp)
{
int error;
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, buflen, _PATH_GROUP, _GROUP_FIELDS);
if (error == 0) {
*gbufp = convert_to_grp(buf, gbuf, &error);
}
UNLOCK;
return fix_error_code(error);
}
int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct \
passwd **result) {
return fix_error_code(getpw_common_r(name, 0, pwd, buf, buflen, result));
}
int getgrnam_r(const char *name, struct group *grp, char *buf, size_t buflen, struct \
group **result) {
return fix_error_code(getgr_common_r(name, 0, grp, buf, buflen, result));
}
int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd \
**result) {
return fix_error_code(getpw_common_r(utoa(uid), 2, pwd, buf, buflen, result));
}
int getgrgid_r(gid_t gid, struct group *grp, char *buf, size_t buflen, struct group \
**result) {
return fix_error_code(getgr_common_r(utoa(gid), 2, grp, buf, buflen, 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, \
_GROUP_FIELDS)) == 0) { /* convert_to_grp warns on error so ret is unused */
gr = convert_to_grp(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;
}
}
}
/* our gr->gr_mem is malloced, free it to reduce memory leaks */
free(gr->gr_mem);
}
}
fclose(f);
}
if (ret < 0) {
/* buffer was to small, retry */
buflen = abs(ret);
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