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 
conversation(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * appdata_ptr)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
session_start(const int flags,const char * user,const char * passwd,const char * ttyName,char ** msg)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
session_end(const char * ttyName)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