Ethereal-dev: [Ethereal-dev] TACACS+ dissector

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: Emanuele Caratti <wiz@xxxxxxxxx>
Date: Sun, 8 Oct 2000 12:16:36 +0200
Hi!

I was in the need of a dissector for tacacs+, so I wrote this one...

If you use this dissector you'll lose the ability to detect udp packet on port
49 as tacacs/xtacacs, because I've not yet merged the old packet-tacacs.c with
mine.

It isn't finished yet, but it is able to dissect authentication and accounting
packet correctly.

To use this dissector you need to link ethereal to libcryto included with
openssl (I also need this lib form ucd-snmp, but this is another story...;-)
).
To set the encryption key, just set the environment variable TACPLUS_PASS.

On my todo list I have:
 - Authorization packet handling
 - tacacs/xtacacs 
 - converting to use tvbuff ( I found this to late... )
 - bug fixes.

Comments and/or suggestions will be much appreciated....

Ciao,
 Emanuele
/* packet-tacacs.c
 * Routines for cisco TACACS+ packet dissection
 * Copyright 2000, Emanuele Caratti <wiz@xxxxxx>
 *
 * $Id: packet-tacacs.c,v 1.12 2000/10/08 09:58:00 wiz Exp wiz $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxx>
 * Copyright 1998 Gerald Combs
 *
 * Copied from old packet-tacacs.c 
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#include <string.h>
#include <glib.h>
#include "packet.h"
#include "packet-tacacs.h"

static int proto_tacacs = -1;
static int hf_tacacs_version = -1;
static int hf_tacacs_type = -1;
static int hf_tacacs_seq_no = -1;
static int hf_tacacs_flags = -1;
static int hf_tacacs_flags_singlecon = -1;
static int hf_tacacs_flags_unencrypted = -1;
static int hf_tacacs_session_id = -1;
static int hf_tacacs_length = -1;

static gint ett_tacacs_hdr = -1;
static gint ett_tacacs_flags = -1;
static gint ett_tacacs_body  = -1;

#define UDP_PORT_TACACS	49
#define TCP_PORT_TACACS	49


static value_string tacplus_type_vals[] = {
	{TAC_PLUS_AUTHEN,	"Authentication"},
	{TAC_PLUS_AUTHOR,	"Authorization"	},
	{TAC_PLUS_ACCT,		"Accounting"	},
	{0, NULL}};

static value_string tacplus_authen_action_vals[] = {
	{TAC_PLUS_AUTHEN_LOGIN, "Inbound Login"},
	{TAC_PLUS_AUTHEN_CHPASS, "Change password request"},
	{TAC_PLUS_AUTHEN_SENDPASS, "Send password request"},
	{TAC_PLUS_AUTHEN_SENDAUTH, "Outbound Request (SENDAUTH)"},
	{0, NULL}};

static value_string tacplus_authen_priv_lvl_vals[] = {
	{TAC_PLUS_PRIV_LVL_MAX, "TAC_PLUS_PRIV_LVL_MAX"},
	{TAC_PLUS_PRIV_LVL_ROOT, "TAC_PLUS_PRIV_LVL_ROOT"},
	{TAC_PLUS_PRIV_LVL_USER, "TAC_PLUS_PRIV_LVL_USER"},
	{TAC_PLUS_PRIV_LVL_MIN, "TAC_PLUS_PRIV_LVL_MIN"},
	{0, NULL}};

static value_string tacplus_authen_type_vals[] = {
	{TAC_PLUS_AUTHEN_TYPE_ASCII,	"ASCII"},
	{TAC_PLUS_AUTHEN_TYPE_PAP,		"PAP"},
	{TAC_PLUS_AUTHEN_TYPE_CHAP,		"CHAP"},
	{TAC_PLUS_AUTHEN_TYPE_ARAP,		"ARAP"},
	{TAC_PLUS_AUTHEN_TYPE_MSCHAP,	"MS-CHAP"},
	{0, NULL}};

static value_string tacplus_authen_service_vals[] = {
	{TAC_PLUS_AUTHEN_SVC_NONE,		"TAC_PLUS_AUTHEN_SVC_NONE"},
	{TAC_PLUS_AUTHEN_SVC_LOGIN,		"Login"	},
	{TAC_PLUS_AUTHEN_SVC_ENABLE,	"ENABLE"},
	{TAC_PLUS_AUTHEN_SVC_PPP,		"PPP"	},
	{TAC_PLUS_AUTHEN_SVC_ARAP,		"ARAP"	},
	{TAC_PLUS_AUTHEN_SVC_PT,		"TAC_PLUS_AUTHEN_SVC_PT"},
	{TAC_PLUS_AUTHEN_SVC_RCMD,		"TAC_PLUS_AUTHEN_SVC_RCMD"},
	{TAC_PLUS_AUTHEN_SVC_X25,		"TAC_PLUS_AUTHEN_SVC_X25"},
	{TAC_PLUS_AUTHEN_SVC_NASI,		"TAC_PLUS_AUTHEN_SVC_NASI"},
	{TAC_PLUS_AUTHEN_SVC_FWPROXY,	"TAC_PLUS_AUTHEN_SVC_FWPROXY"},
	{0, NULL}};

static value_string tacplus_reply_status_vals[] = {
	{TAC_PLUS_AUTHEN_STATUS_PASS, 		"Authentication Passed"},
	{TAC_PLUS_AUTHEN_STATUS_FAIL, 		"Authentication Failed"},
	{TAC_PLUS_AUTHEN_STATUS_GETDATA,	"Send Data"},
	{TAC_PLUS_AUTHEN_STATUS_GETUSER,	"Send Username"},
	{TAC_PLUS_AUTHEN_STATUS_GETPASS,	"Send Password"},
	{TAC_PLUS_AUTHEN_STATUS_RESTART,	"Restart Authentication Sequence"},
	{TAC_PLUS_AUTHEN_STATUS_ERROR,		"Unrecoverable Error"},
	{TAC_PLUS_AUTHEN_STATUS_FOLLOW,		"Use Alternate Server"},
	{0, NULL}};


/* from tac-rfc.1.78.txt */
#define TAC_PLUS_UNENCRYPTED_FLAG		0x01
#define TAC_PLUS_SINGLE_CONNECT_FLAG	0x04
#define TAC_PLUS_MAJOR_VER				0xc0
#define TAC_PLUS_MINOR_VER_DEFAULT		0x00
#define TAC_PLUS_MINOR_VER_ONE			0x01
#define TAC_PLUS_VER_DEFAULT		(TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_DEFAULT)
#define TAC_PLUS_VER_ONE  			(TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_ONE)

