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

static int choose();
static void authenticate();
static void do_start();

/*
 *  Come here when we receive an authentication START packet
 */

void
authen(pak)
u_char *pak;
{
    char msg[55];
    struct authen_start *start;
    HDR *hdr;

    hdr = (HDR *) pak;
    start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE);

    if ((hdr->seq_no != 1) ||
	(ntohl(hdr->datalength) != TAC_AUTHEN_START_FIXED_FIELDS_SIZE + 
	 start->user_len + start->port_len + start->rem_addr_len +
	 start->data_len)) {
	send_authen_error("Invalid AUTHEN/START packet (check keys)");
	return;
    }

    switch (start->action) {
    case TAC_PLUS_AUTHEN_LOGIN:
    case TAC_PLUS_AUTHEN_SENDAUTH:
    case TAC_PLUS_AUTHEN_SENDPASS:
	do_start(pak);
	return;

    default:
	sprintf(msg, "Invalid AUTHEN/START action=%d", start->action);
	send_authen_error(msg);
	return;
    }
}

/*
 * We have a valid AUTHEN/START packet. Fill out data structures and
 * attempt to authenticate.
 */

static void
do_start(pak)
u_char *pak;
{
    struct identity identity;
    struct authen_data authen_data;
    struct authen_type authen_type;
    struct authen_start *start;
    u_char *p;
    int ret;

    if (debug & DEBUG_PACKET_FLAG)
	report(LOG_DEBUG, "Authen Start request");

    /* fixed fields of this packet */
    start = (struct authen_start *) (pak + TAC_PLUS_HDR_SIZE);

    /* variable length data starts here */
    p = pak + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_START_FIXED_FIELDS_SIZE;

    /* The identity structure */

    /* zero out identity struct so that all strings can be NULL terminated */
    bzero(&identity, sizeof(struct identity));

    identity.username = tac_make_string(p, (int)start->user_len);
    p += start->user_len;

    identity.NAS_name = tac_strdup(session.peer);

    identity.NAS_port = tac_make_string(p, (int)start->port_len);
    p += start->port_len;

    if (start->port_len <= 0) {
	strcpy(session.port, "unknown-port");
    } else {
	strcpy(session.port, identity.NAS_port);
    }

    identity.NAC_address = tac_make_string(p, (int)start->rem_addr_len);
    p += start->rem_addr_len;

    identity.priv_lvl = start->priv_lvl;

    /* The authen_data structure */

    bzero(&authen_data, sizeof(struct authen_data));

    authen_data.NAS_id      = &identity;
    authen_data.action      = start->action;
    authen_data.service     = start->service;
    authen_data.type        = start->authen_type;
    authen_data.client_dlen = start->data_len;

    authen_data.client_data = tac_malloc(start->data_len);
    bcopy(p, authen_data.client_data, start->data_len);

    /* The authen_type structure */

    bzero(&authen_type, sizeof(struct authen_type));

    authen_type.authen_type = start->authen_type;

    /* All data structures are now initialised. Now see if we can
     * authenticate this puppy. Begin by choosing a suitable
     * authentication function to call to actually do the work. */

#ifdef TCPWRAPPER
if (check_from_wrap(&identity)) {   
#endif
    ret = choose(&authen_data, &authen_type);
    switch (ret) {
    case 1:
	/* A successful choice. Authenticate */
	authenticate(&authen_data, &authen_type);
	break;

    case 0:
	/* We lost our connection, aborted, or something dreadful happened */
	break;
    }
#ifdef TCPWRAPPER
} else {
send_authen_error("You are not allowed to access here");
}
#endif
    /* free data structures */
    if (authen_data.server_msg) {
	free(authen_data.server_msg);
	authen_data.server_msg = NULL;
    }
    if (authen_data.server_data) {
	free(authen_data.server_data);
	authen_data.server_data = NULL;
    }

    if (authen_data.client_msg) {
	//free(authen_data.client_msg);
	authen_data.client_msg = NULL;
    }

    if (authen_data.client_data) {
	free(authen_data.client_data);
	authen_data.client_data = NULL;
    }

    if (authen_data.method_data) {
	report(LOG_ERR, 
	       "%s: Method data not set to NULL after authentication",
	       session.peer);
    }
    free(identity.username);
    //free(identity.NAS_name);
    free(identity.NAS_port);
    free(identity.NAC_address);
    return;
}

