/*
* System Security Services Daemon. NSS client interface
*
* Copyright (C) Simo Sorce 2007
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* GROUP database NSS interface */
#include <nss.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "sss_cli.h"
#include "nss_mc.h"
static struct sss_nss_getgrent_data {
size_t len;
size_t ptr;
uint8_t *data;
} sss_nss_getgrent_data;
static void sss_nss_getgrent_data_clean(void)
{
if (sss_nss_getgrent_data.data != NULL) {
free(sss_nss_getgrent_data.data);
sss_nss_getgrent_data.data = NULL;
}
sss_nss_getgrent_data.len = 0;
sss_nss_getgrent_data.ptr = 0;
}
enum sss_nss_gr_type {
GETGR_NONE,
GETGR_NAME,
GETGR_GID
};
static struct sss_nss_getgr_data {
enum sss_nss_gr_type type;
union {
char *grname;
gid_t gid;
} id;
uint8_t *repbuf;
size_t replen;
} sss_nss_getgr_data;
static void sss_nss_getgr_data_clean(bool freebuf)
{
if (sss_nss_getgr_data.type == GETGR_NAME) {
free(sss_nss_getgr_data.id.grname);
}
if (freebuf) {
free(sss_nss_getgr_data.repbuf);
}
memset(&sss_nss_getgr_data, 0, sizeof(struct sss_nss_getgr_data));
}
static enum nss_status sss_nss_get_getgr_cache(const char *name, gid_t gid,
enum sss_nss_gr_type type,
uint8_t **repbuf,
size_t *replen,
int *errnop)
{
bool freebuf = true;
enum nss_status status;
int ret = 0;
if (sss_nss_getgr_data.type != type) {
status = NSS_STATUS_NOTFOUND;
goto done;
}
switch (type) {
case GETGR_NAME:
ret = strcmp(name, sss_nss_getgr_data.id.grname);
if (ret != 0) {
status = NSS_STATUS_NOTFOUND;
goto done;
}
break;
case GETGR_GID:
if (sss_nss_getgr_data.id.gid != gid) {
status = NSS_STATUS_NOTFOUND;
goto done;
}
break;
default:
status = NSS_STATUS_TRYAGAIN;
ret = EINVAL;
goto done;
}
/* ok we have it, remove from cache and pass back to the caller */
*repbuf = sss_nss_getgr_data.repbuf;
*replen = sss_nss_getgr_data.replen;
/* prevent _clean() from freeing the buffer */
freebuf = false;
status = NSS_STATUS_SUCCESS;
done:
sss_nss_getgr_data_clean(freebuf);
*errnop = ret;
return status;
}
/* this function always takes ownership of repbuf and NULLs it before
* returning */
static void sss_nss_save_getgr_cache(const char *name, gid_t gid,
enum sss_nss_gr_type type,
uint8_t **repbuf, size_t replen)
{
int ret = 0;
sss_nss_getgr_data.type = type;
sss_nss_getgr_data.repbuf = *repbuf;
sss_nss_getgr_data.replen = replen;
switch (type) {
case GETGR_NAME:
if (name == NULL) {
ret = EINVAL;
goto done;
}
sss_nss_getgr_data.id.grname = strdup(name);
if (!sss_nss_getgr_data.id.grname) {
ret = ENOMEM;
goto done;
}
break;
case GETGR_GID:
if (gid == 0) {
ret = EINVAL;
goto done;
}
sss_nss_getgr_data.id.gid = gid;
break;
default:
ret = EINVAL;
goto done;
}
done:
if (ret) {
sss_nss_getgr_data_clean(true);
}
*repbuf = NULL;
}
/* GETGRNAM Request:
*
* 0-X: string with name
*
* GERTGRGID Request:
*
* 0-7: 32bit number with gid
*
* INITGROUPS Request:
*
* 0-3: 32bit number with gid
* 4-7: 32bit unsigned with max num of entries
*
* Replies:
*
* 0-3: 32bit unsigned number of results
* 4-7: 32bit unsigned (reserved/padding)
* For each result (64bit padded ?):
* 0-3: 32bit number gid
* 4-7: 32bit unsigned number of members
* 8-X: sequence of 0 terminated strings (name, passwd, mem..)
*
* FIXME: do we need to pad so that each result is 32 bit aligned ?
*/
struct sss_nss_gr_rep {
struct group *result;
char *buffer;
size_t buflen;
};
static int sss_nss_getgr_readrep(struct sss_nss_gr_rep *pr,
uint8_t *buf, size_t *len)
{
errno_t ret;
size_t i, l, slen, ptmem, pad, dlen, glen;
char *sbuf;
uint32_t mem_num;
uint32_t c;
if (*len < 11) { /* not enough space for data, bad packet */
return EBADMSG;
}
SAFEALIGN_COPY_UINT32(&c, buf, NULL);
pr->result->gr_gid = c;
SAFEALIGN_COPY_UINT32(&mem_num, buf+sizeof(uint32_t), NULL);
sbuf = (char *)&buf[8];
slen = *len - 8;
dlen = pr->buflen;
pr->result->gr_name = &(pr->buffer[0]);
i = 0;
ret = sss_readrep_copy_string(sbuf, &i,
&slen, &dlen,
&pr->result->gr_name,
NULL);
if (ret != EOK) return ret;
pr->result->gr_passwd = &(pr->buffer[i]);
ret = sss_readrep_copy_string(sbuf, &i,
&slen, &dlen,
&pr->result->gr_passwd,
NULL);
if (ret != EOK) return ret;
/* Make sure pr->buffer[i+pad] is aligned to sizeof(char *) */
pad = PADDING_SIZE(i, char *);
/* now members */
pr->result->gr_mem = DISCARD_ALIGN(&(pr->buffer[i+pad]), char **);
ptmem = (sizeof(char *) * (mem_num + 1)) + pad;
if (ptmem > dlen) {
return ERANGE; /* not ENOMEM, ERANGE is what glibc looks for */
}
dlen -= ptmem;
ptmem += i;
pr->result->gr_mem[mem_num] = NULL; /* terminate array */
for (l = 0; l < mem_num; l++) {
pr->result->gr_mem[l] = &(pr->buffer[ptmem]);
ret = sss_readrep_copy_string(sbuf, &i,
&slen, &dlen,
&pr->result->gr_mem[l],
&glen);
if (ret != EOK) return ret;
ptmem += glen + 1;
}
*len = slen -i;
return 0;
}
/* INITGROUP Reply:
*
* 0-3: 32bit unsigned number of results
* 4-7: 32bit unsigned (reserved/padding)
* For each result:
* 0-4: 32bit number with gid
*/
enum nss_status _nss_sss_initgroups_dyn(const char *user, gid_t group,
long int *start, long int *size,
gid_t **groups, long int limit,
int *errnop)
{
struct sss_cli_req_data rd;
uint8_t *repbuf;
size_t replen;
enum nss_status nret;
size_t buf_index = 0;
size_t user_len;
uint32_t num_ret;
long int l, max_ret;
int ret;
ret = sss_strnlen(user, SSS_NAME_MAX, &user_len);
if (ret != 0) {
*errnop = EINVAL;
return NSS_STATUS_NOTFOUND;
}
ret = sss_nss_mc_initgroups_dyn(user, user_len, group, start, size,
groups, limit);
switch (ret) {
case 0:
*errnop = 0;
return NSS_STATUS_SUCCESS;
case ERANGE:
*errnop = ERANGE;
return NSS_STATUS_TRYAGAIN;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
rd.len = user_len + 1;
rd.data = user;
sss_nss_lock();
/* previous thread might already initialize entry in mmap cache */
ret = sss_nss_mc_initgroups_dyn(user, user_len, group, start, size,
groups, limit);
switch (ret) {
case 0:
*errnop = 0;
nret = NSS_STATUS_SUCCESS;
goto out;
case ERANGE:
*errnop = ERANGE;
nret = NSS_STATUS_TRYAGAIN;
goto out;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
nret = sss_nss_make_request(SSS_NSS_INITGR, &rd,
&repbuf, &replen, errnop);
if (nret != NSS_STATUS_SUCCESS) {
goto out;
}
/* no results if not found */
SAFEALIGN_COPY_UINT32(&num_ret, repbuf, NULL);
if (num_ret == 0) {
free(repbuf);
nret = NSS_STATUS_NOTFOUND;
goto out;
}
max_ret = num_ret;
/* check we have enough space in the buffer */
if ((*size - *start) < num_ret) {
long int newsize;
gid_t *newgroups;
newsize = *size + num_ret;
if ((limit > 0) && (newsize > limit)) {
newsize = limit;
max_ret = newsize - *start;
}
newgroups = (gid_t *)realloc((*groups), newsize * sizeof(**groups));
if (!newgroups) {
*errnop = ENOMEM;
free(repbuf);
nret = NSS_STATUS_TRYAGAIN;
goto out;
}
*groups = newgroups;
*size = newsize;
}
/* Skip first two 32 bit values (number of results and
* reserved padding) */
buf_index = 2 * sizeof(uint32_t);
for (l = 0; l < max_ret; l++) {
SAFEALIGN_COPY_UINT32(&(*groups)[*start], repbuf + buf_index,
&buf_index);
*start += 1;
}
free(repbuf);
nret = NSS_STATUS_SUCCESS;
out:
sss_nss_unlock();
return nret;
}
enum nss_status _nss_sss_getgrnam_r(const char *name, struct group *result,
char *buffer, size_t buflen, int *errnop)
{
struct sss_cli_req_data rd;
struct sss_nss_gr_rep grrep;
uint8_t *repbuf;
size_t replen, len, name_len;
uint32_t num_results;
enum nss_status nret;
int ret;
/* Caught once glibc passing in buffer == 0x0 */
if (!buffer || !buflen) {
*errnop = ERANGE;
return NSS_STATUS_TRYAGAIN;
}
ret = sss_strnlen(name, SSS_NAME_MAX, &name_len);
if (ret != 0) {
*errnop = EINVAL;
return NSS_STATUS_NOTFOUND;
}
ret = sss_nss_mc_getgrnam(name, name_len, result, buffer, buflen);
switch (ret) {
case 0:
*errnop = 0;
return NSS_STATUS_SUCCESS;
case ERANGE:
*errnop = ERANGE;
return NSS_STATUS_TRYAGAIN;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
rd.len = name_len + 1;
rd.data = name;
sss_nss_lock();
/* previous thread might already initialize entry in mmap cache */
ret = sss_nss_mc_getgrnam(name, name_len, result, buffer, buflen);
switch (ret) {
case 0:
*errnop = 0;
nret = NSS_STATUS_SUCCESS;
goto out;
case ERANGE:
*errnop = ERANGE;
nret = NSS_STATUS_TRYAGAIN;
goto out;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
nret = sss_nss_get_getgr_cache(name, 0, GETGR_NAME,
&repbuf, &replen, errnop);
if (nret == NSS_STATUS_NOTFOUND) {
nret = sss_nss_make_request(SSS_NSS_GETGRNAM, &rd,
&repbuf, &replen, errnop);
}
if (nret != NSS_STATUS_SUCCESS) {
goto out;
}
grrep.result = result;
grrep.buffer = buffer;
grrep.buflen = buflen;
/* Get number of results from repbuf. */
SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
/* no results if not found */
if (num_results == 0) {
free(repbuf);
nret = NSS_STATUS_NOTFOUND;
goto out;
}
/* only 1 result is accepted for this function */
if (num_results != 1) {
*errnop = EBADMSG;
free(repbuf);
nret = NSS_STATUS_TRYAGAIN;
goto out;
}
len = replen - 8;
ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len);
if (ret == ERANGE) {
sss_nss_save_getgr_cache(name, 0, GETGR_NAME, &repbuf, replen);
} else {
free(repbuf);
}
if (ret) {
*errnop = ret;
nret = NSS_STATUS_TRYAGAIN;
goto out;
}
nret = NSS_STATUS_SUCCESS;
out:
sss_nss_unlock();
return nret;
}
enum nss_status _nss_sss_getgrgid_r(gid_t gid, struct group *result,
char *buffer, size_t buflen, int *errnop)
{
struct sss_cli_req_data rd;
struct sss_nss_gr_rep grrep;
uint8_t *repbuf;
size_t replen, len;
uint32_t num_results;
enum nss_status nret;
uint32_t group_gid;
int ret;
/* Caught once glibc passing in buffer == 0x0 */
if (!buffer || !buflen) return ERANGE;
ret = sss_nss_mc_getgrgid(gid, result, buffer, buflen);
switch (ret) {
case 0:
*errnop = 0;
return NSS_STATUS_SUCCESS;
case ERANGE:
*errnop = ERANGE;
return NSS_STATUS_TRYAGAIN;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
group_gid = gid;
rd.len = sizeof(uint32_t);
rd.data = &group_gid;
sss_nss_lock();
/* previous thread might already initialize entry in mmap cache */
ret = sss_nss_mc_getgrgid(gid, result, buffer, buflen);
switch (ret) {
case 0:
*errnop = 0;
nret = NSS_STATUS_SUCCESS;
goto out;
case ERANGE:
*errnop = ERANGE;
nret = NSS_STATUS_TRYAGAIN;
goto out;
case ENOENT:
/* fall through, we need to actively ask the parent
* if no entry is found */
break;
default:
/* if using the mmaped cache failed,
* fall back to socket based comms */
break;
}
nret = sss_nss_get_getgr_cache(NULL, gid, GETGR_GID,
&repbuf, &replen, errnop);
if (nret == NSS_STATUS_NOTFOUND) {
nret = sss_nss_make_request(SSS_NSS_GETGRGID, &rd,
&repbuf, &replen, errnop);
}
if (nret != NSS_STATUS_SUCCESS) {
goto out;
}
grrep.result = result;
grrep.buffer = buffer;
grrep.buflen = buflen;
/* Get number of results from repbuf. */
SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
/* no results if not found */
if (num_results == 0) {
free(repbuf);
nret = NSS_STATUS_NOTFOUND;
goto out;
}
/* only 1 result is accepted for this function */
if (num_results != 1) {
*errnop = EBADMSG;
free(repbuf);
nret = NSS_STATUS_TRYAGAIN;
goto out;
}
len = replen - 8;
ret = sss_nss_getgr_readrep(&grrep, repbuf+8, &len);
if (ret == ERANGE) {
sss_nss_save_getgr_cache(NULL, gid, GETGR_GID, &repbuf, replen);
} else {
free(repbuf);
}
if (ret) {
*errnop = ret;
nret = NSS_STATUS_TRYAGAIN;
goto out;
}
nret = NSS_STATUS_SUCCESS;
out:
sss_nss_unlock();
return nret;
}
enum nss_status _nss_sss_setgrent(void)
{
enum nss_status nret;
int errnop;
sss_nss_lock();
/* make sure we do not have leftovers, and release memory */
sss_nss_getgrent_data_clean();
nret = sss_nss_make_request(SSS_NSS_SETGRENT,
NULL, NULL, NULL, &errnop);
if (nret != NSS_STATUS_SUCCESS) {
errno = errnop;
}
sss_nss_unlock();
return nret;
}
static enum nss_status internal_getgrent_r(struct group *result,
char *buffer, size_t buflen,
int *errnop)
{
struct sss_cli_req_data rd;
struct sss_nss_gr_rep grrep;
uint8_t *repbuf;
size_t replen;
uint32_t num_results;
enum nss_status nret;
uint32_t num_entries;
int ret;
/* Caught once glibc passing in buffer == 0x0 */
if (!buffer || !buflen) return ERANGE;
/* if there are leftovers return the next one */
if (sss_nss_getgrent_data.data != NULL &&
sss_nss_getgrent_data.ptr < sss_nss_getgrent_data.len) {
repbuf = (uint8_t *)sss_nss_getgrent_data.data +
sss_nss_getgrent_data.ptr;
replen = sss_nss_getgrent_data.len -
sss_nss_getgrent_data.ptr;
grrep.result = result;
grrep.buffer = buffer;
grrep.buflen = buflen;
ret = sss_nss_getgr_readrep(&grrep, repbuf, &replen);
if (ret) {
*errnop = ret;
return NSS_STATUS_TRYAGAIN;
}
/* advance buffer pointer */
sss_nss_getgrent_data.ptr = sss_nss_getgrent_data.len - replen;
return NSS_STATUS_SUCCESS;
}
/* release memory if any */
sss_nss_getgrent_data_clean();
/* retrieve no more than SSS_NSS_MAX_ENTRIES at a time */
num_entries = SSS_NSS_MAX_ENTRIES;
rd.len = sizeof(uint32_t);
rd.data = &num_entries;
nret = sss_nss_make_request(SSS_NSS_GETGRENT, &rd,
&repbuf, &replen, errnop);
if (nret != NSS_STATUS_SUCCESS) {
return nret;
}
/* Get number of results from repbuf. */
SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
/* no results if not found */
if ((num_results == 0) || (replen - 8 == 0)) {
free(repbuf);
return NSS_STATUS_NOTFOUND;
}
sss_nss_getgrent_data.data = repbuf;
sss_nss_getgrent_data.len = replen;
sss_nss_getgrent_data.ptr = 8; /* skip metadata fields */
/* call again ourselves, this will return the first result */
return internal_getgrent_r(result, buffer, buflen, errnop);
}
enum nss_status _nss_sss_getgrent_r(struct group *result,
char *buffer, size_t buflen, int *errnop)
{
enum nss_status nret;
sss_nss_lock();
nret = internal_getgrent_r(result, buffer, buflen, errnop);
sss_nss_unlock();
return nret;
}
enum nss_status _nss_sss_endgrent(void)
{
enum nss_status nret;
int errnop;
sss_nss_lock();
/* make sure we do not have leftovers, and release memory */
sss_nss_getgrent_data_clean();
nret = sss_nss_make_request(SSS_NSS_ENDGRENT,
NULL, NULL, NULL, &errnop);
if (nret != NSS_STATUS_SUCCESS) {
errno = errnop;
}
sss_nss_unlock();
return nret;
}