/* Distributed Checksum Clearinghouse
 *
 * parse a named checksum
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.2.74-1.44 $Revision$
 */

#include "dcc_ck.h"
#ifndef DCC_WIN32
#include <arpa/inet.h>
#endif

static int				/* -1=fatal 0=failed 1=ok 2=resolved */
dcc_host2ck(DCC_EMSG emsg, DCC_WF *wf,
	    const char *ck,		/* this string of an IP address */
	    const char *fnm, int lineno,
	    DCC_TGTS tgts,		/* # of targets */
	    DCC_PARSED_CK_FNC fnc,	/* do something with each checksum */
	    DCC_PARSED_CIDR_CK_FNC cidr_fnc,
	    DCC_PARSED_CK_LOCK unlock)	/* unlock file if resolve hostname */
{
	int error;
	struct in6_addr addr6, mask6;
	DCC_SUM sum;
	DCC_SOCKU *sup;
	int i, j, bits;

	/* recognize xxx.xxx.xxx.xxx/y for IP address blocks
	 * as well as simple IP addresses */
	bits = dcc_str2cidr(emsg, &addr6, &mask6, ck, fnm, lineno);
	if (bits < 0)
		return 0;

	if (bits > 0) {
		/* use client CIDR block masks */
		if (128-bits >= 8 && cidr_fnc)
			return cidr_fnc(emsg, wf->info, fnm, lineno,
					bits, &addr6, &mask6, tgts);

		/* Allow class-B sized blocks in server whitelists.
		 * Client whitelists come here with non-null cidr_fnc */
		if (128-bits > 16) {
			dcc_pemsg(EX_NOHOST, emsg,
				  "CIDR block length in %s too large%s",
				  ck, fnm_lineno(fnm, lineno));
			return 0;
		}

		for (i = 1 << (128-bits); i > 0; --i) {
			dcc_ck_ipv6(sum, &addr6);
			j = fnc(emsg, wf, fnm, lineno, DCC_CK_IP, sum, tgts);
			if (j <= 0)
				return j;
			addr6.s6_addr32[3] = ntohl(addr6.s6_addr32[3]);
			++addr6.s6_addr32[3];
			addr6.s6_addr32[3] = htonl(addr6.s6_addr32[3]);
		}
		return 1;
	}

	/* we appear to have a host name,
	 * which are not allowed in per-user whitelists */
	if (wf->flags & DCC_WF_PER_USER) {
		dcc_pemsg(EX_NOHOST, emsg,
			  "hostname checksum illegal%s",
			  fnm_lineno(fnm, lineno));
		return 0;
	}

	/* Don't keep the table write-locked while we try to
	 * resolve hostnames into IP addresses.
	 * This unlocks the DCC_WF as well as the file, and so must
	 * not be done to private whitelists.  This implies that we
	 * cannot spend time waiting for DNS resolutions of host names
	 * in private or per-user whitelists. */
	if (unlock) {
		if (!unlock(emsg, wf))
			return -1;
		dcc_wf_unlock(wf);
	}

	memset(&addr6, 0, sizeof(addr6));
	dcc_host_lock();
	/* don't use SOCKS because the only IP addresses it makes
	 * send to white-list are inside the SOCKS firewall */
	if (!dcc_get_host(ck, 1, &error)) {
		dcc_pemsg(EX_NOHOST, emsg,
			  "hostname \"%s\": %s%s",
			  ck, DCC_HSTRERROR(error), fnm_lineno(fnm, lineno));
		dcc_host_unlock();
		if (unlock)
			dcc_wf_lock(wf);
		return 0;
	}

	if (unlock)
		dcc_wf_lock(wf);
	for (sup = dcc_hostaddrs; sup < dcc_hostaddrs_end; ++sup) {
		if (sup->sa.sa_family == AF_INET6) {
			dcc_ck_ipv6(sum, &sup->ipv6.sin6_addr);
		} else {
			dcc_ipv4toipv6(&addr6, sup->ipv4.sin_addr);
			dcc_ck_ipv6(sum, &addr6);
		}
		j = fnc(emsg, wf, fnm, lineno, DCC_CK_IP, sum, tgts);
		if (j <= 0) {
			dcc_host_unlock();
			if (unlock)
				dcc_wf_lock(wf);
			return j;
		}
	}
	dcc_host_unlock();
	return 2;
}



