<commit_before>/*
 * GSSAPI Module
 *
 * Copyright (c) 2005 <NAME> <<EMAIL>>
 *
 * Related standards:
 * - draft-ietf-sasl-gssapi-03 
 * - RFC2222
 *
 * Some parts inspired by an older patch from <NAME>
 *
 * This software is released under the MIT license.
 */

#include "common.h"
#include "mech.h"
#include "passdb.h"
#include "str.h"
#include "str-sanitize.h"
#include "buffer.h"
#include "hex-binary.h"
#include "safe-memset.h"

#ifdef HAVE_GSSAPI

#ifndef HAVE___GSS_USEROK
#  define USE_KRB5_USEROK
#  include <krb5.h>
#endif

#ifdef HAVE_GSSAPI_GSSAPI_H
#  include <gssapi/gssapi.h>
#  ifdef USE_KRB5_USEROK
#    include <gssapi/gssapi_krb5.h>
#  endif
#elif defined (HAVE_GSSAPI_H)
#  include <gssapi.h>
#  ifdef USE_KRB5_USEROK
#    include <gssapi_krb5.h>
#  endif
#endif

#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
#  include <gssapi/gssapi_ext.h>
#endif

/* Non-zero flags defined in RFC 2222 */
enum sasl_gssapi_qop {
	SASL_GSSAPI_QOP_UNSPECIFIED = 0x00,
	SASL_GSSAPI_QOP_AUTH_ONLY   = 0x01,
	SASL_GSSAPI_QOP_AUTH_INT    = 0x02,
	SASL_GSSAPI_QOP_AUTH_CONF   = 0x04
};

struct gssapi_auth_request {
	struct auth_request auth_request;
	gss_ctx_id_t gss_ctx;
	gss_cred_id_t service_cred;

	enum { 
		GSS_STATE_SEC_CONTEXT, 
		GSS_STATE_WRAP, 
		GSS_STATE_UNWRAP
	} sasl_gssapi_state;

	gss_name_t authn_name;
	gss_name_t authz_name;
		
	pool_t pool;
};

static void auth_request_log_gss_error(struct auth_request *request,
				       OM_uint32 status_value, int status_type,
				       const char *description)
{
	OM_uint32 message_context = 0;
	OM_uint32 major_status, minor_status;
	gss_buffer_desc status_string;

	do {
		major_status = gss_display_status(&minor_status, status_value, 
						  status_type, GSS_C_NO_OID,
						  &message_context,
						  &status_string);
	
		auth_request_log_error(request, "gssapi",
			"While %s: %s", description,
			str_sanitize(status_string.value, (size_t)-1));

		major_status = gss_release_buffer(&minor_status,
						  &status_string);
	} while (message_context != 0);
}

static struct auth_request *mech_gssapi_auth_new(void)
{
	struct gssapi_auth_request *request;
	pool_t pool;

	pool = pool_alloconly_create("gssapi_auth_request", 1024);
	request = p_new(pool, struct gssapi_auth_request, 1);
	request->pool = pool;

	request->gss_ctx = GSS_C_NO_CONTEXT;

	request->auth_request.pool = pool;
	return &request->auth_request;
}

static OM_uint32 obtain_service_credentials(struct auth_request *request,
					    gss_cred_id_t *ret)
{
	OM_uint32 major_status, minor_status;
	string_t *principal_name;
	gss_buffer_desc inbuf;
	gss_name_t gss_principal;
	const char *service_name;

	if (strcasecmp(request->service, "POP3") == 0) {
		/* The standard POP3 service name with GSSAPI is called
		   just "pop". */
		service_name = "pop";
	} else {
		service_name = t_str_lcase(request->service);
	}

	principal_name = t_str_new(128);
	str_append(principal_name, service_name);
	str_append_c(principal_name, '@');
	str_append(principal_name, request->auth->gssapi_hostname);

	auth_request_log_info(request, "gssapi",
		"Obtaining credentials for %s", str_c(principal_name));

	inbuf.length = str_len(principal_name);
	inbuf.value = str_c_modifiable(principal_name);

	major_status = gss_import_name(&minor_status, &inbuf, 
				       GSS_C_NT_HOSTBASED_SERVICE,
				       &gss_principal);

	str_free(&principal_name);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE,
					   "importing principal name");
		return major_status;
	}

	major_status = gss_acquire_cred(&minor_status, gss_principal, 0, 
					GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
					ret, NULL, NULL);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE,
					   "acquiring service credentials");
		auth_request_log_gss_error(request, minor_status,
					   GSS_C_MECH_CODE,
					   "acquiring service credentials");
		return major_status;
	}

	gss_release_name(&minor_status, &gss_principal);

	return major_status;
}

