4 * This program is designed to run setuid(root) or with sufficient
5 * privilege to read all of the unix password databases. It is designed
6 * to provide a mechanism for the current user (defined by this
7 * process' uid) to verify their own password.
9 * The password is read from the standard input. The exit status of
10 * this program indicates whether the user is authenticated or not.
12 * Copyright information is located at the end of the file.
22 #endif /* MEMORY_DEBUG */
30 #include <sys/types.h>
37 #include <selinux/selinux.h>
38 #define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
39 static security_context_t prev_context=NULL;
40 static int selinux_enabled=-1;
42 #define SELINUX_ENABLED 0
45 #define MAXPASS 200 /* the maximum length of a password */
47 #include <security/_pam_types.h>
48 #include <security/_pam_macros.h>
52 extern char *crypt(const char *key, const char *salt);
53 extern char *bigcrypt(const char *key, const char *salt);
55 /* syslogging function for errors and other information */
57 static void _log_err(int err, const char *format,...)
61 va_start(args, format);
62 openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH);
63 vsyslog(err, format, args);
68 static int _unix_shadowed(const struct passwd *pwd)
72 if (strcmp(pwd->pw_passwd, "x") == 0) {
75 if (strlen(pwd->pw_name) < sizeof(hashpass) - 2) {
76 strcpy(hashpass, "##");
77 strcpy(hashpass + 2, pwd->pw_name);
78 if (strcmp(pwd->pw_passwd, hashpass) == 0) {
86 static void su_sighandler(int sig)
89 /* emulate the behaviour of the SA_RESETHAND flag */
90 if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
94 _log_err(LOG_NOTICE, "caught signal %d.", sig);
99 static void setup_signals(void)
101 struct sigaction action; /* posix signal structure */
104 * Setup signal handlers
106 (void) memset((void *) &action, 0, sizeof(action));
107 action.sa_handler = su_sighandler;
109 action.sa_flags = SA_RESETHAND;
111 (void) sigaction(SIGILL, &action, NULL);
112 (void) sigaction(SIGTRAP, &action, NULL);
113 (void) sigaction(SIGBUS, &action, NULL);
114 (void) sigaction(SIGSEGV, &action, NULL);
115 action.sa_handler = SIG_IGN;
117 (void) sigaction(SIGTERM, &action, NULL);
118 (void) sigaction(SIGHUP, &action, NULL);
119 (void) sigaction(SIGINT, &action, NULL);
120 (void) sigaction(SIGQUIT, &action, NULL);
123 static int _verify_account(const char * const uname)
126 struct passwd *pwent;
128 pwent = getpwnam(uname);
130 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
131 return PAM_USER_UNKNOWN;
134 spent = getspnam( uname );
136 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
137 return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */
139 printf("%ld:%ld:%ld:%ld:%ld:%ld",
140 spent->sp_lstchg, /* last password change */
141 spent->sp_min, /* days until change allowed. */
142 spent->sp_max, /* days before change required */
143 spent->sp_warn, /* days warning for expiration */
144 spent->sp_inact, /* days before account inactive */
145 spent->sp_expire); /* date when account expires */
150 static int _unix_verify_password(const char *name, const char *p, int nullok)
152 struct passwd *pwd = NULL;
153 struct spwd *spwdent = NULL;
156 int retval = PAM_AUTH_ERR;
159 /* UNIX passwords area */
161 pwd = getpwnam(name); /* Get password file entry... */
164 if (_unix_shadowed(pwd)) {
166 * ...and shadow password file entry for this user,
167 * if shadowing is enabled
170 spwdent = getspnam(name);
173 salt = x_strdup(spwdent->sp_pwdp);
177 if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */
180 save_uid = geteuid();
181 seteuid(pwd->pw_uid);
182 spwdent = getspnam(name);
185 salt = x_strdup(spwdent->sp_pwdp);
187 salt = x_strdup(pwd->pw_passwd);
191 if (pwd == NULL || salt == NULL) {
192 _log_err(LOG_ALERT, "check pass; user unknown");
194 return PAM_USER_UNKNOWN;
197 salt_len = strlen(salt);
199 return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
201 if (p == NULL || strlen(p) == 0) {
202 return PAM_AUTHTOK_ERR;
205 /* the moment of truth -- do we agree with the password? */
206 retval = PAM_AUTH_ERR;
207 if (!strncmp(salt, "$1$", 3)) {
208 pp = Goodcrypt_md5(p, salt);
209 if (strcmp(pp, salt) == 0) {
210 retval = PAM_SUCCESS;
212 pp = Brokencrypt_md5(p, salt);
213 if (strcmp(pp, salt) == 0)
214 retval = PAM_SUCCESS;
216 } else if ((*salt == '*') || (salt_len < 13)) {
217 retval = PAM_AUTH_ERR;
219 pp = bigcrypt(p, salt);
221 * Note, we are comparing the bigcrypt of the password with
222 * the contents of the password field. If the latter was
223 * encrypted with regular crypt (and not bigcrypt) it will
224 * have been truncated for storage relative to the output
225 * of bigcrypt here. As such we need to compare only the
226 * stored string with the subset of bigcrypt's result.
227 * Bug 521314: the strncmp comparison is for legacy support.
229 if (strncmp(pp, salt, salt_len) == 0) {
230 retval = PAM_SUCCESS;
233 p = NULL; /* no longer needed here */
249 static char *getuidname(uid_t uid)
252 static char username[32];
258 strncpy(username, pw->pw_name, sizeof(username));
259 username[sizeof(username) - 1] = '\0';
264 #define SH_TMPFILE "/etc/nshadow"
265 static int _update_shadow(const char *forwho)
267 struct spwd *spwdent = NULL, *stmpent = NULL;
268 FILE *pwfile, *opwfile;
272 char pass[MAXPASS + 1];
273 char towhat[MAXPASS + 1];
276 /* read the password from stdin (a pipe from the pam_unix module) */
278 npass = read(STDIN_FILENO, pass, MAXPASS);
280 if (npass < 0) { /* is it a valid password? */
282 _log_err(LOG_DEBUG, "no password supplied");
283 return PAM_AUTHTOK_ERR;
285 } else if (npass >= MAXPASS) {
287 _log_err(LOG_DEBUG, "password too long");
288 return PAM_AUTHTOK_ERR;
291 /* does pass agree with the official one? */
293 pass[npass] = '\0'; /* NUL terminate */
294 retval = _unix_verify_password(forwho, pass, 0);
295 if (retval != PAM_SUCCESS) {
300 /* read the password from stdin (a pipe from the pam_unix module) */
302 npass = read(STDIN_FILENO, towhat, MAXPASS);
304 if (npass < 0) { /* is it a valid password? */
306 _log_err(LOG_DEBUG, "no new password supplied");
307 return PAM_AUTHTOK_ERR;
309 } else if (npass >= MAXPASS) {
311 _log_err(LOG_DEBUG, "new password too long");
312 return PAM_AUTHTOK_ERR;
316 towhat[npass] = '\0'; /* NUL terminate */
317 spwdent = getspnam(forwho);
318 if (spwdent == NULL) {
319 return PAM_USER_UNKNOWN;
321 oldmask = umask(077);
324 if (SELINUX_ENABLED) {
325 security_context_t shadow_context=NULL;
326 if (getfilecon("/etc/shadow",&shadow_context)<0) {
327 return PAM_AUTHTOK_ERR;
329 if (getfscreatecon(&prev_context)<0) {
330 freecon(shadow_context);
331 return PAM_AUTHTOK_ERR;
333 if (setfscreatecon(shadow_context)) {
334 freecon(shadow_context);
335 freecon(prev_context);
336 return PAM_AUTHTOK_ERR;
338 freecon(shadow_context);
341 pwfile = fopen(SH_TMPFILE, "w");
343 if (pwfile == NULL) {
348 opwfile = fopen("/etc/shadow", "r");
349 if (opwfile == NULL) {
355 if (fstat(fileno(opwfile), &st) == -1) {
362 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
368 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
375 stmpent = fgetspent(opwfile);
378 if (!strcmp(stmpent->sp_namp, forwho)) {
379 stmpent->sp_pwdp = towhat;
380 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
382 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
385 if (putspent(stmpent, pwfile)) {
386 D(("error writing entry to shadow file: %s\n", strerror(errno)));
391 stmpent = fgetspent(opwfile);
395 if (fclose(pwfile)) {
396 D(("error writing entries to shadow file: %s\n", strerror(errno)));
402 if (rename(SH_TMPFILE, "/etc/shadow"))
407 if (SELINUX_ENABLED) {
408 if (setfscreatecon(prev_context)) {
412 freecon(prev_context);
421 return PAM_AUTHTOK_ERR;
425 int main(int argc, char *argv[])
427 char pass[MAXPASS + 1];
430 int force_failure = 0;
431 int retval = PAM_AUTH_ERR;
435 * Catch or ignore as many signal as possible.
440 * we establish that this program is running with non-tty stdin.
441 * this is to discourage casual use. It does *NOT* prevent an
442 * intruder from repeatadly running this program to determine the
443 * password of the current user (brute force attack, but one for
444 * which the attacker must already have gained access to the user's
448 if (isatty(STDIN_FILENO) || argc != 3 ) {
450 ,"inappropriate use of Unix helper binary [UID=%d]"
453 ,"This binary is not designed for running in this way\n"
454 "-- the system administrator has been informed\n");
455 sleep(10); /* this should discourage/annoy the user */
456 return PAM_SYSTEM_ERR;
460 * determine the current user's name is.
461 * On a SELinux enabled system, policy will prevent third parties from using
462 * unix_chkpwd as a password guesser. Leaving the existing check prevents
463 * su from working, Since the current uid is the users and the password is
466 if (SELINUX_ENABLED) {
470 user = getuidname(getuid());
471 /* if the caller specifies the username, verify that user
473 if (strcmp(user, argv[1])) {
480 if (strncmp(argv[2], "verify", 8) == 0) {
481 /* Get the account information from the shadow file */
482 return _verify_account(argv[1]);
485 if (strncmp(option, "shadow", 8) == 0) {
486 /* Attempting to change the password */
487 return _update_shadow(argv[1]);
490 /* read the nullok/nonull option */
491 if (strncmp(option, "nullok", 8) == 0)
496 /* read the password from stdin (a pipe from the pam_unix module) */
498 npass = read(STDIN_FILENO, pass, MAXPASS);
500 if (npass < 0) { /* is it a valid password? */
502 _log_err(LOG_DEBUG, "no password supplied");
504 } else if (npass >= MAXPASS) {
506 _log_err(LOG_DEBUG, "password too long");
510 /* the password is NULL */
512 retval = _unix_verify_password(user, NULL, nullok);
515 /* does pass agree with the official one? */
517 pass[npass] = '\0'; /* NUL terminate */
518 retval = _unix_verify_password(user, pass, nullok);
523 memset(pass, '\0', MAXPASS); /* clear memory of the password */
525 /* return pass or fail */
527 if ((retval != PAM_SUCCESS) || force_failure) {
535 * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
537 * Redistribution and use in source and binary forms, with or without
538 * modification, are permitted provided that the following conditions
540 * 1. Redistributions of source code must retain the above copyright
541 * notice, and the entire permission notice in its entirety,
542 * including the disclaimer of warranties.
543 * 2. Redistributions in binary form must reproduce the above copyright
544 * notice, this list of conditions and the following disclaimer in the
545 * documentation and/or other materials provided with the distribution.
546 * 3. The name of the author may not be used to endorse or promote
547 * products derived from this software without specific prior
548 * written permission.
550 * ALTERNATIVELY, this product may be distributed under the terms of
551 * the GNU Public License, in which case the provisions of the GPL are
552 * required INSTEAD OF the above restrictions. (This clause is
553 * necessary due to a potential bad interaction between the GPL and
554 * the restrictions contained in a BSD-style copyright.)
556 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
557 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
558 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
559 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
560 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
561 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
562 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
563 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
564 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
565 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
566 * OF THE POSSIBILITY OF SUCH DAMAGE.