/*

  DNS library.

  Requires emsg.c, lib.c, mycrc.c, tcplib.c, thread.c from sam

  Usage:

	1/ dns_init (Required)
		Requires an array of IP addresses (Dotted decimal notation) specifying DNS servers,
		and a count specifying the number in the array. Sockets and threading must already
		be set up. Set dothread to non-zero if you want dnslib to run the dns_thlookup
		thread or zero if you do this yourself (E.g. Surgemail).

	2/ dns_query (Required) (Macro for dns_query_t with DNS_TRANSUDP transport)
		Specify the host and type of lookup (DT_xx below). Returns a DNSQUERY structure or
		0 on memory allocation failure. Check the 'status' variable for DNS_NOERROR.

	3/ dns_getrr
		Returns a pointer to an object of the desired type (DNSRR_xx below) for _EACH_
		result from the query (I.e. call this multiple times for resource record types
		that may return multiple records). Returns 0 once there are no more records of
		the required type. Do NOT free these records, dns_endquery will do so (In the
		current implementation this is a pointer to within the DNSQUERY structure).

		NOTE: A -> MX record conversion is handled automatically if an MX lookup is done
		on a host with no associated MX record. Where this occurs, the preference for the
		MX will be set to -1.

	4/ dns_endquery (Required)
		Frees resources (Excluding cached resource records) associated with a query.

	Repeat steps 2-4 for each lookup. ASCII representations of DNSQUERY->status can be
	obtained by calling dns_ascstatus. To reprocess the resource records within a query
	(Bad practice IMO, but you can do it if you want to), call dns_rewindrr at any point
	between dns_query and dns_endquery.

	Do NOT under any circumstances change the content of any DNSRR_xx value that is
	specified as a pointer! These may be pointers directly to the DNS cache. Access to
	these values are reference counted so they are guaranteed to exist until the query is
	complete.

	Calling dns_getrr and dns_endquery with a NULL pointer is safe to reduce the level of
	error checking required.

	Queries may be nested.

	A shortcut is provided for IN-ADDR.ARPA reverse lookups so you can't get it wrong :)
	To do a reverse lookup use dns_reverse. The out pointer MUST point to a buffer large
	enough to take the resulting domain name and NULL terminator (256 bytes). The reult may
	be an empty string,	indicating a failure of some sort. If you need to know the exact reason
	for failure or do additional processing on the query structure, you can pass a DNSQUERY **
	as the third parameter. If this is done you must also call dns_endquery on the resulting
	DNSQUERY *. This may be a NULL pointer, indicating the reason for failure was due to a lack
	of memory.

	To get the number of resource records in the cache, call dns_getcachesz.

	All lookups are UDP unless requested otherwise. TCP lookups should be done if DNS_TRUNCAT
	is returned in response to a UDP query. This can be achieved by calling dns_query_t with
	the same parameters as dns_query, adding DNS_TRANSTCP as the last parameter.

*/

#ifndef DNSLIB_H
#define DNSLIB_H

#include "../sam/lib.h"
#include "../sam/tcplib.h"

#define DNS_MAXSRV 10

#undef BYTE_ORDER
#define BYTE_ORDER_LE 1
#define BYTE_ORDER_BE 2

#if defined(LITTLE_ENDIAN) || defined(i386) || defined(x86)
#	define BYTE_ORDER BYTE_ORDER_LE
#endif


#if defined(vax) || defined(ns32000) || defined(sun386) || defined(i386) || \
    defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \
    defined(__alpha__) || defined(__alpha) || defined(WIN32)
#undef BYTE_ORDER
#	define BYTE_ORDER BYTE_ORDER_LE
#endif

#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \
    defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \
    defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\
    defined(apollo) || defined(__convex__) || defined(_CRAY) || \
    defined(__hppa) || defined(__hp9000) || \
    defined(__hp9000s300) || defined(__hp9000s700) || \
    defined (BIT_ZERO_ON_LEFT) || defined(m68k)
#define BYTE_ORDER BYTE_ORDER_BE
#endif

#if defined(BIG_ENDIAN) || defined(SPARC)
#undef BYTE_ORDER
#define BYTE_ORDER BYTE_ORDER_BE
#endif

#ifndef BYTE_ORDER
#	error You must define BYTE_ORDER for your processor type!
#endif

/* QTYPE definitions */

#define DT_LOW 1
#define DT_HIGH 16 /* Range of valid types */

#define DT_A 1
#define DT_NS 2
#define DT_MD 3
#define DT_MF 4
#define DT_CNAME 5
#define DT_SOA 6
#define DT_MB 7
#define DT_MG 8
#define DT_MR 9
#define DT_NULL 10
#define DT_WKS 11
#define DT_PTR 12
#define DT_HINFO 13
#define DT_MINFO 14
#define DT_MX 15
#define DT_TXT 16
#define DT_MX_RAW 115

