/* packet-kerberos.c
 * Routines for Kerberos
 * Wes Hardaker (c) 2000
 * wjhardaker@ucdavis.edu
 *
 * $Id: packet-kerberos.c,v 1.16 2001/04/15 08:50:37 guy Exp $
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@zing.org>
 * Copyright 1998 Didier Jorand
 *
 * 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>
#include <string.h>
#include <ctype.h>

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

#include <glib.h>

#include "packet.h"

#include "strutil.h"

#include "asn1.h"

#define UDP_PORT_KERBEROS		88
#define TCP_PORT_KERBEROS		88

static gint proto_kerberos = -1;

static gint ett_kerberos = -1;
static gint ett_preauth = -1;
static gint ett_addresses = -1;
static gint ett_request = -1;
static gint ett_princ = -1;
static gint ett_ticket = -1;
static gint ett_encrypted = -1;
static gint ett_etype = -1;
static gint ett_additional_tickets = -1;

#define KRB5_MSG_AS_REQ   10	/* AS-REQ type */
#define KRB5_MSG_AS_REP   11	/* AS-REP type */
#define KRB5_MSG_TGS_REQ  12	/* TGS-REQ type */
#define KRB5_MSG_TGS_REP  13	/* TGS-REP type */
#define KRB5_MSG_AP_REQ   14	/* AP-REQ type */
#define KRB5_MSG_AP_REP   15	/* AP-REP type */

#define KRB5_MSG_SAFE     20	/* KRB-SAFE type */
#define KRB5_MSG_PRIV     21	/* KRB-PRIV type */
#define KRB5_MSG_CRED     22	/* KRB-CRED type */
#define KRB5_MSG_ERROR    30	/* KRB-ERROR type */

/* Type tags within KDC-REQ */
#define KRB5_KDC_REQ_PVNO     1
#define KRB5_KDC_REQ_MSG_TYPE 2
#define KRB5_KDC_REQ_PADATA   3
#define KRB5_KDC_REQ_REQBODY  4

/* Type tags within KDC-REP */
#define KRB5_KDC_REP_PVNO     0
#define KRB5_KDC_REP_MSG_TYPE 1
#define KRB5_KDC_REP_PADATA   2
#define KRB5_KDC_REP_CREALM   3
#define KRB5_KDC_REP_CNAME    4
#define KRB5_KDC_REP_TICKET   5
#define KRB5_KDC_REP_ENC_PART 6

/* Type tags within KDC-REQ-BODY */
#define KRB5_BODY_KDC_OPTIONS            0
#define KRB5_BODY_CNAME                  1
#define KRB5_BODY_REALM                  2
#define KRB5_BODY_SNAME                  3
#define KRB5_BODY_FROM                   4
#define KRB5_BODY_TILL                   5
#define KRB5_BODY_RTIME                  6
#define KRB5_BODY_NONCE                  7
#define KRB5_BODY_ENCTYPE                8
#define KRB5_BODY_ADDRESSES              9
#define KRB5_BODY_ENC_AUTHORIZATION_DATA 10
#define KRB5_BODY_ADDITIONAL_TICKETS     11

/* Type tags within KRB-ERROR */
#define KRB5_ERROR_PVNO       0
#define KRB5_ERROR_MSG_TYPE   1
#define KRB5_ERROR_CTIME      2
#define KRB5_ERROR_CUSEC      3
#define KRB5_ERROR_STIME      4
#define KRB5_ERROR_SUSEC      5
#define KRB5_ERROR_ERROR_CODE 6
#define KRB5_ERROR_CREALM     7
#define KRB5_ERROR_CNAME      8
#define KRB5_ERROR_REALM      9
#define KRB5_ERROR_SNAME      10
#define KRB5_ERROR_ETEXT      11
#define KRB5_ERROR_EDATA      12

/* address type constants */
#define KRB5_ADDR_IPv4       0x02
#define KRB5_ADDR_CHAOS      0x05
#define KRB5_ADDR_XEROX      0x06
#define KRB5_ADDR_ISO        0x07
#define KRB5_ADDR_DECNET     0x0c
#define KRB5_ADDR_APPLETALK  0x10

/* encryption type constants */
#define KRB5_ENCTYPE_NULL                0
#define KRB5_ENCTYPE_DES_CBC_CRC         1
#define KRB5_ENCTYPE_DES_CBC_MD4         2
#define KRB5_ENCTYPE_DES_CBC_MD5         3
#define KRB5_ENCTYPE_DES_CBC_RAW         4
#define KRB5_ENCTYPE_DES3_CBC_SHA        5
#define KRB5_ENCTYPE_DES3_CBC_RAW        6
#define KRB5_ENCTYPE_DES_HMAC_SHA1       8
#define KRB5_ENCTYPE_DES3_CBC_SHA1          0x10 
#define KRB5_ENCTYPE_UNKNOWN                0x1ff
#define KRB5_ENCTYPE_LOCAL_DES3_HMAC_SHA1   0x7007

/* pre-authentication type constants */
#define KRB5_PA_TGS_REQ                1
#define KRB5_PA_ENC_TIMESTAMP          2
#define KRB5_PA_PW_SALT                3
#define KRB5_PA_ENC_ENCKEY             4
#define KRB5_PA_ENC_UNIX_TIME          5
#define KRB5_PA_ENC_SANDIA_SECURID     6
#define KRB5_PA_SESAME                 7
#define KRB5_PA_OSF_DCE                8
#define KRB5_PA_CYBERSAFE_SECUREID     9
#define KRB5_PA_AFS3_SALT              10
#define KRB5_PA_ENCTYPE_INFO             11
#define KRB5_PA_SAM_CHALLENGE          12
#define KRB5_PA_SAM_RESPONSE           13
#define KRB5_PA_DASS                   16

/* Type tags within Ticket */
#define KRB5_TKT_TKT_VNO  0
#define KRB5_TKT_REALM    1
#define KRB5_TKT_SNAME    2
#define KRB5_TKT_ENC_PART 3

/* Principal name-type */
#define KRB5_NT_UNKNOWN     0
#define KRB5_NT_PRINCIPAL   1
#define KRB5_NT_SRV_INST    2
#define KRB5_NT_SRV_HST     3
#define KRB5_NT_SRV_XHST    4
#define KRB5_NT_UID     5

/* error table constants */
/* I prefixed the krb5_err.et constant names with KRB5_ET_ for these */
#define KRB5_ET_KRB5KDC_ERR_NONE                         0
#define KRB5_ET_KRB5KDC_ERR_NAME_EXP                     1
#define KRB5_ET_KRB5KDC_ERR_SERVICE_EXP                  2
#define KRB5_ET_KRB5KDC_ERR_BAD_PVNO                     3
#define KRB5_ET_KRB5KDC_ERR_C_OLD_MAST_KVNO              4
#define KRB5_ET_KRB5KDC_ERR_S_OLD_MAST_KVNO              5
#define KRB5_ET_KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN          6
#define KRB5_ET_KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN          7
#define KRB5_ET_KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE         8
#define KRB5_ET_KRB5KDC_ERR_NULL_KEY                     9
#define KRB5_ET_KRB5KDC_ERR_CANNOT_POSTDATE              10
#define KRB5_ET_KRB5KDC_ERR_NEVER_VALID                  11
#define KRB5_ET_KRB5KDC_ERR_POLICY                       12
#define KRB5_ET_KRB5KDC_ERR_BADOPTION                    13
#define KRB5_ET_KRB5KDC_ERR_ETYPE_NOSUPP                 14
#define KRB5_ET_KRB5KDC_ERR_SUMTYPE_NOSUPP               15
#define KRB5_ET_KRB5KDC_ERR_PADATA_TYPE_NOSUPP           16
#define KRB5_ET_KRB5KDC_ERR_TRTYPE_NOSUPP                17
#define KRB5_ET_KRB5KDC_ERR_CLIENT_REVOKED               18
#define KRB5_ET_KRB5KDC_ERR_SERVICE_REVOKED              19
#define KRB5_ET_KRB5KDC_ERR_TGT_REVOKED                  20
#define KRB5_ET_KRB5KDC_ERR_CLIENT_NOTYET                21
#define KRB5_ET_KRB5KDC_ERR_SERVICE_NOTYET               22
#define KRB5_ET_KRB5KDC_ERR_KEY_EXP                      23
#define KRB5_ET_KRB5KDC_ERR_PREAUTH_FAILED               24
#define KRB5_ET_KRB5KDC_ERR_PREAUTH_REQUIRED             25
#define KRB5_ET_KRB5KDC_ERR_SERVER_NOMATCH               26
#define KRB5_ET_KRB5KRB_AP_ERR_BAD_INTEGRITY             31
#define KRB5_ET_KRB5KRB_AP_ERR_TKT_EXPIRED               32
#define KRB5_ET_KRB5KRB_AP_ERR_TKT_NYV                   33
#define KRB5_ET_KRB5KRB_AP_ERR_REPEAT                    34
#define KRB5_ET_KRB5KRB_AP_ERR_NOT_US                    35
#define KRB5_ET_KRB5KRB_AP_ERR_BADMATCH                  36
#define KRB5_ET_KRB5KRB_AP_ERR_SKEW                      37
#define KRB5_ET_KRB5KRB_AP_ERR_BADADDR                   38
#define KRB5_ET_KRB5KRB_AP_ERR_BADVERSION                39
#define KRB5_ET_KRB5KRB_AP_ERR_MSG_TYPE                  40
#define KRB5_ET_KRB5KRB_AP_ERR_MODIFIED                  41
#define KRB5_ET_KRB5KRB_AP_ERR_BADORDER                  42
#define KRB5_ET_KRB5KRB_AP_ERR_ILL_CR_TKT                43
#define KRB5_ET_KRB5KRB_AP_ERR_BADKEYVER                 44
#define KRB5_ET_KRB5KRB_AP_ERR_NOKEY                     45
#define KRB5_ET_KRB5KRB_AP_ERR_MUT_FAIL                  46
#define KRB5_ET_KRB5KRB_AP_ERR_BADDIRECTION              47
#define KRB5_ET_KRB5KRB_AP_ERR_METHOD                    48
#define KRB5_ET_KRB5KRB_AP_ERR_BADSEQ                    49
#define KRB5_ET_KRB5KRB_AP_ERR_INAPP_CKSUM               50
#define KRB5_ET_KRB5KRB_ERR_GENERIC                      60
#define KRB5_ET_KRB5KRB_ERR_FIELD_TOOLONG                61

