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