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