]> granicus.if.org Git - linux-pam/blob - modules/pam_unix/unix_chkpwd.c
Relevant BUGIDs: none
[linux-pam] / modules / pam_unix / unix_chkpwd.c
1 /*
2  * $Id$
3  *
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.
8  *
9  * The password is read from the standard input. The exit status of
10  * this program indicates whether the user is authenticated or not.
11  *
12  * Copyright information is located at the end of the file.
13  *
14  */
15
16 #include "config.h"
17
18 #ifdef MEMORY_DEBUG
19 # undef exit
20 # undef strdup
21 # undef free
22 #endif /* MEMORY_DEBUG */
23
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <unistd.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <pwd.h>
33 #include <shadow.h>
34 #include <signal.h>
35 #include <time.h>
36 #ifdef WITH_SELINUX
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;
41 #else
42 #define SELINUX_ENABLED 0
43 #endif
44
45 #define MAXPASS         200     /* the maximum length of a password */
46
47 #include <security/_pam_types.h>
48 #include <security/_pam_macros.h>
49
50 #include "md5.h"
51
52 extern char *crypt(const char *key, const char *salt);
53 extern char *bigcrypt(const char *key, const char *salt);
54
55 /* syslogging function for errors and other information */
56
57 static void _log_err(int err, const char *format,...)
58 {
59         va_list args;
60
61         va_start(args, format);
62         openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH);
63         vsyslog(err, format, args);
64         va_end(args);
65         closelog();
66 }
67
68 static int _unix_shadowed(const struct passwd *pwd)
69 {
70         char hashpass[1024];
71         if (pwd != NULL) {
72                 if (strcmp(pwd->pw_passwd, "x") == 0) {
73                         return 1;
74                 }
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) {
79                                 return 1;
80                         }
81                 }
82         }
83         return 0;
84 }
85
86 static void su_sighandler(int sig)
87 {
88 #ifndef SA_RESETHAND
89         /* emulate the behaviour of the SA_RESETHAND flag */
90         if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
91                 signal(sig, SIG_DFL);
92 #endif
93         if (sig > 0) {
94                 _log_err(LOG_NOTICE, "caught signal %d.", sig);
95                 exit(sig);
96         }
97 }
98
99 static void setup_signals(void)
100 {
101         struct sigaction action;        /* posix signal structure */
102
103         /*
104          * Setup signal handlers
105          */
106         (void) memset((void *) &action, 0, sizeof(action));
107         action.sa_handler = su_sighandler;
108 #ifdef SA_RESETHAND
109         action.sa_flags = SA_RESETHAND;
110 #endif
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;
116         action.sa_flags = 0;
117         (void) sigaction(SIGTERM, &action, NULL);
118         (void) sigaction(SIGHUP, &action, NULL);
119         (void) sigaction(SIGINT, &action, NULL);
120         (void) sigaction(SIGQUIT, &action, NULL);
121 }
122
123 static int _verify_account(const char * const uname)
124 {
125         struct spwd *spent;
126         struct passwd *pwent;
127
128         pwent = getpwnam(uname);
129         if (!pwent) {
130                 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
131                 return PAM_USER_UNKNOWN;
132         }
133
134         spent = getspnam( uname );
135         if (!spent) {
136                 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
137                 return PAM_AUTHINFO_UNAVAIL;    /* Couldn't get username from shadow */
138         }
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 */
146
147         return PAM_SUCCESS;
148 }
149
150 static int _unix_verify_password(const char *name, const char *p, int nullok)
151 {
152         struct passwd *pwd = NULL;
153         struct spwd *spwdent = NULL;
154         char *salt = NULL;
155         char *pp = NULL;
156         int retval = PAM_AUTH_ERR;
157         int salt_len;
158
159         /* UNIX passwords area */
160         setpwent();
161         pwd = getpwnam(name);   /* Get password file entry... */
162         endpwent();
163         if (pwd != NULL) {
164                 if (_unix_shadowed(pwd)) {
165                         /*
166                          * ...and shadow password file entry for this user,
167                          * if shadowing is enabled
168                          */
169                         setspent();
170                         spwdent = getspnam(name);
171                         endspent();
172                         if (spwdent != NULL)
173                                 salt = x_strdup(spwdent->sp_pwdp);
174                         else
175                                 pwd = NULL;
176                 } else {
177                         if (strcmp(pwd->pw_passwd, "*NP*") == 0) {      /* NIS+ */
178                                 uid_t save_uid;
179
180                                 save_uid = geteuid();
181                                 seteuid(pwd->pw_uid);
182                                 spwdent = getspnam(name);
183                                 seteuid(save_uid);
184
185                                 salt = x_strdup(spwdent->sp_pwdp);
186                         } else {
187                                 salt = x_strdup(pwd->pw_passwd);
188                         }
189                 }
190         }
191         if (pwd == NULL || salt == NULL) {
192                 _log_err(LOG_ALERT, "check pass; user unknown");
193                 p = NULL;
194                 return PAM_USER_UNKNOWN;
195         }
196
197         salt_len = strlen(salt);
198         if (salt_len == 0) {
199                 return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
200         }
201         if (p == NULL || strlen(p) == 0) {
202                 return PAM_AUTHTOK_ERR;
203         }
204
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;
211                 } else {
212                         pp = Brokencrypt_md5(p, salt);
213                         if (strcmp(pp, salt) == 0)
214                                 retval = PAM_SUCCESS;
215                 }
216         } else if ((*salt == '*') || (salt_len < 13)) {
217             retval = PAM_AUTH_ERR;
218         } else {
219                 pp = bigcrypt(p, salt);
220                 /*
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.
228                  */
229                 if (strncmp(pp, salt, salt_len) == 0) {
230                         retval = PAM_SUCCESS;
231                 }
232         }
233         p = NULL;               /* no longer needed here */
234
235         /* clean up */
236         {
237                 char *tp = pp;
238                 if (pp != NULL) {
239                         while (tp && *tp)
240                                 *tp++ = '\0';
241                         free(pp);
242                 }
243                 pp = tp = NULL;
244         }
245
246         return retval;
247 }
248
249 static char *getuidname(uid_t uid)
250 {
251         struct passwd *pw;
252         static char username[32];
253
254         pw = getpwuid(uid);
255         if (pw == NULL)
256                 return NULL;
257
258         strncpy(username, pw->pw_name, sizeof(username));
259         username[sizeof(username) - 1] = '\0';
260
261         return username;
262 }
263
264 #define SH_TMPFILE              "/etc/nshadow"
265 static int _update_shadow(const char *forwho)
266 {
267     struct spwd *spwdent = NULL, *stmpent = NULL;
268     FILE *pwfile, *opwfile;
269     int err = 1;
270     int oldmask;
271     struct stat st;
272     char pass[MAXPASS + 1];
273     char towhat[MAXPASS + 1];
274     int npass=0;
275
276     /* read the password from stdin (a pipe from the pam_unix module) */
277
278     npass = read(STDIN_FILENO, pass, MAXPASS);
279
280     if (npass < 0) {    /* is it a valid password? */
281
282       _log_err(LOG_DEBUG, "no password supplied");
283       return PAM_AUTHTOK_ERR;
284
285     } else if (npass >= MAXPASS) {
286
287       _log_err(LOG_DEBUG, "password too long");
288       return PAM_AUTHTOK_ERR;
289
290     } else {
291       /* does pass agree with the official one? */
292       int retval=0;
293       pass[npass] = '\0';       /* NUL terminate */
294       retval = _unix_verify_password(forwho, pass, 0);
295       if (retval != PAM_SUCCESS) {
296         return retval;
297       }
298     }
299
300     /* read the password from stdin (a pipe from the pam_unix module) */
301
302     npass = read(STDIN_FILENO, towhat, MAXPASS);
303
304     if (npass < 0) {    /* is it a valid password? */
305
306       _log_err(LOG_DEBUG, "no new password supplied");
307       return PAM_AUTHTOK_ERR;
308
309     } else if (npass >= MAXPASS) {
310
311       _log_err(LOG_DEBUG, "new password too long");
312       return PAM_AUTHTOK_ERR;
313
314     }
315
316     towhat[npass] = '\0';       /* NUL terminate */
317     spwdent = getspnam(forwho);
318     if (spwdent == NULL) {
319         return PAM_USER_UNKNOWN;
320     }
321     oldmask = umask(077);
322
323 #ifdef WITH_SELINUX
324     if (SELINUX_ENABLED) {
325       security_context_t shadow_context=NULL;
326       if (getfilecon("/etc/shadow",&shadow_context)<0) {
327         return PAM_AUTHTOK_ERR;
328       };
329       if (getfscreatecon(&prev_context)<0) {
330         freecon(shadow_context);
331         return PAM_AUTHTOK_ERR;
332       }
333       if (setfscreatecon(shadow_context)) {
334         freecon(shadow_context);
335         freecon(prev_context);
336         return PAM_AUTHTOK_ERR;
337       }
338       freecon(shadow_context);
339     }
340 #endif
341     pwfile = fopen(SH_TMPFILE, "w");
342     umask(oldmask);
343     if (pwfile == NULL) {
344         err = 1;
345         goto done;
346     }
347
348     opwfile = fopen("/etc/shadow", "r");
349     if (opwfile == NULL) {
350         fclose(pwfile);
351         err = 1;
352         goto done;
353     }
354
355     if (fstat(fileno(opwfile), &st) == -1) {
356         fclose(opwfile);
357         fclose(pwfile);
358         err = 1;
359         goto done;
360     }
361
362     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
363         fclose(opwfile);
364         fclose(pwfile);
365         err = 1;
366         goto done;
367     }
368     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
369         fclose(opwfile);
370         fclose(pwfile);
371         err = 1;
372         goto done;
373     }
374
375     stmpent = fgetspent(opwfile);
376     while (stmpent) {
377
378         if (!strcmp(stmpent->sp_namp, forwho)) {
379             stmpent->sp_pwdp = towhat;
380             stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
381             err = 0;
382             D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
383         }
384
385         if (putspent(stmpent, pwfile)) {
386             D(("error writing entry to shadow file: %s\n", strerror(errno)));
387             err = 1;
388             break;
389         }
390
391         stmpent = fgetspent(opwfile);
392     }
393     fclose(opwfile);
394
395     if (fclose(pwfile)) {
396         D(("error writing entries to shadow file: %s\n", strerror(errno)));
397         err = 1;
398     }
399
400  done:
401     if (!err) {
402         if (rename(SH_TMPFILE, "/etc/shadow"))
403             err = 1;
404     }
405
406 #ifdef WITH_SELINUX
407     if (SELINUX_ENABLED) {
408       if (setfscreatecon(prev_context)) {
409         err = 1;
410       }
411       if (prev_context)
412         freecon(prev_context);
413       prev_context=NULL;
414     }
415 #endif
416
417     if (!err) {
418         return PAM_SUCCESS;
419     } else {
420         unlink(SH_TMPFILE);
421         return PAM_AUTHTOK_ERR;
422     }
423 }
424
425 int main(int argc, char *argv[])
426 {
427         char pass[MAXPASS + 1];
428         char *option;
429         int npass, nullok;
430         int force_failure = 0;
431         int retval = PAM_AUTH_ERR;
432         char *user;
433
434         /*
435          * Catch or ignore as many signal as possible.
436          */
437         setup_signals();
438
439         /*
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
445          * account).
446          */
447
448         if (isatty(STDIN_FILENO) || argc != 3 ) {
449                 _log_err(LOG_NOTICE
450                       ,"inappropriate use of Unix helper binary [UID=%d]"
451                          ,getuid());
452                 fprintf(stderr
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;
457         }
458
459         /*
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
464          * for root.
465          */
466         if (SELINUX_ENABLED) {
467           user=argv[1];
468         }
469         else {
470           user = getuidname(getuid());
471           /* if the caller specifies the username, verify that user
472              matches it */
473           if (strcmp(user, argv[1])) {
474             return PAM_AUTH_ERR;
475           }
476         }
477
478         option=argv[2];
479
480         if (strncmp(argv[2], "verify", 8) == 0) {
481           /* Get the account information from the shadow file */
482           return _verify_account(argv[1]);
483         }
484
485         if (strncmp(option, "shadow", 8) == 0) {
486           /* Attempting to change the password */
487           return _update_shadow(argv[1]);
488         }
489
490         /* read the nullok/nonull option */
491         if (strncmp(option, "nullok", 8) == 0)
492           nullok = 1;
493         else
494           nullok = 0;
495
496         /* read the password from stdin (a pipe from the pam_unix module) */
497
498         npass = read(STDIN_FILENO, pass, MAXPASS);
499
500         if (npass < 0) {        /* is it a valid password? */
501
502                 _log_err(LOG_DEBUG, "no password supplied");
503
504         } else if (npass >= MAXPASS) {
505
506                 _log_err(LOG_DEBUG, "password too long");
507
508         } else {
509                 if (npass == 0) {
510                         /* the password is NULL */
511
512                         retval = _unix_verify_password(user, NULL, nullok);
513
514                 } else {
515                         /* does pass agree with the official one? */
516
517                         pass[npass] = '\0';     /* NUL terminate */
518                         retval = _unix_verify_password(user, pass, nullok);
519
520                 }
521         }
522
523         memset(pass, '\0', MAXPASS);    /* clear memory of the password */
524
525         /* return pass or fail */
526
527         if ((retval != PAM_SUCCESS) || force_failure) {
528             return PAM_AUTH_ERR;
529         } else {
530             return PAM_SUCCESS;
531         }
532 }
533
534 /*
535  * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
536  *
537  * Redistribution and use in source and binary forms, with or without
538  * modification, are permitted provided that the following conditions
539  * are met:
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.
549  *
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.)
555  *
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.
567  */