/*
 * kerberos.c
 *
 * Copyright (C) 2011-22 - ntop.org
 * Copyright (C) 2009-11 - ipoque GmbH
 *
 * This file is part of nDPI, an open source deep packet inspection
 * library based on the OpenDPI and PACE technology by ipoque GmbH
 *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ndpi_protocol_ids.h"

#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_KERBEROS

#include "ndpi_api.h"
#include "ndpi_private.h"

/* #define KERBEROS_DEBUG 1 */

#define KERBEROS_PORT 88

static int ndpi_search_kerberos_extra(struct ndpi_detection_module_struct *ndpi_struct,
				      struct ndpi_flow_struct *flow);


/* Reference: https://en.wikipedia.org/wiki/X.690#Length_octets */
static int krb_decode_asn1_length(struct ndpi_detection_module_struct *ndpi_struct,
                                  size_t * const kasn1_offset)
{
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
  int64_t length;
  u_int16_t value_len;

  length = asn1_ber_decode_length(&packet->payload[*kasn1_offset],
				  packet->payload_packet_len - *kasn1_offset,
				  &value_len);

  if (length == -1 ||
      packet->payload_packet_len < *kasn1_offset + value_len + length)
  {
    return -1;
  }

  *kasn1_offset += value_len;

  return (int)length;
}

/* Reference: https://en.wikipedia.org/wiki/X.690#Identifier_octets */
static int krb_decode_asn1_sequence_type(struct ndpi_detection_module_struct *ndpi_struct,
                                         size_t * const kasn1_offset)
{
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;

  if (packet->payload_packet_len <= *kasn1_offset + 1 /* length octet */ ||
      packet->payload[*kasn1_offset] != 0x30 /* Universal Constructed Tag Type: Sequence */)
  {
    return -1;
  }

  (*kasn1_offset)++;

  return krb_decode_asn1_length(ndpi_struct, kasn1_offset);
}

/* Reference: https://en.wikipedia.org/wiki/X.690#Identifier_octets */
static int krb_decode_asn1_string_type(struct ndpi_detection_module_struct *ndpi_struct,
                                       size_t * const kasn1_offset,
                                       char const ** const out)
{
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
  int length;

  if (packet->payload_packet_len <= *kasn1_offset + 1 /* length octet */ ||
      (packet->payload[*kasn1_offset] != 0xA3 /* Context-specific Constructed Tag Type: Bit String */ &&
       packet->payload[*kasn1_offset] != 0xA4 /* Context-specific Constructed Tag Type: Octect String */ &&
       packet->payload[*kasn1_offset] != 0x30 /* Sequence Of */))
  {
    return -1;
  }

  (*kasn1_offset)++;

  length = krb_decode_asn1_length(ndpi_struct, kasn1_offset);
  if (length <= 0)
  {
    return -1;
  }

  if (out != NULL)
  {
    *out = (char *)&packet->payload[*kasn1_offset];
  }

  return length;
}

/* Reference: https://en.wikipedia.org/wiki/X.690#Identifier_octets */
static int krb_decode_asn1_int_type(struct ndpi_detection_module_struct *ndpi_struct,
                                    size_t * const kasn1_offset,
                                    int * const out)
{
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
  int length;

  if (packet->payload_packet_len <= *kasn1_offset + 1 /* length octet */ ||
      packet->payload[*kasn1_offset] != 0x02)
  {
    return -1;
  }

  (*kasn1_offset)++;

  length = krb_decode_asn1_length(ndpi_struct, kasn1_offset);
  if (length <= 0 || length > 4)
  {
    return -1;
  }

  if (out != NULL)
  {
    int i = 0;
    *out = 0;
    for (; i < length; ++i)
    {
      *out |= (unsigned int)packet->payload[*kasn1_offset + i] << (length - i - 1) * 8;
    }
    *kasn1_offset += i;
  }

  return length;
}