/* generate checksum value from the name of the checksum and a string */
int					/* 2=resolved, 1=ok, 0=bad, -1=fatal */
dcc_parse_ck(DCC_EMSG emsg,		/* failure message here */
	     DCC_WF *wf,
	     const char *fnm,		/* 0 or location of cksum */
	     int lineno,
	     const char *type_nm,	/* type of checksum */
	     const char *str,		/* ASCII string to generate checksum */
	     DCC_TGTS tgts,		/* # of targets */
	     DCC_PARSED_CK_FNC fnc,	/* do something with the checksum */
	     DCC_PARSED_CIDR_CK_FNC cidr_fnc,
	     DCC_PARSED_CK_LOCK unlock)	/* unlock for IP address */
{
	DCC_CK_TYPES type;
	DCC_SUM sum;
	const char *pstr;
	char *phdr, c, hdr_buf[80];

	/* look for the name of the checksum
	 * to find the checksumming function */
	type = dcc_str2type(type_nm);
	/* compute the checksum */
	switch (type) {
	case DCC_CK_IP:
		return dcc_host2ck(emsg, wf, str, fnm, lineno,
				   tgts, fnc, cidr_fnc, unlock);

	case DCC_CK_ENV_FROM:
	case DCC_CK_FROM:
	case DCC_CK_MESSAGE_ID:
	case DCC_CK_RECEIVED:
	case DCC_CK_ENV_TO:
		dcc_str2ck(sum, 0, 0, str);
		return fnc(emsg, wf, fnm, lineno, type, sum, tgts);

	case DCC_CK_SUB:
		str += strspn(str, DCC_WHITESPACE);
		pstr = str;
		phdr = hdr_buf;
		for (;;) {
			c = *pstr++;
			if (c == '\0' || c == ':'
			    || DCC_IS_WHITE(c))
				break;
			c = DCC_TO_LOWER(c);
			*phdr++ = c;
			if (phdr >= &hdr_buf[sizeof(hdr_buf)]) {
				dcc_pemsg(EX_DATAERR, emsg,
					  " imposible substitute header name"
					  " in \"%s\"%s",
					  str, fnm_lineno(fnm, lineno));
				return 0;
			}
		}
		pstr += strspn(pstr, DCC_WHITESPACE);
		if (*pstr == '\0' || phdr == hdr_buf) {
			dcc_pemsg(EX_DATAERR, emsg,
				  " substitute header name absent in \"%s\"%s",
				  str, fnm_lineno(fnm, lineno));
			return 0;
		}
		dcc_str2ck(sum, hdr_buf, phdr-hdr_buf, pstr);
		return fnc(emsg, wf, fnm, lineno, type, sum, tgts);

	case DCC_CK_INVALID:
	case DCC_CK_BODY:
	case DCC_CK_FUZ1:
	case DCC_CK_FUZ2:
	case DCC_CK_GREY_MSG:
	case DCC_CK_GREY_TRIPLE:
	case DCC_CK_SRVR_ID:
		break;
	}

	dcc_pemsg(EX_DATAERR, emsg, "unrecognized checksum type \"%s\"%s",
		  type_nm, fnm_lineno(fnm, lineno));
	return 0;
}



/* generate checksum value from the name of the checksum and hex values */
int					/* 1=ok, 0=bad checksum, -1=fatal */
dcc_parse_hex_ck(DCC_EMSG emsg,		/* failure message here */
		 DCC_WF *wf,
		 const char *fnm,	/* 0 or location of cksum */
		 int lineno,
		 const char *type_nm,	/* type of checksum */
		 const char *str,	/* ASCII string to generate checksum */
		 DCC_TGTS tgts,		/* # of targets */
		 DCC_PARSED_CK_FNC fnc)	/* do something with the checksum */
{
	DCC_CK_TYPES type;
	union {
	    u_int32_t n[4];
	    DCC_SUM sum;
	} u;

	type = dcc_str2type(type_nm);
	if (type == DCC_CK_INVALID) {
		dcc_pemsg(EX_DATAERR, emsg,
			  "unrecognized checksum type \"%s\"%s",
			  type_nm, fnm_lineno(fnm, lineno));
		return 0;
	}

	/* compute the checksum */
	if (4 != sscanf(str, DCC_CKSUM_HEX_PAT"\n",
			&u.n[0], &u.n[1], &u.n[2], &u.n[3])
	    && (type != DCC_CK_SUB
		|| 4 != sscanf(str, "%*s "DCC_CKSUM_HEX_PAT"\n",
			       &u.n[0], &u.n[1], &u.n[2], &u.n[3]))) {
		dcc_pemsg(EX_DATAERR, emsg,
			  "unrecognized checksum values \"%s\"%s",
			  str, fnm_lineno(fnm, lineno));
		return 0;
	}
	u.n[0] = htonl(u.n[0]);
	u.n[1] = htonl(u.n[1]);
	u.n[2] = htonl(u.n[2]);
	u.n[3] = htonl(u.n[3]);

	/* apply the function to the checksum */
	return fnc(emsg, wf, fnm, lineno, type, u.sum, tgts);
}
