/*
 * Copyright (c) 2000-2003,2005,2016 by Solar Designer
 * Copyright (c) 2008,2009 by Dmitry V. Levin
 * See LICENSE
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "passwdqc.h"
#include "concat.h"

static const char *skip_prefix(const char *sample, const char *prefix)
{
	size_t len = strlen(prefix);

	if (strncmp(sample, prefix, len))
		return NULL;
	return sample + len;
}

static int
parse_option(passwdqc_params_t *params, char **reason, const char *option)
{
	const char *err = "Invalid parameter value";
	const char *p;
	char *e;
	int i, rc = 0;
	unsigned long v;

	*reason = NULL;
	if ((p = skip_prefix(option, "min="))) {
		for (i = 0; i < 5; i++) {
			if (!strncmp(p, "disabled", 8)) {
				v = INT_MAX;
				p += 8;
			} else {
				v = strtoul(p, &e, 10);
				p = e;
			}
			if (i < 4 && *p++ != ',')
				goto parse_error;
			if (v > INT_MAX)
				goto parse_error;
			if (i && (int)v > params->qc.min[i - 1])
				goto parse_error;
			params->qc.min[i] = v;
		}
		if (*p)
			goto parse_error;
	} else if ((p = skip_prefix(option, "max="))) {
		v = strtoul(p, &e, 10);
		if (*e || v < 8 || v > INT_MAX)
			goto parse_error;
		if (v > 10000)
			v = 10000;
		params->qc.max = v;
	} else if ((p = skip_prefix(option, "passphrase="))) {
		v = strtoul(p, &e, 10);
		if (*e || v > INT_MAX)
			goto parse_error;
		params->qc.passphrase_words = v;
	} else if ((p = skip_prefix(option, "match="))) {
		v = strtoul(p, &e, 10);
		if (*e || v > INT_MAX)
			goto parse_error;
		params->qc.match_length = v;
	} else if ((p = skip_prefix(option, "similar="))) {
		if (!strcmp(p, "permit"))
			params->qc.similar_deny = 0;
		else if (!strcmp(p, "deny"))
			params->qc.similar_deny = 1;
		else
			goto parse_error;
	} else if ((p = skip_prefix(option, "random="))) {
		v = strtoul(p, &e, 10);
		if (!strcmp(e, ",only")) {
			e += 5;
			params->qc.min[4] = INT_MAX;
		}
		if (*e || (v && v < 24) || v > 85)
			goto parse_error;
		params->qc.random_bits = v;
	} else if ((p = skip_prefix(option, "enforce="))) {
		params->pam.flags &= ~F_ENFORCE_MASK;
		if (!strcmp(p, "users"))
			params->pam.flags |= F_ENFORCE_USERS;
		else if (!strcmp(p, "everyone"))
			params->pam.flags |= F_ENFORCE_EVERYONE;
		else if (strcmp(p, "none"))
			goto parse_error;
	} else if (!strcmp(option, "non-unix")) {
		if (params->pam.flags & F_CHECK_OLDAUTHTOK)
			goto parse_error;
		params->pam.flags |= F_NON_UNIX;
	} else if ((p = skip_prefix(option, "retry="))) {
		v = strtoul(p, &e, 10);
		if (*e || v > INT_MAX)
			goto parse_error;
		params->pam.retry = v;
	} else if ((p = skip_prefix(option, "ask_oldauthtok"))) {
		params->pam.flags &= ~F_ASK_OLDAUTHTOK_MASK;
		if (params->pam.flags & F_USE_FIRST_PASS)
			goto parse_error;
		if (!p[0])
			params->pam.flags |= F_ASK_OLDAUTHTOK_PRELIM;
		else if (!strcmp(p, "=update"))
			params->pam.flags |= F_ASK_OLDAUTHTOK_UPDATE;
		else
			goto parse_error;
	} else if (!strcmp(option, "check_oldauthtok")) {
		if (params->pam.flags & F_NON_UNIX)
			goto parse_error;
		params->pam.flags |= F_CHECK_OLDAUTHTOK;
	} else if (!strcmp(option, "use_first_pass")) {
		if (params->pam.flags & F_ASK_OLDAUTHTOK_MASK)
			goto parse_error;
		params->pam.flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
	} else if (!strcmp(option, "use_authtok")) {
		params->pam.flags |= F_USE_AUTHTOK;
	} else if ((p = skip_prefix(option, "config="))) {
		if ((rc = passwdqc_params_load(params, reason, p)))
			goto parse_error;
	} else {
		err = "Invalid parameter";
		goto parse_error;
	}

	return 0;

parse_error:
	e = concat("Error parsing parameter \"", option, "\": ",
	    (rc ? (*reason ? *reason : "Out of memory") : err), NULL);
	free(*reason);
	*reason = e;
	return rc ? rc : -1;
}

int
passwdqc_params_parse(passwdqc_params_t *params, char **reason,
    int argc, const char *const *argv)
{
	int i;

	*reason = NULL;
	for (i = 0; i < argc; ++i) {
		int rc;

		if ((rc = parse_option(params, reason, argv[i])))
			return rc;
	}
	return 0;
}

static const passwdqc_params_t defaults = {
	{
		{INT_MAX, 24, 11, 8, 7},	/* min */
		40,				/* max */
		3,				/* passphrase_words */
		4,				/* match_length */
		1,				/* similar_deny */
		47				/* random_bits */
	},
	{
		F_ENFORCE_EVERYONE,		/* flags */
		3				/* retry */
	}
};

void passwdqc_params_reset(passwdqc_params_t *params)
{
	*params = defaults;
}
