#ifndef	WIN32
#include	"config.h"
#endif /* WIN32 */

#ifdef	USE_GSS
#if defined(WIN32) && defined( _MSC_VER)
#ifdef	_WIN64
#pragma comment(lib, "gssapi64")
#else
#pragma comment(lib, "gssapi32")
#endif /* _WIN64 */
#endif /* WIN32 */

#include "connection.h"
#ifdef	_WIN64
CSTR	GSSAPIDLL = "gssapi64";
#else
CSTR	GSSAPIDLL = "gssapi32";
#endif /* _WIN64 */
#include "socket.h"
#include "environ.h"
#ifndef	MAXHOSTNAMELEN
#include <netdb.h>
#endif

#ifndef	STATUS_ERROR
#define	STATUS_ERROR (-1)
#endif
#ifndef	STATUS_OK
#define	STATUS_OK (0)
#endif

/*
 * GSSAPI authentication system.
 */

#if defined(WIN32) && !defined(_MSC_VER)
/*
 * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
 * that contain the OIDs required. Redefine here, values copied
 * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
 */
static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc =
{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc;
#endif

/*
 * Fetch all errors of a specific type and append to "str".
 */
static void
pg_GSS_error_int(ConnectionClass *conn, const char *mprefix,
				 OM_uint32 stat, int type)
{
	char		*errmsg, *nerrmsg;
	size_t		alclen;
	OM_uint32	lmaj_s,
				lmin_s;
	gss_buffer_desc lmsg;
	OM_uint32	msg_ctx = 0;

	do
	{
		lmaj_s = gss_display_status(&lmin_s, stat, type,
									GSS_C_NO_OID, &msg_ctx, &lmsg);

		errmsg = CC_get_errormsg(conn);
		alclen = strlen(errmsg) + strlen(mprefix) + 2 + strlen(lmsg.value) + strlen("\n") + 1;
		nerrmsg = malloc(alclen);
		snprintf(nerrmsg, alclen, "%s%s: %s\n", errmsg, mprefix, (char *) lmsg.value);
		CC_set_errormsg(conn, nerrmsg);
		free(nerrmsg);

		gss_release_buffer(&lmin_s, &lmsg);
	} while (msg_ctx);
}

/*
 * GSSAPI errors contain two parts; put both into conn->errorMessage.
 */
static void
pg_GSS_error(const char *mprefix, ConnectionClass *conn,
			 OM_uint32 maj_stat, OM_uint32 min_stat)
{
	CC_set_errormsg(conn, "");

	/* Fetch major error codes */
	pg_GSS_error_int(conn, mprefix, maj_stat, GSS_C_GSS_CODE);

	/* Add the minor codes as well */
	pg_GSS_error_int(conn, mprefix, min_stat, GSS_C_MECH_CODE);
}

/*
 * Continue GSS authentication with next token as needed.
 */
int pg_GSS_continue(ConnectionClass *conn, Int4 inlen)
{
	SocketClass	*sock = CC_get_socket(conn);
	OM_uint32	maj_stat,
				min_stat,
				lmin_s;
	gss_buffer_desc	ginbuf, goutbuf;

	ginbuf.length = 0;
mylog("!!! %s inlen=%d in\n", __FUNCTION__, inlen);
	if (sock->gctx != GSS_C_NO_CONTEXT)
	{
mylog("!!! %s -1\n", __FUNCTION__);
		if (inlen > 0)
		{
			if (NULL == (ginbuf.value = malloc(inlen)))
			{
				CC_set_error(conn, CONN_NO_MEMORY_ERROR,
					"realloc error during autehntication", __FUNCTION__);
				return STATUS_ERROR;
			}
mylog("!!! %s -2\n", __FUNCTION__);
			ginbuf.length = inlen;
			SOCK_get_n_char(conn->sock, ginbuf.value, inlen);
			if (0 != SOCK_get_errcode(conn->sock))
			{
				CC_set_error(conn, CONN_INVALID_AUTHENTICATION,
					"communication error during autehntication", __FUNCTION__);
				free(ginbuf.value);
				return STATUS_ERROR;
			}
		}
	}

mylog("!!! %s 0\n", __FUNCTION__);
	maj_stat = gss_init_sec_context(&min_stat,
									GSS_C_NO_CREDENTIAL,
									&sock->gctx,
									sock->gtarg_nam,
									GSS_C_NO_OID,
									GSS_C_MUTUAL_FLAG,
									0,
									GSS_C_NO_CHANNEL_BINDINGS,
		  (sock->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &ginbuf,
									NULL,
									&goutbuf,
									NULL,
									NULL);

mylog("!!! %s 1\n", __FUNCTION__);
	if (ginbuf.length != 0)
	{
		free(ginbuf.value);
		ginbuf.length = 0;
	}

mylog("!!! %s 1-1 outlen=%d\n", __FUNCTION__, goutbuf.length);
	if (goutbuf.length != 0)
	{
		SocketClass	*sock = conn->sock;
		int		slen;
		/*
		 * GSS generated data to send to the server. We don't care if it's the
		 * first or subsequent packet, just send the same kind of password
		 * packet.
		 */
mylog("!!! %s 2\n", __FUNCTION__);
		if (PROTOCOL_74(&(conn->connInfo)))
			SOCK_put_char(sock, 'p');
		slen = goutbuf.length;
		SOCK_put_int(sock, slen + 4, 4);
		SOCK_put_n_char(sock, goutbuf.value, slen);
		SOCK_flush_output(sock);
		gss_release_buffer(&lmin_s, &goutbuf);
		goutbuf.length = 0;
		if (0 != SOCK_get_errcode(sock))
		{
mylog("!!! %s 3\n", __FUNCTION__);
			CC_set_error(conn, CONN_INVALID_AUTHENTICATION,
				"communication error during autehntication", __FUNCTION__);
		}
	}

mylog("!!! %s maj_stat=%d\n", __FUNCTION__, maj_stat);
	if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
	{
mylog("!!! %s 5\n", __FUNCTION__);
		pg_GSS_error("GSSAPI continuation error",
					 conn,
					 maj_stat, min_stat);
		gss_release_name(&lmin_s, &sock->gtarg_nam);
		if (sock->gctx)
			gss_delete_sec_context(&lmin_s, &sock->gctx, GSS_C_NO_BUFFER);
		return STATUS_ERROR;
	}
mylog("!!! %s 6\n", __FUNCTION__);

	if (maj_stat == GSS_S_COMPLETE)
		gss_release_name(&lmin_s, &sock->gtarg_nam);

	return STATUS_OK;
}

/*
 *	This is needed to make /Delayload:gssapi32(64).dll possible
 *	under Windows.
 */
static gss_OID get_c_nt_hostbased_service()
{
#if	defined(WIN32) && defined(_MSC_VER)
	static FARPROC	proc = NULL;

	shortterm_common_lock();
	if (NULL == proc)
	{
		HMODULE	hmodule = GetModuleHandle(GSSAPIDLL);

		if (NULL != hmodule)
			proc = GetProcAddress(hmodule, "GSS_C_NT_HOSTBASED_SERVICE");
	}
	shortterm_common_unlock();
	if (NULL != proc)
		return *((gss_OID *) proc);
	return 0;
#else
	return	GSS_C_NT_HOSTBASED_SERVICE;
#endif
}

/*
 * Send initial GSS authentication token
 */
int pg_GSS_startup(ConnectionClass *conn, void *opt)
{
	SocketClass	*sock = CC_get_socket(conn);
	OM_uint32	maj_stat,
				min_stat;
	gss_buffer_desc temp_gbuf;

mylog("!!! %s in\n", __FUNCTION__);
	if (sock->gctx)
	{
		char	mesg[100];

mylog("!!! %s 0\n", __FUNCTION__);
		snprintf(mesg, sizeof(mesg), "duplicate GSS authentication request");
		CC_set_error(conn, CONN_INVALID_AUTHENTICATION,
				mesg, __FUNCTION__);
		free(opt);
		return STATUS_ERROR;
	}
mylog("!!! %s 2\n", __FUNCTION__);

	/*
	 * Import service principal name so the proper ticket can be acquired by
	 * the GSSAPI system.
	 */
	temp_gbuf.value = (char *) opt;
	temp_gbuf.length = strlen(temp_gbuf.value);

mylog("!!! temp_gbuf.value=%s\n", temp_gbuf.value);
	maj_stat = gss_import_name(&min_stat, &temp_gbuf,
				   // GSS_C_NT_HOSTBASED_SERVICE,
				   get_c_nt_hostbased_service(),
		 &sock->gtarg_nam);
	free(temp_gbuf.value);

	if (maj_stat != GSS_S_COMPLETE)
	{
mylog("!!! %s 3\n", __FUNCTION__);
		pg_GSS_error("GSSAPI name import error",
					 conn,
					 maj_stat, min_stat);
		return STATUS_ERROR;
	}

mylog("!!! %s 4\n", __FUNCTION__);
	/*
	 * Initial packet is the same as a continuation packet with no initial
	 * context.
	 */
	sock->gctx = GSS_C_NO_CONTEXT;

	return pg_GSS_continue(conn, 0);
}

void pg_GSS_cleanup(SocketClass *sock)
{
	OM_uint32	min_s;

	if (sock->gctx)
	{
		gss_delete_sec_context(&min_s, &sock->gctx, GSS_C_NO_BUFFER);
	}
	if (sock->gtarg_nam)
	{
		gss_release_name(&min_s, &sock->gtarg_nam);
	}
}
#endif   /* USE_GSS */
