text: // See the file "COPYING" in the main distribution directory for copyright.
  
  #include "KRB.h"
  
  #include <unistd.h>
  
  #include "types.bif.h"
  #include "events.bif.h"
  
  namespace zeek::analyzer::krb {
  
  bool KRB_Analyzer::krb_available = false;
  #ifdef USE_KRB5
  krb5_context KRB_Analyzer::krb_context = nullptr;
  krb5_keytab KRB_Analyzer::krb_keytab = nullptr;
  std::once_flag KRB_Analyzer::krb_initialized;
  #endif
  
  KRB_Analyzer::KRB_Analyzer(Connection* conn)
  	: Analyzer("KRB", conn)
  	{
  	interp = new binpac::KRB::KRB_Conn(this);
  #ifdef USE_KRB5
  	std::call_once(krb_initialized, Initialize_Krb);
  #endif
  	}
  
  #ifdef USE_KRB5
  static void warn_krb(const char* msg, krb5_context ctx, krb5_error_code code)
  	{
  	auto err = krb5_get_error_message(ctx, code);
  	reporter->Warning("%s (%s)", msg, err);
  	krb5_free_error_message(ctx, err);
  	}
  
  void KRB_Analyzer::Initialize_Krb()
  	{
  	if ( BifConst::KRB::keytab->Len() == 0 )
  		return; // no keytab set
  
  	const char* keytab_filename = BifConst::KRB::keytab->CheckString();
  	if ( access(keytab_filename, R_OK) != 0 )
  		{
  		reporter->Warning("KRB: Can't access keytab (%s)", keytab_filename);
  		return;
  		}
  
  	krb5_error_code retval = krb5_init_context(&krb_context);
  	if ( retval )
  		{
  		warn_krb("KRB: Couldn't initialize the context", krb_context, retval);
  		return;
  		}
  
  	retval = krb5_kt_resolve(krb_context, keytab_filename, &krb_keytab);
  	if ( retval )
  		{
  		warn_krb("KRB: Couldn't resolve keytab", krb_context, retval);
  		return;
  		}
  	krb_available = true;
  	}
  #endif
  
  KRB_Analyzer::~KRB_Analyzer()
  	{
  	delete interp;
  	}
  
  void KRB_Analyzer::Done()
  	{
  	Analyzer::Done();
  	}
  
  void KRB_Analyzer::DeliverPacket(int len, const u_char* data, bool orig,
  				 uint64_t seq, const IP_Hdr* ip, int caplen)
  	{
  	Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
  
  	try
  		{
  		interp->NewData(orig, data, data + len);
  		}
  	catch ( const binpac::Exception& e )
  		{
  		ProtocolViolation(util::fmt("Binpac exception: %s", e.c_msg()));
  		}
  	}
  
  StringValPtr KRB_Analyzer::GetAuthenticationInfo(const String* principal,
                                                   const String* ciphertext,
                                                   const bro_uint_t enctype)
  	{
  #ifdef USE_KRB5
  	if ( !krb_available )
  		return nullptr;
  
  	String delim("/");
  	int pos = principal->FindSubstring(&delim);
  	if ( pos == -1 )
  		{
  		reporter->Warning("KRB: Couldn't parse principal (%s)", principal->CheckString());
  		return nullptr;
  		}
  	std::unique_ptr<String> service = unique_ptr<String>(principal->GetSubstring(0, pos));
  	std::unique_ptr<String> hostname = unique_ptr<String>(principal->GetSubstring(pos + 1, -1));
  	if ( !service || !hostname )
  		{
  		reporter->Warning("KRB: Couldn't parse principal (%s)", principal->CheckString());
  		return nullptr;
  		}
  	krb5_principal sprinc;
  	krb5_error_code retval = krb5_sname_to_principal(krb_context, hostname->CheckString(), service->CheckString(), KRB5_NT_SRV_HST, &sprinc);
  	if ( retval )
  		{
  		warn_krb("KRB: Couldn't generate principal name", krb_context, retval);
  		return nullptr;
  		}
  
  	auto tkt = static_cast<krb5_ticket*>(util::safe_malloc(sizeof(krb5_ticket)));
  	memset(tkt, 0, sizeof(krb5_ticket));
  
  	tkt->server = sprinc;
  	tkt->enc_part.enctype = enctype;
  
  	auto ctd = static_cast<char*>(util::safe_malloc(ciphertext->Len()));
  	memcpy(ctd, ciphertext->Bytes(), ciphertext->Len());
  	tkt->enc_part.ciphertext.data = ctd;
  	tkt->enc_part.ciphertext.length = ciphertext->Len();
  
  	retval = krb5_server_decrypt_ticket_keytab(krb_context, krb_keytab, tkt);
  
  	if ( retval )
  		{
  		krb5_free_ticket(krb_context, tkt);
  		warn_krb("KRB: Couldn't decrypt ticket", krb_context, retval);
  		return nullptr;
  		}
  
  	char* cp;
  	retval = krb5_unparse_name(krb_context, tkt->enc_part2->client, &cp);
  
  	if ( retval )
  		{
  		krb5_free_ticket(krb_context, tkt);
  		warn_krb("KRB: Couldn't unparse name", krb_context, retval);
  		return nullptr;
  		}
  
  	auto ret = make_intrusive<StringVal>(cp);
  
  	krb5_free_unparsed_name(krb_context, cp);
  	krb5_free_ticket(krb_context, tkt);
  
  	return ret;
  #else
  	return nullptr;
  #endif
  	}
  
  } // namespace zeek::analyzer::krb
  