freebsd-skq/lib/libc/gen/getgrent.c
maxim b479e3f6b6 o Record a file offset for a last successfully parsed group file line.
If the initial buffer size (1KB) for the given group line is not big
enough, reset the offset.  It helps to do not miss this line when
getrg() reallocates the larger buffer and tries to parse the line again.

PR:		bin/52433, kern/55031, bin/83696, misc/97640, misc/98111
Submitted by:	bsw71@mail.ru, Philip M. Gollucci, Justin Erenkrantz
Glanced at:	nectar
MFC after:	1 month
2006-06-01 15:45:06 +00:00

1437 lines
31 KiB
C

/*-
* Copyright (c) 2003 Networks Associates Technology, Inc.
* All rights reserved.
*
* This software was developed for the FreeBSD Project by
* Jacques A. Vidrine, Safeport Network Services, and Network
* Associates Laboratories, the Security Research Division of Network
* Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
* ("CBOSS"), as part of the DARPA CHATS research program.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "namespace.h"
#include <sys/param.h>
#ifdef YP
#include <rpc/rpc.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypclnt.h>
#endif
#include <ctype.h>
#include <errno.h>
#ifdef HESIOD
#include <hesiod.h>
#endif
#include <grp.h>
#include <nsswitch.h>
#include <pthread.h>
#include <pthread_np.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "un-namespace.h"
#include "libc_private.h"
#include "nss_tls.h"
#ifdef NS_CACHING
#include "nscache.h"
#endif
enum constants {
GRP_STORAGE_INITIAL = 1 << 10, /* 1 KByte */
GRP_STORAGE_MAX = 1 << 20, /* 1 MByte */
SETGRENT = 1,
ENDGRENT = 2,
HESIOD_NAME_MAX = 256,
};
static const ns_src defaultsrc[] = {
{ NSSRC_COMPAT, NS_SUCCESS },
{ NULL, 0 }
};
int __gr_match_entry(const char *, size_t, enum nss_lookup_type,
const char *, gid_t);
int __gr_parse_entry(char *, size_t, struct group *, char *, size_t,
int *);
static int is_comment_line(const char *, size_t);
union key {
const char *name;
gid_t gid;
};
static struct group *getgr(int (*)(union key, struct group *, char *, size_t,
struct group **), union key);
static int wrap_getgrnam_r(union key, struct group *, char *, size_t,
struct group **);
static int wrap_getgrgid_r(union key, struct group *, char *, size_t,
struct group **);
static int wrap_getgrent_r(union key, struct group *, char *, size_t,
struct group **);
struct files_state {
FILE *fp;
int stayopen;
};
static void files_endstate(void *);
NSS_TLS_HANDLING(files);
static int files_setgrent(void *, void *, va_list);
static int files_group(void *, void *, va_list);
#ifdef HESIOD
struct dns_state {
long counter;
};
static void dns_endstate(void *);
NSS_TLS_HANDLING(dns);
static int dns_setgrent(void *, void *, va_list);
static int dns_group(void *, void *, va_list);
#endif
#ifdef YP
struct nis_state {
char domain[MAXHOSTNAMELEN];
int done;
char *key;
int keylen;
};
static void nis_endstate(void *);
NSS_TLS_HANDLING(nis);
static int nis_setgrent(void *, void *, va_list);
static int nis_group(void *, void *, va_list);
#endif
struct compat_state {
FILE *fp;
int stayopen;
char *name;
enum _compat {
COMPAT_MODE_OFF = 0,
COMPAT_MODE_ALL,
COMPAT_MODE_NAME
} compat;
};
static void compat_endstate(void *);
NSS_TLS_HANDLING(compat);
static int compat_setgrent(void *, void *, va_list);
static int compat_group(void *, void *, va_list);
#ifdef NS_CACHING
static int grp_id_func(char *, size_t *, va_list, void *);
static int grp_marshal_func(char *, size_t *, void *, va_list, void *);
static int grp_unmarshal_func(char *, size_t, void *, va_list, void *);
static int
grp_id_func(char *buffer, size_t *buffer_size, va_list ap, void *cache_mdata)
{
char *name;
gid_t gid;
size_t desired_size, size;
int res = NS_UNAVAIL;
enum nss_lookup_type lookup_type;
lookup_type = (enum nss_lookup_type)cache_mdata;
switch (lookup_type) {
case nss_lt_name:
name = va_arg(ap, char *);
size = strlen(name);
desired_size = sizeof(enum nss_lookup_type) + size + 1;
if (desired_size > *buffer_size) {
res = NS_RETURN;
goto fin;
}
memcpy(buffer, &lookup_type, sizeof(enum nss_lookup_type));
memcpy(buffer + sizeof(enum nss_lookup_type), name, size + 1);
res = NS_SUCCESS;
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
desired_size = sizeof(enum nss_lookup_type) + sizeof(gid_t);
if (desired_size > *buffer_size) {
res = NS_RETURN;
goto fin;
}
memcpy(buffer, &lookup_type, sizeof(enum nss_lookup_type));
memcpy(buffer + sizeof(enum nss_lookup_type), &gid,
sizeof(gid_t));
res = NS_SUCCESS;
break;
default:
/* should be unreachable */
return (NS_UNAVAIL);
}
fin:
*buffer_size = desired_size;
return (res);
}
static int
grp_marshal_func(char *buffer, size_t *buffer_size, void *retval, va_list ap,
void *cache_mdata)
{
char *name;
gid_t gid;
struct group *grp;
char *orig_buf;
size_t orig_buf_size;
struct group new_grp;
size_t desired_size, size, mem_size;
char *p, **mem;
switch ((enum nss_lookup_type)cache_mdata) {
case nss_lt_name:
name = va_arg(ap, char *);
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
break;
case nss_lt_all:
break;
default:
/* should be unreachable */
return (NS_UNAVAIL);
}
grp = va_arg(ap, struct group *);
orig_buf = va_arg(ap, char *);
orig_buf_size = va_arg(ap, size_t);
desired_size = _ALIGNBYTES + sizeof(struct group) + sizeof(char *);
if (grp->gr_name != NULL)
desired_size += strlen(grp->gr_name) + 1;
if (grp->gr_passwd != NULL)
desired_size += strlen(grp->gr_passwd) + 1;
if (grp->gr_mem != NULL) {
mem_size = 0;
for (mem = grp->gr_mem; *mem; ++mem) {
desired_size += strlen(*mem) + 1;
++mem_size;
}
desired_size += _ALIGNBYTES + (mem_size + 1) * sizeof(char *);
}
if (desired_size > *buffer_size) {
/* this assignment is here for future use */
*buffer_size = desired_size;
return (NS_RETURN);
}
memcpy(&new_grp, grp, sizeof(struct group));
memset(buffer, 0, desired_size);
*buffer_size = desired_size;
p = buffer + sizeof(struct group) + sizeof(char *);
memcpy(buffer + sizeof(struct group), &p, sizeof(char *));
p = (char *)_ALIGN(p);
if (new_grp.gr_name != NULL) {
size = strlen(new_grp.gr_name);
memcpy(p, new_grp.gr_name, size);
new_grp.gr_name = p;
p += size + 1;
}
if (new_grp.gr_passwd != NULL) {
size = strlen(new_grp.gr_passwd);
memcpy(p, new_grp.gr_passwd, size);
new_grp.gr_passwd = p;
p += size + 1;
}
if (new_grp.gr_mem != NULL) {
p = (char *)_ALIGN(p);
memcpy(p, new_grp.gr_mem, sizeof(char *) * mem_size);
new_grp.gr_mem = (char **)p;
p += sizeof(char *) * (mem_size + 1);
for (mem = new_grp.gr_mem; *mem; ++mem) {
size = strlen(*mem);
memcpy(p, *mem, size);
*mem = p;
p += size + 1;
}
}
memcpy(buffer, &new_grp, sizeof(struct group));
return (NS_SUCCESS);
}
static int
grp_unmarshal_func(char *buffer, size_t buffer_size, void *retval, va_list ap,
void *cache_mdata)
{
char *name;
gid_t gid;
struct group *grp;
char *orig_buf;
size_t orig_buf_size;
int *ret_errno;
char *p;
char **mem;
switch ((enum nss_lookup_type)cache_mdata) {
case nss_lt_name:
name = va_arg(ap, char *);
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
break;
case nss_lt_all:
break;
default:
/* should be unreachable */
return (NS_UNAVAIL);
}
grp = va_arg(ap, struct group *);
orig_buf = va_arg(ap, char *);
orig_buf_size = va_arg(ap, size_t);
ret_errno = va_arg(ap, int *);
if (orig_buf_size <
buffer_size - sizeof(struct group) - sizeof(char *)) {
*ret_errno = ERANGE;
return (NS_RETURN);
}
memcpy(grp, buffer, sizeof(struct group));
memcpy(&p, buffer + sizeof(struct group), sizeof(char *));
orig_buf = (char *)_ALIGN(orig_buf);
memcpy(orig_buf, buffer + sizeof(struct group) + sizeof(char *) +
_ALIGN(p) - (size_t)p,
buffer_size - sizeof(struct group) - sizeof(char *) -
_ALIGN(p) + (size_t)p);
p = (char *)_ALIGN(p);
NS_APPLY_OFFSET(grp->gr_name, orig_buf, p, char *);
NS_APPLY_OFFSET(grp->gr_passwd, orig_buf, p, char *);
if (grp->gr_mem != NULL) {
NS_APPLY_OFFSET(grp->gr_mem, orig_buf, p, char **);
for (mem = grp->gr_mem; *mem; ++mem)
NS_APPLY_OFFSET(*mem, orig_buf, p, char *);
}
if (retval != NULL)
*((struct group **)retval) = grp;
return (NS_SUCCESS);
}
NSS_MP_CACHE_HANDLING(group);
#endif /* NS_CACHING */
/* XXX IEEE Std 1003.1, 2003 specifies `void setgrent(void)' */
int
setgrent(void)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
NULL, NULL);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_setgrent, (void *)SETGRENT },
#ifdef HESIOD
{ NSSRC_DNS, dns_setgrent, (void *)SETGRENT },
#endif
#ifdef YP
{ NSSRC_NIS, nis_setgrent, (void *)SETGRENT },
#endif
{ NSSRC_COMPAT, compat_setgrent, (void *)SETGRENT },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
(void)_nsdispatch(NULL, dtab, NSDB_GROUP, "setgrent", defaultsrc, 0);
return (1);
}
int
setgroupent(int stayopen)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
NULL, NULL);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_setgrent, (void *)SETGRENT },
#ifdef HESIOD
{ NSSRC_DNS, dns_setgrent, (void *)SETGRENT },
#endif
#ifdef YP
{ NSSRC_NIS, nis_setgrent, (void *)SETGRENT },
#endif
{ NSSRC_COMPAT, compat_setgrent, (void *)SETGRENT },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
(void)_nsdispatch(NULL, dtab, NSDB_GROUP, "setgrent", defaultsrc,
stayopen);
return (1);
}
void
endgrent(void)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
NULL, NULL);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_setgrent, (void *)ENDGRENT },
#ifdef HESIOD
{ NSSRC_DNS, dns_setgrent, (void *)ENDGRENT },
#endif
#ifdef YP
{ NSSRC_NIS, nis_setgrent, (void *)ENDGRENT },
#endif
{ NSSRC_COMPAT, compat_setgrent, (void *)ENDGRENT },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
(void)_nsdispatch(NULL, dtab, NSDB_GROUP, "endgrent", defaultsrc);
}
int
getgrent_r(struct group *grp, char *buffer, size_t bufsize,
struct group **result)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
grp_marshal_func, grp_unmarshal_func);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_group, (void *)nss_lt_all },
#ifdef HESIOD
{ NSSRC_DNS, dns_group, (void *)nss_lt_all },
#endif
#ifdef YP
{ NSSRC_NIS, nis_group, (void *)nss_lt_all },
#endif
{ NSSRC_COMPAT, compat_group, (void *)nss_lt_all },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
int rv, ret_errno;
ret_errno = 0;
*result = NULL;
rv = _nsdispatch(result, dtab, NSDB_GROUP, "getgrent_r", defaultsrc,
grp, buffer, bufsize, &ret_errno);
if (rv == NS_SUCCESS)
return (0);
else
return (ret_errno);
}
int
getgrnam_r(const char *name, struct group *grp, char *buffer, size_t bufsize,
struct group **result)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info =
NS_COMMON_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_name,
grp_id_func, grp_marshal_func, grp_unmarshal_func);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_group, (void *)nss_lt_name },
#ifdef HESIOD
{ NSSRC_DNS, dns_group, (void *)nss_lt_name },
#endif
#ifdef YP
{ NSSRC_NIS, nis_group, (void *)nss_lt_name },
#endif
{ NSSRC_COMPAT, compat_group, (void *)nss_lt_name },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
int rv, ret_errno;
ret_errno = 0;
*result = NULL;
rv = _nsdispatch(result, dtab, NSDB_GROUP, "getgrnam_r", defaultsrc,
name, grp, buffer, bufsize, &ret_errno);
if (rv == NS_SUCCESS)
return (0);
else
return (ret_errno);
}
int
getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t bufsize,
struct group **result)
{
#ifdef NS_CACHING
static const nss_cache_info cache_info =
NS_COMMON_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_id,
grp_id_func, grp_marshal_func, grp_unmarshal_func);
#endif
static const ns_dtab dtab[] = {
{ NSSRC_FILES, files_group, (void *)nss_lt_id },
#ifdef HESIOD
{ NSSRC_DNS, dns_group, (void *)nss_lt_id },
#endif
#ifdef YP
{ NSSRC_NIS, nis_group, (void *)nss_lt_id },
#endif
{ NSSRC_COMPAT, compat_group, (void *)nss_lt_id },
#ifdef NS_CACHING
NS_CACHE_CB(&cache_info)
#endif
{ NULL, NULL, NULL }
};
int rv, ret_errno;
ret_errno = 0;
*result = NULL;
rv = _nsdispatch(result, dtab, NSDB_GROUP, "getgrgid_r", defaultsrc,
gid, grp, buffer, bufsize, &ret_errno);
if (rv == NS_SUCCESS)
return (0);
else
return (ret_errno);
}
static struct group grp;
static char *grp_storage;
static size_t grp_storage_size;
static struct group *
getgr(int (*fn)(union key, struct group *, char *, size_t, struct group **),
union key key)
{
int rv;
struct group *res;
if (grp_storage == NULL) {
grp_storage = malloc(GRP_STORAGE_INITIAL);
if (grp_storage == NULL)
return (NULL);
grp_storage_size = GRP_STORAGE_INITIAL;
}
do {
rv = fn(key, &grp, grp_storage, grp_storage_size, &res);
if (res == NULL && rv == ERANGE) {
free(grp_storage);
if ((grp_storage_size << 1) > GRP_STORAGE_MAX) {
grp_storage = NULL;
errno = ERANGE;
return (NULL);
}
grp_storage_size <<= 1;
grp_storage = malloc(grp_storage_size);
if (grp_storage == NULL)
return (NULL);
}
} while (res == NULL && rv == ERANGE);
if (rv != 0)
errno = rv;
return (res);
}
static int
wrap_getgrnam_r(union key key, struct group *grp, char *buffer, size_t bufsize,
struct group **res)
{
return (getgrnam_r(key.name, grp, buffer, bufsize, res));
}
static int
wrap_getgrgid_r(union key key, struct group *grp, char *buffer, size_t bufsize,
struct group **res)
{
return (getgrgid_r(key.gid, grp, buffer, bufsize, res));
}
static int
wrap_getgrent_r(union key key __unused, struct group *grp, char *buffer,
size_t bufsize, struct group **res)
{
return (getgrent_r(grp, buffer, bufsize, res));
}
struct group *
getgrnam(const char *name)
{
union key key;
key.name = name;
return (getgr(wrap_getgrnam_r, key));
}
struct group *
getgrgid(gid_t gid)
{
union key key;
key.gid = gid;
return (getgr(wrap_getgrgid_r, key));
}
struct group *
getgrent(void)
{
union key key;
key.gid = 0; /* not used */
return (getgr(wrap_getgrent_r, key));
}
static int
is_comment_line(const char *s, size_t n)
{
const char *eom;
eom = &s[n];
for (; s < eom; s++)
if (*s == '#' || !isspace((unsigned char)*s))
break;
return (*s == '#' || s == eom);
}
/*
* files backend
*/
static void
files_endstate(void *p)
{
if (p == NULL)
return;
if (((struct files_state *)p)->fp != NULL)
fclose(((struct files_state *)p)->fp);
free(p);
}
static int
files_setgrent(void *retval, void *mdata, va_list ap)
{
struct files_state *st;
int rv, stayopen;
rv = files_getstate(&st);
if (rv != 0)
return (NS_UNAVAIL);
switch ((enum constants)mdata) {
case SETGRENT:
stayopen = va_arg(ap, int);
if (st->fp != NULL)
rewind(st->fp);
else if (stayopen)
st->fp = fopen(_PATH_GROUP, "r");
break;
case ENDGRENT:
if (st->fp != NULL) {
fclose(st->fp);
st->fp = NULL;
}
break;
default:
break;
}
return (NS_UNAVAIL);
}
static int
files_group(void *retval, void *mdata, va_list ap)
{
struct files_state *st;
enum nss_lookup_type how;
const char *name, *line;
struct group *grp;
gid_t gid;
char *buffer;
size_t bufsize, linesize;
off_t pos;
int rv, stayopen, *errnop;
name = NULL;
gid = (gid_t)-1;
how = (enum nss_lookup_type)mdata;
switch (how) {
case nss_lt_name:
name = va_arg(ap, const char *);
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
break;
case nss_lt_all:
break;
default:
return (NS_NOTFOUND);
}
grp = va_arg(ap, struct group *);
buffer = va_arg(ap, char *);
bufsize = va_arg(ap, size_t);
errnop = va_arg(ap, int *);
*errnop = files_getstate(&st);
if (*errnop != 0)
return (NS_UNAVAIL);
if (st->fp == NULL &&
((st->fp = fopen(_PATH_GROUP, "r")) == NULL)) {
*errnop = errno;
return (NS_UNAVAIL);
}
if (how == nss_lt_all)
stayopen = 1;
else {
rewind(st->fp);
stayopen = st->stayopen;
}
rv = NS_NOTFOUND;
pos = ftello(st->fp);
while ((line = fgetln(st->fp, &linesize)) != NULL) {
if (line[linesize-1] == '\n')
linesize--;
rv = __gr_match_entry(line, linesize, how, name, gid);
if (rv != NS_SUCCESS)
continue;
/* We need room at least for the line, a string NUL
* terminator, alignment padding, and one (char *)
* pointer for the member list terminator.
*/
if (bufsize <= linesize + _ALIGNBYTES + sizeof(char *)) {
fseeko(st->fp, pos, SEEK_SET);
*errnop = ERANGE;
rv = NS_RETURN;
break;
}
pos = ftello(st->fp);
memcpy(buffer, line, linesize);
buffer[linesize] = '\0';
rv = __gr_parse_entry(buffer, linesize, grp,
&buffer[linesize + 1], bufsize - linesize - 1, errnop);
if (rv & NS_TERMINATE)
break;
}
if (!stayopen && st->fp != NULL) {
fclose(st->fp);
st->fp = NULL;
}
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
return (rv);
}
#ifdef HESIOD
/*
* dns backend
*/
static void
dns_endstate(void *p)
{
free(p);
}
static int
dns_setgrent(void *retval, void *cb_data, va_list ap)
{
struct dns_state *st;
int rv;
rv = dns_getstate(&st);
if (rv != 0)
return (NS_UNAVAIL);
st->counter = 0;
return (NS_UNAVAIL);
}
static int
dns_group(void *retval, void *mdata, va_list ap)
{
char buf[HESIOD_NAME_MAX];
struct dns_state *st;
struct group *grp;
const char *name, *label;
void *ctx;
char *buffer, **hes;
size_t bufsize, adjsize, linesize;
gid_t gid;
enum nss_lookup_type how;
int rv, *errnop;
ctx = NULL;
hes = NULL;
name = NULL;
gid = (gid_t)-1;
how = (enum nss_lookup_type)mdata;
switch (how) {
case nss_lt_name:
name = va_arg(ap, const char *);
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
break;
case nss_lt_all:
break;
}
grp = va_arg(ap, struct group *);
buffer = va_arg(ap, char *);
bufsize = va_arg(ap, size_t);
errnop = va_arg(ap, int *);
*errnop = dns_getstate(&st);
if (*errnop != 0)
return (NS_UNAVAIL);
if (hesiod_init(&ctx) != 0) {
*errnop = errno;
rv = NS_UNAVAIL;
goto fin;
}
do {
rv = NS_NOTFOUND;
switch (how) {
case nss_lt_name:
label = name;
break;
case nss_lt_id:
if (snprintf(buf, sizeof(buf), "%lu",
(unsigned long)gid) >= sizeof(buf))
goto fin;
label = buf;
break;
case nss_lt_all:
if (st->counter < 0)
goto fin;
if (snprintf(buf, sizeof(buf), "group-%ld",
st->counter++) >= sizeof(buf))
goto fin;
label = buf;
break;
}
hes = hesiod_resolve(ctx, label,
how == nss_lt_id ? "gid" : "group");
if ((how == nss_lt_id && hes == NULL &&
(hes = hesiod_resolve(ctx, buf, "group")) == NULL) ||
hes == NULL) {
if (how == nss_lt_all)
st->counter = -1;
if (errno != ENOENT)
*errnop = errno;
goto fin;
}
rv = __gr_match_entry(hes[0], strlen(hes[0]), how, name, gid);
if (rv != NS_SUCCESS) {
hesiod_free_list(ctx, hes);
hes = NULL;
continue;
}
/* We need room at least for the line, a string NUL
* terminator, alignment padding, and one (char *)
* pointer for the member list terminator.
*/
adjsize = bufsize - _ALIGNBYTES - sizeof(char *);
linesize = strlcpy(buffer, hes[0], adjsize);
if (linesize >= adjsize) {
*errnop = ERANGE;
rv = NS_RETURN;
goto fin;
}
hesiod_free_list(ctx, hes);
hes = NULL;
rv = __gr_parse_entry(buffer, linesize, grp,
&buffer[linesize + 1], bufsize - linesize - 1, errnop);
} while (how == nss_lt_all && !(rv & NS_TERMINATE));
fin:
if (hes != NULL)
hesiod_free_list(ctx, hes);
if (ctx != NULL)
hesiod_end(ctx);
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
return (rv);
}
#endif /* HESIOD */
#ifdef YP
/*
* nis backend
*/
static void
nis_endstate(void *p)
{
if (p == NULL)
return;
free(((struct nis_state *)p)->key);
free(p);
}
static int
nis_setgrent(void *retval, void *cb_data, va_list ap)
{
struct nis_state *st;
int rv;
rv = nis_getstate(&st);
if (rv != 0)
return (NS_UNAVAIL);
st->done = 0;
free(st->key);
st->key = NULL;
return (NS_UNAVAIL);
}
static int
nis_group(void *retval, void *mdata, va_list ap)
{
char *map;
struct nis_state *st;
struct group *grp;
const char *name;
char *buffer, *key, *result;
size_t bufsize;
gid_t gid;
enum nss_lookup_type how;
int *errnop, keylen, resultlen, rv;
name = NULL;
gid = (gid_t)-1;
how = (enum nss_lookup_type)mdata;
switch (how) {
case nss_lt_name:
name = va_arg(ap, const char *);
map = "group.byname";
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
map = "group.bygid";
break;
case nss_lt_all:
map = "group.byname";
break;
}
grp = va_arg(ap, struct group *);
buffer = va_arg(ap, char *);
bufsize = va_arg(ap, size_t);
errnop = va_arg(ap, int *);
*errnop = nis_getstate(&st);
if (*errnop != 0)
return (NS_UNAVAIL);
if (st->domain[0] == '\0') {
if (getdomainname(st->domain, sizeof(st->domain)) != 0) {
*errnop = errno;
return (NS_UNAVAIL);
}
}
result = NULL;
do {
rv = NS_NOTFOUND;
switch (how) {
case nss_lt_name:
if (strlcpy(buffer, name, bufsize) >= bufsize)
goto erange;
break;
case nss_lt_id:
if (snprintf(buffer, bufsize, "%lu",
(unsigned long)gid) >= bufsize)
goto erange;
break;
case nss_lt_all:
if (st->done)
goto fin;
break;
}
result = NULL;
if (how == nss_lt_all) {
if (st->key == NULL)
rv = yp_first(st->domain, map, &st->key,
&st->keylen, &result, &resultlen);
else {
key = st->key;
keylen = st->keylen;
st->key = NULL;
rv = yp_next(st->domain, map, key, keylen,
&st->key, &st->keylen, &result,
&resultlen);
free(key);
}
if (rv != 0) {
free(result);
free(st->key);
st->key = NULL;
if (rv == YPERR_NOMORE) {
st->done = 1;
rv = NS_NOTFOUND;
} else
rv = NS_UNAVAIL;
goto fin;
}
} else {
rv = yp_match(st->domain, map, buffer, strlen(buffer),
&result, &resultlen);
if (rv == YPERR_KEY) {
rv = NS_NOTFOUND;
continue;
} else if (rv != 0) {
free(result);
rv = NS_UNAVAIL;
continue;
}
}
/* We need room at least for the line, a string NUL
* terminator, alignment padding, and one (char *)
* pointer for the member list terminator.
*/
if (resultlen >= bufsize - _ALIGNBYTES - sizeof(char *))
goto erange;
memcpy(buffer, result, resultlen);
buffer[resultlen] = '\0';
free(result);
rv = __gr_match_entry(buffer, resultlen, how, name, gid);
if (rv == NS_SUCCESS)
rv = __gr_parse_entry(buffer, resultlen, grp,
&buffer[resultlen+1], bufsize - resultlen - 1,
errnop);
} while (how == nss_lt_all && !(rv & NS_TERMINATE));
fin:
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
return (rv);
erange:
*errnop = ERANGE;
return (NS_RETURN);
}
#endif /* YP */
/*
* compat backend
*/
static void
compat_endstate(void *p)
{
struct compat_state *st;
if (p == NULL)
return;
st = (struct compat_state *)p;
free(st->name);
if (st->fp != NULL)
fclose(st->fp);
free(p);
}
static int
compat_setgrent(void *retval, void *mdata, va_list ap)
{
static const ns_src compatsrc[] = {
#ifdef YP
{ NSSRC_NIS, NS_SUCCESS },
#endif
{ NULL, 0 }
};
ns_dtab dtab[] = {
#ifdef HESIOD
{ NSSRC_DNS, dns_setgrent, NULL },
#endif
#ifdef YP
{ NSSRC_NIS, nis_setgrent, NULL },
#endif
{ NULL, NULL, NULL }
};
struct compat_state *st;
int rv, stayopen;
#define set_setent(x, y) do { \
int i; \
\
for (i = 0; i < (sizeof(x)/sizeof(x[0])) - 1; i++) \
x[i].mdata = (void *)y; \
} while (0)
rv = compat_getstate(&st);
if (rv != 0)
return (NS_UNAVAIL);
switch ((enum constants)mdata) {
case SETGRENT:
stayopen = va_arg(ap, int);
if (st->fp != NULL)
rewind(st->fp);
else if (stayopen)
st->fp = fopen(_PATH_GROUP, "r");
set_setent(dtab, mdata);
(void)_nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "setgrent",
compatsrc, 0);
break;
case ENDGRENT:
if (st->fp != NULL) {
fclose(st->fp);
st->fp = NULL;
}
set_setent(dtab, mdata);
(void)_nsdispatch(NULL, dtab, NSDB_GROUP_COMPAT, "endgrent",
compatsrc, 0);
break;
default:
break;
}
st->compat = COMPAT_MODE_OFF;
free(st->name);
st->name = NULL;
return (NS_UNAVAIL);
#undef set_setent
}
static int
compat_group(void *retval, void *mdata, va_list ap)
{
static const ns_src compatsrc[] = {
#ifdef YP
{ NSSRC_NIS, NS_SUCCESS },
#endif
{ NULL, 0 }
};
ns_dtab dtab[] = {
#ifdef YP
{ NSSRC_NIS, nis_group, NULL },
#endif
#ifdef HESIOD
{ NSSRC_DNS, dns_group, NULL },
#endif
{ NULL, NULL, NULL }
};
struct compat_state *st;
enum nss_lookup_type how;
const char *name, *line;
struct group *grp;
gid_t gid;
char *buffer, *p;
void *discard;
size_t bufsize, linesize;
off_t pos;
int rv, stayopen, *errnop;
#define set_lookup_type(x, y) do { \
int i; \
\
for (i = 0; i < (sizeof(x)/sizeof(x[0])) - 1; i++) \
x[i].mdata = (void *)y; \
} while (0)
name = NULL;
gid = (gid_t)-1;
how = (enum nss_lookup_type)mdata;
switch (how) {
case nss_lt_name:
name = va_arg(ap, const char *);
break;
case nss_lt_id:
gid = va_arg(ap, gid_t);
break;
case nss_lt_all:
break;
default:
return (NS_NOTFOUND);
}
grp = va_arg(ap, struct group *);
buffer = va_arg(ap, char *);
bufsize = va_arg(ap, size_t);
errnop = va_arg(ap, int *);
*errnop = compat_getstate(&st);
if (*errnop != 0)
return (NS_UNAVAIL);
if (st->fp == NULL &&
((st->fp = fopen(_PATH_GROUP, "r")) == NULL)) {
*errnop = errno;
rv = NS_UNAVAIL;
goto fin;
}
if (how == nss_lt_all)
stayopen = 1;
else {
rewind(st->fp);
stayopen = st->stayopen;
}
docompat:
switch (st->compat) {
case COMPAT_MODE_ALL:
set_lookup_type(dtab, how);
switch (how) {
case nss_lt_all:
rv = _nsdispatch(&discard, dtab, NSDB_GROUP_COMPAT,
"getgrent_r", compatsrc, grp, buffer, bufsize,
errnop);
break;
case nss_lt_id:
rv = _nsdispatch(&discard, dtab, NSDB_GROUP_COMPAT,
"getgrgid_r", compatsrc, gid, grp, buffer, bufsize,
errnop);
break;
case nss_lt_name:
rv = _nsdispatch(&discard, dtab, NSDB_GROUP_COMPAT,
"getgrnam_r", compatsrc, name, grp, buffer,
bufsize, errnop);
break;
}
if (rv & NS_TERMINATE)
goto fin;
st->compat = COMPAT_MODE_OFF;
break;
case COMPAT_MODE_NAME:
set_lookup_type(dtab, nss_lt_name);
rv = _nsdispatch(&discard, dtab, NSDB_GROUP_COMPAT,
"getgrnam_r", compatsrc, st->name, grp, buffer, bufsize,
errnop);
switch (rv) {
case NS_SUCCESS:
switch (how) {
case nss_lt_name:
if (strcmp(name, grp->gr_name) != 0)
rv = NS_NOTFOUND;
break;
case nss_lt_id:
if (gid != grp->gr_gid)
rv = NS_NOTFOUND;
break;
default:
break;
}
break;
case NS_RETURN:
goto fin;
default:
break;
}
free(st->name);
st->name = NULL;
st->compat = COMPAT_MODE_OFF;
if (rv == NS_SUCCESS)
goto fin;
break;
default:
break;
}
rv = NS_NOTFOUND;
pos = ftello(st->fp);
while ((line = fgetln(st->fp, &linesize)) != NULL) {
if (line[linesize-1] == '\n')
linesize--;
if (linesize > 2 && line[0] == '+') {
p = memchr(&line[1], ':', linesize);
if (p == NULL || p == &line[1])
st->compat = COMPAT_MODE_ALL;
else {
st->name = malloc(p - line);
if (st->name == NULL) {
syslog(LOG_ERR,
"getgrent memory allocation failure");
*errnop = ENOMEM;
rv = NS_UNAVAIL;
break;
}
memcpy(st->name, &line[1], p - line - 1);
st->name[p - line - 1] = '\0';
st->compat = COMPAT_MODE_NAME;
}
goto docompat;
}
rv = __gr_match_entry(line, linesize, how, name, gid);
if (rv != NS_SUCCESS)
continue;
/* We need room at least for the line, a string NUL
* terminator, alignment padding, and one (char *)
* pointer for the member list terminator.
*/
if (bufsize <= linesize + _ALIGNBYTES + sizeof(char *)) {
fseeko(st->fp, pos, SEEK_SET);
*errnop = ERANGE;
rv = NS_RETURN;
break;
}
pos = ftello(st->fp);
memcpy(buffer, line, linesize);
buffer[linesize] = '\0';
rv = __gr_parse_entry(buffer, linesize, grp,
&buffer[linesize + 1], bufsize - linesize - 1, errnop);
if (rv & NS_TERMINATE)
break;
}
fin:
if (!stayopen && st->fp != NULL) {
fclose(st->fp);
st->fp = NULL;
}
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
return (rv);
#undef set_lookup_type
}
/*
* common group line matching and parsing
*/
int
__gr_match_entry(const char *line, size_t linesize, enum nss_lookup_type how,
const char *name, gid_t gid)
{
size_t namesize;
const char *p, *eol;
char *q;
unsigned long n;
int i, needed;
if (linesize == 0 || is_comment_line(line, linesize))
return (NS_NOTFOUND);
switch (how) {
case nss_lt_name: needed = 1; break;
case nss_lt_id: needed = 2; break;
default: needed = 2; break;
}
eol = &line[linesize];
for (p = line, i = 0; i < needed && p < eol; p++)
if (*p == ':')
i++;
if (i < needed)
return (NS_NOTFOUND);
switch (how) {
case nss_lt_name:
namesize = strlen(name);
if (namesize + 1 == (size_t)(p - line) &&
memcmp(line, name, namesize) == 0)
return (NS_SUCCESS);
break;
case nss_lt_id:
n = strtoul(p, &q, 10);
if (q < eol && *q == ':' && gid == (gid_t)n)
return (NS_SUCCESS);
break;
case nss_lt_all:
return (NS_SUCCESS);
default:
break;
}
return (NS_NOTFOUND);
}
int
__gr_parse_entry(char *line, size_t linesize, struct group *grp, char *membuf,
size_t membufsize, int *errnop)
{
char *s_gid, *s_mem, *p, **members;
unsigned long n;
int maxmembers;
memset(grp, 0, sizeof(*grp));
members = (char **)_ALIGN(membuf);
membufsize -= (char *)members - membuf;
maxmembers = membufsize / sizeof(*members);
if (maxmembers <= 0 ||
(grp->gr_name = strsep(&line, ":")) == NULL ||
grp->gr_name[0] == '\0' ||
(grp->gr_passwd = strsep(&line, ":")) == NULL ||
(s_gid = strsep(&line, ":")) == NULL ||
s_gid[0] == '\0')
return (NS_NOTFOUND);
s_mem = line;
n = strtoul(s_gid, &s_gid, 10);
if (s_gid[0] != '\0')
return (NS_NOTFOUND);
grp->gr_gid = (gid_t)n;
grp->gr_mem = members;
while (maxmembers > 1 && s_mem != NULL) {
p = strsep(&s_mem, ",");
if (p != NULL && *p != '\0') {
*members++ = p;
maxmembers--;
}
}
*members = NULL;
if (s_mem == NULL)
return (NS_SUCCESS);
else {
*errnop = ERANGE;
return (NS_RETURN);
}
}