static const true_false_string flags_set_truth = {
	"Yes",
	"No"
};

static char secret_key[128] = "";
static int md5_xor( u_char*, char* );
static char scratch_buffer[1024];

static void
dissect_tacplus_hdr( proto_tree *tacacs_hdr_tree, const u_char *pd, int offset )
{
	tacplus_pkt *tac=(tacplus_pkt*)&pd[offset];
	proto_tree *flags_tree;
	proto_item *tf;
	gchar *codestrval;
	gchar flags[]="<none>";

	codestrval=match_strval( tac->hdr.type, tacplus_type_vals );
	if (codestrval==NULL)
		codestrval="Unknown Packet";

	/* Minor version */
	proto_tree_add_uint_format(tacacs_hdr_tree, hf_tacacs_version, NullTVB,
			H_VER_OFF(offset), 1, tac->hdr.version&0x0f, "Minor version: %d", tac->hdr.version&0x0f );

	/* Packet type: Authen, Author, Account */
	proto_tree_add_uint(tacacs_hdr_tree, hf_tacacs_type, NullTVB,
			H_TYPE_OFF(offset), 1, tac->hdr.type );

	proto_item_set_text( tacacs_hdr_tree, "Header version %d, %s", tac->hdr.version&0x0f, codestrval  );
	/* Sequence Number */
	proto_tree_add_uint( tacacs_hdr_tree, hf_tacacs_seq_no, NullTVB,
			H_SEQ_NO_OFF(offset), 1, tac->hdr.type );

	if( tac->hdr.flags & TAC_PLUS_UNENCRYPTED_FLAG ){
		flags[0]='U';
		flags[1]='\0';
	}
	if( tac->hdr.flags & TAC_PLUS_SINGLE_CONNECT_FLAG ){
		if( *flags == 'U' ){
			strcat(flags,", S");
		} else {
			flags[0]='S';
			flags[1]='\0';
		}
	}
	/* Flags */
	tf=proto_tree_add_uint_format( tacacs_hdr_tree, hf_tacacs_flags, NullTVB,
			H_FLAGS_OFF(offset),1,tac->hdr.flags, "Flags: 0x%02x (%s)", tac->hdr.flags, flags );
	flags_tree = proto_item_add_subtree( tf, ett_tacacs_flags );

	proto_tree_add_boolean( flags_tree, hf_tacacs_flags_unencrypted,
			NullTVB, H_FLAGS_OFF(offset),1, ( tac->hdr.flags & TAC_PLUS_UNENCRYPTED_FLAG ) );

	proto_tree_add_boolean( flags_tree, hf_tacacs_flags_singlecon,
			NullTVB, H_FLAGS_OFF(offset),1, ( tac->hdr.flags & TAC_PLUS_SINGLE_CONNECT_FLAG ) );

	/* Session */
	proto_tree_add_uint( tacacs_hdr_tree, hf_tacacs_session_id,
			NullTVB, H_SESSION_ID_OFF(offset), 4,
			tac->hdr.session_id );

	/* Data Len */
	proto_tree_add_text( tacacs_hdr_tree, NullTVB, H_LENGTH_OFF(offset), sizeof(gint),
			"TACACS+ Packet Body Length: %u", ntohl(tac->hdr.length)  );

}

