/*
* Copyright (c) 1999 Ian Freislich
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: authenticate.c,v 1.34 2003/01/07 12:25:55 ianf Exp $
*/
#include <sys/param.h>
#include <sys/stat.h>
#include <netinet/in.h>
#ifdef USE_PAM
#include <security/pam_appl.h>
#endif
#ifdef SOLARIS
#include <crypt.h>
#endif
#include <db.h>
#include <fcntl.h>
#include <md5.h>
#include <netdb.h>
#include <pwd.h>
#include <radlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>
#include <poputil.h>
#include "config.h"
#include "popd.h"
#include "authenticate.h"
/* global variables */
extern struct config config;
/* function prototypes */
static void dosetuid(struct connection *user);
static int db_file_pass(struct connection *cxn),
#ifndef USE_PAM
unix_pass(struct connection *cxn),
#endif
radius_pass(struct connection *cxn),
checkpassword(struct connection *cxn),
checkchallenge(struct connection *cxn, char *ch);
static char *getsecret(struct connection *cxn);
#ifdef USE_PAM
static void freeresponse(int num, struct pam_response *response);
static int conversation(int num, const struct pam_message **msg,
struct pam_response **result, void *appdata),
pam_pass(struct connection *cxn);
static void
freeresponse(int num, struct pam_response *response)
{
int i;
for (i = 0; i < num; i++)
if (response[i].resp != NULL)
free(response[i].resp);
free(response);
}
static int
conversation(int num, const struct pam_message **msg,
struct pam_response **result, void *appdata)
{
struct pam_response *response;
int i;
response = xmalloc(num * sizeof(struct pam_response));
for (i = 0; i < num; i++)
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_ON:
response[i].resp = strdup(((char **)appdata)[0]);
if (response[i].resp == NULL) {
freeresponse(i, response);
return PAM_CONV_ERR;
}
response[i].resp_retcode = PAM_SUCCESS;
break;
case PAM_PROMPT_ECHO_OFF:
response[i].resp = strdup(((char **)appdata)[1]);
if (response[i].resp == NULL) {
freeresponse(i, response);
return PAM_CONV_ERR;
}
response[i].resp_retcode = PAM_SUCCESS;
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
response[i].resp_retcode = PAM_SUCCESS;
response[i].resp = NULL;
break;
default:
freeresponse(i, response);
return PAM_CONV_ERR;
}
*result = response;
return PAM_SUCCESS;
}
#endif
/*
* Set the effective userid of this proccess to
* the given username. Root privilage is too great for
* access to mailboxes.
* ( assumption here, we are runing as root )
*
* Return on success, exits on failures - no permission and
* no such user
*/
static void
dosetuid(struct connection *cxn)
{
char *username;
struct passwd *pw;
int facility;
if (config.localuser) {
username = cxn->username;
}
else {
username = config.user;
}
facility = LOG_CRIT;
if ((pw = getpwnam(username)) != (struct passwd *)NULL) {
if (!seteuid(pw->pw_uid)) {
cxn->uid = getuid();
cxn->euid = pw->pw_uid;
return;
}
else {
syslog(facility, "Unable to setuid to %s", username);
exit_error(EX_NOPERM, "Unable to setuid to %s",
username);
}
}
else {
syslog(facility, "No such user %s", username);
exit_error(EX_NOUSER, "No such user %s", username);
}
}
#ifndef USE_PAM
static int
unix_pass(struct connection *cxn)
{
struct passwd *pw;
if ((pw = getpwnam(cxn->username)) != (struct passwd *)NULL) {
if (strcmp(pw->pw_passwd, crypt(cxn->password, pw->pw_passwd)))
return(FALSE);
return(TRUE);
}
return(FALSE);
}
#endif
static int
radius_pass(struct connection *cxn)
{
struct rad_handle *authreq;
const void *data;
char *p;
int i;
size_t len;
authreq = rad_auth_open();
for (i = 0; config.radius && config.radius[i].host; i++) {
rad_add_server(authreq, config.radius[i].host,
config.radius[i].port, config.radius[i].secret,
config.radius[i].timeout, config.radius[i].tries);
}
rad_create_request(authreq, RAD_ACCESS_REQUEST);
rad_put_int(authreq, RAD_NAS_PORT_TYPE, RAD_VIRTUAL);
rad_put_string(authreq, RAD_USER_NAME, cxn->auth_string);
rad_put_string(authreq, RAD_USER_PASSWORD, cxn->password);
switch(rad_send_request(authreq)) {
case RAD_ACCESS_ACCEPT:
while ((i = rad_get_attr(authreq, &data, &len)) > 0) {
switch(i) {
case RAD_LOGIN_IP_HOST:
config.proxy_addr = rad_cvt_addr(data);
break;
case RAD_LOGIN_TCP_PORT:
config.proxy_port =
htons(rad_cvt_int(data));
break;
case RAD_REPLY_MESSAGE:
config.proxy_name = rad_cvt_string(data,
len);
if ((p = strchr(config.proxy_name,
':'))) {
*p++ = '\0';
config.proxy_passwd = p;
}
break;
}
}
rad_close(authreq);
return(TRUE);
case RAD_ACCESS_REJECT:
case RAD_ACCESS_CHALLENGE:
/* Fall through */
break;
}
rad_close(authreq);
return(FALSE);
}
/*
* Check the username and password against an entry
* in a db file
*
* Return true on success, false on failure,
*/
static int
db_file_pass(struct connection *cxn)
{
DB *dp;
DBT key, data;
char *encpw;
char *encrypted_pass;
char *path;
path = xmalloc(strlen(config.etcdir) + strlen(config.dbfile) + 5);
sprintf(path, "%s/%s", config.etcdir, config.dbfile);
if (!(dp = dbopen(path, O_RDONLY, S_IRUSR|S_IWUSR, DB_HASH, NULL))) {
free(path);
return(FALSE);
}
key.data = (u_char *)cxn->auth_string;
key.size = strlen(key.data);
if ((dp->get)(dp, &key, &data, 0)) {
(dp->close)(dp);
free(path);
return(FALSE);
}
encrypted_pass = (char*)data.data;
encrypted_pass[data.size] = '\0';
encpw = crypt(cxn->password, encrypted_pass);
if (strcmp(encpw, encrypted_pass)) {
(dp->close)(dp);
free(path);
return(FALSE);
}
(dp->close)(dp);
free(path);
return(TRUE);
}
#ifdef USE_PAM
static int
pam_pass(struct connection *cxn)
{
struct pam_conv pamconv;
pam_handle_t *pamhandle;
char *data[2];
int result;
data[0] = strdup(cxn->auth_string);
data[1] = strdup(cxn->password);
pamconv.conv = &conversation;
pamconv.appdata_ptr = data;
if ((result = pam_start ("popd", NULL, &pamconv, &pamhandle)) !=
PAM_SUCCESS)
return(FALSE);
if ((result = pam_authenticate(pamhandle, 0)) != PAM_SUCCESS)
return(FALSE);
if ((result = pam_acct_mgmt(pamhandle, 0)) != PAM_SUCCESS)
return(FALSE);
/* if ((result = pam_setcred(pamhandle, PAM_ESTABLISH_CRED)) !=
* PAM_SUCCESS)
* return(FALSE);
* if ((result = pam_open_session(pamhandle, 0)) != PAM_SUCCESS)
* return(FALSE);
* pam_end(pamhandle, pam_close_session(pamhandle, 0)); */
pam_end(pamhandle, result);
return TRUE;
}
#endif
/*
* Decide which method to use to authenticate the user,
* perform the authentication and on success setuid to the user.
*
* This routine is the hook for authentication methods.
*
* Return true on success and false on failure
*/
static int
checkpassword(struct connection *cxn)
{
if (config.radius && radius_pass(cxn))
return(TRUE);
else if (config.virtual && db_file_pass(cxn))
return(TRUE);
else if (!config.radius && !config.virtual &&
#ifndef USE_PAM
unix_pass(cxn))
#else
pam_pass(cxn))
#endif
return(TRUE);
return(FALSE);
}
/*
* Retrieve the secret for a given user.
*
* Returns the secret on success and false on failure to
* open the db file, * false if the key is not found
* in the * db file.
*
* Exits if memory can't be allocated for the path.
*/
static char
*getsecret(struct connection *cxn)
{
DB *dp;
DBT key, data;
char *secret;
char *path;
path = xmalloc(strlen(config.etcdir) + strlen(config.secrets) + 5);
sprintf(path, "%s/%s", config.etcdir, config.secrets);
if (!(dp = dbopen(path, O_RDONLY, S_IRUSR|S_IWUSR, DB_HASH, NULL))) {
free(path);
return(NULL);
}
key.data = (u_char *)cxn->auth_string;
key.size = strlen(key.data);
if ((dp->get)(dp, &key, &data, 0)) {
(dp->close)(dp);
free(path);
return(NULL);
}
secret = xcalloc(data.size + 1, 1);
strncpy(secret, (char*)data.data, data.size);
(dp->close)(dp);
free(path);
return(secret);
}
/*
* Check the md5 challenge.
*
* Return true if the challenge is valid,
* False on falure.
*
* Exit if memory can't be allocated for challenge string
*/
static int
checkchallenge(struct connection *cxn, char *ch)
{
char *secret, *string;
MD5_CTX context;
unsigned char digest[16];
if (!(secret = getsecret(cxn)))
return(FALSE);
if (!*secret) {
free(secret);
return(FALSE);
}
string = xmalloc(strlen(config.timestamp) + strlen(secret) + 1);
strcpy(string, config.timestamp);
strcat(string, secret);
MD5Init(&context);
MD5Update(&context, (unsigned char *)string, strlen(string));
MD5Final(digest, &context);
if (!strcmp(binhex(digest, sizeof(digest)), ch)) {
free(string);
free(secret);
dosetuid(cxn);
return(TRUE);
}
free(string);
free(secret);
return(FALSE);
}
/*
* Do the authentication
*
* Commands that we deal with here are:
* auth
* apop
* user
* pass
* quit
*
* Loop continually unitil we have enough information.
* Exit on quit and time out.
*/
int
authenticate(struct connection *cxn)
{
char *arg1, *arg2;
int counter = 0;
for (;;) {
switch (recvcmd(&arg1, &arg2)) {
case AUTH:
message(CMDINVAL);
break;
case APOP:
if (!config.secrets) {
message(CMDISABLE);
break;
}
if (arg1 && arg2) {
cxndetails(cxn, arg1, config.defaultrealm,
config.maildir, config.bulletindir,
config.virtual, config.hashdepth);
if (checkchallenge(cxn, arg2))
return(TRUE);
else
freeconnection(cxn);
sleep(++counter);
if (counter == 4)
return FALSE;
message(CHALLENGEINVAL);
break;
}
message(TOOFEWARGUMENTS);
break;
case USER:
if (cxn->auth_string) {
sendline(SEND_FLUSH, "-ERR Already selected %s",
cxn->auth_string);
break;
}
if (arg1) {
if (cxndetails(cxn, arg1, config.defaultrealm,
config.maildir, config.bulletindir,
config.virtual, config.hashdepth))
sendline(SEND_FLUSH, "+OK enter "
"password for %s",
cxn->auth_string);
break;
}
message(NEEDUSERNAME);
break;
case PASS:
if (arg1 && cxn->auth_string) {
cxn->password = xmalloc(strlen(arg1) + 1);
strcpy(cxn->password, arg1);
if (!config.pwcheck) {
dosetuid(cxn);
return(TRUE);
}
if (checkpassword(cxn)) {
dosetuid(cxn);
return(TRUE);
}
else
freeconnection(cxn);
sleep(++counter);
if (counter == 4) {
freeconnection(cxn);
return FALSE;
}
message(BADPASSWORD);
break;
}
else if (cxn->auth_string == NULL)
message(NEEDUSERNAME);
else if (arg1 == NULL)
message(NEEDPASSWORD);
break;
case QUIT:
return(QUIT);
break;
case TIMEDOUT:
exit_error(EX_NOINPUT,
"timed out waiting for input");
case INVALCMD:
default:
message(CMDINVAL);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1