xref: /dragonfly/lib/libpam/modules/pam_unix/pam_unix.c (revision c98db40744766ab0803912f29557df02814bcd9d)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright 1998 Juniper Networks, Inc.
5  * All rights reserved.
6  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
7  * All rights reserved.
8  *
9  * Portions of this software was developed for the FreeBSD Project by
10  * ThinkSec AS and NAI Labs, the Security Research Division of Network
11  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
12  * ("CBOSS"), as part of the DARPA CHATS research program.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. The name of the author may not be used to endorse or promote
23  *    products derived from this software without specific prior written
24  *    permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  *
38  * $FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 326219 2017-11-26 02:00:33Z pfg $
39  */
40 
41 #include <sys/param.h>
42 #include <sys/socket.h>
43 #include <sys/time.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 
47 #include <login_cap.h>
48 #include <netdb.h>
49 #include <pwd.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <stdio.h>
53 #include <syslog.h>
54 #include <time.h>
55 #include <unistd.h>
56 
57 #include <libutil.h>
58 
59 #ifdef YP
60 #include <ypclnt.h>
61 #endif
62 
63 #define PAM_SM_AUTH
64 #define PAM_SM_ACCOUNT
65 #define   PAM_SM_PASSWORD
66 
67 #include <security/pam_appl.h>
68 #include <security/pam_modules.h>
69 #include <security/pam_mod_misc.h>
70 
71 #define PASSWORD_HASH                   "sha512"
72 #define DEFAULT_WARN                    (2L * 7L * 86400L)  /* Two weeks */
73 #define   SALTSIZE            32
74 
75 #define   LOCKED_PREFIX                 "*LOCKED*"
76 #define   LOCKED_PREFIX_LEN   (sizeof(LOCKED_PREFIX) - 1)
77 
78 static void makesalt(char []);
79 
80 static char password_hash[] =           PASSWORD_HASH;
81 
82 #define PAM_OPT_LOCAL_PASS    "local_pass"
83 #define PAM_OPT_NIS_PASS      "nis_pass"
84 
85 /*
86  * authentication management
87  */
88 PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)89 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
90     int argc __unused, const char *argv[] __unused)
91 {
92           login_cap_t *lc;
93           struct passwd *pwd;
94           int retval;
95           const char *pass, *user, *realpw, *prompt;
96           char *cryptpw;
97 
98           if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
99                     user = getlogin();
100           } else {
101                     retval = pam_get_user(pamh, &user, NULL);
102                     if (retval != PAM_SUCCESS)
103                               return (retval);
104           }
105           pwd = getpwnam(user);
106 
107           PAM_LOG("Got user: %s", user);
108 
109           if (pwd != NULL) {
110                     PAM_LOG("Doing real authentication");
111                     realpw = pwd->pw_passwd;
112                     if (realpw[0] == '\0') {
113                               if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
114                                   openpam_get_option(pamh, PAM_OPT_NULLOK))
115                                         return (PAM_SUCCESS);
116                               PAM_LOG("Password is empty, using fake password");
117                               realpw = "*";
118                     }
119                     lc = login_getpwclass(pwd);
120           } else {
121                     PAM_LOG("Doing dummy authentication");
122                     realpw = "*";
123                     lc = login_getclass(NULL);
124           }
125           prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL);
126           retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt);
127           login_close(lc);
128           if (retval != PAM_SUCCESS)
129                     return (retval);
130           PAM_LOG("Got password");
131           if (strnlen(pass, _PASSWORD_LEN + 1) > _PASSWORD_LEN) {
132                     PAM_LOG("Password is too long, using fake password");
133                     realpw = "*";
134           }
135           cryptpw = crypt(pass, realpw);
136           if (cryptpw != NULL && strcmp(cryptpw, realpw) == 0)
137                     return (PAM_SUCCESS);
138 
139           PAM_VERBOSE_ERROR("UNIX authentication refused");
140           return (PAM_AUTH_ERR);
141 }
142 
143 PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh __unused,int flags __unused,int argc __unused,const char * argv[]__unused)144 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
145     int argc __unused, const char *argv[] __unused)
146 {
147 
148           return (PAM_SUCCESS);
149 }
150 
151 /*
152  * account management
153  */
154 PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)155 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
156     int argc __unused, const char *argv[] __unused)
157 {
158           struct addrinfo hints, *res;
159           struct passwd *pwd;
160           struct timeval tp;
161           login_cap_t *lc;
162           time_t warntime;
163           int retval;
164           const char *user;
165           const void *rhost, *tty;
166           char rhostip[MAXHOSTNAMELEN] = "";
167 
168           retval = pam_get_user(pamh, &user, NULL);
169           if (retval != PAM_SUCCESS)
170                     return (retval);
171 
172           if (user == NULL || (pwd = getpwnam(user)) == NULL)
173                     return (PAM_SERVICE_ERR);
174 
175           PAM_LOG("Got user: %s", user);
176 
177           retval = pam_get_item(pamh, PAM_RHOST, &rhost);
178           if (retval != PAM_SUCCESS)
179                     return (retval);
180 
181           retval = pam_get_item(pamh, PAM_TTY, &tty);
182           if (retval != PAM_SUCCESS)
183                     return (retval);
184 
185           if (*pwd->pw_passwd == '\0' &&
186               (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
187                     return (PAM_NEW_AUTHTOK_REQD);
188 
189           if (strncmp(pwd->pw_passwd, LOCKED_PREFIX, LOCKED_PREFIX_LEN) == 0)
190                     return (PAM_AUTH_ERR);
191 
192           lc = login_getpwclass(pwd);
193           if (lc == NULL) {
194                     PAM_LOG("Unable to get login class for user %s", user);
195                     return (PAM_SERVICE_ERR);
196           }
197 
198           PAM_LOG("Got login_cap");
199 
200           if (pwd->pw_change || pwd->pw_expire)
201                     gettimeofday(&tp, NULL);
202 
203           /*
204            * Check pw_expire before pw_change - no point in letting the
205            * user change the password on an expired account.
206            */
207 
208           if (pwd->pw_expire) {
209                     warntime = login_getcaptime(lc, "warnexpire",
210                         DEFAULT_WARN, DEFAULT_WARN);
211                     if (tp.tv_sec >= pwd->pw_expire) {
212                               login_close(lc);
213                               return (PAM_ACCT_EXPIRED);
214                     } else if (pwd->pw_expire - tp.tv_sec < warntime &&
215                         (flags & PAM_SILENT) == 0) {
216                               pam_error(pamh, "Warning: your account expires on %s",
217                                   ctime(&pwd->pw_expire));
218                     }
219           }
220 
221           retval = PAM_SUCCESS;
222           if (pwd->pw_change) {
223                     warntime = login_getcaptime(lc, "warnpassword",
224                         DEFAULT_WARN, DEFAULT_WARN);
225                     if (tp.tv_sec >= pwd->pw_change) {
226                               retval = PAM_NEW_AUTHTOK_REQD;
227                     } else if (pwd->pw_change - tp.tv_sec < warntime &&
228                         (flags & PAM_SILENT) == 0) {
229                               pam_error(pamh, "Warning: your password expires on %s",
230                                   ctime(&pwd->pw_change));
231                     }
232           }
233 
234           /*
235            * From here on, we must leave retval untouched (unless we
236            * know we're going to fail), because we need to remember
237            * whether we're supposed to return PAM_SUCCESS or
238            * PAM_NEW_AUTHTOK_REQD.
239            */
240 
241           if (rhost && *(const char *)rhost != '\0') {
242                     memset(&hints, 0, sizeof(hints));
243                     hints.ai_family = AF_UNSPEC;
244                     if (getaddrinfo(rhost, NULL, &hints, &res) == 0) {
245                               getnameinfo(res->ai_addr, res->ai_addrlen,
246                                   rhostip, sizeof(rhostip), NULL, 0,
247                                   NI_NUMERICHOST);
248                     }
249                     if (res != NULL)
250                               freeaddrinfo(res);
251           }
252 
253           /*
254            * Check host / tty / time-of-day restrictions
255            */
256 
257           if (!auth_hostok(lc, rhost, rhostip) ||
258               !auth_ttyok(lc, tty) ||
259               !auth_timeok(lc, time(NULL)))
260                     retval = PAM_AUTH_ERR;
261 
262           login_close(lc);
263 
264           return (retval);
265 }
266 
267 /*
268  * password management
269  *
270  * standard Unix and NIS password changing
271  */
272 PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc __unused,const char * argv[]__unused)273 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
274     int argc __unused, const char *argv[] __unused)
275 {
276 #ifdef YP
277           struct ypclnt *ypclnt;
278           const void *yp_domain, *yp_server;
279 #endif
280           char salt[SALTSIZE + 1];
281           login_cap_t *lc;
282           struct passwd *pwd, *old_pwd;
283           const char *user, *old_pass, *new_pass;
284           char *encrypted;
285           time_t passwordtime;
286           int pfd, tfd, retval;
287 
288           if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
289                     user = getlogin();
290           else {
291                     retval = pam_get_user(pamh, &user, NULL);
292                     if (retval != PAM_SUCCESS)
293                               return (retval);
294           }
295           pwd = getpwnam(user);
296 
297           if (pwd == NULL)
298                     return (PAM_AUTHTOK_RECOVERY_ERR);
299 
300           PAM_LOG("Got user: %s", user);
301 
302           if (flags & PAM_PRELIM_CHECK) {
303 
304                     PAM_LOG("PRELIM round");
305 
306                     if (getuid() == 0 &&
307                         (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES)
308                               /* root doesn't need the old password */
309                               return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
310 #ifdef YP
311                     if (getuid() == 0 &&
312                         (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
313 
314                               yp_domain = yp_server = NULL;
315                               pam_get_data(pamh, "yp_domain", &yp_domain);
316                               pam_get_data(pamh, "yp_server", &yp_server);
317 
318                               ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server);
319                               if (ypclnt == NULL)
320                                         return (PAM_BUF_ERR);
321 
322                               if (ypclnt_connect(ypclnt) == -1) {
323                                         ypclnt_free(ypclnt);
324                                         return (PAM_SERVICE_ERR);
325                               }
326 
327                               retval = ypclnt_havepasswdd(ypclnt);
328                               ypclnt_free(ypclnt);
329                               if (retval == 1)
330                                         return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
331                               else if (retval == -1)
332                                         return (PAM_SERVICE_ERR);
333                     }
334 #endif
335                     if (pwd->pw_passwd[0] == '\0'
336                         && openpam_get_option(pamh, PAM_OPT_NULLOK)) {
337                               /*
338                                * No password case. XXX Are we giving too much away
339                                * by not prompting for a password?
340                                * XXX check PAM_DISALLOW_NULL_AUTHTOK
341                                */
342                               old_pass = "";
343                               retval = PAM_SUCCESS;
344                     } else {
345                               retval = pam_get_authtok(pamh,
346                                   PAM_OLDAUTHTOK, &old_pass, NULL);
347                               if (retval != PAM_SUCCESS)
348                                         return (retval);
349                     }
350                     PAM_LOG("Got old password");
351                     /* always encrypt first */
352                     encrypted = crypt(old_pass, pwd->pw_passwd);
353                     if (old_pass[0] == '\0' &&
354                         !openpam_get_option(pamh, PAM_OPT_NULLOK))
355                               return (PAM_PERM_DENIED);
356                     if (encrypted == NULL || strcmp(encrypted, pwd->pw_passwd) != 0)
357                               return (PAM_PERM_DENIED);
358           }
359           else if (flags & PAM_UPDATE_AUTHTOK) {
360                     PAM_LOG("UPDATE round");
361 
362                     retval = pam_get_authtok(pamh,
363                         PAM_OLDAUTHTOK, &old_pass, NULL);
364                     if (retval != PAM_SUCCESS)
365                               return (retval);
366                     PAM_LOG("Got old password");
367 
368                     /* get new password */
369                     for (;;) {
370                               retval = pam_get_authtok(pamh,
371                                   PAM_AUTHTOK, &new_pass, NULL);
372                               if (retval != PAM_TRY_AGAIN)
373                                         break;
374                               pam_error(pamh, "Mismatch; try again, EOF to quit.");
375                     }
376                     PAM_LOG("Got new password");
377                     if (retval != PAM_SUCCESS) {
378                               PAM_VERBOSE_ERROR("Unable to get new password");
379                               return (retval);
380                     }
381 
382                     if (getuid() != 0 && new_pass[0] == '\0' &&
383                         !openpam_get_option(pamh, PAM_OPT_NULLOK))
384                               return (PAM_PERM_DENIED);
385 
386                     if ((old_pwd = pw_dup(pwd)) == NULL)
387                               return (PAM_BUF_ERR);
388 
389                     lc = login_getclass(pwd->pw_class);
390                     if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
391                               openpam_log(PAM_LOG_ERROR,
392                                   "can't set password cipher, relying on default");
393 
394                     /* set password expiry date */
395                     pwd->pw_change = 0;
396                     passwordtime = login_getcaptime(lc, "passwordtime", 0, 0);
397                     if (passwordtime > 0)
398                               pwd->pw_change = time(NULL) + passwordtime;
399 
400                     login_close(lc);
401                     makesalt(salt);
402                     pwd->pw_passwd = crypt(new_pass, salt);
403 #ifdef YP
404                     switch (old_pwd->pw_fields & _PWF_SOURCE) {
405                     case _PWF_FILES:
406 #endif
407                               retval = PAM_SERVICE_ERR;
408                               if (pw_init(NULL, NULL))
409                                         openpam_log(PAM_LOG_ERROR, "pw_init() failed");
410                               else if ((pfd = pw_lock()) == -1)
411                                         openpam_log(PAM_LOG_ERROR, "pw_lock() failed");
412                               else if ((tfd = pw_tmp(-1)) == -1)
413                                         openpam_log(PAM_LOG_ERROR, "pw_tmp() failed");
414                               else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1)
415                                         openpam_log(PAM_LOG_ERROR, "pw_copy() failed");
416                               else if (pw_mkdb(pwd->pw_name) == -1)
417                                         openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed");
418                               else
419                                         retval = PAM_SUCCESS;
420                               pw_fini();
421 #ifdef YP
422                               break;
423                     case _PWF_NIS:
424                               yp_domain = yp_server = NULL;
425                               pam_get_data(pamh, "yp_domain", &yp_domain);
426                               pam_get_data(pamh, "yp_server", &yp_server);
427                               ypclnt = ypclnt_new(yp_domain,
428                                   "passwd.byname", yp_server);
429                               if (ypclnt == NULL) {
430                                         retval = PAM_BUF_ERR;
431                               } else if (ypclnt_connect(ypclnt) == -1 ||
432                                   ypclnt_passwd(ypclnt, pwd, old_pass) == -1) {
433                                         openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error);
434                                         retval = PAM_SERVICE_ERR;
435                               } else {
436                                         retval = PAM_SUCCESS;
437                               }
438                               ypclnt_free(ypclnt);
439                               break;
440                     default:
441                               openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x",
442                                   pwd->pw_fields & _PWF_SOURCE);
443                               retval = PAM_SERVICE_ERR;
444                     }
445 #endif
446                     free(old_pwd);
447           }
448           else {
449                     /* Very bad juju */
450                     retval = PAM_ABORT;
451                     PAM_LOG("Illegal 'flags'");
452           }
453 
454           return (retval);
455 }
456 
457 /* Mostly stolen from passwd(1)'s local_passwd.c - markm */
458 
459 static unsigned char itoa64[] =                   /* 0 ... 63 => ascii - 64 */
460           "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
461 
462 static void
to64(char * s,long v,int n)463 to64(char *s, long v, int n)
464 {
465           while (--n >= 0) {
466                     *s++ = itoa64[v&0x3f];
467                     v >>= 6;
468           }
469 }
470 
471 /* Salt suitable for traditional DES and MD5 */
472 static void
makesalt(char salt[SALTSIZE+1])473 makesalt(char salt[SALTSIZE + 1])
474 {
475           int i;
476 
477           /* These are not really random numbers, they are just
478            * numbers that change to thwart construction of a
479            * dictionary.
480            */
481           for (i = 0; i < SALTSIZE; i += 4)
482                     to64(&salt[i], arc4random(), 4);
483           salt[SALTSIZE] = '\0';
484 }
485 
486 PAM_MODULE_ENTRY("pam_unix");
487