static const value_string krb5_error_codes[] = {
	{ KRB5_ET_KRB5KDC_ERR_NONE, "KRB5KDC_ERR_NONE" },
	{ KRB5_ET_KRB5KDC_ERR_NAME_EXP, "KRB5KDC_ERR_NAME_EXP" },
	{ KRB5_ET_KRB5KDC_ERR_SERVICE_EXP, "KRB5KDC_ERR_SERVICE_EXP" },
	{ KRB5_ET_KRB5KDC_ERR_BAD_PVNO, "KRB5KDC_ERR_BAD_PVNO" },
	{ KRB5_ET_KRB5KDC_ERR_C_OLD_MAST_KVNO, "KRB5KDC_ERR_C_OLD_MAST_KVNO" },
	{ KRB5_ET_KRB5KDC_ERR_S_OLD_MAST_KVNO, "KRB5KDC_ERR_S_OLD_MAST_KVNO" },
	{ KRB5_ET_KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN, "KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN" },
	{ KRB5_ET_KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, "KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN" },
	{ KRB5_ET_KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE, "KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE" },
	{ KRB5_ET_KRB5KDC_ERR_NULL_KEY, "KRB5KDC_ERR_NULL_KEY" },
	{ KRB5_ET_KRB5KDC_ERR_CANNOT_POSTDATE, "KRB5KDC_ERR_CANNOT_POSTDATE" },
	{ KRB5_ET_KRB5KDC_ERR_NEVER_VALID, "KRB5KDC_ERR_NEVER_VALID" },
	{ KRB5_ET_KRB5KDC_ERR_POLICY, "KRB5KDC_ERR_POLICY" },
	{ KRB5_ET_KRB5KDC_ERR_BADOPTION, "KRB5KDC_ERR_BADOPTION" },
	{ KRB5_ET_KRB5KDC_ERR_ETYPE_NOSUPP, "KRB5KDC_ERR_ETYPE_NOSUPP" },
	{ KRB5_ET_KRB5KDC_ERR_SUMTYPE_NOSUPP, "KRB5KDC_ERR_SUMTYPE_NOSUPP" },
	{ KRB5_ET_KRB5KDC_ERR_PADATA_TYPE_NOSUPP, "KRB5KDC_ERR_PADATA_TYPE_NOSUPP" },
	{ KRB5_ET_KRB5KDC_ERR_TRTYPE_NOSUPP, "KRB5KDC_ERR_TRTYPE_NOSUPP" },
	{ KRB5_ET_KRB5KDC_ERR_CLIENT_REVOKED, "KRB5KDC_ERR_CLIENT_REVOKED" },
	{ KRB5_ET_KRB5KDC_ERR_SERVICE_REVOKED, "KRB5KDC_ERR_SERVICE_REVOKED" },
	{ KRB5_ET_KRB5KDC_ERR_TGT_REVOKED, "KRB5KDC_ERR_TGT_REVOKED" },
	{ KRB5_ET_KRB5KDC_ERR_CLIENT_NOTYET, "KRB5KDC_ERR_CLIENT_NOTYET" },
	{ KRB5_ET_KRB5KDC_ERR_SERVICE_NOTYET, "KRB5KDC_ERR_SERVICE_NOTYET" },
	{ KRB5_ET_KRB5KDC_ERR_KEY_EXP, "KRB5KDC_ERR_KEY_EXP" },
	{ KRB5_ET_KRB5KDC_ERR_PREAUTH_FAILED, "KRB5KDC_ERR_PREAUTH_FAILED" },
	{ KRB5_ET_KRB5KDC_ERR_PREAUTH_REQUIRED, "KRB5KDC_ERR_PREAUTH_REQUIRED" },
	{ KRB5_ET_KRB5KDC_ERR_SERVER_NOMATCH, "KRB5KDC_ERR_SERVER_NOMATCH" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BAD_INTEGRITY, "KRB5KRB_AP_ERR_BAD_INTEGRITY" },
	{ KRB5_ET_KRB5KRB_AP_ERR_TKT_EXPIRED, "KRB5KRB_AP_ERR_TKT_EXPIRED" },
	{ KRB5_ET_KRB5KRB_AP_ERR_TKT_NYV, "KRB5KRB_AP_ERR_TKT_NYV" },
	{ KRB5_ET_KRB5KRB_AP_ERR_REPEAT, "KRB5KRB_AP_ERR_REPEAT" },
	{ KRB5_ET_KRB5KRB_AP_ERR_NOT_US, "KRB5KRB_AP_ERR_NOT_US" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADMATCH, "KRB5KRB_AP_ERR_BADMATCH" },
	{ KRB5_ET_KRB5KRB_AP_ERR_SKEW, "KRB5KRB_AP_ERR_SKEW" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADADDR, "KRB5KRB_AP_ERR_BADADDR" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADVERSION, "KRB5KRB_AP_ERR_BADVERSION" },
	{ KRB5_ET_KRB5KRB_AP_ERR_MSG_TYPE, "KRB5KRB_AP_ERR_MSG_TYPE" },
	{ KRB5_ET_KRB5KRB_AP_ERR_MODIFIED, "KRB5KRB_AP_ERR_MODIFIED" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADORDER, "KRB5KRB_AP_ERR_BADORDER" },
	{ KRB5_ET_KRB5KRB_AP_ERR_ILL_CR_TKT, "KRB5KRB_AP_ERR_ILL_CR_TKT" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADKEYVER, "KRB5KRB_AP_ERR_BADKEYVER" },
	{ KRB5_ET_KRB5KRB_AP_ERR_NOKEY, "KRB5KRB_AP_ERR_NOKEY" },
	{ KRB5_ET_KRB5KRB_AP_ERR_MUT_FAIL, "KRB5KRB_AP_ERR_MUT_FAIL" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADDIRECTION, "KRB5KRB_AP_ERR_BADDIRECTION" },
	{ KRB5_ET_KRB5KRB_AP_ERR_METHOD, "KRB5KRB_AP_ERR_METHOD" },
	{ KRB5_ET_KRB5KRB_AP_ERR_BADSEQ, "KRB5KRB_AP_ERR_BADSEQ" },
	{ KRB5_ET_KRB5KRB_AP_ERR_INAPP_CKSUM, "KRB5KRB_AP_ERR_INAPP_CKSUM" },
	{ KRB5_ET_KRB5KRB_ERR_GENERIC, "KRB5KRB_ERR_GENERIC" },
	{ KRB5_ET_KRB5KRB_ERR_FIELD_TOOLONG, "KRB5KRB_ERR_FIELD_TOOLONG" },
	{ 0, NULL }
};