static void
proto_tree_add_tacplus_common_fields( proto_tree *tree, int offset, const u_char *common_fields, const u_char *var_fields )
{
	char *codestrval;
	/* priv_lvl */
	proto_tree_add_text( tree, NullTVB, offset, 1,
			"Privilege Level: %d", *common_fields);

	offset++;
	common_fields++;

	/* authen_type */
	codestrval=match_strval( *common_fields, tacplus_authen_type_vals );
	if (codestrval==NULL)
		codestrval="Unknown Packet";
	proto_tree_add_text( tree, NullTVB, offset, 1,
			"Authentication type: 0x%01x (%s)",
			*common_fields, codestrval );

	offset++;
	common_fields++;

	/* service */
	codestrval=match_strval( *common_fields, tacplus_authen_service_vals );
	if (codestrval==NULL)
		codestrval="Unknown Packet";
	proto_tree_add_text( tree, NullTVB, offset, 1,
			"Service: 0x%01x (%s)",
			*common_fields, codestrval );

	/* user_len && user */
	proto_tree_add_text( tree, NullTVB, ++offset, 1,
			"User len: %d", *++common_fields );

	if( *common_fields ){
		strncpy( scratch_buffer, var_fields, *common_fields );
		scratch_buffer[*common_fields]='\0';
		proto_tree_add_text( tree, NullTVB, offset+var_fields-common_fields, *common_fields,
				"User: %s", scratch_buffer );
		var_fields+=*common_fields;
	}


	/* port_len &&  port */
	proto_tree_add_text( tree, NullTVB, ++offset, 1,
			"Port len: %d", *++common_fields );
	if( *common_fields ){
		strncpy( scratch_buffer, var_fields, *common_fields );
		scratch_buffer[*common_fields]='\0';
		proto_tree_add_text( tree, NullTVB, offset+var_fields-common_fields, *common_fields,
				"Port: %s", scratch_buffer );
		var_fields+=*common_fields;
	}

	/* rem_addr_len && rem_addr */
	proto_tree_add_text( tree, NullTVB, ++offset, 1,
			"Remaddr len: %d", *++common_fields );
	if( *common_fields ){
		strncpy( scratch_buffer, var_fields, *common_fields );
		scratch_buffer[*common_fields]='\0';
		proto_tree_add_text( tree, NullTVB, offset+var_fields-common_fields, *common_fields,
				"Remote Address: %s", scratch_buffer );
		var_fields+=*common_fields;
	}
}

static void
dissect_tacplus_body_authen_req( proto_tree *tacplus_tree, const tacplus_authen_start* tac_body, int offset )
{
	gchar *codestrval;
	/* Action */
	codestrval=match_strval( tac_body->action, tacplus_authen_action_vals );
	if (codestrval==NULL)
		codestrval="Unknown Packet";
	proto_tree_add_text( tacplus_tree, NullTVB,
			AUTHEN_S_ACTION_OFF(offset), 1, 
			"Action: 0x%01x (%s)", tac_body->action, codestrval );

	proto_tree_add_tacplus_common_fields( tacplus_tree ,
			AUTHEN_S_PRIV_LVL_OFF(offset),
			&tac_body->priv_lvl,
			&tac_body->vardata[0]
			);
	
	proto_tree_add_text( tacplus_tree, NullTVB, AUTHEN_S_DATA_LEN_OFF(offset), 1,
			"Data: %d %s", tac_body->data_len, 
			(tac_body->authen_type!=TAC_PLUS_AUTHEN_TYPE_ASCII?"(Not Used)":"") );

	if( tac_body->data_len ){
		/* Data is NAS dependent.... */
		proto_tree_add_text( tacplus_tree, NullTVB,
				tac_body->vardata[
					tac_body->user_len+tac_body->port_len+tac_body->rem_addr_len
					]
				,
				tac_body->data_len, "Data" );
	}
}

static void
dissect_tacplus_body_authen_req_cont( proto_tree *tacplus_tree, const tacplus_authen_continue* tac_body, int offset )
{
	/* Action */
//	if( tac->hdr.seq_no == 1 )
	int t_len;

	proto_tree_add_text( tacplus_tree, NullTVB,
			AUTHEN_R_FLAGS_OFF(offset), 1, "Flags: 0x%02x %s",
			tac_body->flags,
			(tac_body->flags&TAC_PLUS_CONTINUE_FLAG_ABORT?"(Abort)":"") );

	t_len=ntohs(tac_body->user_len);
	proto_tree_add_text( tacplus_tree, NullTVB, AUTHEN_C_USER_LEN_OFF(offset), 2 ,
			"User length: %d", t_len );
	if( tac_body->user_len ){
		strncpy( scratch_buffer, tac_body->vardata, t_len );
		scratch_buffer[t_len]='\0';
		proto_tree_add_text( tacplus_tree, NullTVB, 
				AUTHEN_C_VARDATA_OFF(offset), t_len,
				"User: %s", scratch_buffer );
	}

	proto_tree_add_text( tacplus_tree, NullTVB, AUTHEN_C_DATA_LEN_OFF(offset), 2 ,
			"Data length: %d", ntohs(tac_body->data_len) );

	if( tac_body->data_len ){
		proto_tree_add_text( tacplus_tree, NullTVB,
				AUTHEN_C_VARDATA_OFF(offset)+t_len,
				ntohs(tac_body->data_len), "Data" );
	}

}

