tacacs-F4.0.4.28/pwlib.c (490 lines of code) (raw):
/*
* $Id: pwlib.c,v 1.25 2009-03-17 18:40:20 heas Exp $
*
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
* Copyright (c) 1995-1998 by Cisco systems, Inc.
*
* Permission to use, copy, modify, and distribute this software for
* any purpose and without fee is hereby granted, provided that this
* copyright and permission notice appear on all copies of the
* software and supporting documentation, the name of Cisco Systems,
* Inc. not be used in advertising or publicity pertaining to
* distribution of the program without specific prior permission, and
* notice be given in supporting documentation that modification,
* copying and distribution is by permission of Cisco Systems, Inc.
*
* Cisco Systems, Inc. makes no representations about the suitability
* of this software for any purpose. THIS SOFTWARE IS PROVIDED ``AS
* IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "tac_plus.h"
#include "expire.h"
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
#ifdef SHADOW_PASSWORDS
# include <shadow.h>
#endif
#if HAVE_PAM
# ifdef __APPLE__ /* MacOS X */
# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060
# include <security/pam_appl.h>
# else
# include <pam/pam_appl.h>
# endif
# else
# include <security/pam_appl.h>
# endif
static int pam_tacacs(int, const struct pam_message **, struct pam_response **,
void *);
#endif
/*
* Generic password verification routines for des, file and cleartext passwords
*/
static int etc_passwd_file_verify(char *, char *, struct authen_data *);
static int des_verify(char *, char *);
#if HAVE_PAM
static int pam_verify(char *, char *, struct authen_data *data);
#endif
static int passwd_file_verify(char *, char *, struct authen_data *, char *);
extern char *progname;
/* Adjust data->status depending on whether a user has expired or not */
void
set_expiration_status(char *exp_date, struct authen_data *data)
{
int expired;
/* if the status is anything except pass, there's no point proceeding */
if (data->status != TAC_PLUS_AUTHEN_STATUS_PASS) {
return;
}
/*
* Check the expiration date, if any. If NULL, this check will return
* PW_OK
*/
expired = check_expiration(exp_date);
switch (expired) {
case PW_OK:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password has not expired %s",
exp_date ? exp_date : "<no expiry date set>");
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
break;
case PW_EXPIRING:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password will expire soon %s",
exp_date ? exp_date : "<no expiry date set>");
if (data->server_msg)
free(data->server_msg);
data->server_msg = tac_strdup("Password will expire soon");
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
break;
case PW_EXPIRED:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password has expired %s",
exp_date ? exp_date : "<no expiry date set>");
if (data->server_msg)
free(data->server_msg);
data->server_msg = tac_strdup("Password has expired");
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
break;
default:
report(LOG_ERR, "%s: Bogus return value %d from check_expiration",
session.peer, expired);
data->status = TAC_PLUS_AUTHEN_STATUS_ERROR;
break;
}
return;
}
/*
* Verify that this user/password is valid. Works only for cleartext, file,
* PAM and des passwords. Return 1 if password is valid.
*/
int
verify(char *name, char *passwd, struct authen_data *data, int recurse)
{
char *exp_date;
char *cfg_passwd;
char *p;
if (data->type == TAC_PLUS_AUTHEN_TYPE_PAP) {
cfg_passwd = cfg_get_pap_secret(name, recurse);
} else {
cfg_passwd = cfg_get_login_secret(name, recurse);
}
/*
* If there is no login or pap password for this user, see if there is
* a global password that can be used.
*/
if (cfg_passwd == NULL) {
cfg_passwd = cfg_get_global_secret(name, recurse);
}
/*
* If we still have no password for this user (or no user for that
* matter) but the default authentication = file <file> statement
* has been issued, attempt to use this password file
*/
if (cfg_passwd == NULL) {
char *file = cfg_get_authen_default();
if (file) {
return(passwd_file_verify(name, passwd, data, file));
}
/* otherwise, we fail */
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
/* We have a configured password. Deal with it depending on its type */
#if HAVE_PAM
if (strcmp(cfg_passwd, "PAM") == 0) {
/* try to verify the password via PAM */
if (!pam_verify(name, passwd, data)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
exp_date = cfg_get_expires(name, recurse);
set_expiration_status(exp_date, data);
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
#endif
p = tac_find_substring("cleartext ", cfg_passwd);
if (p != NULL) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "verify daemon %s == NAS %s", p, passwd);
if (strcmp(passwd, p)) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is incorrect");
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is correct");
}
exp_date = cfg_get_expires(name, recurse);
set_expiration_status(exp_date, data);
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
p = tac_find_substring("des ", cfg_passwd);
if (p) {
/* try to verify this des password */
if (!des_verify(passwd, p)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
}
exp_date = cfg_get_expires(name, recurse);
set_expiration_status(exp_date, data);
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
p = tac_find_substring("file ", cfg_passwd);
if (p) {
return(passwd_file_verify(name, passwd, data, p));
}
/*
* Oops. No idea what kind of password this is. This should never
* happen as the parser should never create such passwords.
*/
report(LOG_ERR, "%s: Error cannot identify password type %s for %s",
session.peer,
cfg_passwd && cfg_passwd[0] ? cfg_passwd : "<NULL>",
name ? name : "<unknown>");
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
/*
* Verify that this user/password is valid for the matching password data,
* such as "cleartext foopwd". Works only for cleartext, des and file
* passwords and is used only for or by enable().
* Return 1 if password is valid. The caller needs to check any expiration
* dates itself.
*/
int
verify_pwd(char *username, char *passwd, struct authen_data *data,
char *cfg_passwd)
{
char *p;
/* Deal with the cfg_passwd depending on its type */
p = tac_find_substring("cleartext ", cfg_passwd);
if (p) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "verify daemon %s == NAS %s", p, passwd);
if (strcmp(passwd, p)) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is incorrect");
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is correct");
}
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
p = tac_find_substring("des ", cfg_passwd);
if (p) {
/* try to verify this des password */
if (!des_verify(passwd, p)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
}
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
p = tac_find_substring("file ", cfg_passwd);
if (p) {
if (!passwd_file_verify(username, passwd, data, p)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
}
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
/* Oops. No idea what kind of password this is. This should never
* happen as the parser should never create such passwords.
*/
report(LOG_ERR, "%s: Error cannot identify password type %s for %s",
session.peer,
cfg_passwd && cfg_passwd[0] ? cfg_passwd : "<NULL>",
username ? username : "<unknown>");
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
/* verify that this user/password is valid per /etc/passwd. Return 0 if
* invalid.
*/
static int
etc_passwd_file_verify(char *user, char *supplied_passwd,
struct authen_data *data)
{
struct passwd *pw;
char *exp_date;
char *cfg_passwd;
#ifdef SHADOW_PASSWORDS
char buf[12];
#endif /* SHADOW_PASSWORDS */
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
setpwent();
pw = getpwnam(user);
endpwent();
if (pw == NULL) {
/* no entry exists */
return(0);
}
if (*pw->pw_passwd == '\0' ||
supplied_passwd == NULL ||
*supplied_passwd == '\0') {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
cfg_passwd = pw->pw_passwd;
exp_date = pw->pw_shell;
#ifdef SHADOW_PASSWORDS
if (STREQ(pw->pw_passwd, "x")) {
struct spwd *spwd = getspnam(user);
if (!spwd) {
if (debug & DEBUG_PASSWD_FLAG) {
report(LOG_DEBUG, "No entry for %s in shadow file", user);
}
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
if (debug & DEBUG_PASSWD_FLAG) {
report(LOG_DEBUG, "Found entry for %s in shadow file", user);
}
cfg_passwd = spwd->sp_pwdp;
/*
* Sigh. The Solaris shadow password file contains its own
* expiry date as the number of days after the epoch
* (January 1, 1970) when the password expires.
* Convert this to ascii so that the traditional tacacs
* password expiration routines work correctly.
*/
if (spwd->sp_expire > 0) {
long secs = spwd->sp_expire * 24 * 60 * 60;
char *p = ctime(&secs);
memcpy(buf, p + 4, 7);
memcpy(buf + 7, p + 20, 4);
buf[11] = '\0';
exp_date = buf;
}
}
#endif /* SHADOW_PASSWORDS */
/* try to verify the password */
if (!des_verify(supplied_passwd, cfg_passwd)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
}
/* password ok. Check expiry field */
set_expiration_status(exp_date, data);
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
/*
* verify that this user/password is valid per a passwd(5) style database.
* Return 0 if invalid.
*/
static int
passwd_file_verify(char *user, char *supplied_passwd, struct authen_data *data,
char *filename)
{
struct passwd *pw;
char *exp_date;
char *cfg_passwd;
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
if (filename && STREQ(filename, "/etc/passwd")) {
return(etc_passwd_file_verify(user, supplied_passwd, data));
}
/* an alternate filename */
if (!(access(filename, R_OK) == 0)) {
report(LOG_ERR, "%s %s: Cannot access %s for user %s -- %s",
session.peer, session.port, filename, user, strerror(errno));
return(0);
}
pw = tac_passwd_lookup(user, filename);
if (pw == NULL)
/* no entry exists */
return(0);
if (*pw->pw_passwd == '\0' ||
supplied_passwd == NULL ||
*supplied_passwd == '\0') {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
}
cfg_passwd = pw->pw_passwd;
exp_date = pw->pw_shell;
/* try to verify the password */
if (!des_verify(supplied_passwd, cfg_passwd)) {
data->status = TAC_PLUS_AUTHEN_STATUS_FAIL;
return(0);
} else {
data->status = TAC_PLUS_AUTHEN_STATUS_PASS;
}
/* password ok. Check expiry field */
set_expiration_status(exp_date, data);
return(data->status == TAC_PLUS_AUTHEN_STATUS_PASS);
}
/*
* verify a provided password against a des encrypted one. return 1 if
* verified, 0 otherwise.
*/
static int
des_verify(char *users_passwd, char *encrypted_passwd)
{
char *ep;
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "verify %s %s", users_passwd, encrypted_passwd);
if (users_passwd == NULL ||
*users_passwd == '\0' ||
encrypted_passwd == NULL ||
*encrypted_passwd == '\0') {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "verify returns 0");
return(0);
}
ep = (char *)crypt(users_passwd, encrypted_passwd);
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "%s encrypts to %s", users_passwd, ep);
if (strcmp(ep, encrypted_passwd) == 0) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is correct");
return(1);
}
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is incorrect");
return(0);
}
#if HAVE_PAM
/* pam_conv (PAM conversation) callback */
static int
pam_tacacs(int nmsg, const struct pam_message **pmpp, struct pam_response
**prpp, void *appdata_ptr)
{
int i;
struct authen_cont *acp;
char *passwd = (char *)appdata_ptr;
u_char *reply, *rp;
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_tacacs received %d pam_messages", nmsg);
if (nmsg <= 0 || nmsg > PAM_MAX_NUM_MSG)
return(PAM_CONV_ERR);
if ((*prpp = (struct pam_response *)
tac_malloc(nmsg * sizeof(struct pam_response))) == NULL)
return(PAM_BUF_ERR);
memset((struct pam_repsonse *)*prpp, 0,
nmsg * sizeof(struct pam_response));
for (i = 0; i < nmsg; ++i) {
switch (pmpp[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "%s %s: PAM_PROMPT_ECHO_OFF", session.peer,
session.port);
/* pre-supplied password, such as service=PAP, or prompt for it */
if (passwd != NULL && strlen(passwd) > 0) {
prpp[i]->resp = tac_strdup(passwd);
} else {
send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETPASS,
(char *)pmpp[i]->msg,
pmpp[i]->msg ? strlen(pmpp[i]->msg) : 0,
NULL, 0, TAC_PLUS_AUTHEN_FLAG_NOECHO);
reply = get_authen_continue();
if (!reply) {
/* Typically due to a premature connection close */
report(LOG_ERR, "%s %s: Null reply packet, expecting "
"CONTINUE", session.peer, session.port);
goto fail;
}
acp = (struct authen_cont *)(reply + TAC_PLUS_HDR_SIZE);
rp = reply + TAC_PLUS_HDR_SIZE +
TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
/*
* A response to our GETDATA/GETPASS request. Create a
* null-terminated string for authen_data.
*/
prpp[i]->resp = (char *)tac_malloc(acp->user_msg_len + 1);
memcpy(prpp[i]->resp, rp, acp->user_msg_len);
prpp[i]->resp[acp->user_msg_len] = '\0';
free(reply);
}
break;
case PAM_PROMPT_ECHO_ON:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "%s %s: PAM_PROMPT_ECHO_ON", session.peer,
session.port);
send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETDATA,
(char *)pmpp[i]->msg,
pmpp[i]->msg ? strlen(pmpp[i]->msg) : 0,
NULL, 0, 0);
reply = get_authen_continue();
if (!reply) {
/* Typically due to a premature connection close */
report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
session.peer, session.port);
goto fail;
}
acp = (struct authen_cont *)(reply + TAC_PLUS_HDR_SIZE);
rp = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
/*
* A response to our GETDATA/GETPASS request. Create a
* null-terminated string for authen_data.
*/
prpp[i]->resp = (char *)tac_malloc(acp->user_msg_len + 1);
memcpy(prpp[i]->resp, rp, acp->user_msg_len);
prpp[i]->resp[acp->user_msg_len] = '\0';
free(reply);
break;
case PAM_ERROR_MSG:
send_authen_error((char *)pmpp[i]->msg);
break;
case PAM_TEXT_INFO:
#ifdef PAM_MSG_NOCONF
case PAM_MSG_NOCONF:
#endif
/* so we should not receive these with PAM_SILENT set */
break;
#ifdef PAM_CONV_INTERRUPT
case PAM_CONV_INTERRUPT:
return(PAM_SUCCESS);
#endif
default:
report(LOG_ERR, "%s %s: unknown pam_conv message type %d",
session.peer, session.port, pmpp[i]->msg_style);
goto fail;
}
}
return(PAM_SUCCESS);
fail:
for (i = 0; i < nmsg; ++i) {
if ((*prpp)[i].resp != NULL) {
memset((*prpp)[i].resp, 0, strlen((*prpp)[i].resp));
free((*prpp)[i].resp);
}
}
memset(*prpp, 0, nmsg * sizeof(struct pam_response));
free(*prpp);
*prpp = NULL;
return(PAM_CONV_ERR);
}
/*
* verify a provided user/password via PAM.
* return 1 if verified, 0 otherwise.
*/
static int
pam_verify(char *user, char *passwd, struct authen_data *data)
{
int err;
int acct;
int pam_flag;
struct pam_conv conv = { pam_tacacs, passwd };
pam_handle_t *pamh = NULL;
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_verify %s %s", user, passwd);
if (user == NULL /* XXX || passwd == NULL || *passwd == '\0'*/) {
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_verify returns 0");
return(0);
}
if ((err = pam_start(progname, user, &conv, &pamh)) != PAM_SUCCESS) {
report(LOG_ERR, "pam_start failed: %s", pam_strerror(pamh, err));
pam_end(pamh, err);
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_verify returns 0");
return(0);
}
/* don't ignore PAM messages if password debugging is on */
pam_flag = (debug & DEBUG_PASSWD_FLAG) ? 0 : PAM_SILENT;
switch ((err = pam_authenticate(pamh, pam_flag))) {
case PAM_SUCCESS:
switch((acct = pam_acct_mgmt(pamh, pam_flag))) {
case PAM_SUCCESS:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_acct_mgmt returns PAM_SUCCESS");
pam_end(pamh, err);
if (debug & DEBUG_PASSWD_FLAG)
report (LOG_DEBUG, "pam_verify returns 1");
return(1);
break;
case PAM_NEW_AUTHTOK_REQD:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_acct_mgmt returns PAM_NEW_AUTHTOK_REQD");
if (data->server_msg)
free(data->server_msg);
data->server_msg = tac_strdup("Password will expire soon, please change it immediately");
break;
case PAM_AUTHTOK_EXPIRED:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_acct_mgmt returns PAM_AUTHTOK_EXPIRED");
if (data->server_msg)
free(data->server_msg);
data->server_msg = tac_strdup("Password has expired");
break;
case PAM_ACCT_EXPIRED:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_acct_mgmt returns PAM_ACCT_EXPIRED");
if (data->server_msg)
free(data->server_msg);
data->server_msg = tac_strdup("Account has expired");
break;
default:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_account_mgmt returned unknown value %d",
acct);
break;
}
break;
case PAM_USER_UNKNOWN:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Unknown user");
break;
case PAM_AUTH_ERR:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "Password is incorrect");
break;
default:
if (debug & DEBUG_PASSWD_FLAG)
report(LOG_DEBUG, "pam_authenticate() returned unknown value %d",
err);
break;
}
pam_end(pamh, err);
return(0);
}
#endif