static const value_string krb5_princ_types[] = {
    { KRB5_NT_UNKNOWN              , "Unknown" },
    { KRB5_NT_PRINCIPAL            , "Principal" },
    { KRB5_NT_SRV_INST             , "Service and Instance" },
    { KRB5_NT_SRV_HST              , "Service and Host" },
    { KRB5_NT_SRV_XHST             , "Service and Host Components" },
    { KRB5_NT_UID                  , "Unique ID" },
    { 0                            , NULL },
};

static const value_string krb5_preauthentication_types[] = {
    { KRB5_PA_TGS_REQ              , "PA-TGS-REQ" },
    { KRB5_PA_ENC_TIMESTAMP        , "PA-ENC-TIMESTAMP" },
    { KRB5_PA_PW_SALT              , "PA-PW-SALT" },
    { KRB5_PA_ENC_ENCKEY           , "PA-ENC-ENCKEY" },
    { KRB5_PA_ENC_UNIX_TIME        , "PA-ENC-UNIX-TIME" },
    { KRB5_PA_ENC_SANDIA_SECURID   , "PA-PW-SALT" },
    { KRB5_PA_SESAME               , "PA-SESAME" },
    { KRB5_PA_OSF_DCE              , "PA-OSF-DCE" },
    { KRB5_PA_CYBERSAFE_SECUREID   , "PA-CYBERSAFE-SECURID" },
    { KRB5_PA_AFS3_SALT            , "PA-AFS3-SALT" },
    { KRB5_PA_ENCTYPE_INFO         , "PA-ENCTYPE-INFO" },
    { KRB5_PA_SAM_CHALLENGE        , "PA-SAM-CHALLENGE" },
    { KRB5_PA_SAM_RESPONSE         , "PA-SAM-RESPONSE" },
    { KRB5_PA_DASS                 , "PA-DASS" },
    { 0                            , NULL },
};

static const value_string krb5_encryption_types[] = {
    { KRB5_ENCTYPE_NULL           , "NULL" },
    { KRB5_ENCTYPE_DES_CBC_CRC    , "des-cbc-crc" },
    { KRB5_ENCTYPE_DES_CBC_MD4    , "des-cbc-md4" },
    { KRB5_ENCTYPE_DES_CBC_MD5    , "des-cbc-md5" },
    { KRB5_ENCTYPE_DES_CBC_RAW    , "des-cbc-raw" },
    { KRB5_ENCTYPE_DES3_CBC_SHA   , "des3-cbc-sha" },
    { KRB5_ENCTYPE_DES3_CBC_RAW   , "des3-cbc-raw" },
    { KRB5_ENCTYPE_DES_HMAC_SHA1  , "des-hmac-sha1" },
    { KRB5_ENCTYPE_DES3_CBC_SHA1  , "des3-cbc-sha1" },
    { KRB5_ENCTYPE_UNKNOWN        , "unknown" },
    { KRB5_ENCTYPE_LOCAL_DES3_HMAC_SHA1    , "local-des3-hmac-sha1" },
    { 0                            , NULL },
};

static const value_string krb5_address_types[] = {
    { KRB5_ADDR_IPv4,		"IPv4"},
    { KRB5_ADDR_CHAOS,		"CHAOS"},
    { KRB5_ADDR_XEROX,		"XEROX"},
    { KRB5_ADDR_ISO,		"ISO"},
    { KRB5_ADDR_DECNET,		"DECNET"},
    { KRB5_ADDR_APPLETALK,	"APPLETALK"},
    { 0,                        NULL },
};

static const value_string krb5_msg_types[] = {
	{ KRB5_MSG_TGS_REQ,	"TGS-REQ" },
	{ KRB5_MSG_TGS_REP,	"TGS-REP" },
	{ KRB5_MSG_AS_REQ,	"AS-REQ" },
	{ KRB5_MSG_AS_REP,	"AS-REP" },
	{ KRB5_MSG_AP_REQ,	"AP-REQ" },
	{ KRB5_MSG_AP_REP,	"AP-REP" },
	{ KRB5_MSG_SAFE,	"KRB-SAFE" },
	{ KRB5_MSG_PRIV,	"KRB-PRIV" },
	{ KRB5_MSG_CRED,	"KRB-CRED" },
	{ KRB5_MSG_ERROR,	"KRB-ERROR" },
        { 0,                    NULL },
};

static int dissect_PrincipalName(char *title, ASN1_SCK *asn1p,
                                 packet_info *pinfo, proto_tree *tree,
                                 int start_offset);
static int dissect_Ticket(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
                          proto_tree *tree, int start_offset);
static int dissect_EncryptedData(char *title, ASN1_SCK *asn1p,
				 packet_info *pinfo, proto_tree *tree,
				 int start_offset);
static int dissect_Addresses(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
                             proto_tree *tree, int start_offset);

static const char *
to_error_str(int ret) {
    switch (ret) {

        case ASN1_ERR_EOC_MISMATCH:
            return("EOC mismatch");

        case ASN1_ERR_WRONG_TYPE:
            return("Wrong type for that item");

        case ASN1_ERR_LENGTH_NOT_DEFINITE:
            return("Length was indefinite");

        case ASN1_ERR_LENGTH_MISMATCH:
            return("Length mismatch");

        case ASN1_ERR_WRONG_LENGTH_FOR_TYPE:
            return("Wrong length for that item's type");

    }
    return("Unknown error");
}

static void
krb_proto_tree_add_time(proto_tree *tree, tvbuff_t *tvb, int offset,
			int str_len, char *name, guchar *str)
{
    if (tree)
        proto_tree_add_text(tree, tvb, offset, str_len,
                            "%s: %.4s-%.2s-%.2s %.2s:%.2s:%.2s (%.1s)",
                            name, str, str+4, str+6,
                            str+8, str+10, str+12,
                            str+14);
}


/*
 * You must be kidding.  I'm going to actually use a macro to do something?
 *   bad me.  Bad me.
 */

#define KRB_HEAD_DECODE_OR_DIE(token) \
   start = asn1p->offset; \
   ret = asn1_header_decode (asn1p, &cls, &con, &tag, &def, &item_len); \
   if (ret != ASN1_ERR_NOERROR) {\
       if (check_col(pinfo->fd, COL_INFO)) \
           col_add_fstr(pinfo->fd, COL_INFO, "ERROR: Problem at %s: %s", \
                    token, to_error_str(ret)); \
       return -1; \
   } \
   if (!def) {\
       if (check_col(pinfo->fd, COL_INFO)) \
           col_add_fstr(pinfo->fd, COL_INFO, "not definite: %s", token); \
       fprintf(stderr,"not definite: %s\n", token); \
       return -1; \
   } \
   offset += (asn1p->offset - start);

#define CHECK_APPLICATION_TYPE(expected_tag) \
    (cls == ASN1_APL && con == ASN1_CON && tag == expected_tag)

#define DIE_IF_NOT_APPLICATION_TYPE(token, expected_tag) \
    if (!CHECK_APPLICATION_TYPE(expected_tag)) \
        DIE_WITH_BAD_TYPE(token, expected_tag);

#define CHECK_CONTEXT_TYPE(expected_tag) \
    (cls == ASN1_CTX && con == ASN1_CON && tag == expected_tag)

#define DIE_IF_NOT_CONTEXT_TYPE(token, expected_tag) \
    if (!CHECK_CONTEXT_TYPE(expected_tag)) \
        DIE_WITH_BAD_TYPE(token, expected_tag);

#define DIE_WITH_BAD_TYPE(token, expected_tag) \
    { \
      if (check_col(pinfo->fd, COL_INFO)) \
         col_add_fstr(pinfo->fd, COL_INFO, "ERROR: Problem at %s: %s (tag=%d exp=%d)", \
                      token, to_error_str(ASN1_ERR_WRONG_TYPE), tag, expected_tag); \
      return -1; \
    }

#define KRB_DECODE_APPLICATION_TAGGED_HEAD_OR_DIE(token, expected_tag) \
    KRB_HEAD_DECODE_OR_DIE(token); \
    DIE_IF_NOT_APPLICATION_TYPE(token, expected_tag);

#define KRB_DECODE_CONTEXT_HEAD_OR_DIE(token, expected_tag) \
    KRB_HEAD_DECODE_OR_DIE(token); \
    DIE_IF_NOT_CONTEXT_TYPE(token, expected_tag);

#define KRB_SEQ_HEAD_DECODE_OR_DIE(token) \
   ret = asn1_sequence_decode (asn1p, &item_len, &header_len); \
   if (ret != ASN1_ERR_NOERROR) {\
       if (check_col(pinfo->fd, COL_INFO)) \
           col_add_fstr(pinfo->fd, COL_INFO, "ERROR: Problem at %s: %s", \
                    token, to_error_str(ret)); \
       return -1; \
   } \
   offset += header_len;

