/*
 * Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: t-mm.c,v 1.40 2006/10/05 04:27:39 ca Exp $")

#include "sm/error.h"
#include "sm/sysexits.h"
#include "sm/ctype.h"
#include "sm/heap.h"
#include "sm/memops.h"
#include "sm/maps.h"
#include "sm/mapc.h"
#include "sm/map.h"
#include "sm/mapclasses.h"
#include "sm/bdb.h"
#include "sm/cdb_map.h"

#include "sm/io.h"

static int Verbose = 0;

#define SEP	':'
#define SEP_WSPC	(-1)
#define MAPC_TYPE	"hash"
#define MAPC_NAME	"aliases"
#define MAPC_FILE	"aliases.db"
#define MAX_IN_LEN	(256 * 1024)
#define SM_COMM_CHAR	'#'

#define SMM_FL_LWR_KEY		0x00000001u /* change key to lower case */
#define SMM_FL_SKIP_LEAD_SP	0x00000002u /* skip leading whitespace on RHS */
#define SMM_FL_WHSP_DELIM	0x00000004u /* use whitespace as delimiter */
#define SMM_FL_EMPTY_KEY	0x00000008u /* allow empty keys */
#define SMM_FL_SKIP_COMM	0x00000010u /* skip comments */

#ifndef SM_ALLOW_ROOT
# define SM_ALLOW_ROOT 0
#endif

static char buf[MAX_IN_LEN];
#if MAX_IN_LEN >= INT_MAX
ERROR _MAX_IN_LEN >= _INT_MAX
#endif

static int
sm_makemap(sm_map_P map, int sep, uint32_t flags)
{
	sm_ret_T ret;
	size_t len;
	sm_map_key_T key;
	sm_map_data_T data;
	char *delim, *rhs;
	int c;
	uint u, line;

	line = 0;
	while (fgets(buf, sizeof(buf), stdin) != NULL)
	{
		++line;

		if (SM_IS_FLAG(flags, SMM_FL_SKIP_COMM))
		{
			char *p;

			if (*buf == '\0' || *buf == SM_COMM_CHAR)
				continue;

			p = buf;
			while (*p != '\0' && ISSPACE(*p))
				p++;
			if (*p == '\0')
				continue;
		}

		/* find key/value separator */
		if (SM_IS_FLAG(flags, SMM_FL_WHSP_DELIM))
			delim = strpbrk(buf, " \t");
		else
			delim = strchr(buf, sep);

		if (delim == NULL)
		{
			/* complain about missing delim */
			fprintf(stderr,
				"no key/value separator found; line=%u\n",
					line);
			continue;
		}

		SM_ASSERT(delim < buf + sizeof(buf));
		SM_ASSERT(delim >= buf);
		u = (delim - buf) + 1;
		*delim = '\0';
		c = '\0';

		/* skip over leading space; make this optional? */
		if (SM_IS_FLAG(flags, SMM_FL_SKIP_LEAD_SP))
		{
			while (u < sizeof(buf) && (c = buf[u]) != '\0'
			       && isspace(c) && c != '\n')
				++u;
		}
		if (u >= sizeof(buf) || c == '\0' || c == '\n')
		{
			fprintf(stderr, "rhs is empty, line=%u, u=%u\n",
				line, u);
			continue;
		}
		rhs = buf + u;
		while (u < sizeof(buf) && (c = buf[u]) != '\0' && c != '\n')
			++u;
		if (u >= sizeof(buf) || c != '\n')
		{
			fprintf(stderr, "can't find end of rhs; line=%u\n",
				line);
			continue;
		}
		buf[u] = '\0';

		len = strlen(buf);
		if (SM_IS_FLAG(flags, SMM_FL_LWR_KEY))
		{
			uint j;

			for (j = 0; j < len; j++)
			{
				c = buf[j];
				if (ISUPPER(c))
					buf[j] = TOLOWER(c);
			}
		}

		if (!SM_IS_FLAG(flags, SMM_FL_EMPTY_KEY) && len == 0)
		{
			fprintf(stderr,"empty key; line=%u\n", line);
			continue;
		}
		sm_str_assign(key, NULL, (uchar *)buf, len, len);
		sm_str_assign(data, NULL, (uchar *)rhs, strlen(rhs),
				strlen(rhs));
		if (Verbose > 0)
		{
			fprintf(stderr, "lhs='%s' [%d], rhs='%s' [%d]\n"
				, (char *)sm_str_data(&key)
				, sm_str_getlen(&key)
				, (char *)sm_str_data(&data)
				, sm_str_getlen(&data)
				);
		}

		ret = sm_map_add(map, &key, &data, SMMAP_AFL_UNIQUE);
		if (sm_is_err(ret))
		{
			fprintf(stderr, "sm_map_add=failed, error=%s; line=%u\n"
				, smerr2txt(ret), line);
			sm_map_close(map, 0);
			return ret;
		}
	}

	ret = sm_map_close(map, 0);
	return ret;
}

