/*
   Unix SMB/CIFS implementation.

   Copyright (C) Guenther Deschner 2016

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "librpc/gen_ndr/ndr_spoolss.h"
#include "rpc_client/init_spoolss.h"
#include "libgpo/gpo_ini.h"
#include "printer_driver.h"
#include "source3/include/smb_macros.h"

/* GetPrinterDriverDirectory  -> drivers and dependent files */
#define PRINTER_INF_DIRID_66000

/* GetPrintProcessorDirectory -> print processors */
#define PRINTER_INF_DIRID_66001

/* GetColorDirectory -> color profiles */
#define PRINTER_INF_DIRID_66003

static const char *get_string_unquote(const char *s)
{
	bool ok;
	size_t len;

	if (s == NULL) {
		return NULL;
	}

	len = strlen(s);
	if (len < 2) {
		return s;
	}

	if (s[0] == '"' && s[len-1] == '"') {
		ok = trim_string(discard_const(s), "\"", "\"");
		if (!ok) {
			return NULL;
		}
	}

	return s;
}

/*
 * '%STRING%' indicates STRING is localized in the [Strings] section
 */

static const char *get_string_token(struct gp_inifile_context *ctx,
				    const char *s)
{
	NTSTATUS status;
	bool ok;
	char *key;
	const char *s2;

	if (s != NULL &&  s[0] != '%' && s[strlen(s)-1] != '%') {
		return s;
	}

	ok = trim_string(discard_const(s), "%", "%");
	if (!ok) {
		return NULL;
	}

	key = talloc_asprintf(ctx, "Strings:%s", s);
	if (key == NULL) {
		return NULL;
	}

	status = gp_inifile_getstring(ctx, key, &s2);
	talloc_free(key);
	if (!NT_STATUS_IS_OK(status)) {
		/* what can you do... */
		return s;
	}

	return s2;
}

static NTSTATUS gp_inifile_getstring_ext(struct gp_inifile_context *ctx,
					 const char *key,
					 const char **ret)
{
	NTSTATUS status;
	const char *s;

	status = gp_inifile_getstring(ctx, key, &s);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	s = get_string_unquote(s);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (s[0] == '%' && s[strlen(s)-1] == '%') {
		s = get_string_token(ctx, s);
	}

	s = get_string_unquote(s);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	*ret = s;

	return NT_STATUS_OK;
}