#define KRB_DECODE_OR_DIE(token, fn, val) \
    ret = fn (asn1p, &val, &length); \
    if (ret != ASN1_ERR_NOERROR) { \
       if (check_col(pinfo->fd, COL_INFO)) \
         col_add_fstr(pinfo->fd, COL_INFO, "ERROR: Problem at %s: %s", \
                     token, to_error_str(ret)); \
        return -1; \
    } \

#define KRB_DECODE_UINT32_OR_DIE(token, val) \
    KRB_DECODE_OR_DIE(token, asn1_uint32_decode, val);

#define KRB_DECODE_STRING_OR_DIE(token, expected_tag, val, val_len, item_len) \
    ret = asn1_string_decode (asn1p, &val, &val_len, &item_len, expected_tag); \
    if (ret != ASN1_ERR_NOERROR) { \
       if (check_col(pinfo->fd, COL_INFO)) \
         col_add_fstr(pinfo->fd, COL_INFO, "ERROR: Problem at %s: %s", \
                     token, to_error_str(ret)); \
        return -1; \
    }

#define KRB_DECODE_OCTET_STRING_OR_DIE(token, val, val_len, item_len) \
    KRB_DECODE_STRING_OR_DIE(token, ASN1_OTS, val, val_len, item_len)

#define KRB_DECODE_GENERAL_STRING_OR_DIE(token, val, val_len, item_len) \
    KRB_DECODE_STRING_OR_DIE(token, ASN1_GENSTR, val, val_len, item_len)

#define KRB_DECODE_GENERAL_TIME_OR_DIE(token, val, val_len, item_len) \
    KRB_DECODE_STRING_OR_DIE(token, ASN1_GENTIM, val, val_len, item_len)

/* dissect_type_value_pair decodes (roughly) this:

    SEQUENCE  {
                        INTEGER,
                        OCTET STRING
    }

    which is all over the place in krb5 */

static void
dissect_type_value_pair(ASN1_SCK *asn1p, int *inoff,
                        guint32 *type, int *type_len, int *type_off,
                        guchar **val, int *val_len, int *val_off) {
    int offset = *inoff;
    guint cls, con, tag;
    gboolean def;
    int start;
    guint tmp_len;
    int ret;

    /* SEQUENCE */
    start = asn1p->offset;
    asn1_header_decode (asn1p, &cls, &con, &tag, &def, &tmp_len);
    offset += (asn1p->offset - start);

    /* INT */
    /* wrapper */
    start = asn1p->offset;
    asn1_header_decode (asn1p, &cls, &con, &tag, &def, &tmp_len);
    offset += (asn1p->offset - start);

    if (type_off)
        *type_off = offset;

    /* value */
    ret =  asn1_uint32_decode(asn1p, type, type_len);
    if (ret != ASN1_ERR_NOERROR) {
        fprintf(stderr,"die: type_value_pair: type, %s\n", to_error_str(ret));
        return;
    }
    offset += tmp_len;

    /* OCTET STRING (or generic data) */
    /* wrapper */
    start = asn1p->offset;
    asn1_header_decode (asn1p, &cls, &con, &tag, &def, val_len);
    asn1_header_decode (asn1p, &cls, &con, &tag, &def, val_len);
    offset += asn1p->offset - start;
    
    if (val_off)
        *val_off = offset;

    /* value */
    asn1_string_value_decode (asn1p, *val_len, val);

    *inoff = offset + *val_len;
}