#ifndef HAVE___GSS_USEROK
static gss_name_t
import_name(struct auth_request *request, void *str, size_t len)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc name_buf;
	gss_name_t name;

	name_buf.value = str;
	name_buf.length = len;
	major_status = gss_import_name(&minor_status,
				       &name_buf,
				       GSS_C_NO_OID,
				       &name);
	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE, "gss_import_name");
		return GSS_C_NO_NAME;
	}

	return name;
}
#endif

static void gssapi_sec_context(struct gssapi_auth_request *request,
			       gss_buffer_desc inbuf)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc outbuf;

	major_status = gss_accept_sec_context (
		&minor_status,
		&request->gss_ctx,
		request->service_cred,
		&inbuf,
		GSS_C_NO_CHANNEL_BINDINGS,
		&request->authn_name, 
		NULL, /* mech_type */
		&outbuf,
		NULL, /* ret_flags */
		NULL, /* time_rec */
		NULL  /* delegated_cred_handle */
	);
	
	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(&request->auth_request, major_status,
					   GSS_C_GSS_CODE,
					   "processing incoming data");
		auth_request_log_gss_error(&request->auth_request, minor_status,
					   GSS_C_MECH_CODE,
					   "processing incoming data");

		auth_request_fail(&request->auth_request);
		return;
	} 

	if (major_status == GSS_S_COMPLETE) {
		request->sasl_gssapi_state = GSS_STATE_WRAP;
		auth_request_log_info(&request->auth_request, "gssapi", 
				      "security context state completed.");
	} else {
		auth_request_log_info(&request->auth_request, "gssapi", 
				      "Processed incoming packet correctly, "
				      "waiting for another.");
	}

	request->auth_request.callback(&request->auth_request,
				       AUTH_CLIENT_RESULT_CONTINUE,
				       outbuf.value, outbuf.length);

	major_status = gss_release_buffer(&minor_status, &outbuf);
}

static void gssapi_wrap(struct gssapi_auth_request *request,
			gss_buffer_desc inbuf)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc outbuf;
	unsigned char ret[4];

	/* The clients return data should be empty here */
	
	/* Only authentication, no integrity or confidentiality
	   protection (yet?) */
	ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED |
                  SASL_GSSAPI_QOP_AUTH_ONLY);
	ret[1] = 0xFF;
	ret[2] = 0xFF;
	ret[3] = 0xFF;

	inbuf.length = 4;
	inbuf.value = ret;
	
	major_status = gss_wrap(&minor_status, request->gss_ctx, 0,
				GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(&request->auth_request, major_status,
			GSS_C_GSS_CODE, "sending security layer negotiation");
		auth_request_log_gss_error(&request->auth_request, minor_status,
			GSS_C_MECH_CODE, "sending security layer negotiation");
		auth_request_fail(&request->auth_request);
		return;
	} 

	auth_request_log_info(&request->auth_request, "gssapi", 
			      "Negotiated security layer");

	request->auth_request.callback(&request->auth_request,
				       AUTH_CLIENT_RESULT_CONTINUE,
				       outbuf.value, outbuf.length);

	major_status = gss_release_buffer(&minor_status, &outbuf);

	request->sasl_gssapi_state = GSS_STATE_UNWRAP;
}