/* Server REPLY */
static void
dissect_tacplus_body_authen_rep( proto_tree *tacplus_tree, const tacplus_authen_reply* tac_body, int offset )
{
	gchar *codestrval;
	int tmp;
	int t_len;
	codestrval=match_strval( tac_body->status, tacplus_reply_status_vals );
	if (codestrval==NULL)
		codestrval="Unknown Packet";
	proto_tree_add_text(tacplus_tree, NullTVB,
			AUTHEN_R_STATUS_OFF(offset), 1, "Status: 0x%01x (%s)",
			tac_body->status, codestrval );

	proto_tree_add_text(tacplus_tree, NullTVB,
			AUTHEN_R_FLAGS_OFF(offset), 1, "Flags: 0x%02x %s",
			tac_body->flags,
			(tac_body->flags&TAC_PLUS_REPLY_FLAG_NOECHO?"(NoEcho)":"") );
	
	tmp=AUTHEN_R_VARDATA_OFF(offset);

	t_len=ntohs(tac_body->srv_msg_len);
	proto_tree_add_text( tacplus_tree, NullTVB, AUTHEN_R_SRV_MSG_LEN_OFF(offset), 2 ,
				"Server message length: %d", t_len );
	if( t_len ) {
		strncpy( scratch_buffer, tac_body->vardata, t_len );
		scratch_buffer[t_len]='\0';
		proto_tree_add_text(tacplus_tree, NullTVB, tmp,
				t_len, "Server message: %s", scratch_buffer );
	}

	proto_tree_add_text( tacplus_tree, NullTVB, AUTHEN_R_DATA_LEN_OFF(offset), 2 ,
				"Data length: %d", ntohs(tac_body->data_len) );
	if( tac_body->data_len ){
		proto_tree_add_text( tacplus_tree, NullTVB, tmp,
				ntohs(tac_body->data_len), "Data");
	}
}

static void
dissect_tacplus_body_acct_req( proto_tree *tacplus_tree, const tacplus_account_request *tac_req, int offset )
{
	u_char *arg;
	int tmp, i,arg_off;
	proto_tree_add_text(tacplus_tree, NullTVB,
			ACCT_Q_FLAGS_OFF(offset), 1, 
			"Flags: 0x%04x", tac_req->flags );

	proto_tree_add_text(tacplus_tree, NullTVB,
			ACCT_Q_METHOD_OFF(offset), 1, 
			"Authen Method: 0x%04x", tac_req->authen_method );

	/* authen_type */
	proto_tree_add_tacplus_common_fields( tacplus_tree ,
			ACCT_Q_PRIV_LVL_OFF(offset),
			&tac_req->priv_lvl,
			&tac_req->vardata[tac_req->arg_cnt] );

	proto_tree_add_text( tacplus_tree, NullTVB, ACCT_Q_ARG_CNT_OFF(offset), 1,
			"Arg Cnt: %d", tac_req->arg_cnt  );

	tmp= ACCT_Q_ARG_CNT_OFF(offset)
		+ ( sizeof(u_char)*(tac_req->arg_cnt + 1) )
		+ tac_req->user_len
		+ tac_req->port_len
		+ tac_req->rem_addr_len ;
	arg_off=ACCT_Q_ARG_CNT_OFF(offset)+sizeof(u_char);
	arg=&tac_req->vardata[0]
		+ tac_req->arg_cnt 
		+ tac_req->user_len 
		+ tac_req->port_len 
		+ tac_req->rem_addr_len; 

	for(i=0;i< tac_req->arg_cnt ;i++) { 
		int j;
		j=tac_req->vardata[i];
		strncpy( scratch_buffer, arg, j );
		scratch_buffer[j]='\0';
		proto_tree_add_text( tacplus_tree, NullTVB, tmp, j, "Arg[%d]: %s", i, scratch_buffer );
		tmp+=j;
		arg+=j;
	}
}

static void
dissect_tacplus_body_acct_rep( proto_tree *tacplus_tree, const tacplus_account_reply *tac_body, int offset )
{
	int tmp, t_len;
	t_len=ntohs(tac_body->srv_msg_len);
	tmp=ACCT_R_VARDATA_OFF(offset);
	proto_tree_add_text( tacplus_tree, NullTVB, ACCT_R_SRV_MSG_LEN_OFF(offset), 2 ,
				"Server message length: %d", t_len );
	proto_tree_add_text( tacplus_tree, NullTVB, ACCT_R_DATA_LEN_OFF(offset), 2 ,
				"Data length: %d", ntohs(tac_body->data_len) );

	proto_tree_add_text(tacplus_tree, NullTVB,
			ACCT_R_STATUS_OFF(offset), 1, "Status: 0x%02x",
			tac_body->status  );

	if( t_len ) {
		strncpy( scratch_buffer, &tac_body->vardata[0], t_len );
		scratch_buffer[t_len]='\0';
		proto_tree_add_text(tacplus_tree, NullTVB, tmp,
				t_len, "Server message: %s", scratch_buffer );
	}

	if( tac_body->data_len ){
		proto_tree_add_text( tacplus_tree, NullTVB, tmp,
				ntohs(tac_body->data_len), "Data");
	}
}

