/*
 * Copyright (C) 2023 Hasl Developers
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n-lib.h>

#include <stringprep.h>

#include "haslmechanismanonymous.h"

#include "haslstring.h"

struct _HaslMechanismAnonymous {
	HaslMechanism parent;
};

G_DEFINE_TYPE(HaslMechanismAnonymous, hasl_mechanism_anonymous,
              HASL_TYPE_MECHANISM)

G_DEFINE_QUARK(hasl-mechanism-anonymous-error, hasl_mechanism_anonymous_error)

/******************************************************************************
 * HaslMechanism Implementation
 *****************************************************************************/
static gboolean
hasl_mechanism_anonymous_possible(G_GNUC_UNUSED HaslMechanism *mechanism,
                                  HaslContext *context,
                                  GError **error)
{
	const char *value = NULL;

	value = hasl_context_get_username(context);
	if(hasl_str_is_empty(value)) {
		g_set_error_literal(error,
		                    HASL_MECHANISM_ANONYMOUS_ERROR,
		                    HASL_MECHANISM_ANONYMOUS_ERROR_MISSING_USERNAME,
		                    _("missing username"));

		return FALSE;
	}

	if(!hasl_context_get_allow_clear_text(context)) {
		if(!hasl_context_get_tls(context)) {
			g_set_error_literal(error,
			                    HASL_MECHANISM_ANONYMOUS_ERROR,
			                    HASL_MECHANISM_ANONYMOUS_ERROR_CLEARTEXT_NOT_ALLOWED,
			                    _("cleartext is not allowed without TLS"));

			return FALSE;
		}
	}

	return TRUE;
}

static HaslMechanismResult
hasl_mechanism_anonymous_step(G_GNUC_UNUSED HaslMechanism *mechanism,
                              HaslContext *ctx,
                              G_GNUC_UNUSED const guint8 *server_in,
                              G_GNUC_UNUSED gsize server_in_length,
                              guint8 **client_out,
                              gsize *client_out_length,
                              GError **error)
{
	GByteArray *data = NULL;
	const char *username = NULL;
	char buffer[1024]; /* Up to 1020 bytes is allowed per the RFC. */
	int res = 0;

	username = hasl_context_get_username(ctx);
	if(hasl_str_is_empty(username)) {
		g_set_error_literal(error,
		                    HASL_MECHANISM_ANONYMOUS_ERROR,
		                    HASL_MECHANISM_ANONYMOUS_ERROR_MISSING_USERNAME,
		                    _("no username provided"));

		return HASL_MECHANISM_RESULT_ERROR;
	}

	/* Copy the username into the buffer. */
	g_strlcpy(buffer, username, sizeof(buffer));

	/* Use the stringprep trace profile per the RFC. */
	res = stringprep(buffer, sizeof(buffer), 0, stringprep_trace);
	if(res != STRINGPREP_OK) {
		g_set_error(error,
		            HASL_MECHANISM_ANONYMOUS_ERROR,
		            HASL_MECHANISM_ANONYMOUS_ERROR_STRINGPREP_FAILED,
		            _("stringprep failed: %s"),
		            stringprep_strerror(res));

		return HASL_MECHANISM_RESULT_ERROR;
	}

	/* We have a saslpreped username, so we can build our payload. */
	data = g_byte_array_new();
	g_byte_array_append(data, (guint8 *)buffer, strlen(buffer));

	/* Set our out variables and return. */
	if(client_out_length != NULL) {
		*client_out_length = data->len;
	}

	*client_out = g_byte_array_free(data, FALSE);

	return HASL_MECHANISM_RESULT_SUCCESS;
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
static void
hasl_mechanism_anonymous_init(G_GNUC_UNUSED HaslMechanismAnonymous *anonymous)
{
}

static void
hasl_mechanism_anonymous_class_init(HaslMechanismAnonymousClass *klass) {
	HaslMechanismClass *mechanism_class = HASL_MECHANISM_CLASS(klass);

	mechanism_class->possible = hasl_mechanism_anonymous_possible;
	mechanism_class->step = hasl_mechanism_anonymous_step;
}
