xref: /netbsd-src/external/bsd/ppp/dist/pppd/session.c (revision c2f91946468898c9315cda70af93c338b101ecfa)
1 /*	$NetBSD: session.c,v 1.7 2025/01/09 18:27:46 martin 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@ozlabs.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.7 2025/01/09 18:27:46 martin Exp $");
75 
76 
77 #ifdef HAVE_CONFIG_H
78 #include "config.h"
79 #endif
80 
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <string.h>
84 #include <pwd.h>
85 
86 #ifdef HAVE_CRYPT_H
87 #include <crypt.h>
88 #endif
89 
90 #ifdef HAVE_SHADOW_H
91 #include <shadow.h>
92 #endif
93 
94 #include <time.h>
95 #ifdef SUPPORT_UTMP
96 #include <utmp.h>
97 #endif
98 #ifdef SUPPORT_UTMPX
99 #include <utmpx.h>
100 #endif
101 #include <util.h>
102 #include <fcntl.h>
103 #include <unistd.h>
104 #include "pppd-private.h"
105 #include "session.h"
106 
107 #ifdef PPP_WITH_PAM
108 #include <security/pam_appl.h>
109 #endif /* #ifdef PPP_WITH_PAM */
110 
111 #define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; }
112 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
113 
114 #define SUCCESS_MSG "Session started successfully"
115 #define ABORT_MSG "Session can't be started without a username"
116 #define SERVICE_NAME "ppp"
117 
118 #define SESSION_FAILED  0
119 #define SESSION_OK      1
120 
121 /* We have successfully started a session */
122 static bool logged_in = 0;
123 
124 #ifdef PPP_WITH_PAM
125 /*
126  * Static variables used to communicate between the conversation function
127  * and the server_login function
128  */
129 static const char *PAM_username;
130 static const char *PAM_password;
131 static int   PAM_session = 0;
132 static pam_handle_t *pamh = NULL;
133 
134 /* PAM conversation function
135  * Here we assume (for now, at least) that echo on means login name, and
136  * echo off means password.
137  */
138 
139 static int conversation (int num_msg,
140     const struct pam_message **msg,
141     struct pam_response **resp, void *appdata_ptr)
142 {
143     int replies = 0;
144     struct pam_response *reply = NULL;
145 
146     reply = malloc(sizeof(struct pam_response) * num_msg);
147     if (!reply) return PAM_CONV_ERR;
148 
149     for (replies = 0; replies < num_msg; replies++) {
150         switch (msg[replies]->msg_style) {
151             case PAM_PROMPT_ECHO_ON:
152                 reply[replies].resp_retcode = PAM_SUCCESS;
153                 reply[replies].resp = COPY_STRING(PAM_username);
154                 /* PAM frees resp */
155                 break;
156             case PAM_PROMPT_ECHO_OFF:
157                 reply[replies].resp_retcode = PAM_SUCCESS;
158                 reply[replies].resp = COPY_STRING(PAM_password);
159                 /* PAM frees resp */
160                 break;
161             case PAM_TEXT_INFO:
162                 /* fall through */
163             case PAM_ERROR_MSG:
164                 /* ignore it, but pam still wants a NULL response... */
165                 reply[replies].resp_retcode = PAM_SUCCESS;
166                 reply[replies].resp = NULL;
167                 break;
168             default:
169                 /* Must be an error of some sort... */
170                 free (reply);
171                 return PAM_CONV_ERR;
172         }
173     }
174     *resp = reply;
175     return PAM_SUCCESS;
176 }
177 
178 static struct pam_conv pam_conv_data = {
179     &conversation,
180     NULL
181 };
182 #endif /* #ifdef PPP_WITH_PAM */
183 
184 int
185 session_start(const int flags, const char *user, const char *passwd, const char *ttyName, char **msg)
186 {
187 #ifdef PPP_WITH_PAM
188     bool ok = 1;
189     const char *usr;
190     int pam_error;
191     bool try_session = 0;
192 #else /* #ifdef PPP_WITH_PAM */
193     struct passwd *pw;
194 #ifdef HAVE_CRYPT_H
195     char *cbuf;
196 #endif
197 #ifdef HAVE_SHADOW_H
198     struct spwd *spwd;
199     struct spwd *getspnam();
200     long now = 0;
201 #endif /* #ifdef HAVE_SHADOW_H */
202 #endif /* #ifdef PPP_WITH_PAM */
203 
204     SET_MSG(msg, SUCCESS_MSG);
205 
206     /* If no verification is requested, then simply return an OK */
207     if (!(SESS_ALL & flags)) {
208         return SESSION_OK;
209     }
210 
211     if (user == NULL) {
212        SET_MSG(msg, ABORT_MSG);
213        return SESSION_FAILED;
214     }
215 
216 #ifdef PPP_WITH_PAM
217     /* Find the '\\' in the username */
218     /* This needs to be fixed to support different username schemes */
219     if ((usr = strchr(user, '\\')) == NULL)
220 	usr = user;
221     else
222 	usr++;
223 
224     PAM_session = 0;
225     PAM_username = usr;
226     PAM_password = passwd;
227 
228     dbglog("Initializing PAM (%d) for user %s", flags, usr);
229     pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh);
230     dbglog("---> PAM INIT Result = %d", pam_error);
231     ok = (pam_error == PAM_SUCCESS);
232 
233     if (ok) {
234         ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) &&
235 	    (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS);
236     }
237 
238     if (ok && (SESS_AUTH & flags)) {
239         dbglog("Attempting PAM authentication");
240         pam_error = pam_authenticate (pamh, PAM_SILENT);
241         if (pam_error == PAM_SUCCESS) {
242             /* PAM auth was OK */
243             dbglog("PAM Authentication OK for %s", user);
244         } else {
245             /* No matter the reason, we fail because we're authenticating */
246             ok = 0;
247             if (pam_error == PAM_USER_UNKNOWN) {
248                 dbglog("User unknown, failing PAM authentication");
249                 SET_MSG(msg, "User unknown - cannot authenticate via PAM");
250             } else {
251                 /* Any other error means authentication was bad */
252                 dbglog("PAM Authentication failed: %d: %s", pam_error,
253 		       pam_strerror(pamh, pam_error));
254                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
255             }
256         }
257     }
258 
259     if (ok && (SESS_ACCT & flags)) {
260         dbglog("Attempting PAM account checks");
261         pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
262         if (pam_error == PAM_SUCCESS) {
263             /*
264 	     * PAM account was OK, set the flag which indicates that we should
265 	     * try to perform the session checks.
266 	     */
267             try_session = 1;
268             dbglog("PAM Account OK for %s", user);
269         } else {
270             /*
271 	     * If the account checks fail, then we should not try to perform
272 	     * the session check, because they don't make sense.
273 	     */
274             try_session = 0;
275             if (pam_error == PAM_USER_UNKNOWN) {
276                 /*
277 		 * We're checking the account, so it's ok to not have one
278 		 * because the user might come from the secrets files, or some
279 		 * other plugin.
280 		 */
281                 dbglog("User unknown, ignoring PAM restrictions");
282                 SET_MSG(msg, "User unknown - ignoring PAM restrictions");
283             } else {
284                 /* Any other error means session is rejected */
285                 ok = 0;
286                 dbglog("PAM Account checks failed: %d: %s", pam_error,
287 		       pam_strerror(pamh, pam_error));
288                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
289             }
290         }
291     }
292 
293     if (ok && try_session && (SESS_ACCT & flags)) {
294         /* Only open a session if the user's account was found */
295         pam_error = pam_open_session (pamh, PAM_SILENT);
296         if (pam_error == PAM_SUCCESS) {
297             dbglog("PAM Session opened for user %s", user);
298             PAM_session = 1;
299         } else {
300             dbglog("PAM Session denied for user %s", user);
301             SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
302             ok = 0;
303         }
304     }
305 
306     /* This is needed because apparently the PAM stuff closes the log */
307     reopen_log();
308 
309     /* If our PAM checks have already failed, then we must return a failure */
310     if (!ok) return SESSION_FAILED;
311 
312 #else /* #ifdef PPP_WITH_PAM */
313 
314 /*
315  * Use the non-PAM methods directly.  'pw' will remain NULL if the user
316  * has not been authenticated using local UNIX system services.
317  */
318 
319     pw = NULL;
320     if ((SESS_AUTH & flags)) {
321 	pw = getpwnam(user);
322 
323 	endpwent();
324 	/*
325 	 * Here, we bail if we have no user account, because there is nothing
326 	 * to verify against.
327 	 */
328 	if (pw == NULL)
329 	    return SESSION_FAILED;
330 
331 #ifdef HAVE_SHADOW_H
332 
333 	spwd = getspnam(user);
334 	endspent();
335 
336 	/*
337 	 * If there is no shadow entry for the user, then we can't verify the
338 	 * account.
339 	 */
340 	if (spwd == NULL)
341 	    return SESSION_FAILED;
342 
343 	/*
344 	 * We check validity all the time, because if the password has expired,
345 	 * then clearly we should not authenticate against it (if we're being
346 	 * called for authentication only).  Thus, in this particular instance,
347 	 * there is no real difference between using the AUTH, SESS or ACCT
348 	 * flags, or combinations thereof.
349 	 */
350 	now = time(NULL) / 86400L;
351 	if ((spwd->sp_expire > 0 && now >= spwd->sp_expire)
352 	    || ((spwd->sp_max >= 0 && spwd->sp_max < 10000)
353 	    && spwd->sp_lstchg >= 0
354 	    && now >= spwd->sp_lstchg + spwd->sp_max)) {
355 	    warn("Password for %s has expired", user);
356 	    return SESSION_FAILED;
357 	}
358 
359 	/* We have a valid shadow entry, keep the password */
360 	pw->pw_passwd = spwd->sp_pwdp;
361 
362 #endif /* #ifdef HAVE_SHADOW_H */
363 
364 	/*
365 	 * If no passwd, don't let them login if we're authenticating.
366 	 */
367         if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2)
368             return SESSION_FAILED;
369 #ifdef HAVE_CRYPT_H
370 	cbuf = crypt(passwd, pw->pw_passwd);
371 	if (!cbuf || strcmp(cbuf, pw->pw_passwd) != 0)
372 #endif
373             return SESSION_FAILED;
374     }
375 
376 #endif /* #ifdef PPP_WITH_PAM */
377 
378     /*
379      * Write a wtmp entry for this user.
380      */
381 
382     if (SESS_ACCT & flags) {
383 	if (strncmp(ttyName, "/dev/", 5) == 0)
384 	    ttyName += 5;
385 #ifdef SUPPORT_UTMP
386 	logwtmp(ttyName, user, ifname);		/* Add wtmp login entry */
387 #endif
388 #ifdef SUPPORT_UTMPX
389 	logwtmpx(ttyName, user, ifname, 0, USER_PROCESS);	/* Add wtmpx login entry */
390 #endif
391 
392 	logged_in = 1;
393 
394 #if defined(_PATH_LASTLOG) && !defined(PPP_WITH_PAM)
395 	/*
396 	 * Enter the user in lastlog only if he has been authenticated using
397 	 * local system services.  If he has not, then we don't know what his
398 	 * UID might be, and lastlog is indexed by UID.
399 	 */
400 	if (pw != NULL) {
401             struct lastlog ll;
402             int fd;
403 	    time_t tnow;
404 
405             if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) {
406                 (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET);
407                 memset((void *)&ll, 0, sizeof(ll));
408 		(void)time(&tnow);
409                 ll.ll_time = tnow;
410                 strlcpy(ll.ll_line, ttyName, sizeof(ll.ll_line));
411                 strlcpy(ll.ll_host, ifname, sizeof(ll.ll_host));
412                 (void)write(fd, (char *)&ll, sizeof(ll));
413                 (void)close(fd);
414             }
415 	}
416 #endif /* _PATH_LASTLOG and not PPP_WITH_PAM */
417 	info("user %s logged in on tty %s intf %s", user, ttyName, ifname);
418     }
419 
420     return SESSION_OK;
421 }
422 
423 /*
424  * session_end - Logout the user.
425  */
426 void
427 session_end(const char* ttyName)
428 {
429 #ifdef PPP_WITH_PAM
430     int pam_error = PAM_SUCCESS;
431 
432     if (pamh != NULL) {
433         if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT);
434         PAM_session = 0;
435         pam_end (pamh, pam_error);
436         pamh = NULL;
437 	/* Apparently the pam stuff does closelog(). */
438 	reopen_log();
439     }
440 #endif
441     if (logged_in) {
442 	if (strncmp(ttyName, "/dev/", 5) == 0)
443 	    ttyName += 5;
444 #ifdef SUPPORT_UTMP
445 	logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */
446 #endif
447 #ifdef SUPPORT_UTMPX
448 	logwtmpx(ttyName, "", "", 0, DEAD_PROCESS); /* Wipe out utmpx logout entry */
449 #endif
450 	logged_in = 0;
451     }
452 }
453