/* 
   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"

static int choose_login();
static int choose_sendpass();
static int choose_sendauth();

int 
get_minor_version()
{
    return(session.version & ~TAC_PLUS_MAJOR_VER_MASK);
}

/* 
 * Choose an authentication function. Return CHOOSE_OK if chosen,
 * CHOOSE_GETUSER if we need a username, CHOOSE_FAILED on failure 
 */

int
choose_authen(data, type)
struct authen_data *data;
struct authen_type *type;
{
    char *name = data->NAS_id->username;

    switch (data->action) {
    case TAC_PLUS_AUTHEN_SENDPASS:
	return(choose_sendpass(data, type));

    case TAC_PLUS_AUTHEN_SENDAUTH:
	return(choose_sendauth(data, type));

    case TAC_PLUS_AUTHEN_LOGIN:
	/* For enabling, enable_fn handles everything. Must be minor
	 * version zero 
	 */
	if (data->service == TAC_PLUS_AUTHEN_SVC_ENABLE) {
	    if (session.version != TAC_PLUS_VER_0) {
		/* must be version 0 */
		break;
	    }
	    type->authen_func = enable_fn;
	    strcpy(type->authen_name, "enable_fn");
	    return (CHOOSE_OK);
	}
	return(choose_login(data, type));

    case TAC_PLUS_AUTHEN_CHPASS:
	/* we don't support chpass */
	return(CHOOSE_FAILED);

    default:
	break;
    }

    /* never heard of this lot */
    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
	   session.peer, 
	   session.port,
	   name ? name : "<unknown>",
	   session.version,
	   data->action,
	   type->authen_type);

    return(CHOOSE_FAILED);
}

/* Choose an authentication function for action == LOGIN, service != enable */
static int
choose_login(data, type)
struct authen_data *data;
struct authen_type *type;
{
    char *name = data->NAS_id->username;
    char *cfg_passwd;

    switch(type->authen_type) {
    case TAC_PLUS_AUTHEN_TYPE_ASCII:
	if (session.version != TAC_PLUS_VER_0) {
	    break;
	}

	if (!name[0]) {
	    /* request a user name if not already supplied */
	    return (CHOOSE_GETUSER);
	}

	/* Does this user require s/key? */
	cfg_passwd = cfg_get_login_secret(name, TAC_PLUS_RECURSE);
	if (cfg_passwd && STREQ(cfg_passwd, "skey")) {
	    if (debug & DEBUG_PASSWD_FLAG)
		report(LOG_DEBUG, "%s %s: user %s requires skey", 
		       session.peer, session.port, name);
#ifdef SKEY
	    type->authen_func = skey_fn;
	    strcpy(type->authen_name, "skey_fn");
	    return (CHOOSE_OK);
#else /* SKEY */
	    report(LOG_ERR, 
		   "%s %s: user %s s/key support has not been compiled in",
		   name ? name : "<unknown>",
		   session.peer, session.port);
	    return(CHOOSE_FAILED);
#endif	/* SKEY */
	}

	/* Not an skey user. Must be none, des, cleartext or file password */
	type->authen_func = default_fn;
	strcpy(type->authen_name, "default_fn");
	return (CHOOSE_OK);

    case TAC_PLUS_AUTHEN_TYPE_ARAP:
#ifndef ARAP_DES
	/* 
	 * If we have no des code we can't do ARAP via SENDAUTH. We'll
	 * have to do it via SENDPASS. Return a down-rev reply
	 * packet and hope the NAS is smart enough to deal with it.
	 */
	session.version = TAC_PLUS_VER_0;
	report(LOG_ERR, "%s %s: user %s DES is unavailable",
	       name ? name : "<unknown>", session.peer, session.port);
	return (CHOOSE_FAILED);
#endif /* ARAP_DES */
	/* FALLTHROUGH */

#ifdef MSCHAP
    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
#ifndef MSCHAP_DES
	/* 
	 * If we have no des code we can't do MSCHAP via LOGIN. We'll
	 * have to do it via SENDPASS. Return a down-rev reply
	 * packet and hope the NAS is smart enough to deal with it.
	 */
	session.version = TAC_PLUS_VER_0;
	report(LOG_ERR, "%s %s: user %s DES is unavailable",
	       name ? name : "<unknown>", session.peer, session.port);
	return (CHOOSE_FAILED);
#endif /* MSCHAP_DES */
	/* FALLTHROUGH */
#endif /* MSCHAP */

    case TAC_PLUS_AUTHEN_TYPE_PAP:
    case TAC_PLUS_AUTHEN_TYPE_CHAP:
	if (session.version == TAC_PLUS_VER_0) {
	    type->authen_func = default_v0_fn;
	    strcpy(type->authen_name, "default_v0_fn");
	    return (CHOOSE_OK);
	}

	/* Version 1 login/[pap|chap|arap].
	 * The username must in the initial START packet 
	 */
	if (!name[0]) {
	    report(LOG_ERR, "%s %s: No user in START packet for PAP/CHAP/ARAP",
		   session.peer, session.port);
	    return (CHOOSE_FAILED);
	}
	type->authen_func = default_fn;
	strcpy(type->authen_name, "default_fn");
	return (CHOOSE_OK);

    default:
	break;
    }

