Home | History | Annotate | Download | only in pppd
      1 /*
      2  * session.c - PPP session control.
      3  *
      4  * Copyright (c) 2007 Diego Rivera. All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  *
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  *
     13  * 2. The name(s) of the authors of this software must not be used to
     14  *    endorse or promote products derived from this software without
     15  *    prior written permission.
     16  *
     17  * 3. Redistributions of any form whatsoever must retain the following
     18  *    acknowledgment:
     19  *    "This product includes software developed by Paul Mackerras
     20  *     <paulus (at) samba.org>".
     21  *
     22  * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
     23  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
     24  * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
     25  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     26  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
     27  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
     28  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     29  *
     30  * Derived from auth.c, which is:
     31  *
     32  * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved.
     33  *
     34  * Redistribution and use in source and binary forms, with or without
     35  * modification, are permitted provided that the following conditions
     36  * are met:
     37  *
     38  * 1. Redistributions of source code must retain the above copyright
     39  *    notice, this list of conditions and the following disclaimer.
     40  *
     41  * 2. Redistributions in binary form must reproduce the above copyright
     42  *    notice, this list of conditions and the following disclaimer in
     43  *    the documentation and/or other materials provided with the
     44  *    distribution.
     45  *
     46  * 3. The name "Carnegie Mellon University" must not be used to
     47  *    endorse or promote products derived from this software without
     48  *    prior written permission. For permission or any legal
     49  *    details, please contact
     50  *      Office of Technology Transfer
     51  *      Carnegie Mellon University
     52  *      5000 Forbes Avenue
     53  *      Pittsburgh, PA  15213-3890
     54  *      (412) 268-4387, fax: (412) 268-7395
     55  *      tech-transfer (at) andrew.cmu.edu
     56  *
     57  * 4. Redistributions of any form whatsoever must retain the following
     58  *    acknowledgment:
     59  *    "This product includes software developed by Computing Services
     60  *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
     61  *
     62  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
     63  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
     64  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
     65  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     66  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
     67  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
     68  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     69  */
     70 
     71 #include <stdio.h>
     72 #include <stdlib.h>
     73 #include <string.h>
     74 #include <pwd.h>
     75 #if !defined(__ANDROID__)
     76 #include <crypt.h>
     77 #endif
     78 #ifdef HAS_SHADOW
     79 #include <shadow.h>
     80 #endif
     81 #include <time.h>
     82 #include <utmp.h>
     83 #include <fcntl.h>
     84 #include <unistd.h>
     85 #include "pppd.h"
     86 #include "session.h"
     87 
     88 #ifdef USE_PAM
     89 #include <security/pam_appl.h>
     90 #endif /* #ifdef USE_PAM */
     91 
     92 #define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; }
     93 #define COPY_STRING(s) ((s) ? strdup(s) : NULL)
     94 
     95 #define SUCCESS_MSG "Session started successfully"
     96 #define ABORT_MSG "Session can't be started without a username"
     97 #define SERVICE_NAME "ppp"
     98 
     99 #define SESSION_FAILED  0
    100 #define SESSION_OK      1
    101 
    102 /* We have successfully started a session */
    103 static bool logged_in = 0;
    104 
    105 #ifdef USE_PAM
    106 /*
    107  * Static variables used to communicate between the conversation function
    108  * and the server_login function
    109  */
    110 static const char *PAM_username;
    111 static const char *PAM_password;
    112 static int   PAM_session = 0;
    113 static pam_handle_t *pamh = NULL;
    114 
    115 /* PAM conversation function
    116  * Here we assume (for now, at least) that echo on means login name, and
    117  * echo off means password.
    118  */
    119 
    120 static int conversation (int num_msg,
    121 #ifndef SOL2
    122     const
    123 #endif
    124     struct pam_message **msg,
    125     struct pam_response **resp, void *appdata_ptr)
    126 {
    127     int replies = 0;
    128     struct pam_response *reply = NULL;
    129 
    130     reply = malloc(sizeof(struct pam_response) * num_msg);
    131     if (!reply) return PAM_CONV_ERR;
    132 
    133     for (replies = 0; replies < num_msg; replies++) {
    134         switch (msg[replies]->msg_style) {
    135             case PAM_PROMPT_ECHO_ON:
    136                 reply[replies].resp_retcode = PAM_SUCCESS;
    137                 reply[replies].resp = COPY_STRING(PAM_username);
    138                 /* PAM frees resp */
    139                 break;
    140             case PAM_PROMPT_ECHO_OFF:
    141                 reply[replies].resp_retcode = PAM_SUCCESS;
    142                 reply[replies].resp = COPY_STRING(PAM_password);
    143                 /* PAM frees resp */
    144                 break;
    145             case PAM_TEXT_INFO:
    146                 /* fall through */
    147             case PAM_ERROR_MSG:
    148                 /* ignore it, but pam still wants a NULL response... */
    149                 reply[replies].resp_retcode = PAM_SUCCESS;
    150                 reply[replies].resp = NULL;
    151                 break;
    152             default:
    153                 /* Must be an error of some sort... */
    154                 free (reply);
    155                 return PAM_CONV_ERR;
    156         }
    157     }
    158     *resp = reply;
    159     return PAM_SUCCESS;
    160 }
    161 
    162 static struct pam_conv pam_conv_data = {
    163     &conversation,
    164     NULL
    165 };
    166 #endif /* #ifdef USE_PAM */
    167 
    168 int
    169 session_start(flags, user, passwd, ttyName, msg)
    170     const int flags;
    171     const char *user;
    172     const char *passwd;
    173     const char *ttyName;
    174     char **msg;
    175 {
    176 #ifdef USE_PAM
    177     bool ok = 1;
    178     const char *usr;
    179     int pam_error;
    180     bool try_session = 0;
    181 #else /* #ifdef USE_PAM */
    182     struct passwd *pw;
    183     char *cbuf;
    184 #ifdef HAS_SHADOW
    185     struct spwd *spwd;
    186     struct spwd *getspnam();
    187     long now = 0;
    188 #endif /* #ifdef HAS_SHADOW */
    189 #endif /* #ifdef USE_PAM */
    190 
    191     SET_MSG(msg, SUCCESS_MSG);
    192 
    193     /* If no verification is requested, then simply return an OK */
    194     if (!(SESS_ALL & flags)) {
    195         return SESSION_OK;
    196     }
    197 
    198 #if defined(__ANDROID__)
    199     return SESSION_FAILED;
    200 #endif
    201 
    202     if (user == NULL) {
    203        SET_MSG(msg, ABORT_MSG);
    204        return SESSION_FAILED;
    205     }
    206 
    207 #ifdef USE_PAM
    208     /* Find the '\\' in the username */
    209     /* This needs to be fixed to support different username schemes */
    210     if ((usr = strchr(user, '\\')) == NULL)
    211 	usr = user;
    212     else
    213 	usr++;
    214 
    215     PAM_session = 0;
    216     PAM_username = usr;
    217     PAM_password = passwd;
    218 
    219     dbglog("Initializing PAM (%d) for user %s", flags, usr);
    220     pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh);
    221     dbglog("---> PAM INIT Result = %d", pam_error);
    222     ok = (pam_error == PAM_SUCCESS);
    223 
    224     if (ok) {
    225         ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) &&
    226 	    (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS);
    227     }
    228 
    229     if (ok && (SESS_AUTH & flags)) {
    230         dbglog("Attempting PAM authentication");
    231         pam_error = pam_authenticate (pamh, PAM_SILENT);
    232         if (pam_error == PAM_SUCCESS) {
    233             /* PAM auth was OK */
    234             dbglog("PAM Authentication OK for %s", user);
    235         } else {
    236             /* No matter the reason, we fail because we're authenticating */
    237             ok = 0;
    238             if (pam_error == PAM_USER_UNKNOWN) {
    239                 dbglog("User unknown, failing PAM authentication");
    240                 SET_MSG(msg, "User unknown - cannot authenticate via PAM");
    241             } else {
    242                 /* Any other error means authentication was bad */
    243                 dbglog("PAM Authentication failed: %d: %s", pam_error,
    244 		       pam_strerror(pamh, pam_error));
    245                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
    246             }
    247         }
    248     }
    249 
    250     if (ok && (SESS_ACCT & flags)) {
    251         dbglog("Attempting PAM account checks");
    252         pam_error = pam_acct_mgmt (pamh, PAM_SILENT);
    253         if (pam_error == PAM_SUCCESS) {
    254             /*
    255 	     * PAM account was OK, set the flag which indicates that we should
    256 	     * try to perform the session checks.
    257 	     */
    258             try_session = 1;
    259             dbglog("PAM Account OK for %s", user);
    260         } else {
    261             /*
    262 	     * If the account checks fail, then we should not try to perform
    263 	     * the session check, because they don't make sense.
    264 	     */
    265             try_session = 0;
    266             if (pam_error == PAM_USER_UNKNOWN) {
    267                 /*
    268 		 * We're checking the account, so it's ok to not have one
    269 		 * because the user might come from the secrets files, or some
    270 		 * other plugin.
    271 		 */
    272                 dbglog("User unknown, ignoring PAM restrictions");
    273                 SET_MSG(msg, "User unknown - ignoring PAM restrictions");
    274             } else {
    275                 /* Any other error means session is rejected */
    276                 ok = 0;
    277                 dbglog("PAM Account checks failed: %d: %s", pam_error,
    278 		       pam_strerror(pamh, pam_error));
    279                 SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
    280             }
    281         }
    282     }
    283 
    284     if (ok && try_session && (SESS_ACCT & flags)) {
    285         /* Only open a session if the user's account was found */
    286         pam_error = pam_open_session (pamh, PAM_SILENT);
    287         if (pam_error == PAM_SUCCESS) {
    288             dbglog("PAM Session opened for user %s", user);
    289             PAM_session = 1;
    290         } else {
    291             dbglog("PAM Session denied for user %s", user);
    292             SET_MSG(msg, (char *) pam_strerror (pamh, pam_error));
    293             ok = 0;
    294         }
    295     }
    296 
    297     /* This is needed because apparently the PAM stuff closes the log */
    298     reopen_log();
    299 
    300     /* If our PAM checks have already failed, then we must return a failure */
    301     if (!ok) return SESSION_FAILED;
    302 
    303 #elif !defined(__ANDROID__) /* #ifdef USE_PAM */
    304 
    305 /*
    306  * Use the non-PAM methods directly.  'pw' will remain NULL if the user
    307  * has not been authenticated using local UNIX system services.
    308  */
    309 
    310     pw = NULL;
    311     if ((SESS_AUTH & flags)) {
    312 	pw = getpwnam(user);
    313 
    314 	endpwent();
    315 	/*
    316 	 * Here, we bail if we have no user account, because there is nothing
    317 	 * to verify against.
    318 	 */
    319 	if (pw == NULL)
    320 	    return SESSION_FAILED;
    321 
    322 #ifdef HAS_SHADOW
    323 
    324 	spwd = getspnam(user);
    325 	endspent();
    326 
    327 	/*
    328 	 * If there is no shadow entry for the user, then we can't verify the
    329 	 * account.
    330 	 */
    331 	if (spwd == NULL)
    332 	    return SESSION_FAILED;
    333 
    334 	/*
    335 	 * We check validity all the time, because if the password has expired,
    336 	 * then clearly we should not authenticate against it (if we're being
    337 	 * called for authentication only).  Thus, in this particular instance,
    338 	 * there is no real difference between using the AUTH, SESS or ACCT
    339 	 * flags, or combinations thereof.
    340 	 */
    341 	now = time(NULL) / 86400L;
    342 	if ((spwd->sp_expire > 0 && now >= spwd->sp_expire)
    343 	    || ((spwd->sp_max >= 0 && spwd->sp_max < 10000)
    344 	    && spwd->sp_lstchg >= 0
    345 	    && now >= spwd->sp_lstchg + spwd->sp_max)) {
    346 	    warn("Password for %s has expired", user);
    347 	    return SESSION_FAILED;
    348 	}
    349 
    350 	/* We have a valid shadow entry, keep the password */
    351 	pw->pw_passwd = spwd->sp_pwdp;
    352 
    353 #endif /* #ifdef HAS_SHADOW */
    354 
    355 	/*
    356 	 * If no passwd, don't let them login if we're authenticating.
    357 	 */
    358         if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2)
    359             return SESSION_FAILED;
    360 	cbuf = crypt(passwd, pw->pw_passwd);
    361 	if (!cbuf || strcmp(cbuf, pw->pw_passwd) != 0)
    362             return SESSION_FAILED;
    363     }
    364 
    365 #endif /* #ifdef USE_PAM */
    366 
    367     /*
    368      * Write a wtmp entry for this user.
    369      */
    370 
    371     if (SESS_ACCT & flags) {
    372 	if (strncmp(ttyName, "/dev/", 5) == 0)
    373 	    ttyName += 5;
    374 	logwtmp(ttyName, user, ifname); /* Add wtmp login entry */
    375 	logged_in = 1;
    376 
    377 #if defined(_PATH_LASTLOG) && !defined(USE_PAM)
    378 	/*
    379 	 * Enter the user in lastlog only if he has been authenticated using
    380 	 * local system services.  If he has not, then we don't know what his
    381 	 * UID might be, and lastlog is indexed by UID.
    382 	 */
    383 	if (pw != NULL) {
    384             struct lastlog ll;
    385             int fd;
    386 	    time_t tnow;
    387 
    388             if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) {
    389                 (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET);
    390                 memset((void *)&ll, 0, sizeof(ll));
    391 		(void)time(&tnow);
    392                 ll.ll_time = tnow;
    393                 (void)strncpy(ll.ll_line, ttyName, sizeof(ll.ll_line));
    394                 (void)strncpy(ll.ll_host, ifname, sizeof(ll.ll_host));
    395                 (void)write(fd, (char *)&ll, sizeof(ll));
    396                 (void)close(fd);
    397             }
    398 	}
    399 #endif /* _PATH_LASTLOG and not USE_PAM */
    400 	info("user %s logged in on tty %s intf %s", user, ttyName, ifname);
    401     }
    402 
    403     return SESSION_OK;
    404 }
    405 
    406 /*
    407  * session_end - Logout the user.
    408  */
    409 void
    410 session_end(const char* ttyName)
    411 {
    412 #ifdef USE_PAM
    413     int pam_error = PAM_SUCCESS;
    414 
    415     if (pamh != NULL) {
    416         if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT);
    417         PAM_session = 0;
    418         pam_end (pamh, pam_error);
    419         pamh = NULL;
    420 	/* Apparently the pam stuff does closelog(). */
    421 	reopen_log();
    422     }
    423 #endif
    424     if (logged_in) {
    425 	if (strncmp(ttyName, "/dev/", 5) == 0)
    426 	    ttyName += 5;
    427 	logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */
    428 	logged_in = 0;
    429     }
    430 }
    431