diff --git a/login/login.8 b/login/login.8 index ab3545a1..6907ceaf 100644 --- a/login/login.8 +++ b/login/login.8 @@ -65,6 +65,7 @@ textual interface is forced if this file exists exits 0 if the computer should power off, exits 1 if the computer should reboot, or exits 2 on fatal failure and the boot should halt. .Sh SEE ALSO +.Xr passwd 1 , .Xr crypt_checkpass 3 , .Xr passwd 5 , .Xr init 8 , diff --git a/utils/.gitignore b/utils/.gitignore index 78de33cb..b02d843c 100644 --- a/utils/.gitignore +++ b/utils/.gitignore @@ -31,6 +31,7 @@ mkdir mktemp mv pager +passwd ps pstree pwd diff --git a/utils/Makefile b/utils/Makefile index ca559f29..d29bb8f2 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -43,6 +43,7 @@ mkdir \ mktemp \ mv \ pager \ +passwd \ ps \ pstree \ pwd \ diff --git a/utils/passwd.cpp b/utils/passwd.cpp new file mode 100644 index 00000000..3abb78c6 --- /dev/null +++ b/utils/passwd.cpp @@ -0,0 +1,242 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2015. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU 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 General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program. If not, see . + + passwd.cpp + Password change. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void password(char* buffer, + size_t buffer_size, + const char* whose, + const char* question) +{ + if ( !isatty(0) ) + errx(1, "Input is not a terminal"); + unsigned int termmode; + gettermmode(0, &termmode); + settermmode(0, termmode & ~TERMMODE_ECHO); + if ( whose ) + printf("%s's ", whose); + printf("%s ", question); + fflush(stdout); + fflush(stdin); + // TODO: This may leave a copy of the password in the stdio buffer. + fgets(buffer, buffer_size, stdin); + fflush(stdin); + printf("\n"); + size_t buffer_length = strlen(buffer); + if ( buffer_length && buffer[buffer_length-1] == '\n' ) + buffer[--buffer_length] = '\0'; + settermmode(0, termmode); +} + +static void compact_arguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + { + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } + } +} + +static void version(FILE* fp, const char* argv0) +{ + fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR); + fprintf(fp, "License GPLv3+: GNU GPL version 3 or later .\n"); + fprintf(fp, "This is free software: you are free to change and redistribute it.\n"); + fprintf(fp, "There is NO WARRANTY, to the extent permitted by law.\n"); +} + +static void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [LOGIN]\n", argv0); +} + +int main(int argc, char* argv[]) +{ + const char* cipher = "blowfish,a"; + + const char* argv0 = argv[0]; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' || !arg[1] ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'c': + if ( !*(cipher = arg + 1) ) + { + if ( i + 1 == argc ) + { + warnx("option requires an argument -- 'c'"); + fprintf(stderr, "Try `%s --help' for more information.\n", argv[0]); + exit(125); + } + cipher = argv[i+1]; + argv[++i] = NULL; + } + arg = "c"; + break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else + { + fprintf(stderr, "%s: unrecognized option: %s\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + compact_arguments(&argc, &argv); + + uid_t my_uid = getuid(); + char* my_username = getlogin(); + if ( !my_username ) + err(1, "failed to get username"); + if ( !(my_username = strdup(my_username)) ) + err(1, "stdup"); + + const char* username; + if ( argc <= 1 ) + username = my_username; + else if ( argc <= 2 ) + username = argv[1]; + else + errx(1, "Unexpected extra operand"); + + errno = 0; + struct passwd* pwd = getpwnam(username); + if ( !pwd && errno == 0 ) + errx(1, "%s: No such user", username); + if ( !pwd ) + err(1, "%s", username); + + if ( my_uid != 0 && pwd->pw_uid != my_uid ) + errx(1, "You may not change the password for '%s'", username); + + printf("Changing password for %s.\n", username); + + if ( my_uid != 0 ) + { + char current[128]; + password(current, sizeof(current), pwd->pw_name, + "current password (will not echo)"); + if ( crypt_checkpass(current, pwd->pw_passwd) < 0 ) + errx(1, "Wrong password for '%s'", pwd->pw_name); + explicit_bzero(current, sizeof(current)); + } + + char first[128]; + password(first, sizeof(first), NULL, "Enter new password (will not echo)"); + char second[128]; + password(second, sizeof(second), NULL, "Enter new password (again)"); + if ( strcmp(first, second) != 0 ) + errx(1, "Passwords don't match"); + explicit_bzero(second, sizeof(second)); + char newhash[128]; + 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. + 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()) ) + { + fputs(pwd->pw_name, fp); + fputc(':', fp); + if ( !strcmp(pwd->pw_name, username) ) + fputs(newhash, fp); + else + fputs(pwd->pw_passwd, fp); + fputc(':', fp); + fprintf(fp, "%" PRIuUID, pwd->pw_uid); + fputc(':', fp); + fprintf(fp, "%" PRIuGID, pwd->pw_gid); + fputc(':', fp); + fputs(pwd->pw_gecos, fp); + fputc(':', fp); + fputs(pwd->pw_dir, fp); + fputc(':', fp); + fputs(pwd->pw_shell, fp); + fputc('\n', fp); + if ( ferror(fp) || fflush(stdout) == EOF ) + { + unlink("/etc/passwd.new"); + err(1, "/etc/passwd.new"); + } + } + if ( errno != 0 ) + { + unlink("/etc/passwd.new"); + err(1, "getpwent"); + } + if ( fclose(fp) == EOF ) + { + unlink("/etc/passwd.new"); + err(1, "/etc/passwd.new"); + } + if ( rename("/etc/passwd.new", "/etc/passwd") < 0 ) + { + unlink("/etc/passwd.new"); + err(1, "rename: /etc/passwd.new -> /etc/passwd"); + } + + printf("Changed password for %s.\n", username); + + return 0; +}