/* QCLASS definitions */

#define DC_IN 1
#define DC_CS 2
#define DC_CH 3
#define DC_HS 4

/* RCODE definitions. Values 6-15 reserved by RFC1035 */
#define DNS_NOERROR 0
#define DNS_FRMTERR	1
#define	DNS_SVRFAIL	2
#define	DNS_NAMEERR	3
#define DNS_NOTIMPL	4
#define	DNS_REFUSED	5

#define DNS_TIMEOUT	-1 /* Add ASCII definitions for these errors in dns_ascstatus */
#define DNS_INVALID	-2
#define DNS_TRUNCAT	-3
#define DNS_NOCHANN	-4

#define DNS_TRANSUDP	0
#define DNS_TRANSTCP	1

#define PKTHASH 256
#define MAXANSW 256

/*

  DNSMAXPARTS controls how many parts each Resource Record may contain.
  Of the standard RRs (RFC1035), SOA is is the largest with 12 individual parts.
  Increase this if you need more than 12 records in some variable length RRs
  (E.g. A records are variable length, returning multiple IPs where applicable).

*/
#define DNSMAXPARTS 20

struct dnsquestion {
	char *domain;
	unsigned char dlen;
	short qtype;
	short qclass;
};

struct rdata {
	int parts[DNSMAXPARTS];
};

struct dnsanswer {
	char *domain;
	char dlen;
	short qtype;
	short qclass;
	unsigned int expires;
	unsigned int refcnt;
	short rdlength;
	struct rdata rrdecoded;
	struct dnsanswer *next;
};

typedef struct dnspktd {
	union {
		unsigned short data[6];
		struct {
			unsigned int id:16;
#if BYTE_ORDER==BYTE_ORDER_BE
			unsigned int qr:1;
			unsigned int opcode:4;
			unsigned int aa:1;
			unsigned int tc:1;
			unsigned int rd:1;
			unsigned int ra:1;
			unsigned int :3;
			unsigned int rcode:4;
#elif BYTE_ORDER==BYTE_ORDER_LE
			unsigned int rd:1;
			unsigned int tc:1;
			unsigned int aa:1;
			unsigned int opcode:4;
			unsigned int qr:1;
			unsigned int rcode:4;
			unsigned int :3;
			unsigned int ra:1;
#endif
			unsigned int qdcount:16;
			unsigned int ancount:16;
			unsigned int nscount:16;
			unsigned int arcount:16;

		} fields;
	} header;

	struct dnsquestion question;

	int serverid;
	int allowhi;
	clock_t ck;

	int wait;

	struct dnsanswer **an; /* Pointer to answer pointer array */
	int anupto;

	struct dnspktd *next,*prev;
	int failed;
	int reason;
} DNSPKTD;

struct dnslist {
	char *host;
	unsigned short port;
	struct sockaddr_in addr;
	Chan *tcpchan;
	unsigned char *tcpbf;
	int tcppktsz;
	int tcppktupto;
};

typedef struct dnsquery {
	int status;
	int ancnt;
	int gotans;
	int type;
	int rrupto;
	char *host;
	int israw; /* If MX query should NOT do A record lookup */
	struct dnsanswer *an[MAXANSW];
	struct dnslist ldnssrv[DNS_MAXSRV];
	int work[100*DNSMAXPARTS]; /* Our workarea, typecast =) */
} DNSQUERY;

int dns_getcachesz();

typedef struct dnsrr_mx {
	int pref;
	char *host;
	unsigned long addr;
	char ascaddr[16];
} DNSRR_MX;

typedef struct dnsrr_a {
	int addrcnt;
	unsigned long addr[DNSMAXPARTS];
	char ascaddr[DNSMAXPARTS][16];
} DNSRR_A;

typedef struct dnsrr_txt {
	int len;
	char *text;
} DNSRR_TXT;

typedef struct dnsrr_ptr {
	char *domain;
} DNSRR_PTR;

typedef struct dnsrr_cname {
	char *domain;
} DNSRR_CNAME;

int dnslib_exit(void);

struct dnsquery *dns_query_t(char *host,unsigned short type,int transport);
void *dns_getrr(struct dnsquery *dq);
int dns_endquery(struct dnsquery *qw);
int dns_settimeout(int ms);
char *dns_ascstatus(struct dnsquery *qw);
char *dns_reverse(char *ip,char *out,struct dnsquery **qp);
int dns_init(char **serverlist,int lsvrcnt,int dothread);
int dns_log_lots(int x);
int dns_thlookup(void *x);

void dns_dump(void);

#define dns_query(y,z) dns_query_t(y,z,DNS_TRANSUDP);
char *dnslib_status(int i, char *tmp);

#endif