    /* Illegal value combination */
    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
	   session.peer, 
	   session.port,
	   name ? name : "<unknown>",
	   session.version,
	   data->action,
	   type->authen_type);
    return(CHOOSE_FAILED);
}

static int
choose_sendauth(data, type)
struct authen_data *data;
struct authen_type *type;
{
    char *name = data->NAS_id->username;

    switch (type->authen_type) {
#ifdef MSCHAP
    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
#ifndef MSCHAP_DES
	/* 
	 * If we have no des code we can't do MSCHAP via SENDAUTH. We'll
	 * have to do it via SENDPASS. Return a down-rev reply
	 * packet and hope the NAS is smart enough to deal with it.
	 */
	session.version = TAC_PLUS_VER_0;
	report(LOG_ERR, "%s %s: user %s DES is unavailable",
	       name ? name : "<unknown>", session.peer, session.port);
	return (CHOOSE_FAILED);
#endif /* MSCHAP_DES */
	/* FALLTHROUGH */
#endif /* MSCHAP */

    case TAC_PLUS_AUTHEN_TYPE_CHAP:
    case TAC_PLUS_AUTHEN_TYPE_PAP:
	/* Must be minor version 1 */
	if (session.version != TAC_PLUS_VER_1) {
	    break;
	}

	/* The start packet must contain the username */
	if (!name[0]) {
	    return (CHOOSE_FAILED);
	}
	type->authen_func = sendauth_fn;
	strcpy(type->authen_name, "sendauth_fn");
	return (CHOOSE_OK);

    default:
	break;
    }
    /* Illegal value combination */
    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
	   session.peer, 
	   session.port,
	   name ? name : "<unknown>",
	   session.version,
	   data->action,
	   type->authen_type);

    return(CHOOSE_FAILED);
}

/* Compatibility routine for (obsolete) minor version == 0 */
static int
choose_sendpass(data, type)
struct authen_data *data;
struct authen_type *type;
{
    char *name = data->NAS_id->username;

    switch (type->authen_type) {
    case TAC_PLUS_AUTHEN_TYPE_CHAP:
#ifdef MSCHAP
    case TAC_PLUS_AUTHEN_TYPE_MSCHAP:
#endif /* MSCHAP */
    case TAC_PLUS_AUTHEN_TYPE_PAP:
    case TAC_PLUS_AUTHEN_TYPE_ARAP:
	/* must be minor version 0 */
	if (TAC_PLUS_VER_0 != session.version) {
	    break;
	}

	/* We need a username */
	if (!name[0]) {
	    return (CHOOSE_GETUSER);
	}

	type->authen_func = sendpass_fn;
	strcpy(type->authen_name, "sendpass_fn");
	return (CHOOSE_OK);

    default:
	break;
    }

    /* Illegal value combination */
    report(LOG_ERR, "%s: %s %s Illegal packet ver=%d action=%d type=%d",
	   session.peer, 
	   session.port,
	   name ? name : "<unknown>",
	   session.version, 
	   data->action,
	   type->authen_type);

    return(CHOOSE_FAILED);
}

