Home | History | Annotate | Line # | Download | only in pppd
      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 (at) 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 (at) 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