Add include and comment support to passwd(5) and group(5).

This commit is contained in:
Jonas 'Sortie' Termansen 2023-03-17 00:48:58 +01:00
parent da86ca1873
commit c57ff050e9
15 changed files with 357 additions and 102 deletions

View File

@ -447,7 +447,9 @@ $(LIVE_INITRD): sysroot
mkdir -p $(LIVE_INITRD).d/etc/init
echo require single-user exit-code > $(LIVE_INITRD).d/etc/init/default
echo "root::0:0:root:/root:sh" > $(LIVE_INITRD).d/etc/passwd
echo "include /etc/default/passwd.d/*" >> $(LIVE_INITRD).d/etc/passwd
echo "root::0:root" > $(LIVE_INITRD).d/etc/group
echo "include /etc/default/group.d/*" >> $(LIVE_INITRD).d/etc/group
mkdir -p $(LIVE_INITRD).d/home
mkdir -p $(LIVE_INITRD).d/root -m 700
cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root

View File

@ -478,7 +478,9 @@ pwd/getpwnam.o \
pwd/getpwnam_r.o \
pwd/getpwuid.o \
pwd/getpwuid_r.o \
pwd/__openent.o \
pwd/openpw.o \
pwd/scanpwent.o \
pwd/setpwent.o \
sched/sched_yield.o \
pty/openpty.o \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -18,9 +18,10 @@
*/
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
FILE* opengr(void)
{
return fopen("/etc/group", "r");
return __openent("/etc/group");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -24,8 +24,9 @@ FILE* __grp_file = NULL;
void setgrent(void)
{
if ( __grp_file )
FILE* new_file = opengr();
if ( !new_file && __grp_file )
rewind(__grp_file);
else
__grp_file = opengr();
__grp_file = new_file;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -62,6 +62,8 @@ struct passwd
#if defined(__is_sortix_libc)
extern FILE* __pwd_file;
FILE* __openent(const char*);
#endif
int bcrypt_newhash(const char*, int, char*, size_t);
@ -79,6 +81,7 @@ int getpwnam_r(const char* __restrict, struct passwd* __restrict,
struct passwd* getpwuid(uid_t);
int getpwuid_r(uid_t, struct passwd* __restrict, char* __restrict, size_t,
struct passwd** __restrict);
int scanpwent(char* __restrict, struct passwd* __restrict);
FILE* openpw(void);
void setpwent(void);

104
libc/pwd/__openent.c Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* pwd/__openent.c
* Preprocesses include statements in an entry database.
*/
#include <ctype.h>
#include <errno.h>
#include <glob.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
bool __openent_append(FILE* out, const char* path)
{
FILE* fp = fopen(path, "r");
if ( !fp )
return false;
char* line = NULL;
size_t size = 0;
ssize_t amount;
bool success = true;
while ( success && 0 < (amount = getline(&line, &size, fp)) )
{
size_t length = amount;
size_t offset = 0;
while ( offset < length && isspace((unsigned char) line[offset]) )
offset++;
if ( !line[offset] || line[offset] == '#' )
continue;
size_t include_length = strlen("include");
if ( !strncmp(line + offset, "include", include_length) &&
isspace((unsigned char) line[offset + include_length]) )
{
while ( length && isspace((unsigned char) line[length - 1]) )
line[--length] = '\0';
offset = offset + include_length;
while ( offset < length && isspace((unsigned char) line[offset]) )
offset++;
const char* pattern = line + offset;
glob_t gl;
if ( !glob(pattern, GLOB_NOCHECK, NULL, &gl) )
{
for ( size_t i = 0; success && i < gl.gl_pathc; i++ )
if ( !__openent_append(out, gl.gl_pathv[i]) &&
errno != ENOENT )
success = false;
}
else
success = false;
globfree(&gl);
continue;
}
if ( fwrite(line, 1, length, out) != length )
success = false;
}
free(line);
if ( ferror(fp) )
success = false;
fclose(fp);
return success;
}
FILE* __openent(const char* path)
{
char* data;
size_t size;
FILE* memstream = open_memstream(&data, &size);
if ( !memstream )
return NULL;
if ( !__openent_append(memstream, path) ||
ferror(memstream) || fflush(memstream) == EOF )
{
fclose(memstream);
free(data);
return NULL;
}
fclose(memstream);
size = strlen(data);
FILE* result = fmemopen(NULL, size, "r+");
if ( !result )
return free(data), NULL;
size_t amount = fwrite(data, 1, size, result);
free(data);
if ( amount != size || fflush(result) == EOF )
return fclose(result), NULL;
rewind(result);
return result;
}

View File

@ -17,70 +17,9 @@
* Reads a passwd entry from a FILE.
*/
#include <sys/types.h>
#include <errno.h>
#include <inttypes.h>
#include <pwd.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char* next_field(char** current)
{
char* result = *current;
if ( result )
{
char* next = result;
while ( *next && *next != ':' )
next++;
if ( !*next )
next = NULL;
else
*next++ = '\0';
*current = next;
}
return result;
}
static bool next_field_uintmax(char** current, uintmax_t* result)
{
char* id_str = next_field(current);
if ( !id_str )
return false;
char* id_endptr;
uintmax_t id_umax = strtoumax(id_str, &id_endptr, 10);
if ( *id_endptr )
return false;
*result = id_umax;
return true;
}
static bool next_field_gid(char** current, gid_t* result)
{
uintmax_t id_umax;
if ( !next_field_uintmax(current, &id_umax) )
return false;
gid_t gid = (gid_t) id_umax;
if ( (uintmax_t) gid != (uintmax_t) id_umax )
return false;
*result = gid;
return true;
}
static bool next_field_uid(char** current, uid_t* result)
{
uintmax_t id_umax;
if ( !next_field_uintmax(current, &id_umax) )
return false;
uid_t uid = (uid_t) id_umax;
if ( (uintmax_t) uid != (uintmax_t) id_umax )
return false;
*result = uid;
return true;
}
int fgetpwent_r(FILE* restrict fp,
struct passwd* restrict result,
@ -144,22 +83,7 @@ int fgetpwent_r(FILE* restrict fp,
}
buf[buf_used] = '\0';
char* parse_str = buf;
if ( !(result->pw_name = next_field(&parse_str)) )
goto parse_failure;
if ( !(result->pw_passwd = next_field(&parse_str)) )
goto parse_failure;
if ( !next_field_uid(&parse_str, &result->pw_uid) )
goto parse_failure;
if ( !next_field_gid(&parse_str, &result->pw_gid) )
goto parse_failure;
if ( !(result->pw_gecos = next_field(&parse_str)) )
goto parse_failure;
if ( !(result->pw_dir = next_field(&parse_str)) )
goto parse_failure;
if ( !(result->pw_shell = next_field(&parse_str)) )
goto parse_failure;
if ( parse_str )
if ( !scanpwent(buf, result) )
goto parse_failure;
funlockfile(fp);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -22,5 +22,5 @@
FILE* openpw(void)
{
return fopen("/etc/passwd", "r");
return __openent("/etc/passwd");
}

94
libc/pwd/scanpwent.c Normal file
View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2013, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* pwd/scanfpwent.c
* Parse passwd entry.
*/
#include <ctype.h>
#include <inttypes.h>
#include <pwd.h>
#include <stdbool.h>
#include <stddef.h>
static char* next_field(char** current)
{
char* result = *current;
if ( result )
{
char* next = result;
while ( *next && *next != ':' )
next++;
if ( !*next )
next = NULL;
else
*next++ = '\0';
*current = next;
}
return result;
}
static bool next_field_uintmax(char** current, uintmax_t* result)
{
char* id_str = next_field(current);
if ( !id_str )
return false;
char* id_endptr;
uintmax_t id_umax = strtoumax(id_str, &id_endptr, 10);
if ( *id_endptr )
return false;
*result = id_umax;
return true;
}
static bool next_field_gid(char** current, gid_t* result)
{
uintmax_t id_umax;
if ( !next_field_uintmax(current, &id_umax) )
return false;
gid_t gid = (gid_t) id_umax;
if ( (uintmax_t) gid != (uintmax_t) id_umax )
return false;
*result = gid;
return true;
}
static bool next_field_uid(char** current, uid_t* result)
{
uintmax_t id_umax;
if ( !next_field_uintmax(current, &id_umax) )
return false;
uid_t uid = (uid_t) id_umax;
if ( (uintmax_t) uid != (uintmax_t) id_umax )
return false;
*result = uid;
return true;
}
int scanpwent(char* str, struct passwd* passwd)
{
while ( *str && isspace((unsigned char) *str) )
str++;
if ( !*str || *str == '#' ||
!(passwd->pw_name = next_field(&str)) ||
!(passwd->pw_passwd = next_field(&str)) ||
!next_field_uid(&str, &passwd->pw_uid) ||
!next_field_gid(&str, &passwd->pw_gid) ||
!(passwd->pw_gecos = next_field(&str)) ||
!(passwd->pw_dir = next_field(&str)) ||
!(passwd->pw_shell = next_field(&str)) )
return 0;
return 1;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -24,8 +24,9 @@ FILE* __pwd_file = NULL;
void setpwent(void)
{
if ( __pwd_file )
FILE* new_file = openpw();
if ( !new_file && __pwd_file )
rewind(__pwd_file);
else
__pwd_file = openpw();
__pwd_file = new_file;
}

View File

@ -69,6 +69,24 @@ releasing Sortix x.y, foo." to allow the maintainer to easily
.Xr grep 1
for it after a release.
.Sh CHANGES
.Ss Add include and comment support to passwd(5) and group(5)
The
.Xr passwd 5
and
.Xr group 5
files have gained support for include statements via libc support.
Installations must now include
.Pa /etc/default/passwd.d/*
and
.Pa /etc/default/group.d/*
respectively in order to have system users and groups.
.Pp
An upgrade hook will add the inclusions to
.Pa /etc/passwd
and
.Pa /etc/group .
Applications accessing passwd and group databases must be recompiled with the
latest libc.
.Ss Add memory statistics to struct psctl_stat
The
.Xr psctl 2

View File

@ -55,6 +55,8 @@ install: all
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-tix-manifest-mode
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-leaked-files
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-init
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-passwd
touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-group
sysinstall: $(SYSINSTALL_OBJS)
$(CC) $(SYSINSTALL_OBJS) -o $@ -lmount

View File

@ -296,6 +296,64 @@ void upgrade_prepare(const struct release* old_release,
free(init_target_path);
free(init_default_path);
}
// TODO: After releasing Sortix 1.1, remove this compatibility.
if ( hook_needs_to_be_run(source_prefix, target_prefix,
"sortix-1.1-passwd") )
{
char* passwd_path = join_paths(target_prefix, "/etc/passwd");
if ( !passwd_path )
{
warn("malloc");
_exit(2);
}
FILE* fp = fopen(passwd_path, "a");
if ( fp )
{
printf(" - Including /etc/default/passwd.d/* in /etc/passwd...\n");
if ( fprintf(fp, "include /etc/default/passwd.d/*\n") < 0 ||
fclose(fp) == EOF )
{
warn("%s", passwd_path);
_exit(2);
}
}
else if ( errno != ENOENT )
{
warn("%s", passwd_path);
_exit(2);
}
free(passwd_path);
}
// TODO: After releasing Sortix 1.1, remove this compatibility.
if ( hook_needs_to_be_run(source_prefix, target_prefix,
"sortix-1.1-group") )
{
char* group_path = join_paths(target_prefix, "/etc/group");
if ( !group_path )
{
warn("malloc");
_exit(2);
}
FILE* fp = fopen(group_path, "a");
if ( fp )
{
printf(" - Including /etc/default/group.d/* in /etc/group...\n");
if ( fprintf(fp, "include /etc/default/group.d/*\n") < 0 ||
fclose(fp) == EOF )
{
warn("%s", group_path);
_exit(2);
}
}
else if ( errno != ENOENT )
{
warn("%s", group_path);
_exit(2);
}
free(group_path);
}
}
void upgrade_finalize(const struct release* old_release,

View File

@ -220,16 +220,23 @@ static bool passwd_check(const char* passwd_path,
warn("%s", passwd_path);
return false;
}
struct passwd* pwd;
while ( (errno = 0, pwd = fgetpwent(passwd)) )
char* line = NULL;
size_t size = 0;
ssize_t length;
while ( 0 < (length = getline(&line, &size, passwd) ) )
{
if ( check(pwd, check_ctx) )
if ( line[size - 1] == '\n' )
line[--size] = '\0';
struct passwd pwd;
if ( scanpwent(line, &pwd) && check(&pwd, check_ctx) )
{
free(line);
fclose(passwd);
return true;
}
}
if ( errno != 0 )
free(line);
if ( ferror(passwd) )
warn("%s", passwd_path);
fclose(passwd);
return false;
@ -1025,10 +1032,13 @@ int main(void)
}
explicit_bzero(first, sizeof(first));
if ( !install_configurationf("etc/passwd", "a",
"root:%s:0:0:root:/root:sh\n", hash) )
"root:%s:0:0:root:/root:sh\n"
"include /etc/default/passwd.d/*\n", hash) )
continue;
textf("User '%s' added to /etc/passwd\n", "root");
if ( !install_configurationf("etc/group", "a", "root::0:root\n") )
if ( !install_configurationf("etc/group", "a",
"root::0:root\n"
"include /etc/default/group.d/*\n") )
continue;
install_skel("/root", 0, 0);
textf("Group '%s' added to /etc/group.\n", "root");

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -24,6 +24,7 @@
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -155,24 +156,50 @@ int main(int argc, char* argv[])
if ( crypt_newhash(first, cipher, newhash, sizeof(newhash)) < 0 )
err(1, "crypt_newhash");
explicit_bzero(first, sizeof(first));
// TODO: This is subject to races and is obviously an insecure design.
// The backend and coordination of the passwd database should be moved
// to its own daemon.
FILE* in = fopen("/etc/passwd", "r");
if ( !in )
err(1, "/etc/passwd");
int fd = open("/etc/passwd.new", O_WRONLY | O_CREAT | O_EXCL, 0644);
if ( fd < 0 )
err(1, "/etc/passwd.new");
fchown(fd, 0, 0); // HACK.
FILE* fp = fdopen(fd, "w");
if ( !fp )
err(1, "fdopen");
setpwent();
while ( (errno = 0, pwd = getpwent()) )
{
unlink("/etc/passwd.new");
err(1, "fdopen");
}
char* line = NULL;
size_t size = 0;
ssize_t length;
bool found = false;
while ( 0 < (length = getline(&line, &size, in)) )
{
char* copy = strdup(line);
if ( !copy )
{
unlink("/etc/passwd.new");
err(1, "malloc");
}
if ( copy[length - 1] == '\n' )
copy[--length] = '\0';
struct passwd passwd;
if ( !scanpwent(copy, (pwd = &passwd)) )
{
fputs(line, fp);
free(copy);
continue;
}
fputs(pwd->pw_name, fp);
fputc(':', fp);
if ( !strcmp(pwd->pw_name, username) )
{
fputs(newhash, fp);
found = true;
}
else
fputs(pwd->pw_passwd, fp);
fputc(':', fp);
@ -191,17 +218,25 @@ int main(int argc, char* argv[])
unlink("/etc/passwd.new");
err(1, "/etc/passwd.new");
}
free(copy);
}
if ( errno != 0 )
free(line);
if ( ferror(in) )
{
unlink("/etc/passwd.new");
err(1, "getpwent");
err(1, "/etc/passwd");
}
if ( fclose(fp) == EOF )
fclose(in);
if ( ferror(fp) || fclose(fp) == EOF )
{
unlink("/etc/passwd.new");
err(1, "/etc/passwd.new");
}
if ( !found )
{
unlink("/etc/passwd.new");
errx(1, "user %s is not directly in /etc/passwd", username);
}
if ( rename("/etc/passwd.new", "/etc/passwd") < 0 )
{
unlink("/etc/passwd.new");