xref: /netbsd-src/external/bsd/ppp/dist/pppd/session.c (revision 8e33eff89e26cf71871ead62f0d5063e1313c33a)
1 /*	$NetBSD: session.c,v 1.5 2021/01/09 16:39:28 christos Exp $	*/
2 
3 /*
4  * session.c - PPP session control.
5  *
6  * Copyright (c) 2007 Diego Rivera. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. The name(s) of the authors of this software must not be used to
16  *    endorse or promote products derived from this software without
17  *    prior written permission.
18  *
19  * 3. Redistributions of any form whatsoever must retain the following
20  *    acknowledgment:
21  *    "This product includes software developed by Paul Mackerras
22  *     <paulus@samba.org>".
23  *
24  * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
25  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
26  * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
27  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
28  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
29  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
30  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
31  *
32  * Derived from auth.c, which is:
33  *
34  * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  *
40  * 1. Redistributions of source code must retain the above copyright
41  *    notice, this list of conditions and the following disclaimer.
42  *
43  * 2. Redistributions in binary form must reproduce the above copyright
44  *    notice, this list of conditions and the following disclaimer in
45  *    the documentation and/or other materials provided with the
46  *    distribution.
47  *
48  * 3. The name "Carnegie Mellon University" must not be used to
49  *    endorse or promote products derived from this software without
50  *    prior written permission. For permission or any legal
51  *    details, please contact
52  *      Office of Technology Transfer
53  *      Carnegie Mellon University
54  *      5000 Forbes Avenue
55  *      Pittsburgh, PA  15213-3890
56  *      (412) 268-4387, fax: (412) 268-7395
57  *      tech-transfer@andrew.cmu.edu
58  *
59  * 4. Redistributions of any form whatsoever must retain the following
60  *    acknowledgment:
61  *    "This product includes software developed by Computing Services
62  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
63  *
64  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
65  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
66  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
67  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
68  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
69  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
70  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
71  */
72 
73 #include <sys/cdefs.h>
74 __RCSID("$NetBSD: session.c,v 1.5 2021/01/09 16:39:28 christos Exp $");
75 
76 
77 #include <stdio.h>
78 #include <stdlib.h>
79 #include <string.h>
80 #include <pwd.h>
81 #ifdef HAS_SHADOW
82 #include <shadow.h>
83 #endif
84 #include <time.h>
85 #ifdef SUPPORT_UTMP
86 #include <utmp.h>
87 #endif
88 #ifdef SUPPORT_UTMPX
89 #include <utmpx.h>
90 #endif
91 #include <util.h>
92 #include <fcntl.h>
93 #include <unistd.h>
94 #include "pppd.h"
95 #include "session.h"
96 
97 #ifdef USE_PAM
98 #include <security/pam_appl.h>
99 #endif /* #ifdef USE_PAM */
100 
101 #define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; }
102 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
103 
104 #define SUCCESS_MSG "Session started successfully"
105 #define ABORT_MSG "Session can't be started without a username"
106 #define SERVICE_NAME "ppp"
107 
108 #define SESSION_FAILED  0
109 #define SESSION_OK      1
110 
111 /* We have successfully started a session */
112 static bool logged_in = 0;
113 
114 #ifdef USE_PAM
115 /*
116  * Static variables used to communicate between the conversation function
117  * and the server_login function
118  */
119 static const char *PAM_username;
120 static const char *PAM_password;
121 static int   PAM_session = 0;
122 static pam_handle_t *pamh = NULL;
123 
124 /* PAM conversation function
125  * Here we assume (for now, at least) that echo on means login name, and
126  * echo off means password.
127  */
128 
129 static int conversation (int num_msg,
130     const struct pam_message **msg,
131     struct pam_response **resp, void *appdata_ptr)
132 {
133     int replies = 0;
134     struct pam_response *reply = NULL;
135 
136     reply = malloc(sizeof(struct pam_response) * num_msg);
137     if (!reply) return PAM_CONV_ERR;
138 
139     for (replies = 0; replies < num_msg; replies++) {
140         switch (msg[replies]->msg_style) {
141             case PAM_PROMPT_ECHO_ON:
142                 reply[replies].resp_retcode = PAM_SUCCESS;
143                 reply[replies].resp = COPY_STRING(PAM_username);
144                 /* PAM frees resp */
145                 break;
146             case PAM_PROMPT_ECHO_OFF:
147                 reply[replies].resp_retcode = PAM_SUCCESS;
148                 reply[replies].resp = COPY_STRING(PAM_password);
149                 /* PAM frees resp */
150                 break;
151             case PAM_TEXT_INFO:
152                 /* fall through */
153             case PAM_ERROR_MSG:
154                 /* ignore it, but pam still wants a NULL response... */
155                 reply[replies].resp_retcode = PAM_SUCCESS;
156                 reply[replies].resp = NULL;
157                 break;
158             default:
159                 /* Must be an error of some sort... */
160                 free (reply);
161                 return PAM_CONV_ERR;
162         }
163     }
164     *resp = reply;
165     return PAM_SUCCESS;
166 }
167 
168 static struct pam_conv pam_conv_data = {
169     &conversation,
170     NULL
171 };
172 #endif /* #ifdef USE_PAM */
173 
174 int
175 session_start(const int flags, const char *user, const char *passwd, const char *ttyName, char **msg)
176 {
177 #ifdef USE_PAM
178     bool ok = 1;
179     const char *usr;
180     int pam_error;
181     bool try_session = 0;
182 #else /* #ifdef USE_PAM */
183     struct passwd *pw;
184     char *cbuf;
185 #ifdef HAS_SHADOW
186     struct spwd *spwd;
187     struct spwd *getspnam();
188     long now = 0;
189 #endif /* #ifdef HAS_SHADOW */
190 #endif /* #ifdef USE_PAM */
191 
192     SET_MSG(msg, SUCCESS_MSG);
193 
194     /* If no verification is requested, then simply return an OK */
195     if (!(SESS_ALL & flags)) {
196         return SESSION_OK;
197     }
198 
199     if (user == NULL) {
200        SET_MSG(msg, ABORT_MSG);
201        return SESSION_FAILED;
202     }
203 
204 #ifdef USE_PAM
205     /* Find the '\\' in the username */
206     /* This needs to be fixed to support different username schemes */
207     if ((usr = strchr(user, '\\')) == NULL)
208 	usr = user;
209     else
210 	usr++;
211 
212     PAM_session = 0;
213     PAM_username = usr;
214     PAM_password = passwd;
215 
216     dbglog("Initializing PAM (%d) for user %s", flags, usr);
217     pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh);
218     dbglog("---> PAM INIT Result = %d", pam_error);
219     ok = (pam_error == PAM_SUCCESS);
220 
221     if (ok) {
222         ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) &&
223 	    (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS);
224     }
225 
226     if (ok && (SESS_AUTH & flags)) {
227         dbglog("Attempting PAM authentication");
228         pam_error = pam_authenticate (pamh, PAM_SILENT);
229         if (pam_error == PAM_SUCCESS) {
230             /* PAM auth was OK */
231             dbglog("PAM Authentication OK for %s", user);
232         } else {
233             /* No matter the reason, we fail because we're authenticating */
234             ok = 0;
235             if (pam_error == PAM_USER_UNKNOWN) {
236                 dbglog("User unknown, failing PAM authentication");
237                 SET_MSG(msg, "User unknown - cannot authenticate via PAM");
238             } else {
239                 /* Any other error means authentication was bad */
240                 dbglog("PAM Authentication failed: %d: %s", pam_error,
241 		       pam_strerror(pamh, pam_error));
242                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
243             }
244         }
245     }
246 
247     if (ok && (SESS_ACCT & flags)) {
248         dbglog("Attempting PAM account checks");
249         pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
250         if (pam_error == PAM_SUCCESS) {
251             /*
252 	     * PAM account was OK, set the flag which indicates that we should
253 	     * try to perform the session checks.
254 	     */
255             try_session = 1;
256             dbglog("PAM Account OK for %s", user);
257         } else {
258             /*
259 	     * If the account checks fail, then we should not try to perform
260 	     * the session check, because they don't make sense.
261 	     */
262             try_session = 0;
263             if (pam_error == PAM_USER_UNKNOWN) {
264                 /*
265 		 * We're checking the account, so it's ok to not have one
266 		 * because the user might come from the secrets files, or some
267 		 * other plugin.
268 		 */
269                 dbglog("User unknown, ignoring PAM restrictions");
270                 SET_MSG(msg, "User unknown - ignoring PAM restrictions");
271             } else {
272                 /* Any other error means session is rejected */
273                 ok = 0;
274                 dbglog("PAM Account checks failed: %d: %s", pam_error,
275 		       pam_strerror(pamh, pam_error));
276                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
277             }
278         }
279     }
280 
281     if (ok && try_session && (SESS_ACCT & flags)) {
282         /* Only open a session if the user's account was found */
283         pam_error = pam_open_session (pamh, PAM_SILENT);
284         if (pam_error == PAM_SUCCESS) {
285             dbglog("PAM Session opened for user %s", user);
286             PAM_session = 1;
287         } else {
288             dbglog("PAM Session denied for user %s", user);
289             SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
290             ok = 0;
291         }
292     }
293 
294     /* This is needed because apparently the PAM stuff closes the log */
295     reopen_log();
296 
297     /* If our PAM checks have already failed, then we must return a failure */
298     if (!ok) return SESSION_FAILED;
299 
300 #else /* #ifdef USE_PAM */
301 
302 /*
303  * Use the non-PAM methods directly.  'pw' will remain NULL if the user
304  * has not been authenticated using local UNIX system services.
305  */
306 
307     pw = NULL;
308     if ((SESS_AUTH & flags)) {
309 	pw = getpwnam(user);
310 
311 	endpwent();
312 	/*
313 	 * Here, we bail if we have no user account, because there is nothing
314 	 * to verify against.
315 	 */
316 	if (pw == NULL)
317 	    return SESSION_FAILED;
318 
319 #ifdef HAS_SHADOW
320 
321 	spwd = getspnam(user);
322 	endspent();
323 
324 	/*
325 	 * If there is no shadow entry for the user, then we can't verify the
326 	 * account.
327 	 */
328 	if (spwd == NULL)
329 	    return SESSION_FAILED;
330 
331 	/*
332 	 * We check validity all the time, because if the password has expired,
333 	 * then clearly we should not authenticate against it (if we're being
334 	 * called for authentication only).  Thus, in this particular instance,
335 	 * there is no real difference between using the AUTH, SESS or ACCT
336 	 * flags, or combinations thereof.
337 	 */
338 	now = time(NULL) / 86400L;
339 	if ((spwd->sp_expire > 0 && now >= spwd->sp_expire)
340 	    || ((spwd->sp_max >= 0 && spwd->sp_max < 10000)
341 	    && spwd->sp_lstchg >= 0
342 	    && now >= spwd->sp_lstchg + spwd->sp_max)) {
343 	    warn("Password for %s has expired", user);
344 	    return SESSION_FAILED;
345 	}
346 
347 	/* We have a valid shadow entry, keep the password */
348 	pw->pw_passwd = spwd->sp_pwdp;
349 
350 #endif /* #ifdef HAS_SHADOW */
351 
352 	/*
353 	 * If no passwd, don't let them login if we're authenticating.
354 	 */
355         if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2)
356             return SESSION_FAILED;
357 	cbuf = crypt(passwd, pw->pw_passwd);
358 	if (!cbuf || strcmp(cbuf, pw->pw_passwd) != 0)
359             return SESSION_FAILED;
360     }
361 
362 #endif /* #ifdef USE_PAM */
363 
364     /*
365      * Write a wtmp entry for this user.
366      */
367 
368     if (SESS_ACCT & flags) {
369 	if (strncmp(ttyName, "/dev/", 5) == 0)
370 	    ttyName += 5;
371 #ifdef SUPPORT_UTMP
372 	logwtmp(ttyName, user, ifname);		/* Add wtmp login entry */
373 #endif
374 #ifdef SUPPORT_UTMPX
375 	logwtmpx(ttyName, user, ifname, 0, USER_PROCESS);	/* Add wtmpx login entry */
376 #endif
377 
378 	logged_in = 1;
379 
380 #if defined(_PATH_LASTLOG) && !defined(USE_PAM)
381 	/*
382 	 * Enter the user in lastlog only if he has been authenticated using
383 	 * local system services.  If he has not, then we don't know what his
384 	 * UID might be, and lastlog is indexed by UID.
385 	 */
386 	if (pw != NULL) {
387             struct lastlog ll;
388             int fd;
389 	    time_t tnow;
390 
391             if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) {
392                 (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET);
393                 memset((void *)&ll, 0, sizeof(ll));
394 		(void)time(&tnow);
395                 ll.ll_time = tnow;
396                 strlcpy(ll.ll_line, ttyName, sizeof(ll.ll_line));
397                 strlcpy(ll.ll_host, ifname, sizeof(ll.ll_host));
398                 (void)write(fd, (char *)&ll, sizeof(ll));
399                 (void)close(fd);
400             }
401 	}
402 #endif /* _PATH_LASTLOG and not USE_PAM */
403 	info("user %s logged in on tty %s intf %s", user, ttyName, ifname);
404     }
405 
406     return SESSION_OK;
407 }
408 
409 /*
410  * session_end - Logout the user.
411  */
412 void
413 session_end(const char* ttyName)
414 {
415 #ifdef USE_PAM
416     int pam_error = PAM_SUCCESS;
417 
418     if (pamh != NULL) {
419         if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT);
420         PAM_session = 0;
421         pam_end (pamh, pam_error);
422         pamh = NULL;
423 	/* Apparently the pam stuff does closelog(). */
424 	reopen_log();
425     }
426 #endif
427     if (logged_in) {
428 	if (strncmp(ttyName, "/dev/", 5) == 0)
429 	    ttyName += 5;
430 #ifdef SUPPORT_UTMP
431 	logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */
432 #endif
433 #ifdef SUPPORT_UTMPX
434 	logwtmpx(ttyName, "", "", 0, DEAD_PROCESS); /* Wipe out utmpx logout entry */
435 #endif
436 	logged_in = 0;
437     }
438 }
439