/*
 * Copyright (c) 2002-2005 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-dnstsk-1.c,v 1.37 2007/11/05 05:47:53 ca Exp $")

#include "sm/assert.h"
#include "sm/magic.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/test.h"
#include "sm/evthr.h"
#include "sm/dns.h"
#include "sm/dns-int.h"
#include "sm/dnstsk.h"
#include <stdio.h>

/* Should be protected by a mutex... */
static int Requests = 0;
static bool Terminate = false;
static bool Start = false;

static int Verbose = 0;
static int EvDbg = 0;
static uint MaxCount = 50;
static uint dnstimeout = DNS_TIMEOUT;
static ushort dnsretries = 0;

/* Test program options */
#define T_OPT_USE_TCP	0x0001
#define T_OPT_TWICE	0x0002
#define T_OPT_MX	0x0004
#define T_OPT_MX_A	0x0014	/* MX followed by A */
#define T_OPT_A		0x0008
#define T_OPT_PTR	0x0020
#define T_OPT_DEBUG	0x1000

struct t_req_S
{
	char		**hosts;
	int		  count;
	int		  iter;
	uint		  flags;
	dns_mgr_ctx_P	  dns_mgr_ctx;
	sm_evthr_task_P	  wr_tsk;
};

typedef struct t_req_S	t_req_T, *t_req_P;

static sm_ret_T
fcts(sm_evthr_task_P tsk)
{
	static uint count = 0;
	struct timeval sleept, delay;

	++count;
	if (Verbose > 3 && (count % 10 == 0))
		fprintf(stderr, "fcts: count=%u, ", count);
	if ((Requests <= 0 && Start) || count > MaxCount)
	{
		if (Verbose > 3)
			fprintf(stderr,
				"fcts: Requests=%d, count=%d, MaxCount=%d!\n",
				Requests, count, MaxCount);
		return EVTHR_DEL|EVTHR_TERM;
	}
	if (Terminate)
	{
		if (Verbose > 3)
			fprintf(stderr, "fcts: terminate!\n");
		return EVTHR_DEL|EVTHR_TERM;
	}
	sm_memzero(&sleept, sizeof(sleept));
	gettimeofday(&sleept, NULL);
	delay.tv_sec = 0;
	delay.tv_usec = 10000;
	timeradd(&sleept, &delay, &tsk->evthr_t_sleep);
	return EVTHR_SLPQ;
}

