freebsd-dev/lib/libc/gen/getgrent.c
Ed Schouten 9c24291370 Fix up setgrent(3) to have a POSIX-compliant prototype.
Just like with freelocale(3), I haven't been able to find any piece of
code that actually makes use of this function's return value, both in
base and in ports. The reason for this is that FreeBSD seems to be the
only operating system to have such a prototype. This is why I'm deciding
to not use symbol versioning for this.

It does seem that the pw(8) utility depends on the function's typing and
already had a switch in place to toggle between the FreeBSD and POSIX
variant of this function. Clean this up by always expecting the POSIX
variant.

There is also a single port that has a couple of local declarations of
setgrent(3) that need to be patched up. This is in the process of being
fixed.

PR:		211394 (exp-run)
2016-07-31 08:05:15 +00:00

1553 lines
33 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 <assert.h>
#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 __getgroupmembership(const char *, gid_t, gid_t *, int, int *);
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);
static int gr_addgid(gid_t, gid_t *, int, int *);
static int getgroupmembership_fallback(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 */
#ifdef NS_CACHING
static const nss_cache_info setgrent_cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
NULL, NULL);
#endif
static const ns_dtab setgrent_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(&setgrent_cache_info)
#endif
{ NULL, NULL, NULL }
};
#ifdef NS_CACHING
static const nss_cache_info endgrent_cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
NULL, NULL);
#endif
static const ns_dtab endgrent_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(&endgrent_cache_info)
#endif
{ NULL, NULL, NULL }
};
#ifdef NS_CACHING
static const nss_cache_info getgrent_r_cache_info = NS_MP_CACHE_INFO_INITIALIZER(
group, (void *)nss_lt_all,
grp_marshal_func, grp_unmarshal_func);
#endif
static const ns_dtab getgrent_r_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(&getgrent_r_cache_info)
#endif
{ NULL, NULL, NULL }
};
static int
gr_addgid(gid_t gid, gid_t *groups, int maxgrp, int *grpcnt)
{
int ret, dupc;
for (dupc = 0; dupc < MIN(maxgrp, *grpcnt); dupc++) {
if (groups[dupc] == gid)
return 1;
}
ret = 1;
if (*grpcnt < maxgrp)
groups[*grpcnt] = gid;
else
ret = 0;
(*grpcnt)++;
return ret;
}
static int
getgroupmembership_fallback(void *retval, void *mdata, va_list ap)
{
const ns_src src[] = {
{ mdata, NS_SUCCESS },
{ NULL, 0}
};
struct group grp;
struct group *grp_p;
char *buf;
size_t bufsize;
const char *uname;
gid_t *groups;
gid_t agroup;
int maxgrp, *grpcnt;
int i, rv, ret_errno;
/*
* As this is a fallback method, only provided src
* list will be respected during methods search.
*/
assert(src[0].name != NULL);
uname = va_arg(ap, const char *);
agroup = va_arg(ap, gid_t);
groups = va_arg(ap, gid_t *);
maxgrp = va_arg(ap, int);
grpcnt = va_arg(ap, int *);
rv = NS_UNAVAIL;
buf = malloc(GRP_STORAGE_INITIAL);
if (buf == NULL)
goto out;
bufsize = GRP_STORAGE_INITIAL;
gr_addgid(agroup, groups, maxgrp, grpcnt);
_nsdispatch(NULL, setgrent_dtab, NSDB_GROUP, "setgrent", src, 0);
for (;;) {
do {
ret_errno = 0;
grp_p = NULL;
rv = _nsdispatch(&grp_p, getgrent_r_dtab, NSDB_GROUP,
"getgrent_r", src, &grp, buf, bufsize, &ret_errno);
if (grp_p == NULL && ret_errno == ERANGE) {
free(buf);
if ((bufsize << 1) > GRP_STORAGE_MAX) {
buf = NULL;
errno = ERANGE;
goto out;
}
bufsize <<= 1;
buf = malloc(bufsize);
if (buf == NULL) {
goto out;
}
}
} while (grp_p == NULL && ret_errno == ERANGE);
if (ret_errno != 0) {
errno = ret_errno;
goto out;
}
if (grp_p == NULL)
break;
for (i = 0; grp.gr_mem[i]; i++) {
if (strcmp(grp.gr_mem[i], uname) == 0)
gr_addgid(grp.gr_gid, groups, maxgrp, grpcnt);
}
}
_nsdispatch(NULL, endgrent_dtab, NSDB_GROUP, "endgrent", src);
out:
free(buf);
return (rv);
}
void
setgrent(void)
{
(void)_nsdispatch(NULL, setgrent_dtab, NSDB_GROUP, "setgrent", defaultsrc, 0);
}
int
setgroupent(int stayopen)
{
(void)_nsdispatch(NULL, setgrent_dtab, NSDB_GROUP, "setgrent", defaultsrc,
stayopen);
return (1);
}
void
endgrent(void)
{
(void)_nsdispatch(NULL, endgrent_dtab, NSDB_GROUP, "endgrent", defaultsrc);
}
int
getgrent_r(struct group *grp, char *buffer, size_t bufsize,
struct group **result)
{
int rv, ret_errno;
ret_errno = 0;
*result = NULL;
rv = _nsdispatch(result, getgrent_r_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);
}
int
__getgroupmembership(const char *uname, gid_t agroup, gid_t *groups,
int maxgrp, int *grpcnt)
{
static const ns_dtab dtab[] = {
NS_FALLBACK_CB(getgroupmembership_fallback)
{ NULL, NULL, NULL }
};
assert(uname != NULL);
/* groups may be NULL if just sizing when invoked with maxgrp = 0 */
assert(grpcnt != NULL);
*grpcnt = 0;
(void)_nsdispatch(NULL, dtab, NSDB_GROUP, "getgroupmembership",
defaultsrc, uname, agroup, groups, maxgrp, grpcnt);
/* too many groups found? */
return (*grpcnt > maxgrp ? -1 : 0);
}
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, "re");
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, "re")) == 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 *)) {
*errnop = ERANGE;
rv = NS_RETURN;
break;
}
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;
pos = ftello(st->fp);
}
if (st->fp != NULL && !stayopen) {
fclose(st->fp);
st->fp = NULL;
}
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
else if (rv == NS_RETURN && *errnop == ERANGE && st->fp != NULL)
fseeko(st->fp, pos, SEEK_SET);
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 *)) {
free(result);
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 < (int)(nitems(x) - 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, "re");
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 < (int)(nitems(x) - 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, "re")) == 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 *)) {
*errnop = ERANGE;
rv = NS_RETURN;
break;
}
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;
pos = ftello(st->fp);
}
fin:
if (st->fp != NULL && !stayopen) {
fclose(st->fp);
st->fp = NULL;
}
if (rv == NS_SUCCESS && retval != NULL)
*(struct group **)retval = grp;
else if (rv == NS_RETURN && *errnop == ERANGE && st->fp != NULL)
fseeko(st->fp, pos, SEEK_SET);
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);
}
}