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>
45 /* syslogging function for errors and other information */
47 static void _log_err(int err, const char *format,...)
51 va_start(args, format);
52 openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
53 vsyslog(err, format, args);
58 static int _unix_shadowed(const struct passwd *pwd)
62 if (strcmp(pwd->pw_passwd, "x") == 0) {
65 if (strlen(pwd->pw_name) < sizeof(hashpass) - 2) {
66 strcpy(hashpass, "##");
67 strcpy(hashpass + 2, pwd->pw_name);
68 if (strcmp(pwd->pw_passwd, hashpass) == 0) {
76 static void su_sighandler(int sig)
79 /* emulate the behaviour of the SA_RESETHAND flag */
80 if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
84 _log_err(LOG_NOTICE, "caught signal %d.", sig);
89 static void setup_signals(void)
91 struct sigaction action; /* posix signal structure */
94 * Setup signal handlers
96 (void) memset((void *) &action, 0, sizeof(action));
97 action.sa_handler = su_sighandler;
99 action.sa_flags = SA_RESETHAND;
101 (void) sigaction(SIGILL, &action, NULL);
102 (void) sigaction(SIGTRAP, &action, NULL);
103 (void) sigaction(SIGBUS, &action, NULL);
104 (void) sigaction(SIGSEGV, &action, NULL);
105 action.sa_handler = SIG_IGN;
107 (void) sigaction(SIGTERM, &action, NULL);
108 (void) sigaction(SIGHUP, &action, NULL);
109 (void) sigaction(SIGINT, &action, NULL);
110 (void) sigaction(SIGQUIT, &action, NULL);
113 static int _verify_account(const char * const uname)
116 struct passwd *pwent;
118 pwent = getpwnam(uname);
120 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
121 return PAM_USER_UNKNOWN;
124 spent = getspnam( uname );
126 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
127 return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */
129 printf("%ld:%ld:%ld:%ld:%ld:%ld",
130 spent->sp_lstchg, /* last password change */
131 spent->sp_min, /* days until change allowed. */
132 spent->sp_max, /* days before change required */
133 spent->sp_warn, /* days warning for expiration */
134 spent->sp_inact, /* days before account inactive */
135 spent->sp_expire); /* date when account expires */
140 static int _unix_verify_password(const char *name, const char *p, int nullok)
142 struct passwd *pwd = NULL;
143 struct spwd *spwdent = NULL;
146 int retval = PAM_AUTH_ERR;
149 /* UNIX passwords area */
151 pwd = getpwnam(name); /* Get password file entry... */
154 if (_unix_shadowed(pwd)) {
156 * ...and shadow password file entry for this user,
157 * if shadowing is enabled
160 spwdent = getspnam(name);
163 salt = x_strdup(spwdent->sp_pwdp);
167 if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */
170 save_uid = geteuid();
171 seteuid(pwd->pw_uid);
172 spwdent = getspnam(name);
175 salt = x_strdup(spwdent->sp_pwdp);
177 salt = x_strdup(pwd->pw_passwd);
181 if (pwd == NULL || salt == NULL) {
182 _log_err(LOG_ALERT, "check pass; user unknown");
184 return PAM_USER_UNKNOWN;
187 salt_len = strlen(salt);
189 return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
191 if (p == NULL || strlen(p) == 0) {
192 return PAM_AUTHTOK_ERR;
195 /* the moment of truth -- do we agree with the password? */
196 retval = PAM_AUTH_ERR;
197 if (!strncmp(salt, "$1$", 3)) {
198 pp = Goodcrypt_md5(p, salt);
199 if (strcmp(pp, salt) == 0) {
200 retval = PAM_SUCCESS;
202 pp = Brokencrypt_md5(p, salt);
203 if (strcmp(pp, salt) == 0)
204 retval = PAM_SUCCESS;
206 } else if (*salt == '$') {
208 * Ok, we don't know the crypt algorithm, but maybe
209 * libcrypt nows about it? We should try it.
211 pp = x_strdup (crypt(p, salt));
212 if (strcmp(pp, salt) == 0) {
213 retval = PAM_SUCCESS;
215 } else if ((*salt == '*') || (salt_len < 13)) {
216 retval = PAM_AUTH_ERR;
218 pp = bigcrypt(p, salt);
220 * Note, we are comparing the bigcrypt of the password with
221 * the contents of the password field. If the latter was
222 * encrypted with regular crypt (and not bigcrypt) it will
223 * have been truncated for storage relative to the output
224 * of bigcrypt here. As such we need to compare only the
225 * stored string with the subset of bigcrypt's result.
226 * Bug 521314: the strncmp comparison is for legacy support.
228 if (strncmp(pp, salt, salt_len) == 0) {
229 retval = PAM_SUCCESS;
232 p = NULL; /* no longer needed here */
248 static char *getuidname(uid_t uid)
251 static char username[32];
257 strncpy(username, pw->pw_name, sizeof(username));
258 username[sizeof(username) - 1] = '\0';
263 #define SH_TMPFILE "/etc/nshadow"
264 static int _update_shadow(const char *forwho)
266 struct spwd *spwdent = NULL, *stmpent = NULL;
267 FILE *pwfile, *opwfile;
271 char pass[MAXPASS + 1];
272 char towhat[MAXPASS + 1];
275 /* read the password from stdin (a pipe from the pam_unix module) */
277 npass = read(STDIN_FILENO, pass, MAXPASS);
279 if (npass < 0) { /* is it a valid password? */
281 _log_err(LOG_DEBUG, "no password supplied");
282 return PAM_AUTHTOK_ERR;
284 } else if (npass >= MAXPASS) {
286 _log_err(LOG_DEBUG, "password too long");
287 return PAM_AUTHTOK_ERR;
290 /* does pass agree with the official one? */
292 pass[npass] = '\0'; /* NUL terminate */
293 retval = _unix_verify_password(forwho, pass, 0);
294 if (retval != PAM_SUCCESS) {
299 /* read the password from stdin (a pipe from the pam_unix module) */
301 npass = read(STDIN_FILENO, towhat, MAXPASS);
303 if (npass < 0) { /* is it a valid password? */
305 _log_err(LOG_DEBUG, "no new password supplied");
306 return PAM_AUTHTOK_ERR;
308 } else if (npass >= MAXPASS) {
310 _log_err(LOG_DEBUG, "new password too long");
311 return PAM_AUTHTOK_ERR;
315 towhat[npass] = '\0'; /* NUL terminate */
316 spwdent = getspnam(forwho);
317 if (spwdent == NULL) {
318 return PAM_USER_UNKNOWN;
320 oldmask = umask(077);
323 if (SELINUX_ENABLED) {
324 security_context_t shadow_context=NULL;
325 if (getfilecon("/etc/shadow",&shadow_context)<0) {
326 return PAM_AUTHTOK_ERR;
328 if (getfscreatecon(&prev_context)<0) {
329 freecon(shadow_context);
330 return PAM_AUTHTOK_ERR;
332 if (setfscreatecon(shadow_context)) {
333 freecon(shadow_context);
334 freecon(prev_context);
335 return PAM_AUTHTOK_ERR;
337 freecon(shadow_context);
340 pwfile = fopen(SH_TMPFILE, "w");
342 if (pwfile == NULL) {
347 opwfile = fopen("/etc/shadow", "r");
348 if (opwfile == NULL) {
354 if (fstat(fileno(opwfile), &st) == -1) {
361 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
367 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
374 stmpent = fgetspent(opwfile);
377 if (!strcmp(stmpent->sp_namp, forwho)) {
378 stmpent->sp_pwdp = towhat;
379 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
381 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
384 if (putspent(stmpent, pwfile)) {
385 D(("error writing entry to shadow file: %m"));
390 stmpent = fgetspent(opwfile);
394 if (fclose(pwfile)) {
395 D(("error writing entries to shadow file: %m"));
401 if (rename(SH_TMPFILE, "/etc/shadow"))
406 if (SELINUX_ENABLED) {
407 if (setfscreatecon(prev_context)) {
411 freecon(prev_context);
420 return PAM_AUTHTOK_ERR;
424 int main(int argc, char *argv[])
426 char pass[MAXPASS + 1];
429 int force_failure = 0;
430 int retval = PAM_AUTH_ERR;
434 * Catch or ignore as many signal as possible.
439 * we establish that this program is running with non-tty stdin.
440 * this is to discourage casual use. It does *NOT* prevent an
441 * intruder from repeatadly running this program to determine the
442 * password of the current user (brute force attack, but one for
443 * which the attacker must already have gained access to the user's
447 if (isatty(STDIN_FILENO) || argc != 3 ) {
449 ,"inappropriate use of Unix helper binary [UID=%d]"
452 ,"This binary is not designed for running in this way\n"
453 "-- the system administrator has been informed\n");
454 sleep(10); /* this should discourage/annoy the user */
455 return PAM_SYSTEM_ERR;
459 * Determine what the current user's name is.
460 * On a SELinux enabled system with a strict policy leaving the
461 * existing check prevents shadow password authentication from working.
462 * We must thus skip the check if the real uid is 0.
464 if (SELINUX_ENABLED && getuid() == 0) {
468 user = getuidname(getuid());
469 /* if the caller specifies the username, verify that user
471 if (strcmp(user, argv[1])) {
478 if (strncmp(argv[2], "verify", 8) == 0) {
479 /* Get the account information from the shadow file */
480 return _verify_account(argv[1]);
483 if (strncmp(option, "shadow", 8) == 0) {
484 /* Attempting to change the password */
485 return _update_shadow(argv[1]);
488 /* read the nullok/nonull option */
489 if (strncmp(option, "nullok", 8) == 0)
494 /* read the password from stdin (a pipe from the pam_unix module) */
496 npass = read(STDIN_FILENO, pass, MAXPASS);
498 if (npass < 0) { /* is it a valid password? */
500 _log_err(LOG_DEBUG, "no password supplied");
502 } else if (npass >= MAXPASS) {
504 _log_err(LOG_DEBUG, "password too long");
508 /* the password is NULL */
510 retval = _unix_verify_password(user, NULL, nullok);
513 /* does pass agree with the official one? */
515 pass[npass] = '\0'; /* NUL terminate */
516 retval = _unix_verify_password(user, pass, nullok);
521 memset(pass, '\0', MAXPASS); /* clear memory of the password */
523 /* return pass or fail */
525 if ((retval != PAM_SUCCESS) || force_failure) {
526 _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
534 * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
536 * Redistribution and use in source and binary forms, with or without
537 * modification, are permitted provided that the following conditions
539 * 1. Redistributions of source code must retain the above copyright
540 * notice, and the entire permission notice in its entirety,
541 * including the disclaimer of warranties.
542 * 2. Redistributions in binary form must reproduce the above copyright
543 * notice, this list of conditions and the following disclaimer in the
544 * documentation and/or other materials provided with the distribution.
545 * 3. The name of the author may not be used to endorse or promote
546 * products derived from this software without specific prior
547 * written permission.
549 * ALTERNATIVELY, this product may be distributed under the terms of
550 * the GNU Public License, in which case the provisions of the GPL are
551 * required INSTEAD OF the above restrictions. (This clause is
552 * necessary due to a potential bad interaction between the GPL and
553 * the restrictions contained in a BSD-style copyright.)
555 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
556 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
557 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
558 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
559 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
560 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
561 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
562 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
563 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
564 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
565 * OF THE POSSIBILITY OF SUCH DAMAGE.