2 * This program is designed to run setuid(root) or with sufficient
3 * privilege to read all of the unix password databases. It is designed
4 * to provide a mechanism for the current user (defined by this
5 * process' uid) to verify their own password.
7 * The password is read from the standard input. The exit status of
8 * this program indicates whether the user is authenticated or not.
10 * Copyright information is located at the end of the file.
22 #include <sys/types.h>
29 #include <selinux/selinux.h>
30 #define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
31 static security_context_t prev_context=NULL;
32 static int selinux_enabled=-1;
34 #define SELINUX_ENABLED 0
37 #define MAXPASS 200 /* the maximum length of a password */
39 #include <security/_pam_types.h>
40 #include <security/_pam_macros.h>
44 extern char *crypt(const char *key, const char *salt);
45 extern char *bigcrypt(const char *key, const char *salt);
47 /* syslogging function for errors and other information */
49 static void _log_err(int err, const char *format,...)
53 va_start(args, format);
54 openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
55 vsyslog(err, format, args);
60 static int _unix_shadowed(const struct passwd *pwd)
64 if (strcmp(pwd->pw_passwd, "x") == 0) {
67 if (strlen(pwd->pw_name) < sizeof(hashpass) - 2) {
68 strcpy(hashpass, "##");
69 strcpy(hashpass + 2, pwd->pw_name);
70 if (strcmp(pwd->pw_passwd, hashpass) == 0) {
78 static void su_sighandler(int sig)
81 /* emulate the behaviour of the SA_RESETHAND flag */
82 if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
86 _log_err(LOG_NOTICE, "caught signal %d.", sig);
91 static void setup_signals(void)
93 struct sigaction action; /* posix signal structure */
96 * Setup signal handlers
98 (void) memset((void *) &action, 0, sizeof(action));
99 action.sa_handler = su_sighandler;
101 action.sa_flags = SA_RESETHAND;
103 (void) sigaction(SIGILL, &action, NULL);
104 (void) sigaction(SIGTRAP, &action, NULL);
105 (void) sigaction(SIGBUS, &action, NULL);
106 (void) sigaction(SIGSEGV, &action, NULL);
107 action.sa_handler = SIG_IGN;
109 (void) sigaction(SIGTERM, &action, NULL);
110 (void) sigaction(SIGHUP, &action, NULL);
111 (void) sigaction(SIGINT, &action, NULL);
112 (void) sigaction(SIGQUIT, &action, NULL);
115 static int _verify_account(const char * const uname)
118 struct passwd *pwent;
120 pwent = getpwnam(uname);
122 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
123 return PAM_USER_UNKNOWN;
126 spent = getspnam( uname );
128 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
129 return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */
131 printf("%ld:%ld:%ld:%ld:%ld:%ld",
132 spent->sp_lstchg, /* last password change */
133 spent->sp_min, /* days until change allowed. */
134 spent->sp_max, /* days before change required */
135 spent->sp_warn, /* days warning for expiration */
136 spent->sp_inact, /* days before account inactive */
137 spent->sp_expire); /* date when account expires */
142 static int _unix_verify_password(const char *name, const char *p, int nullok)
144 struct passwd *pwd = NULL;
145 struct spwd *spwdent = NULL;
148 int retval = PAM_AUTH_ERR;
151 /* UNIX passwords area */
153 pwd = getpwnam(name); /* Get password file entry... */
156 if (_unix_shadowed(pwd)) {
158 * ...and shadow password file entry for this user,
159 * if shadowing is enabled
162 spwdent = getspnam(name);
165 salt = x_strdup(spwdent->sp_pwdp);
169 if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */
172 save_uid = geteuid();
173 seteuid(pwd->pw_uid);
174 spwdent = getspnam(name);
177 salt = x_strdup(spwdent->sp_pwdp);
179 salt = x_strdup(pwd->pw_passwd);
183 if (pwd == NULL || salt == NULL) {
184 _log_err(LOG_ALERT, "check pass; user unknown");
186 return PAM_USER_UNKNOWN;
189 salt_len = strlen(salt);
191 return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
193 if (p == NULL || strlen(p) == 0) {
194 return PAM_AUTHTOK_ERR;
197 /* the moment of truth -- do we agree with the password? */
198 retval = PAM_AUTH_ERR;
199 if (!strncmp(salt, "$1$", 3)) {
200 pp = Goodcrypt_md5(p, salt);
201 if (strcmp(pp, salt) == 0) {
202 retval = PAM_SUCCESS;
204 pp = Brokencrypt_md5(p, salt);
205 if (strcmp(pp, salt) == 0)
206 retval = PAM_SUCCESS;
208 } else if ((*salt == '*') || (salt_len < 13)) {
209 retval = PAM_AUTH_ERR;
211 pp = bigcrypt(p, salt);
213 * Note, we are comparing the bigcrypt of the password with
214 * the contents of the password field. If the latter was
215 * encrypted with regular crypt (and not bigcrypt) it will
216 * have been truncated for storage relative to the output
217 * of bigcrypt here. As such we need to compare only the
218 * stored string with the subset of bigcrypt's result.
219 * Bug 521314: the strncmp comparison is for legacy support.
221 if (strncmp(pp, salt, salt_len) == 0) {
222 retval = PAM_SUCCESS;
225 p = NULL; /* no longer needed here */
241 static char *getuidname(uid_t uid)
244 static char username[32];
250 strncpy(username, pw->pw_name, sizeof(username));
251 username[sizeof(username) - 1] = '\0';
256 #define SH_TMPFILE "/etc/nshadow"
257 static int _update_shadow(const char *forwho)
259 struct spwd *spwdent = NULL, *stmpent = NULL;
260 FILE *pwfile, *opwfile;
264 char pass[MAXPASS + 1];
265 char towhat[MAXPASS + 1];
268 /* read the password from stdin (a pipe from the pam_unix module) */
270 npass = read(STDIN_FILENO, pass, MAXPASS);
272 if (npass < 0) { /* is it a valid password? */
274 _log_err(LOG_DEBUG, "no password supplied");
275 return PAM_AUTHTOK_ERR;
277 } else if (npass >= MAXPASS) {
279 _log_err(LOG_DEBUG, "password too long");
280 return PAM_AUTHTOK_ERR;
283 /* does pass agree with the official one? */
285 pass[npass] = '\0'; /* NUL terminate */
286 retval = _unix_verify_password(forwho, pass, 0);
287 if (retval != PAM_SUCCESS) {
292 /* read the password from stdin (a pipe from the pam_unix module) */
294 npass = read(STDIN_FILENO, towhat, MAXPASS);
296 if (npass < 0) { /* is it a valid password? */
298 _log_err(LOG_DEBUG, "no new password supplied");
299 return PAM_AUTHTOK_ERR;
301 } else if (npass >= MAXPASS) {
303 _log_err(LOG_DEBUG, "new password too long");
304 return PAM_AUTHTOK_ERR;
308 towhat[npass] = '\0'; /* NUL terminate */
309 spwdent = getspnam(forwho);
310 if (spwdent == NULL) {
311 return PAM_USER_UNKNOWN;
313 oldmask = umask(077);
316 if (SELINUX_ENABLED) {
317 security_context_t shadow_context=NULL;
318 if (getfilecon("/etc/shadow",&shadow_context)<0) {
319 return PAM_AUTHTOK_ERR;
321 if (getfscreatecon(&prev_context)<0) {
322 freecon(shadow_context);
323 return PAM_AUTHTOK_ERR;
325 if (setfscreatecon(shadow_context)) {
326 freecon(shadow_context);
327 freecon(prev_context);
328 return PAM_AUTHTOK_ERR;
330 freecon(shadow_context);
333 pwfile = fopen(SH_TMPFILE, "w");
335 if (pwfile == NULL) {
340 opwfile = fopen("/etc/shadow", "r");
341 if (opwfile == NULL) {
347 if (fstat(fileno(opwfile), &st) == -1) {
354 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
360 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
367 stmpent = fgetspent(opwfile);
370 if (!strcmp(stmpent->sp_namp, forwho)) {
371 stmpent->sp_pwdp = towhat;
372 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
374 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
377 if (putspent(stmpent, pwfile)) {
378 D(("error writing entry to shadow file: %m"));
383 stmpent = fgetspent(opwfile);
387 if (fclose(pwfile)) {
388 D(("error writing entries to shadow file: %m"));
394 if (rename(SH_TMPFILE, "/etc/shadow"))
399 if (SELINUX_ENABLED) {
400 if (setfscreatecon(prev_context)) {
404 freecon(prev_context);
413 return PAM_AUTHTOK_ERR;
417 int main(int argc, char *argv[])
419 char pass[MAXPASS + 1];
422 int force_failure = 0;
423 int retval = PAM_AUTH_ERR;
427 * Catch or ignore as many signal as possible.
432 * we establish that this program is running with non-tty stdin.
433 * this is to discourage casual use. It does *NOT* prevent an
434 * intruder from repeatadly running this program to determine the
435 * password of the current user (brute force attack, but one for
436 * which the attacker must already have gained access to the user's
440 if (isatty(STDIN_FILENO) || argc != 3 ) {
442 ,"inappropriate use of Unix helper binary [UID=%d]"
445 ,"This binary is not designed for running in this way\n"
446 "-- the system administrator has been informed\n");
447 sleep(10); /* this should discourage/annoy the user */
448 return PAM_SYSTEM_ERR;
452 * Determine what the current user's name is.
453 * On a SELinux enabled system with a strict policy leaving the
454 * existing check prevents shadow password authentication from working.
455 * We must thus skip the check if the real uid is 0.
457 if (SELINUX_ENABLED && getuid() == 0) {
461 user = getuidname(getuid());
462 /* if the caller specifies the username, verify that user
464 if (strcmp(user, argv[1])) {
471 if (strncmp(argv[2], "verify", 8) == 0) {
472 /* Get the account information from the shadow file */
473 return _verify_account(argv[1]);
476 if (strncmp(option, "shadow", 8) == 0) {
477 /* Attempting to change the password */
478 return _update_shadow(argv[1]);
481 /* read the nullok/nonull option */
482 if (strncmp(option, "nullok", 8) == 0)
487 /* read the password from stdin (a pipe from the pam_unix module) */
489 npass = read(STDIN_FILENO, pass, MAXPASS);
491 if (npass < 0) { /* is it a valid password? */
493 _log_err(LOG_DEBUG, "no password supplied");
495 } else if (npass >= MAXPASS) {
497 _log_err(LOG_DEBUG, "password too long");
501 /* the password is NULL */
503 retval = _unix_verify_password(user, NULL, nullok);
506 /* does pass agree with the official one? */
508 pass[npass] = '\0'; /* NUL terminate */
509 retval = _unix_verify_password(user, pass, nullok);
514 memset(pass, '\0', MAXPASS); /* clear memory of the password */
516 /* return pass or fail */
518 if ((retval != PAM_SUCCESS) || force_failure) {
519 _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
527 * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
529 * Redistribution and use in source and binary forms, with or without
530 * modification, are permitted provided that the following conditions
532 * 1. Redistributions of source code must retain the above copyright
533 * notice, and the entire permission notice in its entirety,
534 * including the disclaimer of warranties.
535 * 2. Redistributions in binary form must reproduce the above copyright
536 * notice, this list of conditions and the following disclaimer in the
537 * documentation and/or other materials provided with the distribution.
538 * 3. The name of the author may not be used to endorse or promote
539 * products derived from this software without specific prior
540 * written permission.
542 * ALTERNATIVELY, this product may be distributed under the terms of
543 * the GNU Public License, in which case the provisions of the GPL are
544 * required INSTEAD OF the above restrictions. (This clause is
545 * necessary due to a potential bad interaction between the GPL and
546 * the restrictions contained in a BSD-style copyright.)
548 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
549 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
550 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
551 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
552 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
553 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
554 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
555 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
556 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
557 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
558 * OF THE POSSIBILITY OF SUCH DAMAGE.