/* Choose an authentication function. Return 1 if we successfully
   chose a function.  0 if we couldn't make a choice for some reason */

static int 
choose(datap, typep)
struct authen_data *datap;
struct authen_type *typep;
{
    int iterations = 0;
    int status;
    char *prompt;
    struct authen_cont *cont;
    u_char *reply;
    u_char *p;
    struct identity *identp;

    while (1) {

	/* check interation counter here */

	if (++iterations >= TAC_PLUS_MAX_ITERATIONS) {
	    report(LOG_ERR, "%s: %s Too many iterations for choose_authen",
		   session.peer, 
		   session.port);
	    return (0);
	}
	status = choose_authen(datap, typep);

	if (status && (debug & DEBUG_PACKET_FLAG))
	    report(LOG_DEBUG, "choose_authen returns %d", status);

	switch (status) {

	case CHOOSE_BADTYPE: /* FIXME */
	default:
	    send_authen_error("choose_authen: unexpected failure return");
	    return (0);

	case CHOOSE_OK:
	    if (debug & DEBUG_PACKET_FLAG)
		report(LOG_DEBUG, "choose_authen chose %s", typep->authen_name);
	    return (1);

	case CHOOSE_FAILED:
	    send_authen_error("choose_authen: unacceptable authen method");
	    return (0);

	case CHOOSE_GETUSER:
	    /* respond with GETUSER containing an optional message from
	     * authen_data.server_msg. */

	    datap->status = TAC_PLUS_AUTHEN_STATUS_GETUSER;
	    if (datap->service == TAC_PLUS_AUTHEN_SVC_LOGIN) {
		if ( !(prompt=(char *)cfg_get_host_prompt(datap->NAS_id->NAS_name)) )
		prompt = "\nUser Access Verification\n\nUsername: ";
	    } else {
		prompt = "Username: ";
	    }
	    send_authen_reply(TAC_PLUS_AUTHEN_STATUS_GETUSER, /* status */
			      prompt, /* msg */
			      strlen(prompt), /* msg_len */
			      datap->server_data,
			      datap->server_dlen,
			      0 /* flags */);

	    if (datap->server_data) {
		free(datap->server_data);
		datap->server_dlen = 0;
	    }
	    /* expect a CONT from the NAS */
	    reply = get_authen_continue();
	    if (reply == NULL) {
		/* Typically premature close of connection */
		report(LOG_ERR, "%s %s: Null reply packet, expecting CONTINUE",
		       session.peer, session.port);
		return (0);
	    }

	    cont = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);

	    if (cont->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {
		char buf[65537];
		buf[0] = '\0';
		session.aborted = 1;

		if (cont->user_data_len) {
		    /* An abort message exists. Log it */
		    p = reply + TAC_PLUS_HDR_SIZE + 
			TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE + cont->user_msg_len;

		    bcopy(p, buf, cont->user_data_len);
		    buf[cont->user_data_len] = '\0';
		}
		report(LOG_INFO, "%s %s: Login aborted by request -- msg: %s", 
		       session.peer, session.port, buf);
		free(reply);
		return(0);
	    }

	    p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;

	    identp = datap->NAS_id;

	    if (identp->username) {
		free(identp->username);
	    }
	    identp->username = tac_make_string(p, cont->user_msg_len);
	    free(reply);
	}
    }
    /* NOTREACHED */
}

/* Perform authentication assuming we have successfully chosen an
   authentication method */
static void
authenticate(datap, typep)
struct authen_data *datap;
struct authen_type *typep;
{
    int iterations = 0;
    u_char *reply, *p;
    struct authen_cont *cont;
    int (*func) ();

    if (debug & DEBUG_PACKET_FLAG)
	report(LOG_DEBUG, "Calling authentication function");

    func = typep->authen_func;

    if (!func) {
	send_authen_error("authenticate: cannot find function pointer");
	return;
    }