static sm_ret_T
callback(dns_res_P dns_res, void *ctx)
{
	sm_ret_T ret;
	dns_type_T type;
	dns_rese_P dns_rese;
	dns_mgr_ctx_P dns_mgr_ctx;
	sm_cstr_P q;
	struct in_addr inaddr;

	SM_TEST(dns_res != NULL);
	if (Verbose > 1)
		fprintf(stderr, "callback: got %p, requests=%u ", dns_res, Requests);
	if (dns_res == NULL)
	{
		--Requests;
		return SM_FAILURE;
	}
	if (Verbose > 1)
		fprintf(stderr, "query='%s' ", sm_cstr_data(dns_res->dnsres_query));
	if (Terminate)
	{
		if (Verbose > 3)
			fprintf(stderr, "callback: terminate!\n");
		return SM_FAILURE;
	}
	dns_mgr_ctx = (dns_mgr_ctx_P) ctx;
	if (sm_is_err(dns_res->dnsres_ret))
	{
		if (Verbose > 0)
		{
			fprintf(stderr, "callback: error=%x\n",
				dns_res->dnsres_ret);
			switch (dns_res->dnsres_ret)
			{
			  case DNSR_REFUSED:
				fprintf(stderr, "DNSR_REFUSED\n");
				break;
			  case DNSR_TEMP:
				fprintf(stderr, "DNSR_TEMP\n");
				break;
			  case DNSR_NOTFOUND:
				fprintf(stderr, "DNSR_NOTFOUND\n");
				break;
			  case DNSR_PERM:
				fprintf(stderr, "DNSR_PERM\n");
				break;
			  case DNSR_TIMEOUT:
				fprintf(stderr, "DNSR_TIMEOUT\n");
				break;
			  default:
				fprintf(stderr, "unknown\n");
				break;
			}
		}
		--Requests;
		return SM_FAILURE;
	}

	if (Verbose == 0)
	{
		--Requests;
		return SM_SUCCESS;
	}

	fprintf(stderr, "dnsres: entries=%u\n", dns_res->dnsres_entries);
	fprintf(stderr, "dnsres: max=%u\n", dns_res->dnsres_maxentries);

	for (dns_rese = DRESL_FIRST(dns_res);
	     dns_rese != DRESL_END(dns_res);
	     dns_rese = DRESL_NEXT(dns_rese))
	{
		type = dns_rese->dnsrese_type;
		fprintf(stderr, "ttl=%u\n", dns_rese->dnsrese_ttl);
		switch (type)
		{
		  case T_MX:
			fprintf(stderr, "pref=%u\n", dns_rese->dnsrese_pref);
			fprintf(stderr, "weight=%u\n",
				dns_rese->dnsrese_weight);
			fprintf(stderr, "value=%s\n\n",
				sm_cstr_data(dns_rese->dnsrese_val.dnsresu_name));
			q = dns_rese->dnsrese_val.dnsresu_name;
			if (SM_CSTR_LAST(q) == '.')
			{
				/* HACK */
/*
				sm_cstr_getlen(q)--;
				sm_cstr_data(q)[sm_cstr_getlen(q)] = '\0';
*/
			}
			ret = dns_req_add(dns_mgr_ctx, q, T_A, 0, callback,
					(void *) dns_mgr_ctx);
			if (Verbose > 1)
				fprintf(stderr, "callback: add T_A(%s)=%x\n",
					sm_cstr_data(q),
					ret);
			SM_TEST(sm_is_success(ret));
			if (sm_is_err(ret))
				break;
			++Requests;
			break;
		  case T_A:
			inaddr.s_addr = dns_rese->dnsrese_val.dnsresu_a;
			if (Verbose > 1)
				fprintf(stderr, "IPv4=%X [%s]\n", (uint)
					htonl(dns_rese->dnsrese_val.dnsresu_a),
					inet_ntoa(inaddr));
			else
				fprintf(stderr, "IPv4=%X\n", (uint)
					htonl(dns_rese->dnsrese_val.dnsresu_a));
			break;
		  case T_PTR:
#if 0
			fprintf(stderr, "pref=%u\n", dns_rese->dnsrese_pref);
			fprintf(stderr, "weight=%u\n",
				dns_rese->dnsrese_weight);
#endif /* 0 */
			fprintf(stderr, "value=%s\n\n",
				sm_cstr_data(dns_rese->dnsrese_val.dnsresu_name));
			break;
		  case T_CNAME:
			fprintf(stderr, "CNAME=%s\n\n",
				sm_cstr_data(dns_rese->dnsrese_val.dnsresu_name));
			break;
		  default:
			fprintf(stderr, "unknown type=%d\n", type);
			break;
		}
	}
	--Requests;
	return SM_SUCCESS;
}

static sm_ret_T
lookup(sm_evthr_task_P tsk)
{
	t_req_P t_req;
	int n;
	sm_ret_T ret;
	dns_type_T type;
	sm_cstr_P query;
	uint flags;
	dns_mgr_ctx_P dns_mgr_ctx;
	char *host;

	SM_REQUIRE(tsk != NULL);
	t_req = (t_req_P) tsk->evthr_t_actx;
	if (t_req == NULL)
		return EVTHR_DEL|EVTHR_TERM;

	flags = t_req->flags;
	type = T_A;			/* Just to keep gcc happy */
	dns_mgr_ctx = t_req->dns_mgr_ctx;
  again:
	if (Verbose > 1)
		fprintf(stderr, "lookup, count=%d\n", t_req->count);
	for (n = 0; n < t_req->count; n++)
	{
		if (Terminate)
		{
			if (Verbose > 3)
				fprintf(stderr,
					"lookup: terminate (n=%d/%d)!\n",
					n, t_req->count);
			return SM_FAILURE;
		}
		host = t_req->hosts[n];
		if (host == NULL || *host == '\0')
			return EVTHR_DEL|EVTHR_TERM;
		query = sm_cstr_scpyn0((const uchar *) host, strlen(host));
		SM_TEST(query != NULL);
		if (query == NULL)
			return EVTHR_DEL|EVTHR_TERM;

		if (flags & T_OPT_MX)
		{
			type = T_MX;
			if (Verbose > 1)
				fprintf(stderr, "adding T_MX(%s)\n", host);
			ret = dns_req_add(dns_mgr_ctx, query, type, 0, callback,
					(void *) dns_mgr_ctx);
			if (Verbose > 1)
				fprintf(stderr, "add T_MX(%s)=%x, ", host, ret);
			SM_TEST(sm_is_success(ret));
			if (sm_is_err(ret))
				goto error;
			++Requests;
		}

		if (flags & T_OPT_A)
		{
			type = T_A;
			ret = dns_req_add(dns_mgr_ctx, query, type, 0, callback,
					(void *) dns_mgr_ctx);
			if (Verbose > 1)
				fprintf(stderr, "add T_A(%s)=%x, ", host, ret);
			SM_TEST(sm_is_success(ret));
			if (sm_is_err(ret))
				goto error;
			++Requests;
		}

		if (flags & T_OPT_PTR)
		{
			type = T_PTR;
			ret = dns_req_add(dns_mgr_ctx, query, type, 0, callback,
					(void *) dns_mgr_ctx);
			if (Verbose > 1)
				fprintf(stderr, "add T_PTR(%s)=%x, ", host, ret);
			SM_TEST(sm_is_success(ret));
			if (sm_is_err(ret))
				goto error;
			++Requests;
		}

		/* test: add the same query again... */
		if (flags & T_OPT_TWICE)
		{
			ret = dns_req_add(dns_mgr_ctx, query, type, 0, callback,
					(void *) dns_mgr_ctx);
			if (Verbose > 1)
				fprintf(stderr, "add again %s=%x, ", host, ret);
			SM_TEST(sm_is_success(ret));
			if (sm_is_err(ret))
				goto error;
			++Requests;
		}
		SM_CSTR_FREE(query);
	}
	Start = true;

	if (Terminate)
	{
		if (Verbose > 3)
			fprintf(stderr, "callback: terminate after loop!\n");
		return SM_FAILURE;
	}

	/* Wakeup dns write task */
	ret = evthr_en_wr(t_req->wr_tsk);
	if (Verbose > 1)
		fprintf(stderr, "wakeup=%x\n", ret);
	SM_TEST(sm_is_success(ret));

	if (t_req->iter-- > 0)
	{
		++Requests;
		sleep(2);
		goto again;
	}

	/* Paranoia... */
	t_req->count = 0;

	/* We're done */
	return EVTHR_DEL;

  error:
	Start = true;
	SM_CSTR_FREE(query);
	return EVTHR_DEL|EVTHR_TERM;
}

