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