/*
* 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;
}
}