static void
testdns(uint flags, uint32_t ipv4, t_req_P t_req)
{
	sm_ret_T ret;
	int n;
#if 0
	dns_rslv_ctx_P dns_rslv_ctx;
	dns_mxe_P dns_mxe;
	dns_mx_P dns_mx;
#endif
	dns_mgr_ctx_P dns_mgr_ctx;
	sm_evthr_ctx_P evthr_ctx;
	sm_evthr_task_P	task, task2, task3;
	dns_tsk_P dns_tsk;
	struct timeval sleept, delay, slpt;

	dns_tsk = NULL;
	evthr_ctx = NULL;
	task = task3 = NULL;
	dns_mgr_ctx = NULL;

	ret = thr_init();
	SM_TEST(sm_is_success(ret));
	if (sm_is_err(ret))
		goto errq;
	ret = evthr_init(&evthr_ctx, 1, 6, 10);
	SM_TEST(sm_is_success(ret));
	if (sm_is_err(ret))
		goto errt1;
	SM_TEST(evthr_ctx != NULL);

	if (EvDbg > 0)
		evthr_set_dbglvl(evthr_ctx, EvDbg);
	n = gettimeofday(&sleept, NULL);
	SM_TEST(n == 0);
	ret = dns_rslv_new((int) sleept.tv_usec
#if 0
			, &dns_rslv_ctx
#endif
			);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
		goto errt;
	if (flags & T_OPT_DEBUG)
		_res.options |= RES_DEBUG;
#if 0
	SM_TEST(dns_rslv_ctx != NULL);
	if (dns_rslv_ctx == NULL)
		goto errt;
#endif

	ret = dns_mgr_ctx_new(0, dnstimeout, 0, 0, &dns_mgr_ctx);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
		goto err0;
	SM_TEST(dns_mgr_ctx != NULL);
	if (dns_mgr_ctx == NULL)
		goto err0;
	dns_mgr_ctx->dnsmgr_retries = dnsretries;

	if (Verbose > 4)
		fprintf(stderr, "calling dns_tsk_new\n");
	ret = dns_tsk_new(dns_mgr_ctx,
			(flags & T_OPT_USE_TCP) ? DNS_TSK_FL_USETCP : 0,
			ipv4, &dns_tsk);
	SM_TEST(ret == SM_SUCCESS);
	if (sm_is_err(ret))
	{
		fprintf(stderr, "dns_tsk_new: ret=%x\n", ret);
		goto err1;
	}
	SM_TEST(dns_tsk != NULL);
	if (dns_tsk == NULL)
		goto err1;

	if (Verbose > 4)
		fprintf(stderr, "calling dns_tsk_start\n");
	ret = dns_tsk_start(dns_mgr_ctx, evthr_ctx);
	task = dns_mgr_ctx->dnsmgr_tsk[0];
	SM_TEST(sm_is_success(ret));
	if (sm_is_err(ret))
		goto err1;

	t_req->dns_mgr_ctx = dns_mgr_ctx;
	t_req->wr_tsk = task;
	sleept.tv_usec += 1;
	ret = evthr_task_new(evthr_ctx, &task2, EVTHR_EV_SL, -1, &sleept,
				lookup, (void *) t_req);
	SM_TEST(sm_is_success(ret));
	SM_TEST(task2 != NULL);

	delay.tv_sec = 0;
	delay.tv_usec = 10000;
	timeradd(&sleept, &delay, &slpt);
	ret = evthr_task_new(evthr_ctx, &task3, EVTHR_EV_SL, -1, &slpt,
				fcts, (void *) NULL);
	SM_TEST(sm_is_success(ret));
	SM_TEST(task3 != NULL);

#if 0
	ret = dnstskwr(dns_tsk);
	ret = dns_receive(dns_tsk);
#endif

	if (Verbose > 4)
		fprintf(stderr, "calling evthr_loop\n");
	ret = evthr_loop(evthr_ctx);
	SM_TEST(sm_is_success(ret));

	/* need to get result... */

  err1:
	Terminate = true;
	if (Verbose > 3)
		fprintf(stderr, "calling dns_mgr_ctx_del\n");
	ret = dns_mgr_ctx_del(dns_mgr_ctx);
	SM_TEST(ret == SM_SUCCESS);
  err0:
#if 0
	ret = dns_rslv_free(dns_rslv_ctx);
	SM_TEST(ret == SM_SUCCESS);
#endif
  errt:
	ret = evthr_stop(evthr_ctx);
	SM_TEST(sm_is_success(ret));
  errt1:
	ret = thr_stop();
	SM_TEST(sm_is_success(ret));
  errq:
	return;
}

