/* 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 #include /* for ntohl() */ #include "authen.h" #include "packet.h" #include "report.h" #include "utils.h" #include "choose_authen.h" #include "do_author.h" /* for "struct identity" */ #include "main.h" #include "cfgfile.h" #ifdef TCPWRAPPER #include "tcpwrap.h" #endif static void do_start TAC_ARGS((u_char *pak)); static int choose TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); static void authenticate TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); /* Configurable: */ #define TAC_PLUS_MAX_ITERATIONS 50 /* * Come here when we receive an authentication START packet */ void authen TAC_ARGS((u_char *pak)); 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) || ((unsigned long) ntohl(hdr->datalength) != (unsigned long)(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 TAC_ARGS((u_char *pak)); 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; cfg_request_identity(&identity); /* 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 TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); 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) { 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 */ } static void authenticate TAC_ARGS((struct authen_data *datap, struct authen_type *typep)); /* 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) TAC_ARGS((struct authen_data *data)); 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 */ } }