9a9b91c940
It's finally done.. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
387 lines
7.6 KiB
C++
387 lines
7.6 KiB
C++
|
|
#include <grp.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <bits/ensure.h>
|
|
|
|
#include <mlibc/debug.hpp>
|
|
#include <mlibc/posix-sysdeps.hpp>
|
|
|
|
namespace {
|
|
FILE *global_file;
|
|
|
|
bool open_global_file() {
|
|
if(!global_file) {
|
|
global_file = fopen("/etc/group", "r");
|
|
if(!global_file) {
|
|
errno = EIO;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void close_global_file() {
|
|
if(global_file) {
|
|
fclose(global_file);
|
|
global_file = nullptr;
|
|
}
|
|
}
|
|
|
|
template<typename F>
|
|
void walk_segments(frg::string_view line, char delimiter, F fn) {
|
|
size_t s = 0;
|
|
while(true) {
|
|
size_t d = line.find_first(delimiter, s);
|
|
if(d == size_t(-1))
|
|
break;
|
|
auto chunk = line.sub_string(s, d - s);
|
|
fn(chunk);
|
|
s = d + 1;
|
|
}
|
|
if(line[s]) {
|
|
auto chunk = line.sub_string(s, line.size() - s);
|
|
|
|
if (chunk.size() > 0) {
|
|
// Remove trailing newline
|
|
if (chunk[chunk.size() - 1] == '\n')
|
|
chunk = chunk.sub_string(0, chunk.size() - 1);
|
|
|
|
fn(chunk);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool extract_entry(frg::string_view line, group *entry) {
|
|
frg::string_view segments[5];
|
|
|
|
// Parse the line into 3 or 4 segments (depending if the group has members or not)
|
|
int n = 0;
|
|
walk_segments(line, ':', [&] (frg::string_view s) {
|
|
__ensure(n < 4);
|
|
segments[n++] = s;
|
|
});
|
|
|
|
if(n < 3) // n can be 3 when there are no members in the group
|
|
return false;
|
|
|
|
// TODO: Handle strndup() and malloc() failure.
|
|
auto name = strndup(segments[0].data(), segments[0].size());
|
|
__ensure(name);
|
|
|
|
auto passwd = strndup(segments[1].data(), segments[1].size());
|
|
|
|
auto gid = segments[2].to_number<int>();
|
|
if(!gid)
|
|
return false;
|
|
|
|
size_t n_members = 0;
|
|
walk_segments(segments[3], ',', [&] (frg::string_view) {
|
|
n_members++;
|
|
});
|
|
|
|
auto members = reinterpret_cast<char **>(malloc(sizeof(char *) * (n_members + 1)));
|
|
__ensure(members);
|
|
size_t k = 0;
|
|
walk_segments(segments[3], ',', [&] (frg::string_view m) {
|
|
members[k] = strndup(m.data(), m.size());
|
|
__ensure(members[k]);
|
|
k++;
|
|
});
|
|
members[k] = nullptr;
|
|
|
|
entry->gr_name = name;
|
|
entry->gr_passwd = passwd;
|
|
entry->gr_gid = *gid;
|
|
entry->gr_mem = members;
|
|
return true;
|
|
}
|
|
|
|
void clear_entry(group *entry) {
|
|
free(entry->gr_name);
|
|
if(entry->gr_mem) {
|
|
for(size_t i = 0; entry->gr_mem[i]; i++)
|
|
free(entry->gr_mem[i]);
|
|
free(entry->gr_mem);
|
|
}
|
|
entry->gr_name = nullptr;
|
|
entry->gr_mem = nullptr;
|
|
}
|
|
|
|
template<typename C>
|
|
int walk_file(struct group *entry, C cond) {
|
|
auto file = fopen("/etc/group", "r");
|
|
if(!file) {
|
|
return EIO;
|
|
}
|
|
|
|
char line[512];
|
|
while(fgets(line, 512, file)) {
|
|
if(!extract_entry(line, entry))
|
|
continue;
|
|
if(cond(entry)) {
|
|
fclose(file);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int err = ESRCH;
|
|
if(ferror(file)) {
|
|
err = EIO;
|
|
}
|
|
|
|
fclose(file);
|
|
return err;
|
|
}
|
|
|
|
int copy_to_buffer(struct group *grp, char *buffer, size_t size) {
|
|
// Adjust to correct alignment so that we can put gr_mem first in buffer
|
|
uintptr_t mask = sizeof(char *) - 1;
|
|
size_t offset = (reinterpret_cast<uintptr_t>(buffer) % sizeof(char *) + mask) & ~mask;
|
|
if (size < offset)
|
|
return ERANGE;
|
|
|
|
buffer += offset;
|
|
size -= offset;
|
|
|
|
// Calculate the amount of space we need
|
|
size_t nmemb, required_size = 0;
|
|
for (nmemb = 0; grp->gr_mem[nmemb] != nullptr; nmemb++) {
|
|
// One for the string's null terminator and one for the pointer in gr_mem
|
|
required_size += strlen(grp->gr_mem[nmemb]) + 1 + sizeof(char *);
|
|
}
|
|
|
|
// One for null terminator of gr_name, plus sizeof(char *) for nullptr terminator of gr_mem
|
|
required_size += strlen(grp->gr_name) + 1 + sizeof(char *);
|
|
if (size < required_size)
|
|
return ERANGE;
|
|
|
|
// Put the gr_mem array first in the buffer as we are guaranteed
|
|
// that the pointer is aligned correctly
|
|
char *string_data = buffer + (nmemb + 1) * sizeof(char *);
|
|
|
|
for (size_t i = 0; i < nmemb; i++) {
|
|
reinterpret_cast<char **>(buffer)[i] = string_data;
|
|
string_data = stpcpy(string_data, grp->gr_mem[i]) + 1;
|
|
free(grp->gr_mem[i]);
|
|
}
|
|
|
|
reinterpret_cast<char **>(buffer)[nmemb] = nullptr;
|
|
free(grp->gr_mem);
|
|
grp->gr_mem = reinterpret_cast<char **>(buffer);
|
|
|
|
char *gr_name = stpcpy(string_data, grp->gr_name) + 1;
|
|
free(grp->gr_name);
|
|
grp->gr_name = string_data;
|
|
|
|
__ensure(gr_name <= buffer + size);
|
|
return 0;
|
|
}
|
|
} // namespace
|
|
|
|
void endgrent(void) {
|
|
close_global_file();
|
|
}
|
|
|
|
struct group *getgrent(void) {
|
|
static group entry;
|
|
char line[512];
|
|
|
|
if(!open_global_file()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if(fgets(line, 512, global_file)) {
|
|
clear_entry(&entry);
|
|
if(!extract_entry(line, &entry)) {
|
|
errno = EINVAL;
|
|
return nullptr;
|
|
}
|
|
return &entry;
|
|
}
|
|
|
|
if(ferror(global_file)) {
|
|
errno = EIO;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
struct group *getgrgid(gid_t gid) {
|
|
static group entry;
|
|
|
|
int err = walk_file(&entry, [&] (group *entry) {
|
|
return entry->gr_gid == gid;
|
|
});
|
|
|
|
if (err) {
|
|
errno = err;
|
|
return nullptr;
|
|
}
|
|
|
|
return &entry;
|
|
}
|
|
|
|
int getgrgid_r(gid_t gid, struct group *grp, char *buffer, size_t size, struct group **result) {
|
|
*result = nullptr;
|
|
int err = walk_file(grp, [&] (group *entry) {
|
|
return entry->gr_gid == gid;
|
|
});
|
|
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = copy_to_buffer(grp, buffer, size);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
*result = grp;
|
|
return 0;
|
|
}
|
|
|
|
struct group *getgrnam(const char *name) {
|
|
static group entry;
|
|
|
|
int err = walk_file(&entry, [&] (group *entry) {
|
|
return !strcmp(entry->gr_name, name);
|
|
});
|
|
|
|
if (err) {
|
|
errno = err;
|
|
return nullptr;
|
|
}
|
|
|
|
return &entry;
|
|
}
|
|
|
|
int getgrnam_r(const char *name, struct group *grp, char *buffer, size_t size, struct group **result) {
|
|
*result = nullptr;
|
|
|
|
int err = walk_file(grp, [&] (group *entry) {
|
|
return !strcmp(entry->gr_name, name);
|
|
});
|
|
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = copy_to_buffer(grp, buffer, size);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
*result = grp;
|
|
return 0;
|
|
}
|
|
|
|
void setgrent(void) {
|
|
if(!open_global_file()) {
|
|
return;
|
|
}
|
|
rewind(global_file);
|
|
}
|
|
|
|
int setgroups(size_t size, const gid_t *list) {
|
|
MLIBC_CHECK_OR_ENOSYS(mlibc::sys_setgroups, -1);
|
|
if(int e = mlibc::sys_setgroups(size, list); e) {
|
|
errno = e;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int initgroups(const char *, gid_t) {
|
|
mlibc::infoLogger() << "mlibc: initgroups is a stub" << frg::endlog;
|
|
return 0;
|
|
}
|
|
|
|
int putgrent(const struct group *g, FILE *f) {
|
|
auto invalid = [](const char *s) {
|
|
return s == nullptr || strchr(s, '\n') || strchr(s, ':');
|
|
};
|
|
|
|
if(g == nullptr || invalid(g->gr_name) || invalid(g->gr_passwd)) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
for(int i = 0; g->gr_mem != nullptr && g->gr_mem[i] != nullptr; ++i) {
|
|
if (invalid(g->gr_mem[i])) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// Taken from musl.
|
|
flockfile(f);
|
|
|
|
int r = fprintf(f, "%s:%s:%u:", g->gr_name, g->gr_passwd, g->gr_gid);
|
|
if(r < 0)
|
|
goto leave;
|
|
|
|
for(int i = 0; g->gr_mem != nullptr && g->gr_mem[i] != nullptr; ++i) {
|
|
r = fprintf(f, "%s%s", i ? "," : "", g->gr_mem[i]);
|
|
if(r < 0)
|
|
goto leave;
|
|
}
|
|
|
|
r = fputc('\n', f);
|
|
|
|
leave:
|
|
funlockfile(f);
|
|
return r < 0 ? -1 : 0;
|
|
}
|
|
|
|
struct group *fgetgrent(FILE *file) {
|
|
static group entry;
|
|
char line[512];
|
|
|
|
if(fgets(line, 512, file)) {
|
|
clear_entry(&entry);
|
|
if(!extract_entry(line, &entry)) {
|
|
errno = EINVAL;
|
|
return nullptr;
|
|
}
|
|
return &entry;
|
|
}
|
|
|
|
if(ferror(file)) {
|
|
errno = EIO;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
int getgrouplist(const char *user, gid_t gid, gid_t *groups, int *ngroups) {
|
|
int n = 1;
|
|
int n_limit = *ngroups;
|
|
struct group grp;
|
|
|
|
if(n_limit >= 1)
|
|
*groups++ = gid;
|
|
|
|
int err = walk_file(&grp, [&] (group *entry) {
|
|
size_t i = 0;
|
|
for(; entry->gr_mem[i] && strcmp(user, entry->gr_mem[i]); i++);
|
|
if(!entry->gr_mem[i])
|
|
return false;
|
|
if(++n <= n_limit)
|
|
*groups++ = entry->gr_gid;
|
|
return false;
|
|
});
|
|
|
|
if(err != ESRCH) {
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
|
|
*ngroups = n;
|
|
|
|
return (n > n_limit) ? -1 : n;
|
|
}
|