#ifdef USE_KRB5_USEROK
static bool gssapi_krb5_userok(struct gssapi_auth_request *request)
{
	krb5_context ctx;
	krb5_principal princ;
	krb5_error_code krb5_err;
	OM_uint32 major_status, minor_status;
	gss_buffer_desc princ_name;
	gss_OID name_type;
	const char *princ_display_name;
	bool ret = FALSE;

	/* Parse out the principal's username */
	major_status = gss_display_name(&minor_status, request->authn_name,
					&princ_name, &name_type);
	if (major_status != GSS_S_COMPLETE) {
		auth_request_log_gss_error(&request->auth_request, major_status,
					   GSS_C_GSS_CODE,
					   "gssapi_krb5_userok");
		return FALSE;
	}
	if (name_type != GSS_KRB5_NT_PRINCIPAL_NAME) {
		auth_request_log_error(&request->auth_request, "gssapi",
				       "OID not kerberos principal name");
		return FALSE;
	}
	princ_display_name = t_strndup(princ_name.value, princ_name.length);
	gss_release_buffer(&minor_status, &princ_name);

	/* Init a krb5 context and parse the principal username */
	krb5_err = krb5_init_context(&ctx);
	if (krb5_err != 0) {
		auth_request_log_error(&request->auth_request, "gssapi",
			"krb5_init_context() failed: %d", (int)krb5_err);
		return FALSE;
	}
	krb5_err = krb5_parse_name(ctx, princ_display_name, &princ);
	if (krb5_err != 0) {
		auth_request_log_error(&request->auth_request, "gssapi",
				       "krb5_parse_name() failed: %s",
				       krb5_get_error_message(ctx, krb5_err));
	} else {
		/* See if the principal is authorized to act as the
		   specified user */
		ret = krb5_kuserok(ctx, princ, request->auth_request.user);
		krb5_free_principal(ctx, princ);
	}
	krb5_free_context(ctx);
	return ret;
}
#endif

static void gssapi_unwrap(struct gssapi_auth_request *request,
			  gss_buffer_desc inbuf)
{<commit_msg>Don't use krb5_get_error_message(), it doesn't work with Heimdal Kerberos.<commit_after>/*
 * GSSAPI Module
 *
 * Copyright (c) 2005 <NAME> <<EMAIL>>
 *
 * Related standards:
 * - draft-ietf-sasl-gssapi-03 
 * - RFC2222
 *
 * Some parts inspired by an older patch from <NAME>
 *
 * This software is released under the MIT license.
 */

#include "common.h"
#include "mech.h"
#include "passdb.h"
#include "str.h"
#include "str-sanitize.h"
#include "buffer.h"
#include "hex-binary.h"
#include "safe-memset.h"

#ifdef HAVE_GSSAPI

#ifndef HAVE___GSS_USEROK
#  define USE_KRB5_USEROK
#  include <krb5.h>
#endif

#ifdef HAVE_GSSAPI_GSSAPI_H
#  include <gssapi/gssapi.h>
#  ifdef USE_KRB5_USEROK
#    include <gssapi/gssapi_krb5.h>
#  endif
#elif defined (HAVE_GSSAPI_H)
#  include <gssapi.h>
#  ifdef USE_KRB5_USEROK
#    include <gssapi_krb5.h>
#  endif
#endif

#ifdef HAVE_GSSAPI_GSSAPI_EXT_H
#  include <gssapi/gssapi_ext.h>
#endif

/* Non-zero flags defined in RFC 2222 */
enum sasl_gssapi_qop {
	SASL_GSSAPI_QOP_UNSPECIFIED = 0x00,
	SASL_GSSAPI_QOP_AUTH_ONLY   = 0x01,
	SASL_GSSAPI_QOP_AUTH_INT    = 0x02,
	SASL_GSSAPI_QOP_AUTH_CONF   = 0x04
};

struct gssapi_auth_request {
	struct auth_request auth_request;
	gss_ctx_id_t gss_ctx;
	gss_cred_id_t service_cred;

	enum { 
		GSS_STATE_SEC_CONTEXT, 
		GSS_STATE_WRAP, 
		GSS_STATE_UNWRAP
	} sasl_gssapi_state;

	gss_name_t authn_name;
	gss_name_t authz_name;
		
	pool_t pool;
};

static void auth_request_log_gss_error(struct auth_request *request,
				       OM_uint32 status_value, int status_type,
				       const char *description)
{
	OM_uint32 message_context = 0;
	OM_uint32 major_status, minor_status;
	gss_buffer_desc status_string;

	do {
		major_status = gss_display_status(&minor_status, status_value, 
						  status_type, GSS_C_NO_OID,
						  &message_context,
						  &status_string);
	
		auth_request_log_error(request, "gssapi",
			"While %s: %s", description,
			str_sanitize(status_string.value, (size_t)-1));

		major_status = gss_release_buffer(&minor_status,
						  &status_string);
	} while (message_context != 0);
}

static struct auth_request *mech_gssapi_auth_new(void)
{
	struct gssapi_auth_request *request;
	pool_t pool;

	pool = pool_alloconly_create("gssapi_auth_request", 1024);
	request = p_new(pool, struct gssapi_auth_request, 1);
	request->pool = pool;

	request->gss_ctx = GSS_C_NO_CONTEXT;

	request->auth_request.pool = pool;
	return &request->auth_request;
}

static OM_uint32 obtain_service_credentials(struct auth_request *request,
					    gss_cred_id_t *ret)
{
	OM_uint32 major_status, minor_status;
	string_t *principal_name;
	gss_buffer_desc inbuf;
	gss_name_t gss_principal;
	const char *service_name;

	if (strcasecmp(request->service, "POP3") == 0) {
		/* The standard POP3 service name with GSSAPI is called
		   just "pop". */
		service_name = "pop";
	} else {
		service_name = t_str_lcase(request->service);
	}

	principal_name = t_str_new(128);
	str_append(principal_name, service_name);
	str_append_c(principal_name, '@');
	str_append(principal_name, request->auth->gssapi_hostname);

	auth_request_log_info(request, "gssapi",
		"Obtaining credentials for %s", str_c(principal_name));

	inbuf.length = str_len(principal_name);
	inbuf.value = str_c_modifiable(principal_name);

	major_status = gss_import_name(&minor_status, &inbuf, 
				       GSS_C_NT_HOSTBASED_SERVICE,
				       &gss_principal);

	str_free(&principal_name);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE,
					   "importing principal name");
		return major_status;
	}

	major_status = gss_acquire_cred(&minor_status, gss_principal, 0, 
					GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
					ret, NULL, NULL);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE,
					   "acquiring service credentials");
		auth_request_log_gss_error(request, minor_status,
					   GSS_C_MECH_CODE,
					   "acquiring service credentials");
		return major_status;
	}

	gss_release_name(&minor_status, &gss_principal);

	return major_status;
}

