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>
42 #include "passverify.h"
44 /* syslogging function for errors and other information */
46 static void _log_err(int err, const char *format,...)
50 va_start(args, format);
51 openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
52 vsyslog(err, format, args);
57 static void su_sighandler(int sig)
60 /* emulate the behaviour of the SA_RESETHAND flag */
61 if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
65 _log_err(LOG_NOTICE, "caught signal %d.", sig);
70 static void setup_signals(void)
72 struct sigaction action; /* posix signal structure */
75 * Setup signal handlers
77 (void) memset((void *) &action, 0, sizeof(action));
78 action.sa_handler = su_sighandler;
80 action.sa_flags = SA_RESETHAND;
82 (void) sigaction(SIGILL, &action, NULL);
83 (void) sigaction(SIGTRAP, &action, NULL);
84 (void) sigaction(SIGBUS, &action, NULL);
85 (void) sigaction(SIGSEGV, &action, NULL);
86 action.sa_handler = SIG_IGN;
88 (void) sigaction(SIGTERM, &action, NULL);
89 (void) sigaction(SIGHUP, &action, NULL);
90 (void) sigaction(SIGINT, &action, NULL);
91 (void) sigaction(SIGQUIT, &action, NULL);
94 static int _verify_account(const char * const uname)
99 pwent = getpwnam(uname);
101 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
102 return PAM_USER_UNKNOWN;
105 spent = getspnam( uname );
107 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
108 return PAM_AUTHINFO_UNAVAIL; /* Couldn't get username from shadow */
110 printf("%ld:%ld:%ld:%ld:%ld:%ld",
111 spent->sp_lstchg, /* last password change */
112 spent->sp_min, /* days until change allowed. */
113 spent->sp_max, /* days before change required */
114 spent->sp_warn, /* days warning for expiration */
115 spent->sp_inact, /* days before account inactive */
116 spent->sp_expire); /* date when account expires */
121 static int _unix_verify_password(const char *name, const char *p, int nullok)
123 struct passwd *pwd = NULL;
124 struct spwd *spwdent = NULL;
126 int retval = PAM_AUTH_ERR;
128 /* UNIX passwords area */
130 pwd = getpwnam(name); /* Get password file entry... */
133 if (_unix_shadowed(pwd)) {
135 * ...and shadow password file entry for this user,
136 * if shadowing is enabled
139 spwdent = getspnam(name);
142 salt = x_strdup(spwdent->sp_pwdp);
146 if (strcmp(pwd->pw_passwd, "*NP*") == 0) { /* NIS+ */
149 save_uid = geteuid();
150 seteuid(pwd->pw_uid);
151 spwdent = getspnam(name);
154 salt = x_strdup(spwdent->sp_pwdp);
156 salt = x_strdup(pwd->pw_passwd);
160 if (pwd == NULL || salt == NULL) {
161 _log_err(LOG_WARNING, "check pass; user unknown");
162 retval = PAM_USER_UNKNOWN;
164 retval = verify_pwd_hash(p, salt, nullok);
168 _pam_overwrite(salt);
172 p = NULL; /* no longer needed here */
177 static char *getuidname(uid_t uid)
180 static char username[32];
186 strncpy(username, pw->pw_name, sizeof(username));
187 username[sizeof(username) - 1] = '\0';
192 #define SH_TMPFILE "/etc/nshadow"
193 static int _update_shadow(const char *forwho)
195 struct spwd *spwdent = NULL, *stmpent = NULL;
196 FILE *pwfile, *opwfile;
200 char pass[MAXPASS + 1];
201 char towhat[MAXPASS + 1];
204 /* read the password from stdin (a pipe from the pam_unix module) */
206 npass = read(STDIN_FILENO, pass, MAXPASS);
208 if (npass < 0) { /* is it a valid password? */
210 _log_err(LOG_DEBUG, "no password supplied");
211 return PAM_AUTHTOK_ERR;
213 } else if (npass >= MAXPASS) {
215 _log_err(LOG_DEBUG, "password too long");
216 return PAM_AUTHTOK_ERR;
219 /* does pass agree with the official one? */
221 pass[npass] = '\0'; /* NUL terminate */
222 retval = _unix_verify_password(forwho, pass, 0);
223 if (retval != PAM_SUCCESS) {
228 /* read the password from stdin (a pipe from the pam_unix module) */
230 npass = read(STDIN_FILENO, towhat, MAXPASS);
232 if (npass < 0) { /* is it a valid password? */
234 _log_err(LOG_DEBUG, "no new password supplied");
235 return PAM_AUTHTOK_ERR;
237 } else if (npass >= MAXPASS) {
239 _log_err(LOG_DEBUG, "new password too long");
240 return PAM_AUTHTOK_ERR;
244 towhat[npass] = '\0'; /* NUL terminate */
245 spwdent = getspnam(forwho);
246 if (spwdent == NULL) {
247 return PAM_USER_UNKNOWN;
249 oldmask = umask(077);
252 if (SELINUX_ENABLED) {
253 security_context_t shadow_context=NULL;
254 if (getfilecon("/etc/shadow",&shadow_context)<0) {
255 return PAM_AUTHTOK_ERR;
257 if (getfscreatecon(&prev_context)<0) {
258 freecon(shadow_context);
259 return PAM_AUTHTOK_ERR;
261 if (setfscreatecon(shadow_context)) {
262 freecon(shadow_context);
263 freecon(prev_context);
264 return PAM_AUTHTOK_ERR;
266 freecon(shadow_context);
269 pwfile = fopen(SH_TMPFILE, "w");
271 if (pwfile == NULL) {
276 opwfile = fopen("/etc/shadow", "r");
277 if (opwfile == NULL) {
283 if (fstat(fileno(opwfile), &st) == -1) {
290 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
296 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
303 stmpent = fgetspent(opwfile);
306 if (!strcmp(stmpent->sp_namp, forwho)) {
307 stmpent->sp_pwdp = towhat;
308 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
310 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
313 if (putspent(stmpent, pwfile)) {
314 D(("error writing entry to shadow file: %m"));
319 stmpent = fgetspent(opwfile);
323 if (fclose(pwfile)) {
324 D(("error writing entries to shadow file: %m"));
330 if (rename(SH_TMPFILE, "/etc/shadow"))
335 if (SELINUX_ENABLED) {
336 if (setfscreatecon(prev_context)) {
340 freecon(prev_context);
349 return PAM_AUTHTOK_ERR;
353 int main(int argc, char *argv[])
355 char pass[MAXPASS + 1];
358 int force_failure = 0;
359 int retval = PAM_AUTH_ERR;
363 * Catch or ignore as many signal as possible.
368 * we establish that this program is running with non-tty stdin.
369 * this is to discourage casual use. It does *NOT* prevent an
370 * intruder from repeatadly running this program to determine the
371 * password of the current user (brute force attack, but one for
372 * which the attacker must already have gained access to the user's
376 if (isatty(STDIN_FILENO) || argc != 3 ) {
378 ,"inappropriate use of Unix helper binary [UID=%d]"
381 ,"This binary is not designed for running in this way\n"
382 "-- the system administrator has been informed\n");
383 sleep(10); /* this should discourage/annoy the user */
384 return PAM_SYSTEM_ERR;
388 * Determine what the current user's name is.
389 * On a SELinux enabled system with a strict policy leaving the
390 * existing check prevents shadow password authentication from working.
391 * We must thus skip the check if the real uid is 0.
393 if (SELINUX_ENABLED && getuid() == 0) {
397 user = getuidname(getuid());
398 /* if the caller specifies the username, verify that user
400 if (strcmp(user, argv[1])) {
407 if (strncmp(argv[2], "verify", 8) == 0) {
408 /* Get the account information from the shadow file */
409 return _verify_account(argv[1]);
412 if (strncmp(option, "shadow", 8) == 0) {
413 /* Attempting to change the password */
414 return _update_shadow(argv[1]);
417 /* read the nullok/nonull option */
418 if (strncmp(option, "nullok", 8) == 0)
423 /* read the password from stdin (a pipe from the pam_unix module) */
425 npass = read(STDIN_FILENO, pass, MAXPASS);
427 if (npass < 0) { /* is it a valid password? */
429 _log_err(LOG_DEBUG, "no password supplied");
431 } else if (npass >= MAXPASS) {
433 _log_err(LOG_DEBUG, "password too long");
437 /* the password is NULL */
439 retval = _unix_verify_password(user, NULL, nullok);
442 /* does pass agree with the official one? */
444 pass[npass] = '\0'; /* NUL terminate */
445 retval = _unix_verify_password(user, pass, nullok);
450 memset(pass, '\0', MAXPASS); /* clear memory of the password */
452 /* return pass or fail */
454 if ((retval != PAM_SUCCESS) || force_failure) {
455 _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
463 * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
465 * Redistribution and use in source and binary forms, with or without
466 * modification, are permitted provided that the following conditions
468 * 1. Redistributions of source code must retain the above copyright
469 * notice, and the entire permission notice in its entirety,
470 * including the disclaimer of warranties.
471 * 2. Redistributions in binary form must reproduce the above copyright
472 * notice, this list of conditions and the following disclaimer in the
473 * documentation and/or other materials provided with the distribution.
474 * 3. The name of the author may not be used to endorse or promote
475 * products derived from this software without specific prior
476 * written permission.
478 * ALTERNATIVELY, this product may be distributed under the terms of
479 * the GNU Public License, in which case the provisions of the GPL are
480 * required INSTEAD OF the above restrictions. (This clause is
481 * necessary due to a potential bad interaction between the GPL and
482 * the restrictions contained in a BSD-style copyright.)
484 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
485 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
486 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
487 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
488 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
489 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
490 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
491 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
492 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
493 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
494 * OF THE POSSIBILITY OF SUCH DAMAGE.