#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <gssapi.h>
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_generic.h>
#include <gssapi/gssapi_krb5.h>
#include <gssapi/gssapi_ext.h>

#define k_token "token"
#define k_status "status"
#define k_minor_status "minor_status"
#define k_name_status "name_status"
#define k_name_minor_status "name_minor_status"
#define k_src_name "src_name"
#define k_src_name_type "src_name_type"
#define k_mech_type "mech_type"
#define k_output_token "output_token"
#define k_ret_flags "ret_flags"
#define k_time_rec "time_rec"
#define ks(x) sizeof(x)-1

#define rsc(x) sprintf(int2str,"%d",x); hv_store(ret_hash, int2str, strlen(int2str), newSVpv(#x,ks(#x)), 0)

static gss_ctx_id_t context_hndl = GSS_C_NO_CONTEXT;

MODULE = Catalyst::Authentication::Credential::GSSAPI PACKAGE = Catalyst::Authentication::Credential::GSSAPI PREFIX = cacgssapi_

SV *
cacgssapi_status_codes()
CODE:
{
  char int2str[32] = "";
  HV* ret_hash = newHV();
  rsc(GSS_S_COMPLETE);
  rsc(GSS_S_CALL_INACCESSIBLE_READ);
  rsc(GSS_S_CALL_INACCESSIBLE_WRITE);
  rsc(GSS_S_CALL_BAD_STRUCTURE);
  rsc(GSS_S_BAD_MECH);
  rsc(GSS_S_BAD_NAME);
  rsc(GSS_S_BAD_NAMETYPE);
  rsc(GSS_S_BAD_BINDINGS);
  rsc(GSS_S_BAD_STATUS);
  rsc(GSS_S_BAD_SIG);
  rsc(GSS_S_NO_CRED);
  rsc(GSS_S_NO_CONTEXT);
  rsc(GSS_S_DEFECTIVE_TOKEN);
  rsc(GSS_S_DEFECTIVE_CREDENTIAL);
  rsc(GSS_S_CREDENTIALS_EXPIRED);
  rsc(GSS_S_CONTEXT_EXPIRED);
  rsc(GSS_S_FAILURE);
  rsc(GSS_S_BAD_QOP);
  rsc(GSS_S_UNAUTHORIZED);
  rsc(GSS_S_UNAVAILABLE);
  rsc(GSS_S_DUPLICATE_ELEMENT);
  rsc(GSS_S_NAME_NOT_MN);
  rsc(GSS_S_BAD_MECH_ATTR);
  rsc(GSS_S_CONTINUE_NEEDED);
  rsc(GSS_S_DUPLICATE_TOKEN);
  rsc(GSS_S_OLD_TOKEN);
  rsc(GSS_S_UNSEQ_TOKEN);
  rsc(GSS_S_GAP_TOKEN);
  rsc(GSS_S_CRED_UNAVAIL);
  RETVAL = newRV_noinc((SV*) ret_hash);
}
OUTPUT:
RETVAL

void
cacgssapi_reset_negotiation()
  CODE:
{
  int minor_status = 0;
  if (context_hndl != GSS_C_NO_CONTEXT) {
    gss_delete_sec_context(&minor_status, &context_hndl, GSS_C_NO_BUFFER);
    context_hndl = GSS_C_NO_CONTEXT;
  }
}

SV *
cacgssapi_perform_negotiation(args_hr)
  SV* args_hr
  CODE:
{
  gss_buffer_desc input_token_struct;
  gss_buffer_t input_token = &input_token_struct;
  int release_buffer_status = 0;

  unsigned int status = 0;
  unsigned int minor_status = 0;
  gss_name_t src_name = NULL;
  gss_OID src_name_type = NULL;
  gss_buffer_desc src_name_buf_struct;
  gss_buffer_t src_name_buf = &src_name_buf_struct;
  gss_OID mech_type = NULL;
  gss_buffer_desc output_token_struct;
  gss_buffer_t output_token = &output_token_struct;
  unsigned int ret_flags = 0;
  unsigned int time_rec = 0;
  gss_cred_id_t delegated_cred_handle = NULL;
  if (SvROK(args_hr)) {
    HV* args_val = (HV*)SvRV(args_hr);
    if (SvTYPE(args_val) == SVt_PVHV) {
      if (hv_exists(args_val, k_token, ks(k_token))) {
	SV** tokensv = hv_fetch(args_val, k_token, ks(k_token), 0);
	input_token->value = SvPV(*tokensv, input_token->length);
    
	status = gss_accept_sec_context
	  (
	   &minor_status,
	   &context_hndl,
	   GSS_C_NO_CREDENTIAL,
	   input_token,
	   GSS_C_NO_CHANNEL_BINDINGS,
	   &src_name,
	   &mech_type,
	   output_token,
	   &ret_flags,
	   &time_rec,
	   &delegated_cred_handle);
    
	HV* ret_hash = newHV();
	hv_store(ret_hash, k_status, ks(k_status),
		 newSViv(status), 0);
	hv_store(ret_hash, k_minor_status, ks(k_minor_status),
		 newSViv(minor_status), 0);
	if (src_name) {
	  int name_status = 0;
	  int name_minor_status = 0;
	  name_status = gss_display_name
	    (
	     &name_minor_status,
	     src_name,
	     src_name_buf,
	     &src_name_type
	     );
	  hv_store(ret_hash, k_name_status, ks(k_name_status),
		   newSViv(status), 0);
	  hv_store(ret_hash, k_name_minor_status, ks(k_name_minor_status),
		   newSViv(minor_status), 0);
	  if (src_name_buf) {
	    hv_store(ret_hash, k_src_name, ks(k_src_name),
		     newSVpv((char*)src_name_buf->value,
			     src_name_buf->length), 0);
	    gss_release_buffer(&release_buffer_status, src_name_buf);
	  }
	  if (src_name_type) {
	    hv_store(ret_hash, k_src_name_type, ks(k_src_name_type),
		     newSVpv((char*)src_name_type->elements,
			     src_name_type->length), 0);
	  }
	  int release_name_status;
	  gss_release_name(&release_name_status, &src_name);
	}
	if (mech_type) {
	  hv_store(ret_hash, k_mech_type, ks(k_mech_type),
		   newSVpv((char*)mech_type->elements,
			   mech_type->length), 0);
	}
	if (output_token) {
	  hv_store(ret_hash, k_output_token, ks(k_output_token),
		   newSVpv((char*)output_token->value,
			   output_token->length), 0);
	}
	hv_store(ret_hash, k_ret_flags, ks(k_ret_flags),
		 newSViv(ret_flags), 0);
	hv_store(ret_hash, k_time_rec, ks(k_time_rec),
		 newSViv(time_rec), 0);
    
	gss_release_buffer(&release_buffer_status, output_token);
	
	RETVAL = newRV_noinc((SV*) ret_hash);
      } else {
        //fprintf(stderr, "No token in arguments\n");
	RETVAL = &PL_sv_undef;
      }
    } else {
      //fprintf(stderr, "Not a hash reference\n");
      RETVAL = &PL_sv_undef;
    }
  } else {
    //fprintf(stderr, "Not a reference\n");
    RETVAL = &PL_sv_undef;
  }
}
OUTPUT:
RETVAL


// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------