static int
sm_openmap(const char *mapname, const char *mapfile, const char *maptype, int sep, uint32_t flags)
{
	sm_ret_T ret;
	sm_maps_P maps;
	sm_map_P map;
	sm_cstr_P mtype, mname;

	maps = NULL;
	mtype = mname = NULL;

	ret = sm_maps_init(&maps);
	if (maps == NULL)
		return ret;

	mtype = sm_cstr_scpyn0((const uchar *)maptype, strlen(maptype));
	if (mtype == NULL)
		goto error;

	mname = sm_cstr_scpyn0((const uchar *)mapname, strlen(mapname));
	if (mname == NULL)
		goto error;

	ret = sm_bdb_class_create(maps);
#if MTA_USE_TINYCDB
	ret = sm_cdb_class_create(maps);
#endif

#if 0
	/* "delete" the file to make sure the result won't contain garbage? */
	ret = truncate(mapfile, 0);
#endif /* 0 */
	map = NULL;
	ret = sm_map_open(maps, mname, mtype, SMAP_MODE_CREATE, mapfile,
			SMAP_MODE_RDWR, &map, SMPO_END);
	if (!sm_is_success(ret))
		goto error;

	ret = sm_makemap(map, sep, flags);

	ret = sm_maps_term(maps);
	SM_CSTR_FREE(mtype);
	SM_CSTR_FREE(mname);
	return ret;

  error:
	sm_maps_term(maps);
	return ret;
}

static void
usage(const char *prg)
{
	fprintf(stderr, "usage: %s [options]\n"
		"-E        allow empty key\n"
		"-f        do not convert keys to lower case\n"
		"-F file   name of db file [%s]\n"
		"-l n      list available map classes (n: verbosity)\n"
		"-n name   name of map [%s]\n"
		"-s        do not ignore comment or empty lines\n"
		"-T type   map type [%s]\n"
		"-t c      separator [%c]\n"
		"-V        increase verbosity\n"
		"-w        use whitespace as separator\n"
		"Options -t and -w are mutually exclusive.\n"
		"\n%s creates a %s map from the data provided via stdin.\n"
		"Entries must be of the form\n"
		"lhs%crhs\n"
		"(where '%c' is the default separator)\n"
		"Entries cannot span multiple lines.\n"
		, prg
		, MAPC_FILE
		, MAPC_NAME
		, MAPC_TYPE
		, SEP
		, prg
		, MAPC_TYPE
		, SEP
		, SEP
		);
	exit(EX_USAGE);
}

int
main(int argc, char *argv[])
{
	int c, sep;
	uint32_t flags;
	int list_mapc;
	char *mapname, *mapfile, *maptype, *prg;

	prg = argv[0];
	sep = (int) SEP;
	mapname = MAPC_NAME;
	mapfile = MAPC_FILE;
	maptype = MAPC_TYPE;
	list_mapc = -1;
	flags = SMM_FL_LWR_KEY|SMM_FL_SKIP_LEAD_SP|SMM_FL_SKIP_COMM;
#if !SM_ALLOW_ROOT
	if (getuid() == 0 || geteuid() == 0)
	{
		fprintf(stderr,
			"%s: ERROR: do not run this as super-user!\n",
			prg);
		exit(EX_USAGE);
	}
#endif
	while ((c = getopt(argc, argv, "EfF:hl:n:SsT:t:Vw")) != -1)
	{
		switch (c)
		{
		  case 'E':
			flags |= SMM_FL_EMPTY_KEY;
			break;
		  case 'f':
			flags &= ~SMM_FL_LWR_KEY;
			break;
		  case 'F':
			SM_STRDUP_OPT(mapfile, optarg);
			break;
		  case 'l':
			list_mapc = (int) strtol(optarg, NULL, 0);
			break;
		  case 'n':
			SM_STRDUP_OPT(mapname, optarg);
			break;
		  case 's':
			flags &= ~SMM_FL_SKIP_COMM;
			break;
		  case 'S':
			flags &= ~SMM_FL_SKIP_LEAD_SP;
			break;
		  case 'T':
			SM_STRDUP_OPT(maptype, optarg);
			break;
		  case 't':
			if (SM_IS_FLAG(flags, SMM_FL_WHSP_DELIM))
			{
				usage(prg);
				/* NOTREACHED */
				return EX_USAGE;
			}
			sep = (int) (*optarg);
			break;
		  case 'V':
			++Verbose;
			break;
		  case 'w':
			if (sep != (int) SEP)
			{
				usage(prg);
				/* NOTREACHED */
				return EX_USAGE;
			}
			flags |= SMM_FL_WHSP_DELIM;
			break;
		  case 'h':
		  default:
			usage(prg);
			/* NOTREACHED */
			return EX_USAGE;
		}
	}
	if (list_mapc >= 0)
	{
		sm_ret_T ret;
		sm_maps_P maps;

		maps = NULL;
		ret = sm_maps_init(&maps);
		if (NULL == maps)
			exit(EX_OSERR);
		ret = sm_maps_create(maps);
		if (sm_is_err(ret))
			exit(EX_OSERR);
		c = sm_mapc_list(smioout, maps, list_mapc,
				SMMAPC_FL_FILE|SMMAPC_FL_ADD);
	}
	else
		c = sm_openmap(mapname, mapfile, maptype, sep, flags);
	return c;
}


syntax highlighted by Code2HTML, v. 0.9.1