]> 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         int 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                 return PAM_AUTHTOK_ERR;
193         }
194
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;
201                 } else {
202                         pp = Brokencrypt_md5(p, salt);
203                         if (strcmp(pp, salt) == 0)
204                                 retval = PAM_SUCCESS;
205                 }
206         } else if (*salt == '$') {
207                 /*
208                  * Ok, we don't know the crypt algorithm, but maybe
209                  * libcrypt nows about it? We should try it.
210                  */
211                 pp = x_strdup (crypt(p, salt));
212                 if (strcmp(pp, salt) == 0) {
213                         retval = PAM_SUCCESS;
214                 }
215         } else if ((*salt == '*') || (salt_len < 13)) {
216             retval = PAM_AUTH_ERR;
217         } else {
218                 pp = bigcrypt(p, salt);
219                 /*
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.
227                  */
228                 if (strncmp(pp, salt, salt_len) == 0) {
229                         retval = PAM_SUCCESS;
230                 }
231         }
232         p = NULL;               /* no longer needed here */
233
234         /* clean up */
235         {
236                 char *tp = pp;
237                 if (pp != NULL) {
238                         while (tp && *tp)
239                                 *tp++ = '\0';
240                         free(pp);
241                 }
242                 pp = tp = NULL;
243         }
244
245         return retval;
246 }
247
248 static char *getuidname(uid_t uid)
249 {
250         struct passwd *pw;
251         static char username[32];
252
253         pw = getpwuid(uid);
254         if (pw == NULL)
255                 return NULL;
256
257         strncpy(username, pw->pw_name, sizeof(username));
258         username[sizeof(username) - 1] = '\0';
259
260         return username;
261 }
262
263 #define SH_TMPFILE              "/etc/nshadow"
264 static int _update_shadow(const char *forwho)
265 {
266     struct spwd *spwdent = NULL, *stmpent = NULL;
267     FILE *pwfile, *opwfile;
268     int err = 1;
269     int oldmask;
270     struct stat st;
271     char pass[MAXPASS + 1];
272     char towhat[MAXPASS + 1];
273     int npass=0;
274
275     /* read the password from stdin (a pipe from the pam_unix module) */
276
277     npass = read(STDIN_FILENO, pass, MAXPASS);
278
279     if (npass < 0) {    /* is it a valid password? */
280
281       _log_err(LOG_DEBUG, "no password supplied");
282       return PAM_AUTHTOK_ERR;
283
284     } else if (npass >= MAXPASS) {
285
286       _log_err(LOG_DEBUG, "password too long");
287       return PAM_AUTHTOK_ERR;
288
289     } else {
290       /* does pass agree with the official one? */
291       int retval=0;
292       pass[npass] = '\0';       /* NUL terminate */
293       retval = _unix_verify_password(forwho, pass, 0);
294       if (retval != PAM_SUCCESS) {
295         return retval;
296       }
297     }
298
299     /* read the password from stdin (a pipe from the pam_unix module) */
300
301     npass = read(STDIN_FILENO, towhat, MAXPASS);
302
303     if (npass < 0) {    /* is it a valid password? */
304
305       _log_err(LOG_DEBUG, "no new password supplied");
306       return PAM_AUTHTOK_ERR;
307
308     } else if (npass >= MAXPASS) {
309
310       _log_err(LOG_DEBUG, "new password too long");
311       return PAM_AUTHTOK_ERR;
312
313     }
314
315     towhat[npass] = '\0';       /* NUL terminate */
316     spwdent = getspnam(forwho);
317     if (spwdent == NULL) {
318         return PAM_USER_UNKNOWN;
319     }
320     oldmask = umask(077);
321
322 #ifdef WITH_SELINUX
323     if (SELINUX_ENABLED) {
324       security_context_t shadow_context=NULL;
325       if (getfilecon("/etc/shadow",&shadow_context)<0) {
326         return PAM_AUTHTOK_ERR;
327       };
328       if (getfscreatecon(&prev_context)<0) {
329         freecon(shadow_context);
330         return PAM_AUTHTOK_ERR;
331       }
332       if (setfscreatecon(shadow_context)) {
333         freecon(shadow_context);
334         freecon(prev_context);
335         return PAM_AUTHTOK_ERR;
336       }
337       freecon(shadow_context);
338     }
339 #endif
340     pwfile = fopen(SH_TMPFILE, "w");
341     umask(oldmask);
342     if (pwfile == NULL) {
343         err = 1;
344         goto done;
345     }
346
347     opwfile = fopen("/etc/shadow", "r");
348     if (opwfile == NULL) {
349         fclose(pwfile);
350         err = 1;
351         goto done;
352     }
353
354     if (fstat(fileno(opwfile), &st) == -1) {
355         fclose(opwfile);
356         fclose(pwfile);
357         err = 1;
358         goto done;
359     }
360
361     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
362         fclose(opwfile);
363         fclose(pwfile);
364         err = 1;
365         goto done;
366     }
367     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
368         fclose(opwfile);
369         fclose(pwfile);
370         err = 1;
371         goto done;
372     }
373
374     stmpent = fgetspent(opwfile);
375     while (stmpent) {
376
377         if (!strcmp(stmpent->sp_namp, forwho)) {
378             stmpent->sp_pwdp = towhat;
379             stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
380             err = 0;
381             D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
382         }
383
384         if (putspent(stmpent, pwfile)) {
385             D(("error writing entry to shadow file: %m"));
386             err = 1;
387             break;
388         }
389
390         stmpent = fgetspent(opwfile);
391     }
392     fclose(opwfile);
393
394     if (fclose(pwfile)) {
395         D(("error writing entries to shadow file: %m"));
396         err = 1;
397     }
398
399  done:
400     if (!err) {
401         if (rename(SH_TMPFILE, "/etc/shadow"))
402             err = 1;
403     }
404
405 #ifdef WITH_SELINUX
406     if (SELINUX_ENABLED) {
407       if (setfscreatecon(prev_context)) {
408         err = 1;
409       }
410       if (prev_context)
411         freecon(prev_context);
412       prev_context=NULL;
413     }
414 #endif
415
416     if (!err) {
417         return PAM_SUCCESS;
418     } else {
419         unlink(SH_TMPFILE);
420         return PAM_AUTHTOK_ERR;
421     }
422 }
423
424 int main(int argc, char *argv[])
425 {
426         char pass[MAXPASS + 1];
427         char *option;
428         int npass, nullok;
429         int force_failure = 0;
430         int retval = PAM_AUTH_ERR;
431         char *user;
432
433         /*
434          * Catch or ignore as many signal as possible.
435          */
436         setup_signals();
437
438         /*
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
444          * account).
445          */
446
447         if (isatty(STDIN_FILENO) || argc != 3 ) {
448                 _log_err(LOG_NOTICE
449                       ,"inappropriate use of Unix helper binary [UID=%d]"
450                          ,getuid());
451                 fprintf(stderr
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;
456         }
457
458         /*
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.
463          */
464         if (SELINUX_ENABLED && getuid() == 0) {
465           user=argv[1];
466         }
467         else {
468           user = getuidname(getuid());
469           /* if the caller specifies the username, verify that user
470              matches it */
471           if (strcmp(user, argv[1])) {
472             return PAM_AUTH_ERR;
473           }
474         }
475
476         option=argv[2];
477
478         if (strncmp(argv[2], "verify", 8) == 0) {
479           /* Get the account information from the shadow file */
480           return _verify_account(argv[1]);
481         }
482
483         if (strncmp(option, "shadow", 8) == 0) {
484           /* Attempting to change the password */
485           return _update_shadow(argv[1]);
486         }
487
488         /* read the nullok/nonull option */
489         if (strncmp(option, "nullok", 8) == 0)
490           nullok = 1;
491         else
492           nullok = 0;
493
494         /* read the password from stdin (a pipe from the pam_unix module) */
495
496         npass = read(STDIN_FILENO, pass, MAXPASS);
497
498         if (npass < 0) {        /* is it a valid password? */
499
500                 _log_err(LOG_DEBUG, "no password supplied");
501
502         } else if (npass >= MAXPASS) {
503
504                 _log_err(LOG_DEBUG, "password too long");
505
506         } else {
507                 if (npass == 0) {
508                         /* the password is NULL */
509
510                         retval = _unix_verify_password(user, NULL, nullok);
511
512                 } else {
513                         /* does pass agree with the official one? */
514
515                         pass[npass] = '\0';     /* NUL terminate */
516                         retval = _unix_verify_password(user, pass, nullok);
517
518                 }
519         }
520
521         memset(pass, '\0', MAXPASS);    /* clear memory of the password */
522
523         /* return pass or fail */
524
525         if ((retval != PAM_SUCCESS) || force_failure) {
526             _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
527             return PAM_AUTH_ERR;
528         } else {
529             return PAM_SUCCESS;
530         }
531 }
532
533 /*
534  * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
535  *
536  * Redistribution and use in source and binary forms, with or without
537  * modification, are permitted provided that the following conditions
538  * are met:
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.
548  *
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.)
554  *
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.
566  */