#ifndef HAVE___GSS_USEROK
static gss_name_t
import_name(struct auth_request *request, void *str, size_t len)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc name_buf;
	gss_name_t name;

	name_buf.value = str;
	name_buf.length = len;
	major_status = gss_import_name(&minor_status,
				       &name_buf,
				       GSS_C_NO_OID,
				       &name);
	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(request, major_status,
					   GSS_C_GSS_CODE, "gss_import_name");
		return GSS_C_NO_NAME;
	}

	return name;
}
#endif

static void gssapi_sec_context(struct gssapi_auth_request *request,
			       gss_buffer_desc inbuf)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc outbuf;

	major_status = gss_accept_sec_context (
		&minor_status,
		&request->gss_ctx,
		request->service_cred,
		&inbuf,
		GSS_C_NO_CHANNEL_BINDINGS,
		&request->authn_name, 
		NULL, /* mech_type */
		&outbuf,
		NULL, /* ret_flags */
		NULL, /* time_rec */
		NULL  /* delegated_cred_handle */
	);
	
	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(&request->auth_request, major_status,
					   GSS_C_GSS_CODE,
					   "processing incoming data");
		auth_request_log_gss_error(&request->auth_request, minor_status,
					   GSS_C_MECH_CODE,
					   "processing incoming data");

		auth_request_fail(&request->auth_request);
		return;
	} 

	if (major_status == GSS_S_COMPLETE) {
		request->sasl_gssapi_state = GSS_STATE_WRAP;
		auth_request_log_info(&request->auth_request, "gssapi", 
				      "security context state completed.");
	} else {
		auth_request_log_info(&request->auth_request, "gssapi", 
				      "Processed incoming packet correctly, "
				      "waiting for another.");
	}

	request->auth_request.callback(&request->auth_request,
				       AUTH_CLIENT_RESULT_CONTINUE,
				       outbuf.value, outbuf.length);

	major_status = gss_release_buffer(&minor_status, &outbuf);
}