static gboolean
dissect_kerberos_main(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    int offset = 0;
    proto_tree *kerberos_tree = NULL;
    proto_tree *etype_tree = NULL;
    proto_tree *preauth_tree = NULL;
    proto_tree *request_tree = NULL;
    proto_tree *additional_tickets_tree = NULL;
    ASN1_SCK asn1, *asn1p = &asn1;
    proto_item *item = NULL;

    guint length;
    guint cls, con, tag;
    gboolean def;
    guint item_len, total_len;
    int start, end, message_end, sequence_end;

    int ret;

    guint protocol_message_type;
    
    guint32 version;
    guint32 msg_type;
    guint32 preauth_type;
    guint32 tmp_int;

    /* simple holders */
    int str_len;
    guchar *str;
    int tmp_pos1, tmp_pos2;

    asn1_open(&asn1, tvb, 0);

    /* top header */
    KRB_HEAD_DECODE_OR_DIE("top");
    protocol_message_type = tag;
    if (tree) {
        item = proto_tree_add_item(tree, proto_kerberos, tvb, offset,
                                   item_len, FALSE);
        kerberos_tree = proto_item_add_subtree(item, ett_kerberos);
    }
    message_end = asn1p->offset + item_len;
    
    /* second header */
    KRB_HEAD_DECODE_OR_DIE("top2");

    /* version number */
    KRB_HEAD_DECODE_OR_DIE("version-wrap");
    KRB_DECODE_UINT32_OR_DIE("version", version);

    if (kerberos_tree) {
        proto_tree_add_text(kerberos_tree, tvb, offset, length,
                            "Version: %d",
                            version);
    }
    offset += length;

    /* message type */
    KRB_HEAD_DECODE_OR_DIE("message-type-wrap");
    KRB_DECODE_UINT32_OR_DIE("message-type", msg_type);

    if (kerberos_tree) {
        proto_tree_add_text(kerberos_tree, tvb, offset, length,
                            "MSG Type: %s",
                            val_to_str(msg_type, krb5_msg_types,
                                       "Unknown msg type %#x"));
    }
    offset += length;

    if (check_col(pinfo->fd, COL_INFO))
        col_add_str(pinfo->fd, COL_INFO, val_to_str(msg_type, krb5_msg_types,
                                             "Unknown msg type %#x"));

        /* is preauthentication present? */
    KRB_HEAD_DECODE_OR_DIE("padata-or-body");
    if (((protocol_message_type == KRB5_MSG_AS_REQ ||
          protocol_message_type == KRB5_MSG_TGS_REQ) &&
         tag == KRB5_KDC_REQ_PADATA) ||
        ((protocol_message_type == KRB5_MSG_AS_REP ||
          protocol_message_type == KRB5_MSG_TGS_REP) &&
         tag == KRB5_KDC_REP_PADATA)) {
        /* pre-authentication supplied */

        if (tree) {
            item = proto_tree_add_text(kerberos_tree, tvb, offset,
                                       item_len, "Pre-Authentication");
            preauth_tree = proto_item_add_subtree(item, ett_preauth);
        }

        KRB_HEAD_DECODE_OR_DIE("sequence of pa-data");
        end = asn1p->offset + item_len;

        while(asn1p->offset < end) {
            dissect_type_value_pair(asn1p, &offset,
                                    &preauth_type, &item_len, &tmp_pos1,
                                    &str, &str_len, &tmp_pos2);

            if (preauth_tree) {
                proto_tree_add_text(preauth_tree, tvb, tmp_pos1,
                                    item_len, "Type: %s",
                                    val_to_str(preauth_type,
                                               krb5_preauthentication_types,
                                               "Unknown preauth type %#x"));
                proto_tree_add_text(preauth_tree, tvb, tmp_pos2,
                                    str_len, "Value: %s",
                                    bytes_to_str(str, str_len));
            }
        }
        KRB_HEAD_DECODE_OR_DIE("message-body");
    }

    switch (protocol_message_type) {

    case KRB5_MSG_AS_REQ:
    case KRB5_MSG_TGS_REQ:
/*
  AS-REQ ::=         [APPLICATION 10] KDC-REQ
  TGS-REQ ::=        [APPLICATION 12] KDC-REQ
    
  KDC-REQ ::=        SEQUENCE {
           pvno[1]               INTEGER,
           msg-type[2]           INTEGER,
           padata[3]             SEQUENCE OF PA-DATA OPTIONAL,
           req-body[4]           KDC-REQ-BODY
  }

  KDC-REQ-BODY ::=   SEQUENCE {
            kdc-options[0]       KDCOptions,
            cname[1]             PrincipalName OPTIONAL,
                         -- Used only in AS-REQ
            realm[2]             Realm, -- Server's realm
                         -- Also client's in AS-REQ
            sname[3]             PrincipalName OPTIONAL,
            from[4]              KerberosTime OPTIONAL,
            till[5]              KerberosTime,
            rtime[6]             KerberosTime OPTIONAL,
            nonce[7]             INTEGER,
            etype[8]             SEQUENCE OF INTEGER, -- EncryptionType,
                         -- in preference order
            addresses[9]         HostAddresses OPTIONAL,
            enc-authorization-data[10]   EncryptedData OPTIONAL,
                         -- Encrypted AuthorizationData encoding
            additional-tickets[11]       SEQUENCE OF Ticket OPTIONAL
  }

*/
        /* request body */
        KRB_HEAD_DECODE_OR_DIE("body-sequence");
        if (tree) {
            item = proto_tree_add_text(kerberos_tree, tvb, offset,
                                       item_len, "Request");
            request_tree = proto_item_add_subtree(item, ett_request);
        }
        sequence_end = asn1p->offset + item_len;

        /* kdc options */
        KRB_HEAD_DECODE_OR_DIE("kdc options");

        KRB_HEAD_DECODE_OR_DIE("kdc options:bits");

        if (request_tree) {
                proto_tree_add_text(request_tree, tvb, offset, item_len,
                                    "Options: %s",
                                    tvb_bytes_to_str(asn1p->tvb, asn1p->offset,
                                                     item_len));
        }
        offset += item_len;
        asn1p->offset += item_len;

        KRB_HEAD_DECODE_OR_DIE("Client Name or Realm");

        if (CHECK_CONTEXT_TYPE(KRB5_BODY_CNAME)) {
            item_len = dissect_PrincipalName("Client Name", asn1p, pinfo,
                                             request_tree, offset);
            if (item_len == -1)
                return -1;
            offset += item_len;
            KRB_HEAD_DECODE_OR_DIE("Realm");
        }

        DIE_IF_NOT_CONTEXT_TYPE("Realm", KRB5_BODY_REALM);
        KRB_DECODE_GENERAL_STRING_OR_DIE("Realm", str, str_len, item_len);
        if (request_tree) {
            proto_tree_add_text(request_tree, tvb, offset, item_len,
                                "Realm: %.*s", str_len, str);
        }
        offset += item_len;

        KRB_HEAD_DECODE_OR_DIE("Server Name");
        if (CHECK_CONTEXT_TYPE(KRB5_BODY_SNAME)) {
            item_len = dissect_PrincipalName("Server Name", asn1p, pinfo,
                                             request_tree, offset);
            if (item_len == -1)
                return -1;
            offset += item_len;
            KRB_HEAD_DECODE_OR_DIE("From or Till");
        }

        if (CHECK_CONTEXT_TYPE(KRB5_BODY_FROM)) {
            KRB_DECODE_GENERAL_TIME_OR_DIE("From", str, str_len, item_len);
            krb_proto_tree_add_time(request_tree, asn1p->tvb, offset, item_len,
                                    "Start Time", str);
            offset += item_len;
            KRB_HEAD_DECODE_OR_DIE("Till");
        }

        DIE_IF_NOT_CONTEXT_TYPE("Till", KRB5_BODY_TILL);
        KRB_DECODE_GENERAL_TIME_OR_DIE("Till", str, str_len, item_len);
        krb_proto_tree_add_time(request_tree, asn1p->tvb, offset, item_len,
                                "End Time", str);
        offset += item_len;

        KRB_HEAD_DECODE_OR_DIE("Renewable Until or Nonce");
        if (CHECK_CONTEXT_TYPE(KRB5_BODY_RTIME)) {
            KRB_DECODE_GENERAL_TIME_OR_DIE("Renewable Until", str, str_len, item_len);
            krb_proto_tree_add_time(request_tree, asn1p->tvb, offset, item_len,
                                    "Renewable Until", str);
            offset += item_len;
            KRB_HEAD_DECODE_OR_DIE("Nonce");
        }
            
        DIE_IF_NOT_CONTEXT_TYPE("Nonce", KRB5_BODY_NONCE);
        KRB_DECODE_UINT32_OR_DIE("Nonce", tmp_int);
        if (request_tree) {
            proto_tree_add_text(request_tree, tvb, offset, length,
                                "Random Number: %u",
                                tmp_int);
        }
        offset += length;
        
        KRB_DECODE_CONTEXT_HEAD_OR_DIE("encryption type spot",
                                              KRB5_BODY_ENCTYPE);
        KRB_HEAD_DECODE_OR_DIE("encryption type list");
        if (kerberos_tree) {
            item = proto_tree_add_text(request_tree, tvb, offset,
                                       item_len, "Encryption Types");
            etype_tree = proto_item_add_subtree(item, ett_etype);
        }
        total_len = item_len;
        while(total_len > 0) {
            KRB_DECODE_UINT32_OR_DIE("encryption type", tmp_int);
            if (etype_tree) {
                proto_tree_add_text(etype_tree, tvb, offset, length,
                                    "Type: %s",
                                    val_to_str(tmp_int,
                                               krb5_encryption_types,
                                               "Unknown encryption type %#x"));
            }
            offset += length;
            total_len -= length;
        }

        if (asn1p->offset >= sequence_end)
            break;
        KRB_HEAD_DECODE_OR_DIE("addresses or enc-authorization-data");
        if (CHECK_CONTEXT_TYPE(KRB5_BODY_ADDRESSES)) {
            /* addresses supplied */

            length = dissect_Addresses("Addresses", asn1p, pinfo, kerberos_tree,
                                       offset);
            if (offset == -1)
                return -1;
            offset += length;
            if (asn1p->offset >= sequence_end)
                break;
            KRB_HEAD_DECODE_OR_DIE("enc-authorization-data or additional-tickets");
        }

        if (CHECK_CONTEXT_TYPE(KRB5_BODY_ENC_AUTHORIZATION_DATA)) {
            /* enc-authorization-data supplied */
            length = dissect_EncryptedData("Encrypted Payload", asn1p, pinfo,
                                           kerberos_tree, offset);
            if (length == -1)
                return -1;
            offset += length;
            if (asn1p->offset >= sequence_end)
                break;
            KRB_HEAD_DECODE_OR_DIE("additional-tickets");
        }

        /* additional-tickets supplied */
        if (tree) {
            item = proto_tree_add_text(kerberos_tree, tvb, offset,
                                       item_len, "Additional Tickets");
            additional_tickets_tree = proto_item_add_subtree(item, ett_additional_tickets);
        }
        end = asn1p->offset + item_len;
        while(asn1p->offset < end) {
            KRB_DECODE_CONTEXT_HEAD_OR_DIE("ticket", KRB5_KDC_REP_TICKET);
            length = dissect_Ticket("ticket", asn1p, pinfo, additional_tickets_tree,
                                    offset);
            if (length == -1)
                return -1;
            offset += length;
        }

        break;

    case KRB5_MSG_AS_REP:
    case KRB5_MSG_TGS_REP:
/*
   AS-REP ::=    [APPLICATION 11] KDC-REP
   TGS-REP ::=   [APPLICATION 13] KDC-REP

   KDC-REP ::=   SEQUENCE {
                 pvno[0]                    INTEGER,
                 msg-type[1]                INTEGER,
                 padata[2]                  SEQUENCE OF PA-DATA OPTIONAL,
                 crealm[3]                  Realm,
                 cname[4]                   PrincipalName,
                 ticket[5]                  Ticket,
                 enc-part[6]                EncryptedData
   }
*/

	DIE_IF_NOT_CONTEXT_TYPE("crealm", KRB5_KDC_REP_CREALM);
        KRB_DECODE_GENERAL_STRING_OR_DIE("realm name", str, str_len, item_len);
        if (kerberos_tree) {
            proto_tree_add_text(kerberos_tree, tvb, offset, item_len,
                                "Realm: %.*s", str_len, str);
        }
        offset += item_len;

        KRB_DECODE_CONTEXT_HEAD_OR_DIE("cname", KRB5_KDC_REP_CNAME);
        item_len = dissect_PrincipalName("Client Name", asn1p, pinfo,
                                         kerberos_tree, offset);
        if (item_len == -1)
            return -1;
        offset += item_len;
        
        KRB_DECODE_CONTEXT_HEAD_OR_DIE("ticket", KRB5_KDC_REP_TICKET);
        length = dissect_Ticket("ticket", asn1p, pinfo, kerberos_tree, offset);
        if (length == -1)
            return -1;
        offset += length;

        KRB_DECODE_CONTEXT_HEAD_OR_DIE("enc-msg-part",
                                              KRB5_KDC_REP_ENC_PART);
        length = dissect_EncryptedData("Encrypted Payload", asn1p, pinfo,
                                       kerberos_tree, offset);
        if (length == -1)
            return -1;
        offset += length;
        break;

    case KRB5_MSG_ERROR:
/*
  KRB-ERROR ::=   [APPLICATION 30] SEQUENCE {
                   pvno[0]               INTEGER,
                   msg-type[1]           INTEGER,
                   ctime[2]              KerberosTime OPTIONAL,
                   cusec[3]              INTEGER OPTIONAL,
                   stime[4]              KerberosTime,
                   susec[5]              INTEGER,
                   error-code[6]         INTEGER,
                   crealm[7]             Realm OPTIONAL,
                   cname[8]              PrincipalName OPTIONAL,
                   realm[9]              Realm, -- Correct realm
                   sname[10]             PrincipalName, -- Correct name
                   e-text[11]            GeneralString OPTIONAL,
                   e-data[12]            OCTET STRING OPTIONAL
   }
  }

*/

	/* ctime */
        if (CHECK_CONTEXT_TYPE(KRB5_ERROR_CTIME)) {
            KRB_DECODE_GENERAL_TIME_OR_DIE("ctime", str, str_len, item_len);
            krb_proto_tree_add_time(kerberos_tree, asn1p->tvb, offset, item_len,
                                    "ctime", str);
            offset += item_len;
			KRB_HEAD_DECODE_OR_DIE("cusec");
        }

	/* cusec */
        if (CHECK_CONTEXT_TYPE(KRB5_ERROR_CUSEC)) {
			KRB_DECODE_UINT32_OR_DIE("cusec", tmp_int);
	    if (kerberos_tree) {
		proto_tree_add_text(kerberos_tree, tvb, offset, length,
	                            "cusec: %u",
	                            tmp_int);
	    }

            offset += item_len;
			KRB_HEAD_DECODE_OR_DIE("sutime");
        }

	DIE_IF_NOT_CONTEXT_TYPE("sutime", KRB5_ERROR_STIME);
	KRB_DECODE_GENERAL_TIME_OR_DIE("stime", str, str_len, item_len);
	krb_proto_tree_add_time(kerberos_tree, asn1p->tvb, offset, item_len,
	                            "stime", str);
    	offset += item_len;

	KRB_HEAD_DECODE_OR_DIE("susec");
	DIE_IF_NOT_CONTEXT_TYPE("susec", KRB5_ERROR_SUSEC);		
	KRB_DECODE_UINT32_OR_DIE("susec", tmp_int);
	if (kerberos_tree) {
		proto_tree_add_text(kerberos_tree, tvb, offset, length,
		                    "susec: %u",
		                    tmp_int);
	}
	offset += item_len;

	KRB_HEAD_DECODE_OR_DIE("errcode");
	DIE_IF_NOT_CONTEXT_TYPE("errcode", KRB5_ERROR_ERROR_CODE);
	KRB_DECODE_UINT32_OR_DIE("errcode", tmp_int);
	if (kerberos_tree) {
	    proto_tree_add_text(kerberos_tree, tvb, offset, length,
	                        "Error Code: %s",
				val_to_str(tmp_int, krb5_error_codes,
                                           "Unknown error code %#x"));
	}
        offset += item_len;
	KRB_HEAD_DECODE_OR_DIE("crealm");

        if (CHECK_CONTEXT_TYPE(KRB5_ERROR_CREALM)) {
        	KRB_DECODE_GENERAL_STRING_OR_DIE("crealm", str, str_len, item_len);
        	if (kerberos_tree) {
		    proto_tree_add_text(kerberos_tree, tvb, offset, item_len,
                                	"crealm: %.*s", str_len, str);
        	}
        	offset += item_len;
		KRB_HEAD_DECODE_OR_DIE("cname");
	}

	if (CHECK_CONTEXT_TYPE(KRB5_ERROR_CNAME)) {
	    item_len = dissect_PrincipalName("cname", asn1p, pinfo,
                                         kerberos_tree, offset);
	    if (item_len == -1)
		return -1;
	    offset += item_len;
	    KRB_HEAD_DECODE_OR_DIE("realm");
	}

	DIE_IF_NOT_CONTEXT_TYPE("realm", KRB5_ERROR_REALM);
        KRB_DECODE_GENERAL_STRING_OR_DIE("realm", str, str_len, item_len);
        if (kerberos_tree) {
            proto_tree_add_text(kerberos_tree, tvb, offset, item_len,
                                "realm: %.*s", str_len, str);
        }
        offset += item_len;
	KRB_HEAD_DECODE_OR_DIE("sname");

	DIE_IF_NOT_CONTEXT_TYPE("sname", KRB5_ERROR_SNAME);
	item_len = dissect_PrincipalName("sname", asn1p, pinfo,
                                         kerberos_tree, offset);
	if (item_len == -1)
		return -1;
	offset += item_len;

        if (asn1p->offset >= message_end)
            break;
	KRB_HEAD_DECODE_OR_DIE("e-text");
	if ( CHECK_CONTEXT_TYPE(KRB5_ERROR_ETEXT) ) {
            KRB_DECODE_GENERAL_STRING_OR_DIE("etext", str, str_len, item_len);
            if (kerberos_tree) {
		proto_tree_add_text(kerberos_tree, tvb, offset, item_len,
                                	"etext: %.*s", str_len, str);
            }
            offset += item_len;
            if (asn1p->offset >= message_end)
                break;
	    KRB_HEAD_DECODE_OR_DIE("e-data");
	}

	if ( CHECK_CONTEXT_TYPE(KRB5_ERROR_EDATA) ) {
	    guchar *data;
	    guint data_len;

 	    KRB_DECODE_OCTET_STRING_OR_DIE("e-data", data, data_len, item_len);

	    if (kerberos_tree) {
		proto_tree_add_text(kerberos_tree, tvb, offset, data_len,
                            "Error Data: %s", bytes_to_str(data, item_len));
	    }
	    offset += data_len;
	}

        break;
    }
    return offset;
}

