/* * session.c - PPP session control. * * Copyright (c) 2007 Diego Rivera. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. The name(s) of the authors of this software must not be used to * endorse or promote products derived from this software without * prior written permission. * * 3. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Paul Mackerras * <paulus@samba.org>". * * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Derived from auth.c, which is: * * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pwd.h> #if !defined(__ANDROID__) #include <crypt.h> #endif #ifdef HAS_SHADOW #include <shadow.h> #endif #include <time.h> #include <utmp.h> #include <fcntl.h> #include <unistd.h> #include "pppd.h" #include "session.h" #ifdef USE_PAM #include <security/pam_appl.h> #endif /* #ifdef USE_PAM */ #define SET_MSG(var, msg) if (var != NULL) { var[0] = msg; } #define COPY_STRING(s) ((s) ? strdup(s) : NULL) #define SUCCESS_MSG "Session started successfully" #define ABORT_MSG "Session can't be started without a username" #define SERVICE_NAME "ppp" #define SESSION_FAILED 0 #define SESSION_OK 1 /* We have successfully started a session */ static bool logged_in = 0; #ifdef USE_PAM /* * Static variables used to communicate between the conversation function * and the server_login function */ static const char *PAM_username; static const char *PAM_password; static int PAM_session = 0; static pam_handle_t *pamh = NULL; /* PAM conversation function * Here we assume (for now, at least) that echo on means login name, and * echo off means password. */ static int conversation (int num_msg, #ifndef SOL2 const #endif struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) { int replies = 0; struct pam_response *reply = NULL; reply = malloc(sizeof(struct pam_response) * num_msg); if (!reply) return PAM_CONV_ERR; for (replies = 0; replies < num_msg; replies++) { switch (msg[replies]->msg_style) { case PAM_PROMPT_ECHO_ON: reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = COPY_STRING(PAM_username); /* PAM frees resp */ break; case PAM_PROMPT_ECHO_OFF: reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = COPY_STRING(PAM_password); /* PAM frees resp */ break; case PAM_TEXT_INFO: /* fall through */ case PAM_ERROR_MSG: /* ignore it, but pam still wants a NULL response... */ reply[replies].resp_retcode = PAM_SUCCESS; reply[replies].resp = NULL; break; default: /* Must be an error of some sort... */ free (reply); return PAM_CONV_ERR; } } *resp = reply; return PAM_SUCCESS; } static struct pam_conv pam_conv_data = { &conversation, NULL }; #endif /* #ifdef USE_PAM */ int session_start(flags, user, passwd, ttyName, msg) const int flags; const char *user; const char *passwd; const char *ttyName; char **msg; { #ifdef USE_PAM bool ok = 1; const char *usr; int pam_error; bool try_session = 0; #else /* #ifdef USE_PAM */ struct passwd *pw; char *cbuf; #ifdef HAS_SHADOW struct spwd *spwd; struct spwd *getspnam(); long now = 0; #endif /* #ifdef HAS_SHADOW */ #endif /* #ifdef USE_PAM */ SET_MSG(msg, SUCCESS_MSG); /* If no verification is requested, then simply return an OK */ if (!(SESS_ALL & flags)) { return SESSION_OK; } #if defined(__ANDROID__) return SESSION_FAILED; #endif if (user == NULL) { SET_MSG(msg, ABORT_MSG); return SESSION_FAILED; } #ifdef USE_PAM /* Find the '\\' in the username */ /* This needs to be fixed to support different username schemes */ if ((usr = strchr(user, '\\')) == NULL) usr = user; else usr++; PAM_session = 0; PAM_username = usr; PAM_password = passwd; dbglog("Initializing PAM (%d) for user %s", flags, usr); pam_error = pam_start (SERVICE_NAME, usr, &pam_conv_data, &pamh); dbglog("---> PAM INIT Result = %d", pam_error); ok = (pam_error == PAM_SUCCESS); if (ok) { ok = (pam_set_item(pamh, PAM_TTY, ttyName) == PAM_SUCCESS) && (pam_set_item(pamh, PAM_RHOST, ifname) == PAM_SUCCESS); } if (ok && (SESS_AUTH & flags)) { dbglog("Attempting PAM authentication"); pam_error = pam_authenticate (pamh, PAM_SILENT); if (pam_error == PAM_SUCCESS) { /* PAM auth was OK */ dbglog("PAM Authentication OK for %s", user); } else { /* No matter the reason, we fail because we're authenticating */ ok = 0; if (pam_error == PAM_USER_UNKNOWN) { dbglog("User unknown, failing PAM authentication"); SET_MSG(msg, "User unknown - cannot authenticate via PAM"); } else { /* Any other error means authentication was bad */ dbglog("PAM Authentication failed: %d: %s", pam_error, pam_strerror(pamh, pam_error)); SET_MSG(msg, (char *) pam_strerror (pamh, pam_error)); } } } if (ok && (SESS_ACCT & flags)) { dbglog("Attempting PAM account checks"); pam_error = pam_acct_mgmt (pamh, PAM_SILENT); if (pam_error == PAM_SUCCESS) { /* * PAM account was OK, set the flag which indicates that we should * try to perform the session checks. */ try_session = 1; dbglog("PAM Account OK for %s", user); } else { /* * If the account checks fail, then we should not try to perform * the session check, because they don't make sense. */ try_session = 0; if (pam_error == PAM_USER_UNKNOWN) { /* * We're checking the account, so it's ok to not have one * because the user might come from the secrets files, or some * other plugin. */ dbglog("User unknown, ignoring PAM restrictions"); SET_MSG(msg, "User unknown - ignoring PAM restrictions"); } else { /* Any other error means session is rejected */ ok = 0; dbglog("PAM Account checks failed: %d: %s", pam_error, pam_strerror(pamh, pam_error)); SET_MSG(msg, (char *) pam_strerror (pamh, pam_error)); } } } if (ok && try_session && (SESS_ACCT & flags)) { /* Only open a session if the user's account was found */ pam_error = pam_open_session (pamh, PAM_SILENT); if (pam_error == PAM_SUCCESS) { dbglog("PAM Session opened for user %s", user); PAM_session = 1; } else { dbglog("PAM Session denied for user %s", user); SET_MSG(msg, (char *) pam_strerror (pamh, pam_error)); ok = 0; } } /* This is needed because apparently the PAM stuff closes the log */ reopen_log(); /* If our PAM checks have already failed, then we must return a failure */ if (!ok) return SESSION_FAILED; #elif !defined(__ANDROID__) /* #ifdef USE_PAM */ /* * Use the non-PAM methods directly. 'pw' will remain NULL if the user * has not been authenticated using local UNIX system services. */ pw = NULL; if ((SESS_AUTH & flags)) { pw = getpwnam(user); endpwent(); /* * Here, we bail if we have no user account, because there is nothing * to verify against. */ if (pw == NULL) return SESSION_FAILED; #ifdef HAS_SHADOW spwd = getspnam(user); endspent(); /* * If there is no shadow entry for the user, then we can't verify the * account. */ if (spwd == NULL) return SESSION_FAILED; /* * We check validity all the time, because if the password has expired, * then clearly we should not authenticate against it (if we're being * called for authentication only). Thus, in this particular instance, * there is no real difference between using the AUTH, SESS or ACCT * flags, or combinations thereof. */ now = time(NULL) / 86400L; if ((spwd->sp_expire > 0 && now >= spwd->sp_expire) || ((spwd->sp_max >= 0 && spwd->sp_max < 10000) && spwd->sp_lstchg >= 0 && now >= spwd->sp_lstchg + spwd->sp_max)) { warn("Password for %s has expired", user); return SESSION_FAILED; } /* We have a valid shadow entry, keep the password */ pw->pw_passwd = spwd->sp_pwdp; #endif /* #ifdef HAS_SHADOW */ /* * If no passwd, don't let them login if we're authenticating. */ if (pw->pw_passwd == NULL || strlen(pw->pw_passwd) < 2) return SESSION_FAILED; cbuf = crypt(passwd, pw->pw_passwd); if (!cbuf || strcmp(cbuf, pw->pw_passwd) != 0) return SESSION_FAILED; } #endif /* #ifdef USE_PAM */ /* * Write a wtmp entry for this user. */ if (SESS_ACCT & flags) { if (strncmp(ttyName, "/dev/", 5) == 0) ttyName += 5; logwtmp(ttyName, user, ifname); /* Add wtmp login entry */ logged_in = 1; #if defined(_PATH_LASTLOG) && !defined(USE_PAM) /* * Enter the user in lastlog only if he has been authenticated using * local system services. If he has not, then we don't know what his * UID might be, and lastlog is indexed by UID. */ if (pw != NULL) { struct lastlog ll; int fd; time_t tnow; if ((fd = open(_PATH_LASTLOG, O_RDWR, 0)) >= 0) { (void)lseek(fd, (off_t)(pw->pw_uid * sizeof(ll)), SEEK_SET); memset((void *)&ll, 0, sizeof(ll)); (void)time(&tnow); ll.ll_time = tnow; (void)strncpy(ll.ll_line, ttyName, sizeof(ll.ll_line)); (void)strncpy(ll.ll_host, ifname, sizeof(ll.ll_host)); (void)write(fd, (char *)&ll, sizeof(ll)); (void)close(fd); } } #endif /* _PATH_LASTLOG and not USE_PAM */ info("user %s logged in on tty %s intf %s", user, ttyName, ifname); } return SESSION_OK; } /* * session_end - Logout the user. */ void session_end(const char* ttyName) { #ifdef USE_PAM int pam_error = PAM_SUCCESS; if (pamh != NULL) { if (PAM_session) pam_error = pam_close_session (pamh, PAM_SILENT); PAM_session = 0; pam_end (pamh, pam_error); pamh = NULL; /* Apparently the pam stuff does closelog(). */ reopen_log(); } #endif if (logged_in) { if (strncmp(ttyName, "/dev/", 5) == 0) ttyName += 5; logwtmp(ttyName, "", ""); /* Wipe out utmp logout entry */ logged_in = 0; } }