static void gssapi_wrap(struct gssapi_auth_request *request,
			gss_buffer_desc inbuf)
{
	OM_uint32 major_status, minor_status;
	gss_buffer_desc outbuf;
	unsigned char ret[4];

	/* The clients return data should be empty here */
	
	/* Only authentication, no integrity or confidentiality
	   protection (yet?) */
	ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED |
                  SASL_GSSAPI_QOP_AUTH_ONLY);
	ret[1] = 0xFF;
	ret[2] = 0xFF;
	ret[3] = 0xFF;

	inbuf.length = 4;
	inbuf.value = ret;
	
	major_status = gss_wrap(&minor_status, request->gss_ctx, 0,
				GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf);

	if (GSS_ERROR(major_status)) {
		auth_request_log_gss_error(&request->auth_request, major_status,
			GSS_C_GSS_CODE, "sending security layer negotiation");
		auth_request_log_gss_error(&request->auth_request, minor_status,
			GSS_C_MECH_CODE, "sending security layer negotiation");
		auth_request_fail(&request->auth_request);
		return;
	} 

	auth_request_log_info(&request->auth_request, "gssapi", 
			      "Negotiated security layer");

	request->auth_request.callback(&request->auth_request,
				       AUTH_CLIENT_RESULT_CONTINUE,
				       outbuf.value, outbuf.length);

	major_status = gss_release_buffer(&minor_status, &outbuf);

	request->sasl_gssapi_state = GSS_STATE_UNWRAP;
}

#ifdef USE_KRB5_USEROK
static bool gssapi_krb5_userok(struct gssapi_auth_request *request)
{
	krb5_context ctx;
	krb5_principal princ;
	krb5_error_code krb5_err;
	OM_uint32 major_status, minor_status;
	gss_buffer_desc princ_name;
	gss_OID name_type;
	const char *princ_display_name;
	bool ret = FALSE;

	/* Parse out the principal's username */
	major_status = gss_display_name(&minor_status, request->authn_name,
					&princ_name, &name_type);
	if (major_status != GSS_S_COMPLETE) {
		auth_request_log_gss_error(&request->auth_request, major_status,
					   GSS_C_GSS_CODE,
					   "gssapi_krb5_userok");
		return FALSE;
	}
	if (name_type != GSS_KRB5_NT_PRINCIPAL_NAME) {
		auth_request_log_error(&request->auth_request, "gssapi",
				       "OID not kerberos principal name");
		return FALSE;
	}
	princ_display_name = t_strndup(princ_name.value, princ_name.length);
	gss_release_buffer(&minor_status, &princ_name);

	/* Init a krb5 context and parse the principal username */
	krb5_err = krb5_init_context(&ctx);
	if (krb5_err != 0) {
		auth_request_log_error(&request->auth_request, "gssapi",
			"krb5_init_context() failed: %d", (int)krb5_err);
		return FALSE;
	}
	krb5_err = krb5_parse_name(ctx, princ_display_name, &princ);
	if (krb5_err != 0) {
		/* writing the error string would be better, but we probably
		   rarely get here and there doesn't seem to be a standard
		   way of getting it */
		auth_request_log_error(&request->auth_request, "gssapi",
				       "krb5_parse_name() failed: %d",
				       (int)krb5_err);
	} else {
		/* See if the principal is authorized to act as the
		   specified user */
		ret = krb5_kuserok(ctx, princ, request->auth_request.user);
		krb5_free_principal(ctx, princ);
	}
	krb5_free_context(ctx);
	return ret;
}
#endif

static void gssapi_unwrap(struct gssapi_auth_request *request,
			  gss_buffer_desc inbuf)
{
{'fraction_non_alphanumeric': 0.10160604889894585, 'fraction_numerical': 0.011038136762514488, 'mean_word_length': 4.565110565110565, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 89, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1742380', 'n_tokens_mistral': 7726, 'n_tokens_neox': 6946, 'n_words': 1246}