static void
dissect_kerberos(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
    if (check_col(pinfo->fd, COL_PROTOCOL))
        col_set_str(pinfo->fd, COL_PROTOCOL, "KRB5");

    dissect_kerberos_main(tvb, pinfo, tree);
}

static int
dissect_PrincipalName(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
                       proto_tree *tree, int start_offset)
{
/*
   PrincipalName ::=   SEQUENCE {
                       name-type[0]     INTEGER,
                       name-string[1]   SEQUENCE OF GeneralString
   }
*/
    proto_tree *princ_tree = NULL;
    int offset = start_offset;

    guint32 princ_type;

    int start;
    guint cls, con, tag;
    guint header_len, item_len, total_len, type_len;
    int ret;

    proto_item *item = NULL;
    guint length;
    gboolean def;

    int type_offset;

    guchar *name;
    guint name_len;

    /* principal name */
    KRB_SEQ_HEAD_DECODE_OR_DIE("principal section");

    if (tree) {
      item = proto_tree_add_text(tree, asn1p->tvb, start_offset,
                                 (offset - start_offset) + item_len, "%s",
                                 title);
      princ_tree = proto_item_add_subtree(item, ett_princ);
    } else {
      item = NULL;
      princ_tree = NULL;
    }

    KRB_DECODE_CONTEXT_HEAD_OR_DIE("principal type", 0);
    KRB_DECODE_UINT32_OR_DIE("princ-type", princ_type);
    type_offset = offset;
    type_len = item_len;
    offset += length;

    if (princ_tree) {
      proto_tree_add_text(princ_tree, asn1p->tvb, type_offset, type_len,
						"Type: %s",
						val_to_str(princ_type, krb5_princ_types,
                                           "Unknown name type %#x"));
    }

    KRB_DECODE_CONTEXT_HEAD_OR_DIE("principal name-string", 1);
    KRB_SEQ_HEAD_DECODE_OR_DIE("principal name-string sequence-of");
    total_len = item_len;
    if (total_len == 0) {
      /* There are no name strings in this PrincipalName, so we can't
         put any in the top-level item. */
      return offset - start_offset;
    }

    /* Put the first name string in the top-level item. */
    KRB_DECODE_GENERAL_STRING_OR_DIE("principal name", name, name_len, item_len);
    if (princ_tree) {
        proto_item_set_text(item, "%s: %.*s", title, (int) name_len, name);
        proto_tree_add_text(princ_tree, asn1p->tvb, offset, item_len,
                            "Name: %.*s", (int) name_len, name);
    }
    total_len -= item_len;
    offset += item_len;

    /* Now process the rest of the strings.
       XXX - put them in the item as well? */
    while (total_len > 0) {
        KRB_DECODE_GENERAL_STRING_OR_DIE("principal name", name, name_len, item_len);
        if (princ_tree) {
            proto_tree_add_text(princ_tree, asn1p->tvb, offset, item_len,
                                "Name: %.*s", (int) name_len, name);
        }
        total_len -= item_len;
        offset += item_len;
    }
    return offset - start_offset;
}

