diff -up shadow-4.14.0/libmisc/agetpass.c.orig shadow-4.14.0/libmisc/agetpass.c --- shadow-4.14.0/libmisc/agetpass.c.orig 2024-01-24 20:06:20.557577853 +0100 +++ shadow-4.14.0/libmisc/agetpass.c 2024-01-24 21:21:06.379445080 +0100 @@ -32,6 +32,7 @@ * SYNOPSIS * [[gnu::malloc(erase_pass)]] * char *agetpass(const char *prompt); + * char *agetpass_stdin(); * * void erase_pass(char *pass); * @@ -64,6 +65,10 @@ * erased by calling erase_pass(), to avoid possibly leaking the * password. * + * agetpass_stdin() + * This function is the same as previous one (agetpass). Just the + * password is read from stdin and terminal is not required. + * * erase_pass() * This function first clears the password, by calling * explicit_bzero(3) (or an equivalent call), and then frees the @@ -92,8 +97,8 @@ */ -char * -agetpass(const char *prompt) +static char * +agetpass_internal(const char *prompt, int flags) { char *pass; size_t len; @@ -110,7 +115,7 @@ agetpass(const char *prompt) if (pass == NULL) return NULL; - if (readpassphrase(prompt, pass, PASS_MAX + 2, RPP_REQUIRE_TTY) == NULL) + if (readpassphrase(prompt, pass, PASS_MAX + 2, flags) == NULL) goto fail; len = strlen(pass); @@ -126,6 +131,17 @@ fail: return NULL; } +char * +agetpass(const char *prompt) +{ + return agetpass_internal(prompt, RPP_REQUIRE_TTY); +} + +char * +agetpass_stdin() +{ + return agetpass_internal(NULL, RPP_STDIN); +} void erase_pass(char *pass) diff -up shadow-4.14.0/lib/prototypes.h.orig shadow-4.14.0/lib/prototypes.h --- shadow-4.14.0/lib/prototypes.h.orig 2024-01-24 22:06:18.786184942 +0100 +++ shadow-4.14.0/lib/prototypes.h 2024-01-24 20:19:45.299231059 +0100 @@ -47,6 +47,7 @@ extern int expire (const struct passwd * extern void erase_pass(char *pass); ATTR_MALLOC(erase_pass) extern char *agetpass(const char *prompt); +extern char *agetpass_stdin(); /* isexpired.c */ extern int isexpired (const struct passwd *, /*@null@*/const struct spwd *); diff -up shadow-4.14.0/man/passwd.1.xml.orig shadow-4.14.0/man/passwd.1.xml --- shadow-4.14.0/man/passwd.1.xml.orig 2024-01-24 20:33:31.438972506 +0100 +++ shadow-4.14.0/man/passwd.1.xml 2024-01-29 17:36:31.082495245 +0100 @@ -341,6 +341,17 @@ + + + , + + + + This option is used to indicate that passwd should read the new password from standard + input, which can be a pipe. + + + diff -up shadow-4.14.0/src/passwd.c.orig shadow-4.14.0/src/passwd.c --- shadow-4.14.0/src/passwd.c.orig 2024-01-24 13:57:15.714549266 +0100 +++ shadow-4.14.0/src/passwd.c 2024-01-29 17:33:59.421508534 +0100 @@ -65,7 +65,8 @@ static bool Sflg = false, /* -S - show password status */ uflg = false, /* -u - unlock the user's password */ wflg = false, /* -w - set warning days */ - xflg = false; /* -x - set maximum days */ + xflg = false, /* -x - set maximum days */ + sflg = false; /* -s - read passwd from stdin */ /* * set to 1 if there are any flags which require root privileges, @@ -156,6 +157,7 @@ usage (int status) (void) fputs (_(" -w, --warndays WARN_DAYS set expiration warning days to WARN_DAYS\n"), usageout); (void) fputs (_(" -x, --maxdays MAX_DAYS set maximum number of days before password\n" " change to MAX_DAYS\n"), usageout); + (void) fputs (_(" -s, --stdin read new token from stdin\n"), usageout); (void) fputs ("\n", usageout); exit (status); } @@ -275,7 +277,7 @@ static int new_password (const struct pa pass_max_len = getdef_num ("PASS_MAX_LEN", 8); } } - if (!qflg) { + if (!qflg && !sflg) { if (pass_max_len == -1) { (void) printf (_( "Enter the new password (minimum of %d characters)\n" @@ -289,55 +291,67 @@ static int new_password (const struct pa } } - warned = false; - for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) { - cp = agetpass (_("New password: ")); + if (sflg) { + /* + * root is setting the passphrase from stdin + */ + cp = agetpass_stdin (); if (NULL == cp) { - memzero (orig, sizeof orig); - memzero (pass, sizeof pass); return -1; } - if (warned && (strcmp (pass, cp) != 0)) { - warned = false; - } STRFCPY (pass, cp); erase_pass (cp); + } else { + warned = false; + for (i = getdef_num ("PASS_CHANGE_TRIES", 5); i > 0; i--) { + cp = agetpass (_("New password: ")); + if (NULL == cp) { + memzero (orig, sizeof orig); + memzero (pass, sizeof pass); + return -1; + } + if (warned && (strcmp (pass, cp) != 0)) { + warned = false; + } + STRFCPY (pass, cp); + erase_pass (cp); - if (!amroot && !obscure(orig, pass, pw)) { - (void) puts (_("Try again.")); - continue; - } + if (!amroot && !obscure(orig, pass, pw)) { + (void) puts (_("Try again.")); + continue; + } - /* - * If enabled, warn about weak passwords even if you are - * root (enter this password again to use it anyway). - * --marekm - */ - if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN") - && !obscure(orig, pass, pw)) { - (void) puts (_("\nWarning: weak password (enter it again to use it anyway).")); - warned = true; - continue; + /* + * If enabled, warn about weak passwords even if you are + * root (enter this password again to use it anyway). + * --marekm + */ + if (amroot && !warned && getdef_bool ("PASS_ALWAYS_WARN") + && !obscure(orig, pass, pw)) { + (void) puts (_("\nWarning: weak password (enter it again to use it anyway).")); + warned = true; + continue; + } + cp = agetpass (_("Re-enter new password: ")); + if (NULL == cp) { + memzero (orig, sizeof orig); + memzero (pass, sizeof pass); + return -1; + } + if (strcmp (cp, pass) != 0) { + erase_pass (cp); + (void) fputs (_("They don't match; try again.\n"), stderr); + } else { + erase_pass (cp); + break; + } } - cp = agetpass (_("Re-enter new password: ")); - if (NULL == cp) { - memzero (orig, sizeof orig); + memzero (orig, sizeof orig); + + if (i == 0) { memzero (pass, sizeof pass); return -1; } - if (strcmp (cp, pass) != 0) { - erase_pass (cp); - (void) fputs (_("They don't match; try again.\n"), stderr); - } else { - erase_pass (cp); - break; - } - } - memzero (orig, sizeof orig); - - if (i == 0) { - memzero (pass, sizeof pass); - return -1; } /* @@ -714,6 +728,7 @@ static void update_shadow (void) * -u unlock the password of the named account (*) * -w # set sp_warn to # days (*) * -x # set sp_max to # days (*) + * -s read password from stdin * * (*) requires root permission to execute. * @@ -781,10 +796,11 @@ int main (int argc, char **argv) {"unlock", no_argument, NULL, 'u'}, {"warndays", required_argument, NULL, 'w'}, {"maxdays", required_argument, NULL, 'x'}, + {"stdin", no_argument, NULL, 's'}, {NULL, 0, NULL, '\0'} }; - while ((c = getopt_long (argc, argv, "adehi:kln:qr:R:P:Suw:x:", + while ((c = getopt_long (argc, argv, "adehi:kln:qr:R:P:Suw:x:s", long_options, NULL)) != -1) { switch (c) { case 'a': @@ -877,6 +893,15 @@ int main (int argc, char **argv) xflg = true; anyflag = true; break; + case 's': + if (!amroot) { + (void) fprintf (stderr, + _("%s: only root can use --stdin/-s option\n"), + Prog); + usage (E_BAD_ARG); + } + sflg = true; + break; default: usage (E_BAD_ARG); } @@ -1068,7 +1093,16 @@ int main (int argc, char **argv) * Don't set the real UID for PAM... */ if (!anyflag && use_pam) { - do_pam_passwd (name, qflg, kflg); + if (sflg) { + cp = agetpass_stdin (); + if (cp == NULL) { + exit (E_FAILURE); + } + do_pam_passwd_non_interactive ("passwd", name, cp); + erase_pass (cp); + } else { + do_pam_passwd (name, qflg, kflg); + } exit (E_SUCCESS); } #endif /* USE_PAM */ @@ -1102,4 +1136,3 @@ int main (int argc, char **argv) return E_SUCCESS; } -