static void
dissect_tacplus_body( proto_tree *tacplus_tree, const u_char *pd, int offset )
{
	tacplus_pkt *tac=(tacplus_pkt*)&pd[offset];
	gchar *codestrval;
	if( !(tac->hdr.flags & TAC_PLUS_UNENCRYPTED_FLAG) ) {
		if( *secret_key ) {
			/* Decode packet */
			md5_xor( (u_char*)&pd[offset], secret_key );
		} else {
			proto_tree_add_text(tacplus_tree, NullTVB, offset+TAC_PLUS_HDR_SIZE,
					offset+TAC_PLUS_HDR_SIZE+tac->hdr.length, "Crypted TACACS+ msg");
			return;
		}
	}

	switch( tac->hdr.type ) {
		case TAC_PLUS_AUTHEN:
			if( tac->hdr.seq_no & 0x01 ) {
				if( tac->hdr.seq_no == 1 )
					dissect_tacplus_body_authen_req( tacplus_tree,
							&tac->body.authen.s, offset );
				else 
					dissect_tacplus_body_authen_req_cont( tacplus_tree,
							&tac->body.authen.c, offset );
			} else
				dissect_tacplus_body_authen_rep( tacplus_tree,
						&tac->body.authen.r, offset );
			return;
			break;
		case TAC_PLUS_AUTHOR:
			codestrval="Authorization";
			break;
		case TAC_PLUS_ACCT:
			if( tac->hdr.seq_no & 0x01 )
				dissect_tacplus_body_acct_req( tacplus_tree, &tac->body.acct.q, offset );
			else
				dissect_tacplus_body_acct_rep( tacplus_tree, &tac->body.acct.r, offset );
			return;
			break;
		default:
			codestrval="Bogus type";
			break;
	}
	proto_tree_add_text(tacplus_tree, NullTVB, offset+TAC_PLUS_HDR_SIZE,
			offset+TAC_PLUS_HDR_SIZE+tac->hdr.length, codestrval );

}

static void
dissect_tacplus(const u_char *pd, int offset, frame_data *fd, proto_tree *tree)
{
	proto_tree      *ti;
	tacplus_pkt		*tac;
	(u_char*)tac=&pd[offset];

	OLD_CHECK_DISPLAY_AS_DATA( proto_tacacs, pd, offset, fd, tree);

	if (check_col(fd, COL_PROTOCOL))
		col_add_str(fd, COL_PROTOCOL, "TACACS+");
	if (check_col(fd, COL_INFO))
	{
		char *type;
		switch( tac->hdr.type ) {
			case 0x01:
				type="Authen";
				break;
			case 0x02:
				type="Author";
				break;
			case 0x03:
				type="Acct";
				break;
			default:
				type="Unknown";
		}

		col_add_fstr(fd, COL_INFO, "[%s:%c] Session=%u, Seq=%d", 
			type,
			(tac->hdr.seq_no&0x01?'Q':'R'),
			tac->hdr.session_id,
			tac->hdr.seq_no
		 );	  
	}

	if (tree) 
	{
		ti = proto_tree_add_item(tree, proto_tacacs, NullTVB, offset,
				END_OF_FRAME, FALSE);

		/* FIXME: Add check for END_OF_FRAME < TAC_PLUS_HDR_SIZE */
		/* Tacacs+ Header Tree */
		/* proto_tree_add_notext */
		dissect_tacplus_hdr(
				proto_item_add_subtree(
					proto_tree_add_text(ti, NullTVB, offset, TAC_PLUS_HDR_SIZE, "Header" )
					, ett_tacacs_hdr ),
				pd, offset );
		/* Tacacs+ Body Tree */
		dissect_tacplus_body(
				proto_item_add_subtree(
					proto_tree_add_text(ti, NullTVB, offset+TAC_PLUS_HDR_SIZE, END_OF_FRAME, "Body" )
					, ett_tacacs_body ),
				pd, offset );

	}
}