static int
dissect_Addresses(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
                  proto_tree *tree, int start_offset) {
    proto_tree *address_tree = NULL;
    int offset = start_offset;

    int start, end;
    guint cls, con, tag;
    guint item_len;
    int ret;

    proto_item *item = NULL;
    gboolean def;

    int tmp_pos1, tmp_pos2;
    guint32 address_type;

    int str_len;
    guchar *str;

    KRB_HEAD_DECODE_OR_DIE("sequence of addresses");
    if (tree) {
        item = proto_tree_add_text(tree, asn1p->tvb, offset,
                                   item_len, "Addresses");
        address_tree = proto_item_add_subtree(item, ett_addresses);
    }

    start = offset;
    end = asn1p->offset + item_len;

    while(asn1p->offset < end) {
        dissect_type_value_pair(asn1p, &offset,
                                &address_type, &item_len, &tmp_pos1,
                                &str, &str_len, &tmp_pos2);

        if (address_tree) {
            proto_tree_add_text(address_tree, asn1p->tvb, tmp_pos1,
                                item_len, "Type: %s",
                                val_to_str(address_type, krb5_address_types,
                                           "Unknown address type %#x"));
            switch(address_type) {
                case KRB5_ADDR_IPv4:
                    proto_tree_add_text(address_tree, asn1p->tvb, tmp_pos2,
                                        str_len, "Value: %d.%d.%d.%d",
                                        str[0], str[1], str[2], str[3]);
                    break;
                    
                default:
                    proto_tree_add_text(address_tree, asn1p->tvb, tmp_pos2,
                                        str_len, "Value: %s",
                                        bytes_to_str(str, str_len));
            }
        }
    }
    
    return offset - start_offset;
}

static int
dissect_EncryptedData(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
		      proto_tree *tree, int start_offset)
{
/*
   EncryptedData ::=   SEQUENCE {
                       etype[0]     INTEGER, -- EncryptionType
                       kvno[1]      INTEGER OPTIONAL,
                       cipher[2]    OCTET STRING -- ciphertext
   }
*/
    proto_tree *encr_tree = NULL;
    int offset = start_offset;

    int start;
    guint cls, con, tag;
    guint header_len, item_len, data_len;
    int ret;

    proto_item *item = NULL;
    guint length;
    gboolean def;
    guint32 val;

    guchar *data;

    KRB_SEQ_HEAD_DECODE_OR_DIE("encrypted data section");

    if (tree) {
        item = proto_tree_add_text(tree, asn1p->tvb, start_offset,
                                   (offset - start_offset) + item_len,
                                   "Encrypted Data: %s", title);
        encr_tree = proto_item_add_subtree(item, ett_princ);
    }

    /* type */
    KRB_DECODE_CONTEXT_HEAD_OR_DIE("encryption type", 0);
    KRB_DECODE_UINT32_OR_DIE("encr-type", val);
    if (encr_tree) {
        proto_tree_add_text(encr_tree, asn1p->tvb, offset, length,
                            "Type: %s",
                            val_to_str(val, krb5_encryption_types,
                                       "Unknown encryption type %#x"));
    }
    offset += length;

    /* kvno */
    KRB_HEAD_DECODE_OR_DIE("kvno-wrap or cipher-wrap");
    if (CHECK_CONTEXT_TYPE(1)) {
      KRB_DECODE_UINT32_OR_DIE("kvno", val);
      if (encr_tree) {
          proto_tree_add_text(encr_tree, asn1p->tvb, offset, length,
                              "KVNO: %d", val);
      }
      offset += length;
      KRB_HEAD_DECODE_OR_DIE("cipher-wrap");
    }

    DIE_IF_NOT_CONTEXT_TYPE("cipher-wrap", 2);
    KRB_DECODE_OCTET_STRING_OR_DIE("cipher", data, data_len, item_len);

    if (encr_tree) {
        proto_tree_add_text(encr_tree, asn1p->tvb, offset, data_len,
                            "CipherText: %s", bytes_to_str(data, item_len));
    }
    offset += data_len;
    
    return offset - start_offset;
}

static int
dissect_Ticket(char *title, ASN1_SCK *asn1p, packet_info *pinfo,
	       proto_tree *tree, int start_offset)
{
/*
   Ticket ::=                    [APPLICATION 1] SEQUENCE {
                                 tkt-vno[0]                   INTEGER,
                                 realm[1]                     Realm,
                                 sname[2]                     PrincipalName,
                                 enc-part[3]                  EncryptedData
   }
*/
    proto_tree *ticket_tree = NULL;
    int offset = start_offset;

    int start;
    guint cls, con, tag;
    guint header_len, item_len, total_len;
    int ret;

    proto_item *item = NULL;
    guint length;
    gboolean def;
    guint32 val;

    int str_len;
    guchar *str;

    KRB_DECODE_APPLICATION_TAGGED_HEAD_OR_DIE("Ticket section", 1);
    KRB_SEQ_HEAD_DECODE_OR_DIE("Ticket sequence");
    total_len = item_len;

    if (tree) {
        item = proto_tree_add_text(tree, asn1p->tvb, start_offset,
                                   (offset - start_offset) + item_len,
                                   "Ticket");
        ticket_tree = proto_item_add_subtree(item, ett_ticket);
    }

    /* type */
    KRB_DECODE_CONTEXT_HEAD_OR_DIE("Ticket tkt-vno", KRB5_TKT_TKT_VNO);
    KRB_DECODE_UINT32_OR_DIE("Ticket tkt-vno", val);
    if (ticket_tree) {
        proto_tree_add_text(ticket_tree, asn1p->tvb, offset, length,
                            "Version: %u", val);
    }
    offset += length;
    total_len -= length;

    /* realm name */
    KRB_DECODE_CONTEXT_HEAD_OR_DIE("Ticket realm", KRB5_TKT_REALM);
    KRB_DECODE_GENERAL_STRING_OR_DIE("Ticket realm string", str, str_len, item_len);
    if (ticket_tree) {
        proto_tree_add_text(ticket_tree, asn1p->tvb, offset, item_len,
                            "Realm: %.*s", str_len, str);
    }
    offset += item_len;
    total_len -= item_len;

    /* server name (sname) */
    KRB_DECODE_CONTEXT_HEAD_OR_DIE("Ticket sname", KRB5_TKT_SNAME);
    item_len = dissect_PrincipalName("Service Name", asn1p, pinfo, ticket_tree,
                                     offset);
    if (item_len == -1)
        return -1;
    offset += item_len;

    /* encrypted part */
    KRB_DECODE_CONTEXT_HEAD_OR_DIE("enc-part", KRB5_TKT_ENC_PART);
    length = dissect_EncryptedData("Ticket data", asn1p, pinfo, ticket_tree,
				   offset);
    if (length == -1)
        return -1;
    offset += length;

    return offset - start_offset;
}


void
proto_register_kerberos(void) {
/*
    static hf_register_info hf[] = {
    };
*/
    static gint *ett[] = {
        &ett_kerberos,
        &ett_preauth,
        &ett_request,
        &ett_princ,
        &ett_encrypted,
        &ett_ticket,
        &ett_addresses,
        &ett_etype,
        &ett_additional_tickets,
    };
    proto_kerberos = proto_register_protocol("Kerberos", "KRB5", "kerberos");
/*
    proto_register_field_array(proto_kerberos, hf, array_length(hf));
*/
    proto_register_subtree_array(ett, array_length(ett));
}

void
proto_reg_handoff_kerberos(void)
{
    dissector_add("udp.port", UDP_PORT_KERBEROS, dissect_kerberos,
	proto_kerberos);
    dissector_add("tcp.port", TCP_PORT_KERBEROS, dissect_kerberos,
	proto_kerberos);
}

