/*
* Handle group entries via struct group
*
* Copyright (C) 2019 Simon Ruderich
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "cash_nss.h"
#include "file.h"
#include "search.h"
// NOTE: This file is very similar to pw.c, keep in sync!
struct group_entry {
uint64_t gid;
// off_name = 0, not stored on disk
uint16_t off_passwd;
uint16_t off_mem_off;
uint16_t mem_count; // group member count
/*
* Data contains all strings (name, passwd) concatenated, with their
* trailing NUL. The off_* variables point to beginning of each string.
*
* After that the offsets of the members of the group are stored as
* mem_count uint16_t values, followed by the member names concatenated as
* with the strings above.
*
* All offsets are relative to the beginning of data.
*/
uint16_t data_size; // size of data in bytes
const char data[];
} __attribute__((packed));
static bool entry_to_group(const struct group_entry *e, struct group *g, char *tmp, size_t space) {
// Space required for the gr_mem array
const size_t mem_size = (size_t)(e->mem_count + 1) * sizeof(char *);
if (space < e->data_size + mem_size) {
return false;
}
char **groups = (char **)tmp;
const uint16_t *offs_mem = (const uint16_t *)(e->data + e->off_mem_off);
for (uint16_t i = 0; i < e->mem_count; i++) {
groups[i] = tmp + mem_size + offs_mem[i];
}
groups[e->mem_count] = NULL;
// This unnecessarily copies offs_mem[] as well but keeps the code simpler
// and the meaning of variables consistent with pw.c
memcpy(tmp + mem_size, e->data, e->data_size);
g->gr_gid = (gid_t)e->gid;
g->gr_name = tmp + mem_size + 0;
g->gr_passwd = tmp + mem_size + e->off_passwd;
g->gr_mem = groups;
return true;
}
static struct file static_file = {
.fd = -1,
};
static pthread_mutex_t static_file_lock = PTHREAD_MUTEX_INITIALIZER;
enum nss_status _nss_cash_setgrent(int x) {
(void)x;
pthread_mutex_lock(&static_file_lock);
// Unmap is necessary to detect changes when the file was replaced on
// disk
unmap_file(&static_file);
// getgrent_r will open the file if necessary when called
pthread_mutex_unlock(&static_file_lock);
return NSS_STATUS_SUCCESS;
}
enum nss_status _nss_cash_endgrent(void) {
pthread_mutex_lock(&static_file_lock);
unmap_file(&static_file);
pthread_mutex_unlock(&static_file_lock);
return NSS_STATUS_SUCCESS;
}
static enum nss_status internal_getgrent_r(struct group *result, char *buffer, size_t buflen) {
// First call to getgrent_r, load file from disk
if (static_file.header == NULL) {
if (!map_file(NSSCASH_GROUP_FILE, &static_file)) {
return NSS_STATUS_UNAVAIL;
}
}
const struct header *h = static_file.header;
// End of "file", stop
if (static_file.next_index >= h->count) {
errno = ENOENT;
return NSS_STATUS_NOTFOUND;
}
uint64_t *off_orig = (uint64_t *)(h->data + h->off_orig_index);
const char *e = h->data + h->off_data + off_orig[static_file.next_index];
if (!entry_to_group((struct group_entry *)e, result, buffer, buflen)) {
errno = ERANGE;
return NSS_STATUS_TRYAGAIN;
}
static_file.next_index++;
return NSS_STATUS_SUCCESS;
}
enum nss_status _nss_cash_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) {
pthread_mutex_lock(&static_file_lock);
enum nss_status s = internal_getgrent_r(result, buffer, buflen);
pthread_mutex_unlock(&static_file_lock);
if (s != NSS_STATUS_SUCCESS) {
*errnop = errno;
}
return s;
}
static enum nss_status internal_getgr(struct search_key *key, struct group *result, char *buffer, size_t buflen, int *errnop) {
struct file f;
if (!map_file(NSSCASH_GROUP_FILE, &f)) {
*errnop = errno;
return NSS_STATUS_UNAVAIL;
}
const struct header *h = f.header;
key->data = h->data + h->off_data;
uint64_t off_index = (key->name != NULL)
? h->off_name_index
: h->off_id_index;
uint64_t *off = search(key, h->data + off_index, h->count);
if (off == NULL) {
unmap_file(&f);
errno = ENOENT;
*errnop = errno;
return NSS_STATUS_NOTFOUND;
}
const char *e = key->data + *off;
if (!entry_to_group((struct group_entry *)e, result, buffer, buflen)) {
unmap_file(&f);
errno = ERANGE;
*errnop = errno;
return NSS_STATUS_TRYAGAIN;
}
unmap_file(&f);
return NSS_STATUS_SUCCESS;
}
enum nss_status _nss_cash_getgrgid_r(gid_t gid, struct group *result, char *buffer, size_t buflen, int *errnop) {
uint64_t id = (uint64_t)gid;
struct search_key key = {
.id = &id,
.offset = offsetof(struct group_entry, gid),
};
return internal_getgr(&key, result, buffer, buflen, errnop);
}
enum nss_status _nss_cash_getgrnam_r(const char *name, struct group *result, char *buffer, size_t buflen, int *errnop) {
struct search_key key = {
.name = name,
.offset = sizeof(struct group_entry), // name is first value in data[]
};
return internal_getgr(&key, result, buffer, buflen, errnop);
}