static void
usage(const char *prg)
{
	fprintf(stderr, "Usage: %s [options] names\n", prg);
	fprintf(stderr, "Options:\n"
		"-a	query for A record\n"
		"-d	query twice\n"
		"-D	turn on debugging\n"
		"-i ip	use ip address for nameserver [127.0.0.1]\n"
		"-m	do not query for MX record\n"
		"-O n	set DNS timeout\n"
		"-p	query for PTR record (input: D.C.B.A.in-addr.arpa)\n"
		"-t n	limit request time (unit: 0.1s)\n"
		"-T	use TCP instead of UDP\n"
		"-V	increase verbosity\n"
		);
}

int
main(int argc, char *argv[])
{
	int r, i, iter;
	uint flags;
	uint32_t ipv4;
	t_req_T t_req;
	char **h, *prg;

	prg = argv[0];
	flags = T_OPT_MX;
	ipv4 = htonl(LOCALHOST_IP);
	iter = 0;
	while ((r = getopt(argc, argv, "adDe:i:I:mO:pr:t:TV")) != -1)
	{
		switch (r)
		{
		  case 'a':
			flags |= T_OPT_A;
			break;
		  case 'd':
			flags |= T_OPT_TWICE;
			break;
		  case 'D':
			flags |= T_OPT_DEBUG;
			break;
		  case 'e':
			EvDbg = atoi(optarg);
			break;
		  case 'i':
			ipv4 = inet_addr(optarg);
			break;
		  case 'I':
			iter = atoi(optarg);
			break;
		  case 'm':
			flags &= ~T_OPT_MX;
			break;
		  case 'O':
			dnstimeout = atoi(optarg);
			break;
		  case 'p':
			flags |= T_OPT_PTR;
			flags &= ~T_OPT_MX;
			break;
		  case 'r':
			dnsretries = atoi(optarg);
			break;
		  case 't':
			i = atoi(optarg);
			if (i <= 0)
				return 1;
			MaxCount = (uint) i;
			break;
		  case 'T':
			flags |= T_OPT_USE_TCP;
			break;
		  case 'V':
			Verbose++;
			break;
		  default:
			usage(argv[0]);
			return 1;
		}
	}

	sm_test_begin(argc, argv, "test getmxrr");
	t_req.count = argc - optind;
	h = (char **) sm_malloc(sizeof(char *) * t_req.count);
	SM_TEST(h != NULL);
	if (h == NULL)
		goto error;
	t_req.hosts = h;
	t_req.flags = flags;
	t_req.iter = iter;
	for (r = optind; r < argc; r++)
	{
		*h = argv[r];
		++h;
	}
	testdns(flags, ipv4, &t_req);
	sm_free(t_req.hosts);
  error:
	return sm_test_end();
}


syntax highlighted by Code2HTML, v. 0.9.1