/*

  MISC definitions from RFC1510:
  
   Realm ::=           GeneralString

   KerberosTime ::=   GeneralizedTime

   HostAddress ::=    SEQUENCE  {
                      addr-type[0]             INTEGER,
                      address[1]               OCTET STRING
   }

   HostAddresses ::=   SEQUENCE OF SEQUENCE {
                       addr-type[0]             INTEGER,
                       address[1]               OCTET STRING
   }

   AuthorizationData ::=   SEQUENCE OF SEQUENCE {
                           ad-type[0]               INTEGER,
                           ad-data[1]               OCTET STRING
   }
                   APOptions ::=   BIT STRING {
                                   reserved(0),
                                   use-session-key(1),
                                   mutual-required(2)
                   }


                   TicketFlags ::=   BIT STRING {
                                     reserved(0),
                                     forwardable(1),
                                     forwarded(2),
                                     proxiable(3),
                                     proxy(4),
                                     may-postdate(5),
                                     postdated(6),
                                     invalid(7),
                                     renewable(8),
                                     initial(9),
                                     pre-authent(10),
                                     hw-authent(11)
                   }

                  KDCOptions ::=   BIT STRING {
                                   reserved(0),
                                   forwardable(1),
                                   forwarded(2),
                                   proxiable(3),
                                   proxy(4),
                                   allow-postdate(5),
                                   postdated(6),
                                   unused7(7),
                                   renewable(8),
                                   unused9(9),
                                   unused10(10),
                                   unused11(11),
                                   renewable-ok(27),
                                   enc-tkt-in-skey(28),
                                   renew(30),
                                   validate(31)
                  }


            LastReq ::=   SEQUENCE OF SEQUENCE {
                          lr-type[0]               INTEGER,
                          lr-value[1]              KerberosTime
            }

   Ticket ::=                    [APPLICATION 1] SEQUENCE {
                                 tkt-vno[0]                   INTEGER,
                                 realm[1]                     Realm,
                                 sname[2]                     PrincipalName,
                                 enc-part[3]                  EncryptedData
   }

  -- Encrypted part of ticket
  EncTicketPart ::=     [APPLICATION 3] SEQUENCE {
                        flags[0]             TicketFlags,
                        key[1]               EncryptionKey,
                        crealm[2]            Realm,
                        cname[3]             PrincipalName,
                        transited[4]         TransitedEncoding,
                        authtime[5]          KerberosTime,
                        starttime[6]         KerberosTime OPTIONAL,
                        endtime[7]           KerberosTime,
                        renew-till[8]        KerberosTime OPTIONAL,
                        caddr[9]             HostAddresses OPTIONAL,
                        authorization-data[10]   AuthorizationData OPTIONAL
  }

  -- encoded Transited field
  TransitedEncoding ::=         SEQUENCE {
                                tr-type[0]  INTEGER, -- must be registered
                                contents[1]          OCTET STRING
  }

  -- Unencrypted authenticator
  Authenticator ::=    [APPLICATION 2] SEQUENCE    {
                 authenticator-vno[0]          INTEGER,
                 crealm[1]                     Realm,
                 cname[2]                      PrincipalName,
                 cksum[3]                      Checksum OPTIONAL,
                 cusec[4]                      INTEGER,
                 ctime[5]                      KerberosTime,
                 subkey[6]                     EncryptionKey OPTIONAL,
                 seq-number[7]                 INTEGER OPTIONAL,
                 authorization-data[8]         AuthorizationData OPTIONAL
  }

  PA-DATA ::=        SEQUENCE {
           padata-type[1]        INTEGER,
           padata-value[2]       OCTET STRING,
                         -- might be encoded AP-REQ
  }

   padata-type     ::= PA-ENC-TIMESTAMP
   padata-value    ::= EncryptedData -- PA-ENC-TS-ENC

   PA-ENC-TS-ENC   ::= SEQUENCE {
           patimestamp[0]               KerberosTime, -- client's time
           pausec[1]                    INTEGER OPTIONAL
   }

   EncASRepPart ::=    [APPLICATION 25[25]] EncKDCRepPart
   EncTGSRepPart ::=   [APPLICATION 26] EncKDCRepPart

   EncKDCRepPart ::=   SEQUENCE {
               key[0]                       EncryptionKey,
               last-req[1]                  LastReq,
               nonce[2]                     INTEGER,
               key-expiration[3]            KerberosTime OPTIONAL,
               flags[4]                     TicketFlags,
               authtime[5]                  KerberosTime,
               starttime[6]                 KerberosTime OPTIONAL,
               endtime[7]                   KerberosTime,
               renew-till[8]                KerberosTime OPTIONAL,
               srealm[9]                    Realm,
               sname[10]                    PrincipalName,
               caddr[11]                    HostAddresses OPTIONAL
   }

   AP-REQ ::=      [APPLICATION 14] SEQUENCE {
                   pvno[0]                       INTEGER,
                   msg-type[1]                   INTEGER,
                   ap-options[2]                 APOptions,
                   ticket[3]                     Ticket,
                   authenticator[4]              EncryptedData
   }

   APOptions ::=   BIT STRING {
                   reserved(0),
                   use-session-key(1),
                   mutual-required(2)
   }

   AP-REP ::=         [APPLICATION 15] SEQUENCE {
              pvno[0]                   INTEGER,
              msg-type[1]               INTEGER,
              enc-part[2]               EncryptedData
   }

   EncAPRepPart ::=   [APPLICATION 27]     SEQUENCE {
              ctime[0]                  KerberosTime,
              cusec[1]                  INTEGER,
              subkey[2]                 EncryptionKey OPTIONAL,
              seq-number[3]             INTEGER OPTIONAL
   }

   KRB-SAFE ::=        [APPLICATION 20] SEQUENCE {
               pvno[0]               INTEGER,
               msg-type[1]           INTEGER,
               safe-body[2]          KRB-SAFE-BODY,
               cksum[3]              Checksum
   }

   KRB-SAFE-BODY ::=   SEQUENCE {
               user-data[0]          OCTET STRING,
               timestamp[1]          KerberosTime OPTIONAL,
               usec[2]               INTEGER OPTIONAL,
               seq-number[3]         INTEGER OPTIONAL,
               s-address[4]          HostAddress,
               r-address[5]          HostAddress OPTIONAL
   }

   KRB-PRIV ::=         [APPLICATION 21] SEQUENCE {
                pvno[0]                   INTEGER,
                msg-type[1]               INTEGER,
                enc-part[3]               EncryptedData
   }

   EncKrbPrivPart ::=   [APPLICATION 28] SEQUENCE {
                user-data[0]              OCTET STRING,
                timestamp[1]              KerberosTime OPTIONAL,
                usec[2]                   INTEGER OPTIONAL,
                seq-number[3]             INTEGER OPTIONAL,
                s-address[4]              HostAddress, -- sender's addr
                r-address[5]              HostAddress OPTIONAL
                                                      -- recip's addr
   }

   KRB-CRED         ::= [APPLICATION 22]   SEQUENCE {
                    pvno[0]                INTEGER,
                    msg-type[1]            INTEGER, -- KRB_CRED
                    tickets[2]             SEQUENCE OF Ticket,
                    enc-part[3]            EncryptedData
   }

   EncKrbCredPart   ::= [APPLICATION 29]   SEQUENCE {
                    ticket-info[0]         SEQUENCE OF KrbCredInfo,
                    nonce[1]               INTEGER OPTIONAL,
                    timestamp[2]           KerberosTime OPTIONAL,
                    usec[3]                INTEGER OPTIONAL,
                    s-address[4]           HostAddress OPTIONAL,
                    r-address[5]           HostAddress OPTIONAL
   }

   KrbCredInfo      ::=                    SEQUENCE {
                    key[0]                 EncryptionKey,
                    prealm[1]              Realm OPTIONAL,
                    pname[2]               PrincipalName OPTIONAL,
                    flags[3]               TicketFlags OPTIONAL,
                    authtime[4]            KerberosTime OPTIONAL,
                    starttime[5]           KerberosTime OPTIONAL,
                    endtime[6]             KerberosTime OPTIONAL
                    renew-till[7]          KerberosTime OPTIONAL,
                    srealm[8]              Realm OPTIONAL,
                    sname[9]               PrincipalName OPTIONAL,
                    caddr[10]              HostAddresses OPTIONAL
   }

   KRB-ERROR ::=   [APPLICATION 30] SEQUENCE {
                   pvno[0]               INTEGER,
                   msg-type[1]           INTEGER,
                   ctime[2]              KerberosTime OPTIONAL,
                   cusec[3]              INTEGER OPTIONAL,
                   stime[4]              KerberosTime,
                   susec[5]              INTEGER,
                   error-code[6]         INTEGER,
                   crealm[7]             Realm OPTIONAL,
                   cname[8]              PrincipalName OPTIONAL,
                   realm[9]              Realm, -- Correct realm
                   sname[10]             PrincipalName, -- Correct name
                   e-text[11]            GeneralString OPTIONAL,
                   e-data[12]            OCTET STRING OPTIONAL
   }

   e-data    This field contains additional data about the error for use
             by the application to help it recover from or handle the
             error.  If the errorcode is KDC_ERR_PREAUTH_REQUIRED, then
             the e-data field will contain an encoding of a sequence of
             padata fields, each corresponding to an acceptable pre-
             authentication method and optionally containing data for
             the method:

      METHOD-DATA ::=    SEQUENCE of PA-DATA

   If the error-code is KRB_AP_ERR_METHOD, then the e-data field will
   contain an encoding of the following sequence:

      METHOD-DATA ::=    SEQUENCE {
                         method-type[0]   INTEGER,
                         method-data[1]   OCTET STRING OPTIONAL
      }

      EncryptionKey ::=   SEQUENCE {
                         keytype[0]    INTEGER,
                         keyvalue[1]   OCTET STRING
      }

      Checksum ::=   SEQUENCE {
                         cksumtype[0]   INTEGER,
                         checksum[1]    OCTET STRING
      }

*/
