]> 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 "passverify.h"
43
44 /* syslogging function for errors and other information */
45
46 static void _log_err(int err, const char *format,...)
47 {
48         va_list args;
49
50         va_start(args, format);
51         openlog("unix_chkpwd", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
52         vsyslog(err, format, args);
53         va_end(args);
54         closelog();
55 }
56
57 static void su_sighandler(int sig)
58 {
59 #ifndef SA_RESETHAND
60         /* emulate the behaviour of the SA_RESETHAND flag */
61         if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV )
62                 signal(sig, SIG_DFL);
63 #endif
64         if (sig > 0) {
65                 _log_err(LOG_NOTICE, "caught signal %d.", sig);
66                 exit(sig);
67         }
68 }
69
70 static void setup_signals(void)
71 {
72         struct sigaction action;        /* posix signal structure */
73
74         /*
75          * Setup signal handlers
76          */
77         (void) memset((void *) &action, 0, sizeof(action));
78         action.sa_handler = su_sighandler;
79 #ifdef SA_RESETHAND
80         action.sa_flags = SA_RESETHAND;
81 #endif
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;
87         action.sa_flags = 0;
88         (void) sigaction(SIGTERM, &action, NULL);
89         (void) sigaction(SIGHUP, &action, NULL);
90         (void) sigaction(SIGINT, &action, NULL);
91         (void) sigaction(SIGQUIT, &action, NULL);
92 }
93
94 static int _verify_account(const char * const uname)
95 {
96         struct spwd *spent;
97         struct passwd *pwent;
98
99         pwent = getpwnam(uname);
100         if (!pwent) {
101                 _log_err(LOG_ALERT, "could not identify user (from getpwnam(%s))", uname);
102                 return PAM_USER_UNKNOWN;
103         }
104
105         spent = getspnam( uname );
106         if (!spent) {
107                 _log_err(LOG_ALERT, "could not get username from shadow (%s))", uname);
108                 return PAM_AUTHINFO_UNAVAIL;    /* Couldn't get username from shadow */
109         }
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 */
117
118         return PAM_SUCCESS;
119 }
120
121 static int _unix_verify_password(const char *name, const char *p, int nullok)
122 {
123         struct passwd *pwd = NULL;
124         struct spwd *spwdent = NULL;
125         char *salt = NULL;
126         int retval = PAM_AUTH_ERR;
127
128         /* UNIX passwords area */
129         setpwent();
130         pwd = getpwnam(name);   /* Get password file entry... */
131         endpwent();
132         if (pwd != NULL) {
133                 if (_unix_shadowed(pwd)) {
134                         /*
135                          * ...and shadow password file entry for this user,
136                          * if shadowing is enabled
137                          */
138                         setspent();
139                         spwdent = getspnam(name);
140                         endspent();
141                         if (spwdent != NULL)
142                                 salt = x_strdup(spwdent->sp_pwdp);
143                         else
144                                 pwd = NULL;
145                 } else {
146                         if (strcmp(pwd->pw_passwd, "*NP*") == 0) {      /* NIS+ */
147                                 uid_t save_uid;
148
149                                 save_uid = geteuid();
150                                 seteuid(pwd->pw_uid);
151                                 spwdent = getspnam(name);
152                                 seteuid(save_uid);
153
154                                 salt = x_strdup(spwdent->sp_pwdp);
155                         } else {
156                                 salt = x_strdup(pwd->pw_passwd);
157                         }
158                 }
159         }
160         if (pwd == NULL || salt == NULL) {
161                 _log_err(LOG_WARNING, "check pass; user unknown");
162                 retval = PAM_USER_UNKNOWN;
163         } else {
164                 retval = verify_pwd_hash(p, salt, nullok);
165         }
166
167         if (salt) {
168                 _pam_overwrite(salt);
169                 _pam_drop(salt);
170         }
171
172         p = NULL;               /* no longer needed here */
173
174         return retval;
175 }
176
177 static char *getuidname(uid_t uid)
178 {
179         struct passwd *pw;
180         static char username[32];
181
182         pw = getpwuid(uid);
183         if (pw == NULL)
184                 return NULL;
185
186         strncpy(username, pw->pw_name, sizeof(username));
187         username[sizeof(username) - 1] = '\0';
188
189         return username;
190 }
191
192 #define SH_TMPFILE              "/etc/nshadow"
193 static int _update_shadow(const char *forwho)
194 {
195     struct spwd *spwdent = NULL, *stmpent = NULL;
196     FILE *pwfile, *opwfile;
197     int err = 1;
198     int oldmask;
199     struct stat st;
200     char pass[MAXPASS + 1];
201     char towhat[MAXPASS + 1];
202     int npass=0;
203
204     /* read the password from stdin (a pipe from the pam_unix module) */
205
206     npass = read(STDIN_FILENO, pass, MAXPASS);
207
208     if (npass < 0) {    /* is it a valid password? */
209
210       _log_err(LOG_DEBUG, "no password supplied");
211       return PAM_AUTHTOK_ERR;
212
213     } else if (npass >= MAXPASS) {
214
215       _log_err(LOG_DEBUG, "password too long");
216       return PAM_AUTHTOK_ERR;
217
218     } else {
219       /* does pass agree with the official one? */
220       int retval=0;
221       pass[npass] = '\0';       /* NUL terminate */
222       retval = _unix_verify_password(forwho, pass, 0);
223       if (retval != PAM_SUCCESS) {
224         return retval;
225       }
226     }
227
228     /* read the password from stdin (a pipe from the pam_unix module) */
229
230     npass = read(STDIN_FILENO, towhat, MAXPASS);
231
232     if (npass < 0) {    /* is it a valid password? */
233
234       _log_err(LOG_DEBUG, "no new password supplied");
235       return PAM_AUTHTOK_ERR;
236
237     } else if (npass >= MAXPASS) {
238
239       _log_err(LOG_DEBUG, "new password too long");
240       return PAM_AUTHTOK_ERR;
241
242     }
243
244     towhat[npass] = '\0';       /* NUL terminate */
245     spwdent = getspnam(forwho);
246     if (spwdent == NULL) {
247         return PAM_USER_UNKNOWN;
248     }
249     oldmask = umask(077);
250
251 #ifdef WITH_SELINUX
252     if (SELINUX_ENABLED) {
253       security_context_t shadow_context=NULL;
254       if (getfilecon("/etc/shadow",&shadow_context)<0) {
255         return PAM_AUTHTOK_ERR;
256       };
257       if (getfscreatecon(&prev_context)<0) {
258         freecon(shadow_context);
259         return PAM_AUTHTOK_ERR;
260       }
261       if (setfscreatecon(shadow_context)) {
262         freecon(shadow_context);
263         freecon(prev_context);
264         return PAM_AUTHTOK_ERR;
265       }
266       freecon(shadow_context);
267     }
268 #endif
269     pwfile = fopen(SH_TMPFILE, "w");
270     umask(oldmask);
271     if (pwfile == NULL) {
272         err = 1;
273         goto done;
274     }
275
276     opwfile = fopen("/etc/shadow", "r");
277     if (opwfile == NULL) {
278         fclose(pwfile);
279         err = 1;
280         goto done;
281     }
282
283     if (fstat(fileno(opwfile), &st) == -1) {
284         fclose(opwfile);
285         fclose(pwfile);
286         err = 1;
287         goto done;
288     }
289
290     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
291         fclose(opwfile);
292         fclose(pwfile);
293         err = 1;
294         goto done;
295     }
296     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
297         fclose(opwfile);
298         fclose(pwfile);
299         err = 1;
300         goto done;
301     }
302
303     stmpent = fgetspent(opwfile);
304     while (stmpent) {
305
306         if (!strcmp(stmpent->sp_namp, forwho)) {
307             stmpent->sp_pwdp = towhat;
308             stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
309             err = 0;
310             D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
311         }
312
313         if (putspent(stmpent, pwfile)) {
314             D(("error writing entry to shadow file: %m"));
315             err = 1;
316             break;
317         }
318
319         stmpent = fgetspent(opwfile);
320     }
321     fclose(opwfile);
322
323     if (fclose(pwfile)) {
324         D(("error writing entries to shadow file: %m"));
325         err = 1;
326     }
327
328  done:
329     if (!err) {
330         if (rename(SH_TMPFILE, "/etc/shadow"))
331             err = 1;
332     }
333
334 #ifdef WITH_SELINUX
335     if (SELINUX_ENABLED) {
336       if (setfscreatecon(prev_context)) {
337         err = 1;
338       }
339       if (prev_context)
340         freecon(prev_context);
341       prev_context=NULL;
342     }
343 #endif
344
345     if (!err) {
346         return PAM_SUCCESS;
347     } else {
348         unlink(SH_TMPFILE);
349         return PAM_AUTHTOK_ERR;
350     }
351 }
352
353 int main(int argc, char *argv[])
354 {
355         char pass[MAXPASS + 1];
356         char *option;
357         int npass, nullok;
358         int force_failure = 0;
359         int retval = PAM_AUTH_ERR;
360         char *user;
361
362         /*
363          * Catch or ignore as many signal as possible.
364          */
365         setup_signals();
366
367         /*
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
373          * account).
374          */
375
376         if (isatty(STDIN_FILENO) || argc != 3 ) {
377                 _log_err(LOG_NOTICE
378                       ,"inappropriate use of Unix helper binary [UID=%d]"
379                          ,getuid());
380                 fprintf(stderr
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;
385         }
386
387         /*
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.
392          */
393         if (SELINUX_ENABLED && getuid() == 0) {
394           user=argv[1];
395         }
396         else {
397           user = getuidname(getuid());
398           /* if the caller specifies the username, verify that user
399              matches it */
400           if (strcmp(user, argv[1])) {
401             return PAM_AUTH_ERR;
402           }
403         }
404
405         option=argv[2];
406
407         if (strncmp(argv[2], "verify", 8) == 0) {
408           /* Get the account information from the shadow file */
409           return _verify_account(argv[1]);
410         }
411
412         if (strncmp(option, "shadow", 8) == 0) {
413           /* Attempting to change the password */
414           return _update_shadow(argv[1]);
415         }
416
417         /* read the nullok/nonull option */
418         if (strncmp(option, "nullok", 8) == 0)
419           nullok = 1;
420         else
421           nullok = 0;
422
423         /* read the password from stdin (a pipe from the pam_unix module) */
424
425         npass = read(STDIN_FILENO, pass, MAXPASS);
426
427         if (npass < 0) {        /* is it a valid password? */
428
429                 _log_err(LOG_DEBUG, "no password supplied");
430
431         } else if (npass >= MAXPASS) {
432
433                 _log_err(LOG_DEBUG, "password too long");
434
435         } else {
436                 if (npass == 0) {
437                         /* the password is NULL */
438
439                         retval = _unix_verify_password(user, NULL, nullok);
440
441                 } else {
442                         /* does pass agree with the official one? */
443
444                         pass[npass] = '\0';     /* NUL terminate */
445                         retval = _unix_verify_password(user, pass, nullok);
446
447                 }
448         }
449
450         memset(pass, '\0', MAXPASS);    /* clear memory of the password */
451
452         /* return pass or fail */
453
454         if ((retval != PAM_SUCCESS) || force_failure) {
455             _log_err(LOG_NOTICE, "password check failed for user (%s)", user);
456             return PAM_AUTH_ERR;
457         } else {
458             return PAM_SUCCESS;
459         }
460 }
461
462 /*
463  * Copyright (c) Andrew G. Morgan, 1996. All rights reserved
464  *
465  * Redistribution and use in source and binary forms, with or without
466  * modification, are permitted provided that the following conditions
467  * are met:
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.
477  *
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.)
483  *
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.
495  */