void
proto_register_tacacs(void)
{
	static hf_register_info hf[] = {
		{&hf_tacacs_version,
			{"Tacacs+ Version", "tacacs.version",
				FT_UINT8, BASE_DEC, NULL, 0xf0,
				"Tacacs+ version"}},
		{&hf_tacacs_type,
			{"Type", "tacacs.type",
				FT_UINT8, BASE_HEX, VALS(tacplus_type_vals), 0x0,
				"TACACS+ packet type"}},
		{&hf_tacacs_seq_no,
			{"Sequence No", "tacacs.seq_no",
				FT_UINT8, BASE_DEC, NULL, 0x0,
				"Sequence number"}},
		{&hf_tacacs_flags,
			{"Flags", "tacacs.flags",
				FT_UINT8, BASE_HEX, NULL, 0x0,
				""}},
#if 1
		{&hf_tacacs_flags_singlecon,
			{"Single connection","tacacs.flags.singlecon",
				FT_BOOLEAN, 8, TFS(&flags_set_truth), TAC_PLUS_SINGLE_CONNECT_FLAG, 
				""}},
		{&hf_tacacs_flags_unencrypted,
			{"Unecrypted","tacacs.flags.unencypted",
				FT_BOOLEAN, 8, TFS(&flags_set_truth), TAC_PLUS_UNENCRYPTED_FLAG, 
				""}},
#endif
		{&hf_tacacs_session_id,
			{"Session Id", "tacacs.session_id",
				FT_UINT32, BASE_DEC, NULL, 0x0,
				"Session ID"}},
		{&hf_tacacs_length,
			{"Length", "tacacs.length",
				FT_UINT32, BASE_DEC, NULL, 0x0,
				"Pkt length"}}
	};

	static gint *ett[] = {
		&ett_tacacs_hdr,
		&ett_tacacs_flags,
		&ett_tacacs_body,
	};

	proto_tacacs = proto_register_protocol("TACACS+", "tacacs");
	proto_register_field_array(proto_tacacs, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	if(getenv("TACPLUS_PASS")){
		strcpy(secret_key,getenv("TACPLUS_PASS") );
	} else {
		*secret_key='\0';
	}
}

void
proto_reg_handoff_tacacs(void)
{
#if 0
	guint32 tacp_port=TCP_PORT_TACACS;
	if( getenv( "TACPLUS_PORT" ) ){
		tacp_port=strtoul( getenv("TACPLUS_PORT"),NULL,10 );
	}
#else 
# define tacp_port TCP_PORT_TACACS
#endif
	old_dissector_add("tcp.port", tacp_port, dissect_tacplus );
}


#include <openssl/md5.h>
#define MD5_LEN 16
/* create_md5_hash and md5_xor functions are originally written by
 * Roman Volkoff,  rv@xxxxxx, for his library libTACACS
 * http://provider.kht.ru/software/tacacs/libtacacs/
 */ 
static void
create_md5_hash(int session_id, char *key, u_char version, u_char seq_no,
				u_char * prev_hash, u_char * hash)
{
	u_char *md_stream, *mdp;
	int   md_len;
	MD5_CTX mdcontext;

	md_len = sizeof(session_id) + strlen(key) + sizeof(version) +
		sizeof(seq_no);

	if (prev_hash) {
		md_len += MD5_LEN;
	}
	mdp = md_stream = (u_char *) malloc(md_len);
	bcopy(&session_id, mdp, sizeof(session_id));
	mdp += sizeof(session_id);

	bcopy(key, mdp, strlen(key));
	mdp += strlen(key);

	bcopy(&version, mdp, sizeof(version));
	mdp += sizeof(version);

	bcopy(&seq_no, mdp, sizeof(seq_no));
	mdp += sizeof(seq_no);

	if (prev_hash) {
		bcopy(prev_hash, mdp, MD5_LEN);
		mdp += MD5_LEN;
	}
	MD5_Init(&mdcontext);
	MD5_Update(&mdcontext, md_stream, md_len);
	MD5_Final(hash, &mdcontext);
	free(md_stream);
	return;
}

static int
md5_xor( u_char *pkt, char *key )
{
	tacplus_pkt_hdr * hdr=(tacplus_pkt_hdr*)pkt;
	int i,j;
	u_char hash[MD5_LEN];       /* the md5 hash */
	u_char last_hash[MD5_LEN];  /* the last hash we generated */
	u_char *prev_hashp = (u_char *) NULL;       /* pointer to last created hash */

	int data_len=ntohl(hdr->length);
	u_char* data=pkt+TAC_PLUS_HDR_SIZE;

	for (i = 0; i < data_len; i += 16) {

		create_md5_hash(hdr->session_id, key, hdr->version,
					hdr->seq_no, prev_hashp, hash);

		bcopy(hash, last_hash, MD5_LEN);
		prev_hashp = last_hash;

		for (j = 0; j < 16; j++) {
			if ((i + j) >= data_len) 
				return 0;
			data[i + j] ^= hash[j];
		}
	}
	return 0;
}
#ifndef __PACKET_TACACS_H__
#define __PACKET_TACACS_H__
/* packet-tacacs.h
 * Routines for cisco tacplus packet dissection
 * Copyright 2000, Emanuele Caratti <wiz@xxxxxx>
 *
 * $Id: packet-tacacs.h,v 1.5 2000/10/07 18:06:02 wiz Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxx>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */



#define TAC_PLUS_HDR_SIZE 12

#define MD5_LEN           16
#define MSCHAP_DIGEST_LEN 49

/* Tacacs+ packet type */
enum
{
	TAC_PLUS_AUTHEN = 0x01,		/* Authentication */
	TAC_PLUS_AUTHOR = 0x02,		/* Authorization  */
	TAC_PLUS_ACCT = 0x03		/* Accounting     */
};

/* Flags */
#define TAC_PLUS_ENCRYPTED 0x0 
#define TAC_PLUS_CLEAR     0x1

/* Authentication action to perform */
enum
{
	TAC_PLUS_AUTHEN_LOGIN = 0x01,
	TAC_PLUS_AUTHEN_CHPASS = 0x02,
	TAC_PLUS_AUTHEN_SENDPASS = 0x03,	/* deprecated */
	TAC_PLUS_AUTHEN_SENDAUTH = 0x04
};

/* Authentication priv_levels */
enum
{
	TAC_PLUS_PRIV_LVL_MAX	= 0x0f,
	TAC_PLUS_PRIV_LVL_ROOT	= 0x0f,
	TAC_PLUS_PRIV_LVL_USER	= 0x01,
	TAC_PLUS_PRIV_LVL_MIN	= 0x00,
};

/* authen types */
enum
{
	TAC_PLUS_AUTHEN_TYPE_ASCII 		= 0x01,	/*  ascii  */
	TAC_PLUS_AUTHEN_TYPE_PAP 		= 0x02,	/*  pap    */
	TAC_PLUS_AUTHEN_TYPE_CHAP 		= 0x03,	/*  chap   */
	TAC_PLUS_AUTHEN_TYPE_ARAP 		= 0x04,	/*  arap   */
	TAC_PLUS_AUTHEN_TYPE_MSCHAP 	= 0x05	/*  mschap */
};

/* authen services */
enum
{
	TAC_PLUS_AUTHEN_SVC_NONE	= 0x00,
	TAC_PLUS_AUTHEN_SVC_LOGIN	= 0x01,
	TAC_PLUS_AUTHEN_SVC_ENABLE	= 0x02,
	TAC_PLUS_AUTHEN_SVC_PPP		= 0x03,
	TAC_PLUS_AUTHEN_SVC_ARAP	= 0x04,
	TAC_PLUS_AUTHEN_SVC_PT		= 0x05,
	TAC_PLUS_AUTHEN_SVC_RCMD	= 0x06,
	TAC_PLUS_AUTHEN_SVC_X25		= 0x07,
	TAC_PLUS_AUTHEN_SVC_NASI	= 0x08,
	TAC_PLUS_AUTHEN_SVC_FWPROXY	= 0x09
};

/* status of reply packet, that client get from server in authen */
enum
{
	TAC_PLUS_AUTHEN_STATUS_PASS		= 0x01,
	TAC_PLUS_AUTHEN_STATUS_FAIL		= 0x02,
	TAC_PLUS_AUTHEN_STATUS_GETDATA	= 0x03,
	TAC_PLUS_AUTHEN_STATUS_GETUSER	= 0x04,
	TAC_PLUS_AUTHEN_STATUS_GETPASS	= 0x05,
	TAC_PLUS_AUTHEN_STATUS_RESTART	= 0x06,
	TAC_PLUS_AUTHEN_STATUS_ERROR	= 0x07,
	TAC_PLUS_AUTHEN_STATUS_FOLLOW	= 0x21
};

/* Authen reply Flags */
#define TAC_PLUS_REPLY_FLAG_NOECHO		0x01
/* Authen continue Flags */
#define TAC_PLUS_CONTINUE_FLAG_ABORT    0x01

/* methods of authentication */
enum {
	TAC_PLUS_AUTHEN_METH_NOT_SET	= 0x00,
	TAC_PLUS_AUTHEN_METH_NONE		= 0x01,
	TAC_PLUS_AUTHEN_METH_KRB5		= 0x03,
	TAC_PLUS_AUTHEN_METH_LINE		= 0x03,
	TAC_PLUS_AUTHEN_METH_ENABLE		= 0x04,
	TAC_PLUS_AUTHEN_METH_LOCAL		= 0x05,
	TAC_PLUS_AUTHEN_METH_TACACSPLUS	= 0x06,
	TAC_PLUS_AUTHEN_METH_GUEST		= 0x08,
	TAC_PLUS_AUTHEN_METH_RADIUS		= 0x10,
	TAC_PLUS_AUTHEN_METH_KRB4		= 0x11,
	TAC_PLUS_AUTHEN_METH_RCMD		= 0x20
};

/* authorization status */
enum
{
	TAC_PLUS_AUTHOR_STATUS_PASS_ADD		= 0x01,
	TAC_PLUS_AUTHOR_STATUS_PASS_REPL	= 0x02,
	TAC_PLUS_AUTHOR_STATUS_FAIL			= 0x10,
	TAC_PLUS_AUTHOR_STATUS_ERROR		= 0x11,
	TAC_PLUS_AUTHOR_STATUS_FOLLOW		= 0x21
};

/* accounting flag */
#define TAC_PLUS_ACCT_FLAG_MORE     0x1     /* deprecated */
#define TAC_PLUS_ACCT_FLAG_START    0x2
#define TAC_PLUS_ACCT_FLAG_STOP     0x4
#define TAC_PLUS_ACCT_FLAG_WATCHDOG 0x8

/* accounting status */
enum {
	TAC_PLUS_ACCT_STATUS_SUCCESS	= 0x01,
	TAC_PLUS_ACCT_STATUS_ERROR		= 0x02,
	TAC_PLUS_ACCT_STATUS_FOLLOW		= 0x21
};

/* Header offsets */
#define H_VER_OFF(x)			((x))
#define H_TYPE_OFF(x)			(H_VER_OFF(x)+1)
#define H_SEQ_NO_OFF(x)			(H_TYPE_OFF(x)+1)
#define H_FLAGS_OFF(x)			(H_SEQ_NO_OFF(x)+1)
#define H_SESSION_ID_OFF(x)		(H_FLAGS_OFF(x)+1)
#define H_LENGTH_OFF(x)			(H_SESSION_ID_OFF(x)+4)

/* authen START offsets */
#define AUTHEN_S_ACTION_OFF(x)			((x)+TAC_PLUS_HDR_SIZE)
#define AUTHEN_S_PRIV_LVL_OFF(x)		(AUTHEN_S_ACTION_OFF(x)+1)
#define AUTHEN_S_AUTHEN_TYPE_OFF(x)		(AUTHEN_S_PRIV_LVL_OFF(x)+1)
#define AUTHEN_S_SERVICE_OFF(x)			(AUTHEN_S_AUTHEN_TYPE_OFF(x)+1)
#define AUTHEN_S_USER_LEN_OFF(x)		(AUTHEN_S_SERVICE_OFF(x)+1)
#define AUTHEN_S_PORT_LEN_OFF(x)		(AUTHEN_S_USER_LEN_OFF(x)+1)
#define AUTHEN_S_REM_ADDR_LEN_OFF(x)	(AUTHEN_S_PORT_LEN_OFF(x)+1)
#define AUTHEN_S_DATA_LEN_OFF(x)		(AUTHEN_S_REM_ADDR_LEN_OFF(x)+1)
#define AUTHEN_S_VARDATA_OFF(x)		(((x)+20)) /* variable data offset (user, port, etc ) */

/* authen REPLY fields offset */
#define AUTHEN_R_STATUS_OFF(x)			((x)+TAC_PLUS_HDR_SIZE)
#define AUTHEN_R_FLAGS_OFF(x)			(AUTHEN_R_STATUS_OFF(x)+1)
#define AUTHEN_R_SRV_MSG_LEN_OFF(x)		(AUTHEN_R_FLAGS_OFF(x)+1)
#define AUTHEN_R_DATA_LEN_OFF(x)		(AUTHEN_R_SRV_MSG_LEN_OFF(x)+2)
#define AUTHEN_R_VARDATA_OFF(x)			(AUTHEN_R_DATA_LEN_OFF(x)+2)

/* authen CONTINUE fields offset */
#define AUTHEN_C_USER_LEN_OFF(x)		((x)+TAC_PLUS_HDR_SIZE)
#define AUTHEN_C_DATA_LEN_OFF(x)		(AUTHEN_C_USER_LEN_OFF(x)+2)
#define AUTHEN_C_FLAGS_OFF(x)			(AUTHEN_C_DATA_LEN_OFF(x)+2)
#define AUTHEN_C_VARDATA_OFF(x)			(AUTHEN_C_FLAGS_OFF(x)+1)

/* acct REQUEST fields offsets */
#define ACCT_Q_FLAGS_OFF(x)				((x)+TAC_PLUS_HDR_SIZE)
#define ACCT_Q_METHOD_OFF(x)			(ACCT_Q_FLAGS_OFF(x)+1)
#define ACCT_Q_PRIV_LVL_OFF(x)			(ACCT_Q_METHOD_OFF(x)+1)
#define ACCT_Q_AUTHEN_TYPE_OFF(x)		(ACCT_Q_PRIV_LVL_OFF(x)+1)
#define ACCT_Q_SERVICE_OFF(x)			(ACCT_Q_AUTHEN_TYPE_OFF(x)+1)
#define ACCT_Q_USER_LEN_OFF(x)			(ACCT_Q_SERVICE_OFF(x)+1)
#define ACCT_Q_PORT_LEN_OFF(x)			(ACCT_Q_USER_LEN_OFF(x)+1)
#define ACCT_Q_REM_ADDR_LEN_OFF(x)		(ACCT_Q_PORT_LEN_OFF(x)+1)
#define ACCT_Q_ARG_CNT_OFF(x)			(ACCT_Q_REM_ADDR_LEN_OFF(x)+1)

/* acct REPLY fields offsets */
#define ACCT_R_SRV_MSG_LEN_OFF(x)		((x)+TAC_PLUS_HDR_SIZE)
#define ACCT_R_DATA_LEN_OFF(x)			(ACCT_R_SRV_MSG_LEN_OFF(x)+2)
#define ACCT_R_STATUS_OFF(x)			(ACCT_R_DATA_LEN_OFF(x)+2)
#define ACCT_R_VARDATA_OFF(x)			(ACCT_R_STATUS_OFF(x)+1)

/* AUTHORIZATION */

/* Packet structures */

typedef struct  {
	u_char version;
	u_char type;
	u_char seq_no;
	u_char flags;
	guint32 session_id;     
	guint32 length; 
} tacplus_pkt_hdr; 

/* Authentication START packet */
typedef	struct {
	u_char	action;
	u_char	priv_lvl;
	u_char	authen_type;
	u_char	service;
	u_char	user_len;
	u_char	port_len;
	u_char	rem_addr_len;
	u_char	data_len;
	u_char	vardata[1];
} tacplus_authen_start ;

/* Authentication CONTINUE packet */
typedef struct {
	guint16	user_len;
	guint16 data_len;
	u_char	flags;
	u_char	vardata[1];
} tacplus_authen_continue ;

/* Authentication REPLY packet */
typedef struct {
	u_char	status;
	u_char	flags;
	guint16	srv_msg_len;
	guint16	data_len;
	u_char	vardata[1];
} tacplus_authen_reply;


/* Authentication sub-PACKET */
typedef union {
	tacplus_authen_start 	s; /* start */
	tacplus_authen_continue c; /* continue */
	tacplus_authen_reply	r; /* reply (from srv) */
} tacplus_authen_pkt;

/* AUTHORIZATION request */

typedef struct {
	u_char	authen_method;
	u_char	priv_lvl;
	u_char	authen_type;
	u_char	authen_service;
	u_char	user_len;
	u_char	port_len;
	u_char	rem_addr_len;
	u_char	arg_cnt;
	u_char	vardata[1];
} tacplus_author_request;

/* ACCOUNTING request */
typedef struct {
	u_char	flags;
	u_char	authen_method;
	u_char	priv_lvl;
	u_char	authen_type;
	u_char	authen_service;
	u_char	user_len;
	u_char	port_len;
	u_char	rem_addr_len;
	u_char	arg_cnt;
	u_char	vardata[1];
} tacplus_account_request;

typedef struct {
	guint16	srv_msg_len;
	guint16 data_len;
	u_char	status;
	u_char	vardata[1];
} tacplus_account_reply;

typedef union {
	tacplus_account_request q; /* Request */
	tacplus_account_reply	r; /* Reply */
} tacplus_account_pkt;

/* TACACS+ Packet */
typedef struct {
	tacplus_pkt_hdr hdr;
	union {
		tacplus_authen_pkt authen;
		tacplus_account_pkt acct;
	} body;
} tacplus_pkt;



#endif   /* __PACKET_TACACS_H__ */