    while (1) {
	if (session.aborted)
	    return;

	if (++iterations >= TAC_PLUS_MAX_ITERATIONS) {
	    send_authen_error("Too many iterations while authenticating");
	    return;
	}

	if ((*func) (datap)) {
	    send_authen_error("Unexpected authentication function failure");
	    return;
	}

	switch (datap->status) {

	default:
	    send_authen_error("Illegal status value from authentication function");
	    return;

	case TAC_PLUS_AUTHEN_STATUS_PASS:
	    /* A successful authentication */
	    send_authen_reply(TAC_PLUS_AUTHEN_STATUS_PASS,
			      datap->server_msg,
			      datap->server_msg ? strlen(datap->server_msg) : 0,
			      datap->server_data,
			      datap->server_dlen,
			      0);
	    return;

	case TAC_PLUS_AUTHEN_STATUS_ERROR:
	    /* never supposed to happen. reply with a server_msg if any, and
	     * bail out */
	    send_authen_error(datap->server_msg ? datap->server_msg :
			    "authentication function: unspecified failure");
	    return;

	case TAC_PLUS_AUTHEN_STATUS_FAIL:

	    /* An invalid user/password combination */
	    send_authen_reply(TAC_PLUS_AUTHEN_STATUS_FAIL,
			      datap->server_msg,
			      datap->server_msg ? strlen(datap->server_msg) : 0,
			      NULL,
			      0,
			      0);
	    
	    return;

	case TAC_PLUS_AUTHEN_STATUS_GETUSER:
	case TAC_PLUS_AUTHEN_STATUS_GETPASS:
	case TAC_PLUS_AUTHEN_STATUS_GETDATA:

	    /* ship GETPASS/GETDATA containing
	     * datap->server_msg to NAS. */

	    send_authen_reply(datap->status,
			      datap->server_msg,
			      datap->server_msg ? strlen(datap->server_msg) : 0,
			      datap->server_data,
			      datap->server_dlen,
			      datap->flags);

	    datap->flags = 0;

	    if (datap->server_msg) {
		free(datap->server_msg);
		datap->server_msg = NULL;
	    }
	    if (datap->server_data) {
		free(datap->server_data);
		datap->server_data = NULL;
	    }
	    if (datap->client_msg) {
		free(datap->client_msg);
		datap->client_msg = NULL;
	    }
	    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);

		/* Tell the authentication function it should clean up
		   any private data */

		datap->flags |= TAC_PLUS_CONTINUE_FLAG_ABORT;

		if (datap->method_data)
		    ((*func) (datap));

		datap->flags = 0;
		return;
	    }

	    cont = (struct authen_cont *) (reply + TAC_PLUS_HDR_SIZE);

	    if (cont->flags & TAC_PLUS_CONTINUE_FLAG_ABORT) {

		session.aborted = 1;

		/* Tell the authentication function to clean up
		   its private data, if there is any */

		datap->flags |= TAC_PLUS_CONTINUE_FLAG_ABORT;
		if (datap->method_data)
		    ((*func) (datap));
		datap->flags = 0;

		if (cont->user_data_len) {

		    /* An abort message exists. Create a
		       null-terminated string for authen_data */

		    datap->client_data = (char *) 
			tac_malloc(cont->user_data_len + 1);

		    p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE +
			cont->user_msg_len;

		    bcopy(p, datap->client_data, cont->user_data_len);
		    datap->client_data[cont->user_data_len] = '\0';
		}

		free(reply);
		return;
	    }

	    p = reply + TAC_PLUS_HDR_SIZE + TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;

	    switch (datap->status) {

	    case TAC_PLUS_AUTHEN_STATUS_GETDATA:
	    case TAC_PLUS_AUTHEN_STATUS_GETPASS:
		/* A response to our GETDATA/GETPASS request. Create a
		 * null-terminated string for authen_data */
		datap->client_msg = (char *) tac_malloc(cont->user_msg_len + 1);
		bcopy(p, datap->client_msg, cont->user_msg_len);
		datap->client_msg[cont->user_msg_len] = '\0';
		free(reply);
		continue;

	    case TAC_PLUS_AUTHEN_STATUS_GETUSER:
	    default:
		report(LOG_ERR, "%s: authenticate: cannot happen",
		       session.peer);
		send_authen_error("authenticate: cannot happen");
		free(reply);
		return;
	    }
	}
	/* NOTREACHED */
    }
}

