]> 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 #include "bigcrypt.h"
44
45 /* syslogging function for errors and other information */
46
47 static void _log_err(int err, const char *format,...)
48 {
49         va_list args;
50
51         va_start(args, format);
52         openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
53         vsyslog(err, format, args);
54         va_end(args);
55         closelog();
56 }
57
58 static int _unix_shadowed(const struct passwd *pwd)
59 {
60         char hashpass[1024];
61         if (pwd != NULL) {
62                 if (strcmp(pwd->pw_passwd, "x") == 0) {
63                         return 1;
64                 }
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) {
69                                 return 1;
70                         }
71                 }
72         }
73         return 0;
74 }
75
76 static void su_sighandler(int sig)
77 {
78 #ifndef SA_RESETHAND
79         /* emulate the behaviour of the SA_RESETHAND flag */
80         if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
81                 signal(sig, SIG_DFL);
82 #endif
83         if (sig > 0) {
84                 _log_err(LOG_NOTICE, "caught signal %d.", sig);
85                 exit(sig);
86         }
87 }
88
89 static void setup_signals(void)
90 {
91         struct sigaction action;        /* posix signal structure */
92
93         /*
94          * Setup signal handlers
95          */
96         (void) memset((void *) &action, 0, sizeof(action));
97         action.sa_handler = su_sighandler;
98 #ifdef SA_RESETHAND
99         action.sa_flags = SA_RESETHAND;
100 #endif
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;
106         action.sa_flags = 0;
107         (void) sigaction(SIGTERM, &action, NULL);
108         (void) sigaction(SIGHUP, &action, NULL);
109         (void) sigaction(SIGINT, &action, NULL);
110         (void) sigaction(SIGQUIT, &action, NULL);
111 }
112
113 static int _verify_account(const char * const uname)
114 {
115         struct spwd *spent;
116         struct passwd *pwent;
117
118         pwent = getpwnam(uname);
119         if (!pwent) {
120                 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
121                 return PAM_USER_UNKNOWN;
122         }
123
124         spent = getspnam( uname );
125         if (!spent) {
126                 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
127                 return PAM_AUTHINFO_UNAVAIL;    /* Couldn't get username from shadow */
128         }
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 */
136
137         return PAM_SUCCESS;
138 }
139
140 static int _unix_verify_password(const char *name, const char *p, int nullok)
141 {
142         struct passwd *pwd = NULL;
143         struct spwd *spwdent = NULL;
144         char *salt = NULL;
145         char *pp = NULL;
146         int retval = PAM_AUTH_ERR;
147         size_t salt_len;
148
149         /* UNIX passwords area */
150         setpwent();
151         pwd = getpwnam(name);   /* Get password file entry... */
152         endpwent();
153         if (pwd != NULL) {
154                 if (_unix_shadowed(pwd)) {
155                         /*
156                          * ...and shadow password file entry for this user,
157                          * if shadowing is enabled
158                          */
159                         setspent();
160                         spwdent = getspnam(name);
161                         endspent();
162                         if (spwdent != NULL)
163                                 salt = x_strdup(spwdent->sp_pwdp);
164                         else
165                                 pwd = NULL;
166                 } else {
167                         if (strcmp(pwd->pw_passwd, "*NP*") == 0) {      /* NIS+ */
168                                 uid_t save_uid;
169
170                                 save_uid = geteuid();
171                                 seteuid(pwd->pw_uid);
172                                 spwdent = getspnam(name);
173                                 seteuid(save_uid);
174
175                                 salt = x_strdup(spwdent->sp_pwdp);
176                         } else {
177                                 salt = x_strdup(pwd->pw_passwd);
178                         }
179                 }
180         }
181         if (pwd == NULL || salt == NULL) {
182                 _log_err(LOG_ALERT, "check pass; user unknown");
183                 p = NULL;
184                 return PAM_USER_UNKNOWN;
185         }
186
187         salt_len = strlen(salt);
188         if (salt_len == 0) {
189                 return (nullok == 0) ? PAM_AUTH_ERR : PAM_SUCCESS;
190         }
191         if (p == NULL || strlen(p) == 0) {
192                 _pam_overwrite(salt);
193                 _pam_drop(salt);
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 (pp && strcmp(pp, salt) == 0) {
202                         retval = PAM_SUCCESS;
203                 } else {
204                         _pam_overwrite(pp);
205                         _pam_drop(pp);
206                         pp = Brokencrypt_md5(p, salt);
207                         if (pp && strcmp(pp, salt) == 0)
208                                 retval = PAM_SUCCESS;
209                 }
210         } else if (*salt == '$') {
211                 /*
212                  * Ok, we don't know the crypt algorithm, but maybe
213                  * libcrypt nows about it? We should try it.
214                  */
215                 pp = x_strdup (crypt(p, salt));
216                 if (pp && strcmp(pp, salt) == 0) {
217                         retval = PAM_SUCCESS;
218                 }
219         } else if (*salt == '*' || *salt == '!' || salt_len < 13) {
220             retval = PAM_AUTH_ERR;
221         } else {
222                 pp = bigcrypt(p, salt);
223                 /*
224                  * Note, we are comparing the bigcrypt of the password with
225                  * the contents of the password field. If the latter was
226                  * encrypted with regular crypt (and not bigcrypt) it will
227                  * have been truncated for storage relative to the output
228                  * of bigcrypt here. As such we need to compare only the
229                  * stored string with the subset of bigcrypt's result.
230                  * Bug 521314.
231                  */
232                 if (salt_len == 13 && strlen(pp) > salt_len) {
233                     _pam_overwrite(pp+salt_len);
234                 }
235                 
236                 if (strcmp(pp, salt) == 0) {
237                         retval = PAM_SUCCESS;
238                 }
239         }
240         p = NULL;               /* no longer needed here */
241
242         /* clean up */
243         _pam_overwrite(pp);
244         _pam_drop(pp);
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: %m"));
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: %m"));
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 what the current user's name is.
461          * On a SELinux enabled system with a strict policy leaving the
462          * existing check prevents shadow password authentication from working.
463          * We must thus skip the check if the real uid is 0.
464          */
465         if (SELINUX_ENABLED && getuid() == 0) {
466           user=argv[1];
467         }
468         else {
469           user = getuidname(getuid());
470           /* if the caller specifies the username, verify that user
471              matches it */
472           if (strcmp(user, argv[1])) {
473             return PAM_AUTH_ERR;
474           }
475         }
476
477         option=argv[2];
478
479         if (strncmp(argv[2], "verify", 8) == 0) {
480           /* Get the account information from the shadow file */
481           return _verify_account(argv[1]);
482         }
483
484         if (strncmp(option, "shadow", 8) == 0) {
485           /* Attempting to change the password */
486           return _update_shadow(argv[1]);
487         }
488
489         /* read the nullok/nonull option */
490         if (strncmp(option, "nullok", 8) == 0)
491           nullok = 1;
492         else
493           nullok = 0;
494
495         /* read the password from stdin (a pipe from the pam_unix module) */
496
497         npass = read(STDIN_FILENO, pass, MAXPASS);
498
499         if (npass < 0) {        /* is it a valid password? */
500
501                 _log_err(LOG_DEBUG, "no password supplied");
502
503         } else if (npass >= MAXPASS) {
504
505                 _log_err(LOG_DEBUG, "password too long");
506
507         } else {
508                 if (npass == 0) {
509                         /* the password is NULL */
510
511                         retval = _unix_verify_password(user, NULL, nullok);
512
513                 } else {
514                         /* does pass agree with the official one? */
515
516                         pass[npass] = '\0';     /* NUL terminate */
517                         retval = _unix_verify_password(user, pass, nullok);
518
519                 }
520         }
521
522         memset(pass, '\0', MAXPASS);    /* clear memory of the password */
523
524         /* return pass or fail */
525
526         if ((retval != PAM_SUCCESS) || force_failure) {
527             _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
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  */