/* Tags in which we are not interested. */
static int krb_decode_asn1_blocks_skip(struct ndpi_detection_module_struct *ndpi_struct,
                                       size_t * const kasn1_offset)
{
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
  int length;

  if (packet->payload_packet_len <= *kasn1_offset + 1 /* length octet */ ||
      (packet->payload[*kasn1_offset] != 0xA0 /* Constructed Context-specific NULL */ &&
       packet->payload[*kasn1_offset] != 0xA1 /* Constructed Context-specific BOOLEAN */ &&
       packet->payload[*kasn1_offset] != 0xA2 /* Constructed Context-specific INTEGER */))
  {
    return -1;
  }

  (*kasn1_offset)++;

  length = krb_decode_asn1_length(ndpi_struct, kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  return length;
}

static void krb_strncpy_lower(char * const dst, size_t dst_siz,
                              char const * const src, size_t src_siz)
{
  int i, dst_len = (int)ndpi_min(src_siz, dst_siz - 1);

   dst[dst_len] = '\0';

   for(i = 0; i < dst_len; ++i)
   {
     if (ndpi_isprint(src[i]) == 0)
     {
       dst[i] = '?';
     } else {
       dst[i] = tolower(src[i]);
     }
   }
}

/* Reference: https://datatracker.ietf.org/doc/html/rfc4120 */
static int krb_parse(struct ndpi_detection_module_struct * const ndpi_struct,
                     struct ndpi_flow_struct * const flow,
                     size_t payload_offset)
{
  size_t kasn1_offset = payload_offset;
  int length, krb_version, msg_type;
  char const * text;

  length = krb_decode_asn1_sequence_type(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_blocks_skip(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_int_type(ndpi_struct, &kasn1_offset, &krb_version); /* pvno */
  if (length != 1 || krb_version != 5)
  {
    return -1;
  }

  length = krb_decode_asn1_blocks_skip(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_int_type(ndpi_struct, &kasn1_offset, &msg_type); /* msg-type */
  if (length != 1 || msg_type != 0x0d /* TGS-REP */)
  {
    return -1;
  }

  krb_decode_asn1_blocks_skip(ndpi_struct, &kasn1_offset);

  length = krb_decode_asn1_sequence_type(ndpi_struct, &kasn1_offset); /* Optional PADATA */
  if (length > 0)
  {
    kasn1_offset += length;
  }

  length = krb_decode_asn1_string_type(ndpi_struct, &kasn1_offset, &text);
  if (length < 3)
  {
    return -1;
  }

  kasn1_offset += length;
  text += 2;
  length -= 2;
  if (flow->protos.kerberos.domain[0] == '\0')
  {
    krb_strncpy_lower(flow->protos.kerberos.domain, sizeof(flow->protos.kerberos.domain),
                      text, length);
  }

  length = krb_decode_asn1_string_type(ndpi_struct, &kasn1_offset, NULL);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_sequence_type(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_blocks_skip(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }
  kasn1_offset += length;

  length = krb_decode_asn1_blocks_skip(ndpi_struct, &kasn1_offset);
  if (length < 0)
  {
    return -1;
  }

  length = krb_decode_asn1_string_type(ndpi_struct, &kasn1_offset, &text);
  if (length < 3)
  {
    return -1;
  }

  kasn1_offset += length;
  text += 2;
  length -= 2;
  if (flow->protos.kerberos.hostname[0] == '\0' && text[length - 1] != '$')
  {
    krb_strncpy_lower(flow->protos.kerberos.hostname, sizeof(flow->protos.kerberos.hostname),
                      text, length);
  } else if (flow->protos.kerberos.username[0] == '\0') {
    krb_strncpy_lower(flow->protos.kerberos.username, sizeof(flow->protos.kerberos.username),
                      text, length - 1);
  }

  return 0;
}

static void ndpi_int_kerberos_add_connection(struct ndpi_detection_module_struct *ndpi_struct,
					     struct ndpi_flow_struct *flow) {
  ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_KERBEROS, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
  NDPI_LOG_DBG(ndpi_struct, "trace KERBEROS\n");
}

/* ************************************************* */

static void ndpi_search_kerberos(struct ndpi_detection_module_struct *ndpi_struct,
				 struct ndpi_flow_struct *flow) {
  struct ndpi_packet_struct *packet = &ndpi_struct->packet;
  u_int16_t sport = packet->tcp ? ntohs(packet->tcp->source) : ntohs(packet->udp->source);
  u_int16_t dport = packet->tcp ? ntohs(packet->tcp->dest) : ntohs(packet->udp->dest);
  const u_int8_t *original_packet_payload = NULL;
  u_int16_t original_payload_packet_len = 0;

  if((sport != KERBEROS_PORT) && (dport != KERBEROS_PORT)) {
    NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
    return;
  }
  
  NDPI_LOG_DBG(ndpi_struct, "search KERBEROS\n");

#ifdef KERBEROS_DEBUG
  printf("\n[Kerberos] Process packet [len: %u]\n", packet->payload_packet_len);
#endif
    
  if(flow->kerberos_buf.pktbuf != NULL) {
    u_int missing = flow->kerberos_buf.pktbuf_maxlen - flow->kerberos_buf.pktbuf_currlen;

    if(packet->payload_packet_len <= missing) {
      memcpy(&flow->kerberos_buf.pktbuf[flow->kerberos_buf.pktbuf_currlen], packet->payload, packet->payload_packet_len);
      flow->kerberos_buf.pktbuf_currlen += packet->payload_packet_len;

      if(flow->kerberos_buf.pktbuf_currlen == flow->kerberos_buf.pktbuf_maxlen) {
	original_packet_payload = packet->payload;
	original_payload_packet_len = packet->payload_packet_len;
	packet->payload = (u_int8_t *)flow->kerberos_buf.pktbuf;
	packet->payload_packet_len = flow->kerberos_buf.pktbuf_currlen;
#ifdef KERBEROS_DEBUG
	printf("[Kerberos] Packet is now full: processing\n");
#endif
      } else {
#ifdef KERBEROS_DEBUG
	printf("[Kerberos] Missing %u bytes: skipping\n",
	       flow->kerberos_buf.pktbuf_maxlen - flow->kerberos_buf.pktbuf_currlen);
#endif

	return;
      }
    }
  }

  /* I have observed 0a,0c,0d,0e at packet->payload[19/21], maybe there are other possibilities */
  if(packet->payload_packet_len >= 4) {
    u_int32_t kerberos_len, expected_len;
    u_int16_t base_offset = 0;

    if(packet->tcp) {
      kerberos_len = ntohl(get_u_int32_t(packet->payload, 0)),
	expected_len = packet->payload_packet_len - 4;
      base_offset = 4;
    } else
      base_offset = 0, kerberos_len = expected_len = packet->payload_packet_len;

#ifdef KERBEROS_DEBUG
    printf("[Kerberos] [Kerberos len: %u][expected_len: %u]\n", kerberos_len, expected_len);
#endif

    if(kerberos_len < 12000) {
      /*
	Kerberos packets might be too long for a TCP packet
	so it could be split across two packets. Instead of
	rebuilding the stream we use a heuristic approach
      */
      if(kerberos_len > expected_len) {
	if(packet->tcp) {
	  if(flow->kerberos_buf.pktbuf == NULL) {
	    flow->kerberos_buf.pktbuf = (char*)ndpi_malloc(kerberos_len+4);

	    if(flow->kerberos_buf.pktbuf != NULL) {
	      flow->kerberos_buf.pktbuf_maxlen = kerberos_len+4;	      
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Allocated %u bytes\n", flow->kerberos_buf.pktbuf_maxlen);
#endif	      
	    }
	  }
	  
	  if(flow->kerberos_buf.pktbuf != NULL) {
	    if(packet->payload_packet_len <= flow->kerberos_buf.pktbuf_maxlen) {
	      memcpy(flow->kerberos_buf.pktbuf, packet->payload, packet->payload_packet_len);
	      flow->kerberos_buf.pktbuf_currlen = packet->payload_packet_len;
	    }
	  }
	}
	
	return;
      } else if(kerberos_len == expected_len) {
	if(packet->payload_packet_len > 64) {
	  u_int16_t koffset, i;

	  for(i=8; i<16; i++)
	    if((packet->payload[base_offset+i] == 0x03)
	       && (packet->payload[base_offset+i+1] == 0x02)
	       && (packet->payload[base_offset+i+2] == 0x01)
	       && (packet->payload[base_offset+i+3] != 0x05)
	       )
	      break;

	  koffset = base_offset + i + 3;

#ifdef KERBEROS_DEBUG
	  printf("[Kerberos] [msg-type: 0x%02X/%u][koffset: %u]\n",
		 packet->payload[koffset], packet->payload[koffset], koffset);
#endif

	  if(((packet->payload[koffset] == 0x0A)
	      || (packet->payload[koffset] == 0x0C)
	      || (packet->payload[koffset] == 0x1E)
	      || (packet->payload[koffset] == 0x0D)
	      || (packet->payload[koffset] == 0x0E))) {
	    u_int32_t koffsetp, body_offset = 0, pad_len;
	    u_int8_t msg_type = packet->payload[koffset];

#ifdef KERBEROS_DEBUG
	    printf("[Kerberos] Packet found 0x%02X/%u\n", msg_type, msg_type);
#endif

	    ndpi_int_kerberos_add_connection(ndpi_struct, flow);

	    if(msg_type != 0x0d) /* TGS-REP */ {
	      /* Process only on requests */
	      if(packet->payload[koffset+1] == 0xA3) {
		if(packet->payload[koffset+3] == 0x30)
		  pad_len = packet->payload[koffset+4];
		else {
		  /* Long pad */
		  pad_len = packet->payload[koffset+2];
		  for(i=3; i<10; i++) if(packet->payload[koffset+i] == pad_len) break;

		  pad_len = (packet->payload[koffset+i+1] << 8) + packet->payload[koffset+i+2];
		  koffset += i-2;
		}
	      } else
		pad_len = 0;

#ifdef KERBEROS_DEBUG
	      printf("pad_len=0x%02X/%u\n", pad_len, pad_len);
#endif

	      if(pad_len > 0) {
		koffsetp = koffset + 2;
		for(i=0; i<4; i++) if(packet->payload[koffsetp] != 0x30) koffsetp++; /* ASN.1 */
#ifdef KERBEROS_DEBUG
		printf("koffsetp=%u [%02X %02X] [byte 0 must be 0x30]\n", koffsetp, packet->payload[koffsetp], packet->payload[koffsetp+1]);
#endif
	      } else
		koffsetp = koffset;

	      body_offset = koffsetp + 1 + pad_len;

	      for(i=0; i<10; i++) if(body_offset<packet->payload_packet_len && packet->payload[body_offset] != 0x05) body_offset++; /* ASN.1 */
#ifdef KERBEROS_DEBUG
	      printf("body_offset=%u [%02X %02X] [byte 0 must be 0x05]\n", body_offset, packet->payload[body_offset], packet->payload[body_offset+1]);
#endif
	    }
	    
	    if(msg_type == 0x0A) /* AS-REQ */ {
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Processing AS-REQ\n");
#endif


	      if(body_offset < packet->payload_packet_len) {
		u_int16_t name_offset = body_offset + 13;
		
		for(i=0; (i<20) && (name_offset < packet->payload_packet_len); i++) {
		  if(packet->payload[name_offset] != 0x1b)
		    name_offset++; /* ASN.1 */
		}
		
#ifdef KERBEROS_DEBUG
		printf("name_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", name_offset, packet->payload[name_offset], packet->payload[name_offset+1]);
#endif

		if(name_offset < packet->payload_packet_len - 1) {
		  u_int cname_len = 0;

		  name_offset += 1;
		  if(name_offset < packet->payload_packet_len - 1 &&
		     ndpi_isprint(packet->payload[name_offset+1]) == 0) /* Isn't printable ? */
		  {
		    name_offset++;
		  }

		  if(name_offset < packet->payload_packet_len - 3 &&
		     packet->payload[name_offset+1] == 0x1b)
		  {
		    name_offset += 2;
		  }
		  
		  cname_len = packet->payload[name_offset];

		  if((cname_len+name_offset) < packet->payload_packet_len) {
		    u_int realm_len, realm_offset;
		    char cname_str[48];
		    u_int8_t num_cname = 0;

			cname_str[0] = '\0'; // required, because cname_len

		    while(++num_cname <= 2) {
		      if (name_offset + cname_len + 1 >= packet->payload_packet_len)
		        cname_len = 0;
		      krb_strncpy_lower(cname_str, sizeof(cname_str), (char*)&packet->payload[name_offset+1], cname_len);

#ifdef KERBEROS_DEBUG
		      printf("[AS-REQ][s/dport: %u/%u][Kerberos Cname][len: %u][%s]\n", sport, dport, cname_len, cname_str);
#endif

		      if(((strcmp(cname_str, "host") == 0) || (strcmp(cname_str, "ldap") == 0)) && (packet->payload[name_offset+1+cname_len] == 0x1b) && num_cname == 1) {
		        name_offset += cname_len + 2;
		        if (name_offset < packet->payload_packet_len)
		          cname_len = packet->payload[name_offset];
		      } else{
		        break;
		      }
		    }

		    realm_offset = cname_len + name_offset + 3;

		    /* if cname does not end with a $ then it's a username */
		    if(cname_len > 0 && name_offset + cname_len + 1 < packet->payload_packet_len
		       && (cname_len < sizeof(cname_str))
		       && (cname_str[cname_len-1] == '$')) {
		      cname_str[cname_len-1] = '\0';
		      ndpi_snprintf(flow->protos.kerberos.hostname, sizeof(flow->protos.kerberos.hostname), "%s", cname_str);
		    } else
		      ndpi_snprintf(flow->protos.kerberos.username, sizeof(flow->protos.kerberos.username), "%s", cname_str);

		    for(i=0; (i < 14) && (realm_offset <  packet->payload_packet_len); i++) {
		      if(packet->payload[realm_offset] != 0x1b)
			realm_offset++; /* ASN.1 */
		    }
		    
#ifdef KERBEROS_DEBUG
		    printf("realm_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", realm_offset,
			   packet->payload[realm_offset], packet->payload[realm_offset+1]);
#endif
		    
		    realm_offset += 1;
		    //if(num_cname == 2) realm_offset++;
		    if(realm_offset  < packet->payload_packet_len) {
		      realm_len = packet->payload[realm_offset];

		      if((realm_offset+realm_len) < packet->payload_packet_len) {
			char realm_str[48];

			realm_offset += 1;
			krb_strncpy_lower(realm_str, sizeof(realm_str), (char*)&packet->payload[realm_offset], realm_len);

#ifdef KERBEROS_DEBUG
			printf("[AS-REQ][Kerberos Realm][len: %u][%s]\n", realm_len, realm_str);
#endif
		        ndpi_snprintf(flow->protos.kerberos.domain, sizeof(flow->protos.kerberos.domain), "%s", realm_str);
		      }
		    }
		  }
		}
	      } 
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Setting extra func from AS-REQ\n");
#endif
	      flow->max_extra_packets_to_check = 5; /* Reply may be split into multiple segments */
	      flow->extra_packets_func = ndpi_search_kerberos_extra;
	    } else if(msg_type == 0x0e) /* AS-REQ */ {
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Processing AS-REQ\n");
#endif
	      /* Nothing specific to do; stop dissecting this flow */
	      flow->extra_packets_func = NULL;

	    } else if(msg_type == 0x0c) /* TGS-REQ */ {
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Processing TGS-REQ\n");
#endif

	      if(body_offset < packet->payload_packet_len) {
		u_int16_t name_offset, padding_offset = body_offset + 4;

		name_offset = padding_offset;
		for(i=0; i<14 && name_offset < packet->payload_packet_len; i++) if(packet->payload[name_offset] != 0x1b) name_offset++; /* ASN.1 */

#ifdef KERBEROS_DEBUG
		printf("name_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", name_offset, packet->payload[name_offset], packet->payload[name_offset+1]);
#endif

		if(name_offset < (packet->payload_packet_len - 1)) {
		  u_int realm_len;

		  name_offset++;
		  realm_len = packet->payload[name_offset];

		  if((realm_len+name_offset) < packet->payload_packet_len) {
		    char realm_str[48];

		    name_offset += 1;
		    krb_strncpy_lower(realm_str, sizeof(realm_str), (char*)&packet->payload[name_offset], realm_len);

#ifdef KERBEROS_DEBUG
		    printf("[TGS-REQ][s/dport: %u/%u][Kerberos Realm][len: %u][%s]\n", sport, dport, realm_len, realm_str);
#endif
		    ndpi_snprintf(flow->protos.kerberos.domain, sizeof(flow->protos.kerberos.domain), "%s", realm_str);

		    /* If necessary we can decode sname */
		    if(flow->kerberos_buf.pktbuf) {
			    ndpi_free(flow->kerberos_buf.pktbuf);
			    packet->payload = original_packet_payload;
			    packet->payload_packet_len = original_payload_packet_len;
		    }
		    flow->kerberos_buf.pktbuf = NULL;
		  }
		}
	      }
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Setting extra func from TGS-REQ\n");
#endif
	      if(!packet->udp) {
	        flow->max_extra_packets_to_check = 5; /* Reply may be split into multiple segments */
	        flow->extra_packets_func = ndpi_search_kerberos_extra;
	      }

	      if(flow->kerberos_buf.pktbuf != NULL) {
		ndpi_free(flow->kerberos_buf.pktbuf);
		packet->payload = original_packet_payload;
		packet->payload_packet_len = original_payload_packet_len;
		flow->kerberos_buf.pktbuf = NULL;
	      }

	      return;
	    } else if(msg_type == 0x0d) /* TGS-REP */ {
	      NDPI_LOG_DBG(ndpi_struct, "[Kerberos] Processing TGS-REP\n");

	      if (krb_parse(ndpi_struct, flow, 8) != 0)
	      {
	        NDPI_LOG_DBG(ndpi_struct, "[TGS-REP] Invalid packet received\n");
	        return;
	      }
	      NDPI_LOG_DBG(ndpi_struct,
	                   "[TGS-REP][s/dport: %u/%u][Kerberos Hostname,Domain,Username][%s,%s,%s]\n",
	                   sport, dport, flow->protos.kerberos.hostname, flow->protos.kerberos.domain,
	                   flow->protos.kerberos.username);
	      flow->extra_packets_func = NULL;
	    } else if(msg_type == 0x1e) /* Error */ {
#ifdef KERBEROS_DEBUG
	      printf("[Kerberos] Processing KRB-Error\n");
#endif
	      /* Nothing specific to do; stop dissecting this flow */
	      flow->extra_packets_func = NULL;
	    }

	    return;
	  }
	}
      }
    } else {
#ifdef KERBEROS_DEBUG
      printf("[Kerberos][s/dport: %u/%u] Skipping packet: too long [kerberos_len: %u]\n",
	     sport, dport, kerberos_len);
#endif

      if(flow->protos.kerberos.domain[0] != '\0')
	return;
    }
  }

  NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}

static int ndpi_search_kerberos_extra(struct ndpi_detection_module_struct *ndpi_struct,
				      struct ndpi_flow_struct *flow)
{
  struct ndpi_packet_struct *packet = &ndpi_struct->packet;

#ifdef KERBEROS_DEBUG
  printf("[Kerberos] Extra function\n");
#endif

  /* Unfortunately, generic "extra function" code doesn't honour protocol bitmask */
  /* TODO: handle that in ndpi_main.c for all the protocols */
  if(packet->payload_packet_len == 0 ||
     packet->tcp_retransmission)
    return 1;

  /* Possibly dissect the reply */
  ndpi_search_kerberos(ndpi_struct, flow);

  /* Possibly more processing */
  return flow->extra_packets_func != NULL;
}

void init_kerberos_dissector(struct ndpi_detection_module_struct *ndpi_struct,
			     u_int32_t *id) {
  ndpi_set_bitmask_protocol_detection("Kerberos", ndpi_struct, *id,
				      NDPI_PROTOCOL_KERBEROS,
				      ndpi_search_kerberos,
				      NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION,
				      SAVE_DETECTION_BITMASK_AS_UNKNOWN,
				      ADD_TO_DETECTION_BITMASK);

  *id += 1;
}