From c57ff050e9e2b8f525a689d2ef16362ef463a5ec Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Fri, 17 Mar 2023 00:48:58 +0100 Subject: [PATCH] Add include and comment support to passwd(5) and group(5). --- Makefile | 2 + libc/Makefile | 2 + libc/grp/opengr.c | 5 +- libc/grp/setgrent.c | 7 +- libc/include/pwd.h | 5 +- libc/pwd/__openent.c | 104 +++++++++++++++++++++++++ libc/pwd/fgetpwent_r.c | 78 +------------------ libc/pwd/openpw.c | 4 +- libc/pwd/scanpwent.c | 94 ++++++++++++++++++++++ libc/pwd/setpwent.c | 7 +- share/man/man7/following-development.7 | 18 +++++ sysinstall/Makefile | 2 + sysinstall/hooks.c | 58 ++++++++++++++ sysinstall/sysinstall.c | 22 ++++-- utils/passwd.c | 51 ++++++++++-- 15 files changed, 357 insertions(+), 102 deletions(-) create mode 100644 libc/pwd/__openent.c create mode 100644 libc/pwd/scanpwent.c diff --git a/Makefile b/Makefile index fc2b3cab..83047f32 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/libc/Makefile b/libc/Makefile index ef4f20de..48f0f2ba 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -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 \ diff --git a/libc/grp/opengr.c b/libc/grp/opengr.c index b6433180..d93ae254 100644 --- a/libc/grp/opengr.c +++ b/libc/grp/opengr.c @@ -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 +#include #include FILE* opengr(void) { - return fopen("/etc/group", "r"); + return __openent("/etc/group"); } diff --git a/libc/grp/setgrent.c b/libc/grp/setgrent.c index 2504d97e..c5d88d2e 100644 --- a/libc/grp/setgrent.c +++ b/libc/grp/setgrent.c @@ -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; } diff --git a/libc/include/pwd.h b/libc/include/pwd.h index 79794477..f8ac5e56 100644 --- a/libc/include/pwd.h +++ b/libc/include/pwd.h @@ -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); diff --git a/libc/pwd/__openent.c b/libc/pwd/__openent.c new file mode 100644 index 00000000..31e41a4a --- /dev/null +++ b/libc/pwd/__openent.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/libc/pwd/fgetpwent_r.c b/libc/pwd/fgetpwent_r.c index 62f6f964..71c6babb 100644 --- a/libc/pwd/fgetpwent_r.c +++ b/libc/pwd/fgetpwent_r.c @@ -17,70 +17,9 @@ * Reads a passwd entry from a FILE. */ -#include - #include -#include #include -#include -#include #include -#include -#include - -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); diff --git a/libc/pwd/openpw.c b/libc/pwd/openpw.c index b08b0bc5..4cb8f33b 100644 --- a/libc/pwd/openpw.c +++ b/libc/pwd/openpw.c @@ -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"); } diff --git a/libc/pwd/scanpwent.c b/libc/pwd/scanpwent.c new file mode 100644 index 00000000..dc82aadc --- /dev/null +++ b/libc/pwd/scanpwent.c @@ -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 +#include +#include +#include +#include + +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; +} diff --git a/libc/pwd/setpwent.c b/libc/pwd/setpwent.c index 6537b294..4a985301 100644 --- a/libc/pwd/setpwent.c +++ b/libc/pwd/setpwent.c @@ -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; } diff --git a/share/man/man7/following-development.7 b/share/man/man7/following-development.7 index 790cb629..8cf1c000 100644 --- a/share/man/man7/following-development.7 +++ b/share/man/man7/following-development.7 @@ -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 diff --git a/sysinstall/Makefile b/sysinstall/Makefile index d6ea8e42..52b2ef57 100644 --- a/sysinstall/Makefile +++ b/sysinstall/Makefile @@ -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 diff --git a/sysinstall/hooks.c b/sysinstall/hooks.c index 2603c712..d7e12564 100644 --- a/sysinstall/hooks.c +++ b/sysinstall/hooks.c @@ -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, diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index 09239bb5..eec58e7e 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -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"); diff --git a/utils/passwd.c b/utils/passwd.c index b85fdb1b..204d95c5 100644 --- a/utils/passwd.c +++ b/utils/passwd.c @@ -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 #include #include +#include #include #include #include @@ -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");