static NTSTATUS find_manufacturer_name(struct gp_inifile_context *ctx,
				       TALLOC_CTX *mem_ctx,
				       const char *section_name,
				       const char **manufacturer_name)
{
	NTSTATUS status;
	size_t num_keys = 0;
	const char **keys = NULL;
	const char **values = NULL;
	const char *s;
	char *p;

	status = gp_inifile_enum_section(ctx, section_name, &num_keys, &keys, &values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (num_keys < 1) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	s = talloc_strdup(mem_ctx, keys[0]);
	if (s == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	p = strchr(s, ':');
	if (p == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	*p = '\0';
	p++;

	s = get_string_unquote(p);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	s = get_string_token(ctx, s);

	s = get_string_unquote(s);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (s != NULL) {
		*manufacturer_name = talloc_strdup(mem_ctx, s);
		if (*manufacturer_name == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	talloc_free(keys);
	talloc_free(values);

	return NT_STATUS_OK;
}

static NTSTATUS find_manufacturer_url(struct gp_inifile_context *ctx,
				      TALLOC_CTX *mem_ctx,
				      const char *section_name,
				      const char *manufacturer_name,
				      const char **manufacturer_url)
{
	NTSTATUS status;
	size_t num_keys = 0;
	const char **keys = NULL;
	const char **values = NULL;
	const char *s;
	char *p;

	status = gp_inifile_enum_section(ctx, section_name, &num_keys, &keys, &values);

	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (num_keys < 1) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	p = strchr(keys[0], ':');
	if (p == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	*p = '\0';
	p++;

	s = get_string_unquote(p);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	s = get_string_token(ctx, s);

	s = get_string_unquote(s);
	if (s == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	if (strequal(s, manufacturer_name)) {
		s = get_string_unquote(values[0]);
		if (s == NULL) {
			return NT_STATUS_INTERNAL_ERROR;
		}
	}

	if (s != NULL) {
		*manufacturer_url = talloc_strdup(mem_ctx, s);
		if (*manufacturer_url == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	talloc_free(keys);
	talloc_free(values);

	return NT_STATUS_OK;
}

static NTSTATUS add_string_to_spoolss_array(TALLOC_CTX *mem_ctx,
					    const char *s,
					    struct spoolss_StringArray **r)
{
	size_t count = 2;
	struct spoolss_StringArray *a = *r;
	bool ok;
	int i;

	if (a == NULL) {
		a = talloc_zero(mem_ctx, struct spoolss_StringArray);
		if (a == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (a->string == NULL) {
		a->string = talloc_zero_array(a, const char *, count);
		if (a->string == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	for (i = 0; a->string[i] != NULL; i++) { ;; }
	count = i;

	ok = add_string_to_array(mem_ctx, s, &a->string, &count);
	if (!ok) {
		return NT_STATUS_NO_MEMORY;
	}

	a->string = talloc_realloc(mem_ctx, a->string, const char *, count + 1);
	if (a->string == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	a->string[count] = NULL;

	*r = a;

	return NT_STATUS_OK;
}

static NTSTATUS add_dependent_driver_file(TALLOC_CTX *mem_ctx,
					  const char *file,
					  struct spoolss_StringArray **r)
{
	char *p;

	if (file == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (file[0] == '@') {
		file++;
	}

	p = strchr(file, ',');
	if (p != NULL) {
		*p = '\0';
	}

	return add_string_to_spoolss_array(mem_ctx, file, r);
}

/*
 * https://msdn.microsoft.com/de-de/windows/hardware/drivers/install/inf-manufacturer-section
 *
 * [Manufacturer]
 * "Kyocera"=Kyocera,NTx86.5.1,NTx86.6.0,NTamd64.5.1,NTamd64.6.0
 */

static NTSTATUS enum_devices_in_toc(struct gp_inifile_context *ctx,
				    TALLOC_CTX *mem_ctx,
				    size_t *pnum_devices,
				    const char ***pdevices,
				    const char ***pdevice_values)
{
	NTSTATUS status;
	size_t i, num_manufacturers = 0;
	const char **manufacturers = NULL;
	const char **values = NULL;
	char *p;
	bool ok;

	status = gp_inifile_enum_section(ctx, "Manufacturer", &num_manufacturers, &manufacturers, &values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	for (i = 0; i < num_manufacturers; i++) {

		const char *models_section_name;
		const char *s;
		char **decorations;
		int j;

		DEBUG(11,("processing manufacturer: %s\n", manufacturers[i]));

		status = gp_inifile_getstring(ctx, manufacturers[i], &s);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		decorations = str_list_make_v3(mem_ctx, s, ",");
		if (decorations == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		models_section_name = decorations[0];

		for (j = 1; decorations[j] != NULL; j++) {

			/*
			 * https://msdn.microsoft.com/de-de/windows/hardware/drivers/install/inf-models-section
			 */

			const char *decorated_models_section_name;
			size_t d, num_devices = 0;
			const char **devices = NULL;
			const char **device_values = NULL;
			size_t c = 0;

			decorated_models_section_name = talloc_asprintf(mem_ctx, "%s.%s",
									models_section_name,
									decorations[j]);
			if (decorated_models_section_name == NULL) {
				return NT_STATUS_NO_MEMORY;
			}

			DEBUG(11,("processing decorated models_section_name: %s\n",
				decorated_models_section_name));

			status = gp_inifile_enum_section(ctx, decorated_models_section_name,
							 &num_devices, &devices,
							 &device_values);
			if (!NT_STATUS_IS_OK(status)) {
				return status;
			}
			for (d = 0; d < num_devices; d++) {

				DEBUG(11,("processing device: %s\n",
					devices[d]));

				s = talloc_strdup(mem_ctx, devices[d]);
				if (s == NULL) {
					return NT_STATUS_NO_MEMORY;
				}

				p = strchr(s, ':');
				if (p == NULL) {
					return NT_STATUS_DRIVER_INTERNAL_ERROR;
				}

				*p = '\0';
				p++;

				s = get_string_unquote(p);

				ok = add_string_to_array(mem_ctx, s, pdevices, pnum_devices);
				if (!ok) {
					return NT_STATUS_NO_MEMORY;
				}
				ok = add_string_to_array(mem_ctx, device_values[d], pdevice_values, &c);
				if (!ok) {
					return NT_STATUS_NO_MEMORY;
				}
			}
		}
	}

	return NT_STATUS_OK;
}

static NTSTATUS find_device_in_toc(struct gp_inifile_context *ctx,
				   TALLOC_CTX *mem_ctx,
				   const char *device_description,
				   const char **value)
{
	NTSTATUS status;
	size_t d, num_devices = 0;
	const char **devices = NULL;
	const char **device_values = NULL;

	if (device_description == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	status = enum_devices_in_toc(ctx, mem_ctx,
				     &num_devices,
				     &devices,
				     &device_values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	for (d = 0; d < num_devices; d++) {

		if (strequal(device_description, devices[d])) {

			DEBUG(10,("found device_description: %s\n",
				device_description));

			*value = talloc_strdup(mem_ctx, device_values[d]);
			if (*value == NULL) {
				return NT_STATUS_NO_MEMORY;
			}
			DEBUGADD(10,("and returned: %s\n", *value));

			return NT_STATUS_OK;
		}
	}

	return NT_STATUS_DRIVER_INTERNAL_ERROR;
}

/*
 * https://msdn.microsoft.com/de-de/windows/hardware/drivers/install/inf-copyfiles-directive
 */

static NTSTATUS process_driver_section_copyfiles(struct gp_inifile_context *ctx,
						 TALLOC_CTX *mem_ctx,
						 const char *driver_section,
						 struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	size_t i, num_keys = 0;
	char *p, *key;
	const char **keys = NULL;
	const char **values = NULL;
	char *str;
	const char *s;

	key = talloc_asprintf(mem_ctx, "%s:%s", driver_section, "CopyFiles");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	DEBUG(10,("Checking for CopyFiles entry in %s\n", driver_section));

	status = gp_inifile_getstring(ctx, key, &s);
	if (!NT_STATUS_IS_OK(status)) {
		return NT_STATUS_OK;
	}

	DEBUG(10,("these are the files to copy: %s\n", s));

	while (next_token_talloc(mem_ctx, &s, &str, ",")) {

		DEBUG(10,("trying section: %s\n", str));

		if (str[0] == '@') {
			DEBUG(10,("adding dependent driver file: %s\n", str));
			status = add_dependent_driver_file(mem_ctx, str, &r->dependent_files);
			if (!NT_STATUS_IS_OK(status)) {
				return status;
			}
			continue;
		}

		status = gp_inifile_enum_section(ctx, str, &num_keys, &keys, &values);
		if (NT_STATUS_IS_OK(status)) {
			for (i = 0; i < num_keys; i++) {
				p = strchr(keys[i], ':');
				if (p == NULL) {
					return NT_STATUS_INVALID_PARAMETER;
				}
				*p = '\0';
				p++;

				DEBUG(10,("adding dependent driver file: %s\n", p));

				status = add_dependent_driver_file(mem_ctx, p, &r->dependent_files);
				if (!NT_STATUS_IS_OK(status)) {
					return status;
				}
			}
			TALLOC_FREE(keys);
			TALLOC_FREE(values);
		}
	}

	return NT_STATUS_OK;
}

#define process_driver_section_val(_ctx, _mem_ctx, _section, _r, _key, _element) \
do { \
	NTSTATUS _status; \
	const char *__key, *_s; \
	__key = talloc_asprintf(_mem_ctx, "%s:%s", _section, _key); \
	NT_STATUS_HAVE_NO_MEMORY(__key); \
	_status = gp_inifile_getstring(_ctx, __key, &_s); \
	if (NT_STATUS_IS_OK(_status)) { \
		(_r)->_element = talloc_strdup(mem_ctx, _s); \
		NT_STATUS_HAVE_NO_MEMORY((_r)->_element); \
	} \
} while(0);

static NTSTATUS process_driver_section_colorprofiles(struct gp_inifile_context *ctx,
						     TALLOC_CTX *mem_ctx,
						     const char *section,
						     struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	const char *key, *s;

	key = talloc_asprintf(mem_ctx, "%s:%s", section, "ColorProfiles");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring_ext(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {

		status = add_string_to_spoolss_array(mem_ctx, s, &r->color_profiles);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	return NT_STATUS_OK;
}

static NTSTATUS process_driver_section_printprocessor(struct gp_inifile_context *ctx,
						      TALLOC_CTX *mem_ctx,
						      const char *section,
						      struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	char *key, *p;
	const char *s;

	key = talloc_asprintf(mem_ctx, "%s:%s", section, "PrintProcessor");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring_ext(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {
		s = get_string_unquote(s);

		p = strchr(s, ',');
		if (p == NULL) {
			return NT_STATUS_INVALID_PARAMETER;
		}
		*p = '\0';
		r->print_processor = talloc_strdup(mem_ctx, s);
		if (r->print_processor == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	return NT_STATUS_OK;
}

static NTSTATUS process_driver_section_data_section(struct gp_inifile_context *ctx,
						    TALLOC_CTX *mem_ctx,
						    const char *section,
						    struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	char *key;
	const char *s;

	key = talloc_asprintf(mem_ctx, "%s:%s", section, "DataSection");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {
		process_driver_section_val(ctx, mem_ctx, s, r,
					   "DriverFile", driver_path);
		process_driver_section_val(ctx, mem_ctx, s, r,
					   "HelpFile", help_file);
		process_driver_section_val(ctx, mem_ctx, s, r,
					   "DataFile", data_file);
		process_driver_section_val(ctx, mem_ctx, s, r,
					   "ConfigFile", config_file);
	}

	return NT_STATUS_OK;
}


static NTSTATUS process_one_core_driver_section(struct gp_inifile_context *core_ctx,
						TALLOC_CTX *mem_ctx,
						const char *driver_section,
						struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	size_t i, num_keys = 0;
	const char **keys = NULL;
	const char **values = NULL;

	DEBUG(10,("CoreDriverSection is: %s\n", driver_section));

	status = gp_inifile_enum_section(core_ctx, driver_section, &num_keys, &keys, &values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	for (i = 0; i < num_keys; i++) {

		status = process_driver_section_copyfiles(core_ctx, mem_ctx, driver_section, r);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		process_driver_section_val(core_ctx, mem_ctx, driver_section, r,
					   "DriverFile", driver_path);
		process_driver_section_val(core_ctx, mem_ctx, driver_section, r,
					   "HelpFile", help_file);
		process_driver_section_val(core_ctx, mem_ctx, driver_section, r,
					   "ConfigFile", config_file);

		status = process_driver_section_colorprofiles(core_ctx, mem_ctx, driver_section, r);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	talloc_free(keys);
	talloc_free(values);

	return NT_STATUS_OK;
}

/*
 * CoreDriverSections="{D20EA372-DD35-4950-9ED8-A6335AFE79F0},UNIDRV_BIDI.OEM,UNIDRV_BIDI_DATA","{D20EA372-DD35-4950-9ED8-A6335AFE79F2},PCLXL.OEM","{D20EA372-DD35-4950-9ED8-A6335AFE79F3},sRGBPROFILE.OEM"
 */
static NTSTATUS process_core_driver_sections(struct gp_inifile_context *core_ctx,
					     TALLOC_CTX *mem_ctx,
					     const char *value,
					     struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	char *p;
	char **list;
	int i;

	list = str_list_make_v3(mem_ctx, value, ",");
	if (list == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	for (i = 0; list[i] != NULL; i++) {
		char **array;
		int a;

		/* FIXME: do we have to validate the core driver guid ? */

		p = strchr(list[i], ',');
		if (p != NULL) {
			*p = '\0';
			p++;
		}

		DEBUG(10,("CoreDriverSections we have to process: %s\n", p));

		array = str_list_make_v3(mem_ctx, p, ",");
		if (array == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		for (a = 0; array[a] != NULL; a++) {

			if (core_ctx == NULL) {
				DEBUG(0,("Need to process CoreDriverSections but "
					"have no Core Driver Context!\n"));
				return NT_STATUS_DRIVER_INTERNAL_ERROR;
			}

			status = process_one_core_driver_section(core_ctx, mem_ctx, array[a], r);
			if (!NT_STATUS_IS_OK(status)) {
				continue;
			}
		}
	}

	return NT_STATUS_OK;
}

/*
 * https://msdn.microsoft.com/de-de/windows/hardware/drivers/install/inf-ddinstall-section
 */
static NTSTATUS find_driver_files(struct gp_inifile_context *ctx,
				  struct gp_inifile_context *core_ctx,
				  TALLOC_CTX *mem_ctx,
				  const char *driver_name,
				  struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	char *key;
	const char *s;
	const char *value;
	char *install_section_name;
	bool ok;
	char *hw_id;

	status = find_device_in_toc(ctx, mem_ctx, driver_name, &value);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	r->driver_name = talloc_strdup(mem_ctx, driver_name);
	if (r->driver_name == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	ok = next_token_talloc(mem_ctx, &value, &install_section_name, ",");
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	DEBUG(10,("driver_name: %s, value: %s, install_section_name: %s\n",
		driver_name, value, install_section_name));

	/* Hardware Id is optional */
	ok = next_token_talloc(mem_ctx, &value, &hw_id, ",");
	if (ok) {
		r->hardware_id = hw_id;
	}

	status = process_driver_section_copyfiles(ctx, mem_ctx, install_section_name, r);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	process_driver_section_val(ctx, mem_ctx, install_section_name, r,
				   "DriverFile", driver_path);
	process_driver_section_val(ctx, mem_ctx, install_section_name, r,
				   "HelpFile", help_file);
	process_driver_section_val(ctx, mem_ctx, install_section_name, r,
				   "DataFile", data_file);
	process_driver_section_val(ctx, mem_ctx, install_section_name, r,
				   "ConfigFile", config_file);

	status = process_driver_section_printprocessor(ctx, mem_ctx, install_section_name, r);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = process_driver_section_data_section(ctx, mem_ctx, install_section_name, r);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	key = talloc_asprintf(mem_ctx, "%s:%s", install_section_name, "CoreDriverSections");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {

		DEBUG(10,("found CoreDriverSections: %s\n", s));

		status = process_core_driver_sections(core_ctx, mem_ctx, s, r);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	return NT_STATUS_OK;
}

struct inf_context {
	struct gp_inifile_context *ctx;
	struct gp_inifile_context *core_ctx;
};

static NTSTATUS init_inf_context(TALLOC_CTX *mem_ctx,
				 const char *inf_filename,
				 const char *core_filename,
				 struct inf_context **_inf_ctx)
{
	NTSTATUS status;
	struct gp_inifile_context *ctx;
	struct gp_inifile_context *core_ctx = NULL;
	struct inf_context *inf_ctx;

	inf_ctx = talloc_zero(mem_ctx, struct inf_context);
	if (inf_ctx == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_init_context_direct(mem_ctx,
						inf_filename,
						&ctx);
	if (!NT_STATUS_IS_OK(status)) {
		DEBUG(10,("init_inf_context: failed to load %s\n", inf_filename));
		return status;
	}

	if (ctx->generated_filename != NULL) {
		unlink(ctx->generated_filename);
	}

	if (core_filename != NULL) {
		status = gp_inifile_init_context_direct(mem_ctx,
							core_filename,
							&core_ctx);
		if (!NT_STATUS_IS_OK(status)) {
			DEBUG(10,("init_inf_context: failed to load %s\n", core_filename));
			return status;
		}

		if (core_ctx->generated_filename != NULL) {
			unlink(core_ctx->generated_filename);
		}
	}

	inf_ctx->ctx = ctx;
	inf_ctx->core_ctx = core_ctx;

	*_inf_ctx = inf_ctx;

	return NT_STATUS_OK;
}

static NTSTATUS process_driver_driverver(struct gp_inifile_context *ctx,
					 struct spoolss_AddDriverInfo8 *r)
{
	NTSTATUS status;
	const char *s;
	char *p;
	bool ok;
	const char *str;

	status = gp_inifile_getstring(ctx, "Version:DriverVer", &s);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	str = talloc_strdup(ctx, s);
	if (str == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	p = strchr(str, ',');
	if (p) {
		*p = '\0';
		p++;
	}

	ok = spoolss_timestr_to_NTTIME(str, &r->driver_date);
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	ok = spoolss_driver_version_to_qword(p, &r->driver_version);
	if (!ok) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	return NT_STATUS_OK;
}

/*
 * Parse a SourceDisksNames section,
 * https://msdn.microsoft.com/de-de/windows/hardware/drivers/install/inf-sourcedisksnames-section?f=255&MSPPError=-2147217396
 */
static NTSTATUS process_source_disk_name(struct gp_inifile_context *ctx,
					 TALLOC_CTX *mem_ctx,
					 const char *short_environment,
					 const char **source_disk_name)
{
	NTSTATUS status;
	bool ok;
	const char *key;
	size_t i, num_keys = 0;
	const char **keys = NULL;
	const char **values = NULL;

	key = talloc_asprintf(mem_ctx, "SourceDisksNames.%s", short_environment);
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_enum_section(ctx, key, &num_keys, &keys, &values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	if (keys == NULL && values == NULL) {
		key = "SourceDisksNames";

		status = gp_inifile_enum_section(ctx, key, &num_keys, &keys, &values);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	for (i = 0; i < num_keys; i++) {

		/*
		 * 1   = %Disk1%,,,"Amd64"
		 * diskid = disk-description[,[tag-or-cab-file],[unused],[path],[flags][,tag-file]]
		 */
		char *disk_description, *tag_or_cab_file, *unused, *path;

		ok = next_token_no_ltrim_talloc(mem_ctx, &values[i], &disk_description, ",");
		if (!ok) {
			continue;
		}

		ok = next_token_no_ltrim_talloc(mem_ctx, &values[i], &tag_or_cab_file, ",");
		if (!ok) {
			continue;
		}

		ok = next_token_no_ltrim_talloc(mem_ctx, &values[i], &unused, ",");
		if (!ok) {
			continue;
		}

		ok = next_token_no_ltrim_talloc(mem_ctx, &values[i], &path, ",");
		if (!ok) {
			continue;
		}

		*source_disk_name = path;

		return NT_STATUS_OK;
	}

	return NT_STATUS_NOT_FOUND;
}

static NTSTATUS setup_driver_by_name(TALLOC_CTX *mem_ctx,
				     struct inf_context *inf_ctx,
				     const char *filename,
				     const char *environment,
				     const char *driver_name,
				     struct spoolss_AddDriverInfo8 *r,
				     const char **source_disk_name)
{
	NTSTATUS status;
	struct gp_inifile_context *ctx = inf_ctx->ctx;
	struct gp_inifile_context *core_ctx = inf_ctx->core_ctx;
	char *key;
	bool ok;
	const char *short_environment;
	const char *s;

	short_environment = spoolss_get_short_filesys_environment(environment);
	if (short_environment == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	status = find_driver_files(ctx, core_ctx, mem_ctx, driver_name, r);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = process_source_disk_name(ctx, mem_ctx,
					  short_environment,
					  source_disk_name);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	r->inf_path = talloc_strdup(mem_ctx, filename);
	if (r->inf_path == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	r->architecture = talloc_strdup(mem_ctx, environment);
	if (r->architecture == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	if (r->print_processor == NULL) {
		r->print_processor = talloc_strdup(mem_ctx, "winprint");
		if (r->print_processor == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
	}

	status = gp_inifile_getstring_ext(ctx, "Version:Class", &s);
	if (NT_STATUS_IS_OK(status)) {
		if (strequal(s, "Printer")) {
			r->printer_driver_attributes |= PRINTER_DRIVER_CLASS;
		}
	}

	status = gp_inifile_getstring(ctx, "Version:Signature", &s);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}
	if (!strequal(s, "\"$Windows NT$\"")) {
		return NT_STATUS_INVALID_SIGNATURE;
	}

	r->version = SPOOLSS_DRIVER_VERSION_200X;
	status = gp_inifile_getstring(ctx, "Version:ClassVer", &s);
	if (NT_STATUS_IS_OK(status)) {
		int cmp = strncasecmp_m(s, "4.0", 3);
		if (cmp == 0) {
			r->version = SPOOLSS_DRIVER_VERSION_2012;
		}
		if (strequal(s, "3.0")) {
			r->version = SPOOLSS_DRIVER_VERSION_200X;
		}
	}

	status = gp_inifile_getstring_ext(ctx, "Version:Provider", &s);
	if (NT_STATUS_IS_OK(status)) {
		if (s != NULL) {
			r->provider = talloc_strdup(mem_ctx, s);
			if (r->provider == NULL) {
				return NT_STATUS_NO_MEMORY;
			}
		}
	}

	status = process_driver_driverver(ctx, r);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	r->printer_driver_attributes &= ~PRINTER_DRIVER_SANDBOX_ENABLED;

	status = gp_inifile_getstring(ctx, "Version:DriverIsolation", &s);
	if (NT_STATUS_IS_OK(status)) {
		int cmp = strncasecmp_m(s, "2", 1);
		if (cmp == 0) {
			r->printer_driver_attributes |= PRINTER_DRIVER_SANDBOX_ENABLED;
		}
		cmp = strncasecmp_m(s, "0", 1);
		if (cmp == 0) {
			r->printer_driver_attributes &= ~PRINTER_DRIVER_SANDBOX_ENABLED;
		}
	}

	status = find_manufacturer_name(ctx, mem_ctx, "Manufacturer", &r->manufacturer_name);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = find_manufacturer_url(ctx, mem_ctx, "OEM URLS", r->manufacturer_name, &r->manufacturer_url);
	if (!NT_STATUS_IS_OK(status)) {
		/* not critical */
	}

	status = gp_inifile_getbool(ctx, "PrinterPackageInstallation:PackageAware", &ok);
	if (NT_STATUS_IS_OK(status)) {
		if (ok) {
			r->printer_driver_attributes |= PRINTER_DRIVER_PACKAGE_AWARE;
		}
	}

	key = talloc_asprintf(mem_ctx, "%s.%s:%s",
		"PrinterPackageInstallation", short_environment, "PackageAware");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getbool(ctx, key, &ok);
	if (NT_STATUS_IS_OK(status)) {
		if (ok) {
			r->printer_driver_attributes |= PRINTER_DRIVER_PACKAGE_AWARE;
		}
	}

	key = talloc_asprintf(mem_ctx, "%s.%s:%s",
		"PrinterPackageInstallation", short_environment, "CoreDriverDependencies");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {
		char **list;
		r->core_driver_dependencies = talloc_zero(mem_ctx, struct spoolss_StringArray);
		if (r->core_driver_dependencies == NULL) {
			return NT_STATUS_NO_MEMORY;
		}

		list = str_list_make_v3(r->core_driver_dependencies, s, ",");
		if (list == NULL) {
			return NT_STATUS_NO_MEMORY;
		}
		r->core_driver_dependencies->string = const_str_list(list);
	}

	key = talloc_asprintf(mem_ctx, "%s.%s:%s",
		"PrinterPackageInstallation", short_environment, "InboxVersionRequired");
	if (key == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	status = gp_inifile_getstring(ctx, key, &s);
	if (NT_STATUS_IS_OK(status)) {
		if (strequal(s, "UseDriverVer")) {
			r->min_inbox_driver_ver_date = r->driver_date;
			r->min_inbox_driver_ver_version = r->driver_version;
		}
	}

	return NT_STATUS_OK;
}

/****************************************************************
 parse a printer inf file
****************************************************************/

NTSTATUS driver_inf_parse(TALLOC_CTX *mem_ctx,
			  const char *core_driver_inf,
			  const char *filename,
			  const char *environment,
			  const char *driver_name,
			  struct spoolss_AddDriverInfo8 *r,
			  const char **source_disk_name)
{
	NTSTATUS status;
	struct inf_context *inf_ctx;

	if (!filename || !environment) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	status = init_inf_context(mem_ctx,
				  filename,
				  core_driver_inf,
				  &inf_ctx);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = setup_driver_by_name(mem_ctx, inf_ctx,
				      filename,
				      environment,
				      driver_name,
				      r,
				      source_disk_name);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	return NT_STATUS_OK;
}

NTSTATUS driver_inf_list(TALLOC_CTX *mem_ctx,
			 const char *core_driver_inf,
			 const char *filename,
			 const char *environment,
			 uint32_t *count,
			 struct spoolss_AddDriverInfo8 **_r)
{
	NTSTATUS status;
	const char *short_environment;
	size_t d, num_devices = 0;
	const char **devices = NULL;
	const char **device_values = NULL;
	struct inf_context *inf_ctx;

	if (!filename || !environment) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	short_environment = spoolss_get_short_filesys_environment(environment);
	if (short_environment == NULL) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	status = init_inf_context(mem_ctx,
				  filename,
				  core_driver_inf,
				  &inf_ctx);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	status = enum_devices_in_toc(inf_ctx->ctx, mem_ctx,
				     &num_devices,
				     &devices,
				     &device_values);
	if (!NT_STATUS_IS_OK(status)) {
		return status;
	}

	for (d = 0; d < num_devices; d++) {

		struct spoolss_AddDriverInfo8 r;
		const char *source_disk_name;

		ZERO_STRUCT(r);

		status = setup_driver_by_name(mem_ctx, inf_ctx, filename,
					      environment, devices[d], &r,
					      &source_disk_name);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}

		ADD_TO_ARRAY(mem_ctx, struct spoolss_AddDriverInfo8, r, _r, count);
	}

	return NT_STATUS_OK;
}
