/* Distributed Checksum Clearinghouse
 *
 * compute simple checksums
 *
 * 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.45 $Revision$
 */

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


/* substitute or locally configured checksums */
typedef struct {
    u_int	nm_len;
    const char	*nm;			/* name of the checksum */
} DCC_SUB_CK;
static DCC_SUB_CK sub_cks[DCC_MAX_SUB_CKS];
static u_int num_sub_cks;


/* get the checksum of an IPv6 address */
void
dcc_ck_ipv6(DCC_SUM sum, const struct in6_addr *addr)
{
	MD5_CTX ctx;

	MD5Init(&ctx);
	MD5Update(&ctx, (void *)addr, sizeof(*addr));
	MD5Final(sum, &ctx);
}



/* add an IP address to set of checksums */
void
dcc_get_ipv6_ck(DCC_GOT_CKS *cks, const struct in6_addr *addrp)
{
	cks->sums[DCC_CK_IP].type = DCC_CK_IP;
	cks->sums[DCC_CK_IP].rpt = 1;
	cks->sums[DCC_CK_IP].tgts = DCC_TGTS_INVALID;
	cks->ip_addr = *addrp;
	dcc_ck_ipv6(cks->sums[DCC_CK_IP].sum, addrp);
	cks->flags |= DCC_CKS_HAVE_SUM;
}



/* Make DCC_CK_IP from a string containing an IPv4 or IPv6 address.
 *	Because inet_pton() is picky, the string must be unambiguous and
 *	fussy. */
u_char
dcc_get_str_ip_ck(DCC_GOT_CKS *cks, const char *str, struct in6_addr *addrp)
{
	DCC_SOCKU su;
	struct in6_addr addr;

	if (!dcc_str2ip(&su, str))
		return 0;

	if (!addrp)
		addrp = &addr;

	if (su.sa.sa_family == AF_INET) {
		/* treat IPv4 addresses as IPv6 so that everyone computes
		 * the same checksum */
		dcc_ipv4toipv6(addrp, su.ipv4.sin_addr);
	} else {
		*addrp = su.ipv6.sin6_addr;
	}

	dcc_get_ipv6_ck(cks, addrp);
	return 1;
}



/* Compute a checksum from a string with matching but optional carets or
 * quotes, after stripping the quotes or carets.
 * Ignore case and white space */
void
dcc_str2ck(DCC_SUM sum,
	   const char *hdr,		/* substitute header type */
	   u_int hdr_len,
	   const char *str)		/* string to checksum */
{
	MD5_CTX ctx;
	u_int len;
	char *p;
	char c, cbuf[DCC_HDR_CK_MAX];

	/* ignore whitespace and case */
	p = cbuf;
	while ((c = *str++) != '\0' && p <= LAST(cbuf)) {
		if (DCC_IS_WHITE(c))
			continue;
		*p++ = DCC_TO_LOWER(c);
	}
	str = cbuf;
	len = p - str;
	/* "<>" is legal at least as a sender
	 * Remove a matching pair of leading and trailing <> or " characters */
	if (len >= 2
	    && ((*str == '<' && *(p-1) == '>')
		|| (*str == '"' && *(p-1) == '"'))) {
		++str;
		len -= 2;
		p -= 2;
	}
	/* strip trailing periods, mostly for mail_host */
	while (len >= 1
	       && *(p-1) == '.') {
		--len;
		--p;
	}
	MD5Init(&ctx);
	if (hdr)
		MD5Update(&ctx, hdr, hdr_len);
	MD5Update(&ctx, str, len);
	MD5Final(sum, &ctx);
}



/* make checksum from a string for headers and envelope */
u_char					/* 1=ok 0=bad string */
dcc_get_cks(DCC_GOT_CKS *cks, DCC_CK_TYPES type, const char *str, u_char rpt)
{
	DCC_GOT_SUM *g;

	g = &cks->sums[type];

	switch (type) {
	case DCC_CK_INVALID:
	case DCC_CK_IP:
	case DCC_CK_SUB:
	case DCC_CK_SRVR_ID:
	case DCC_CK_BODY:
	case DCC_CK_FUZ1:
	case DCC_CK_FUZ2:
	case DCC_CK_GREY_MSG:
	case DCC_CK_GREY_TRIPLE:
		dcc_logbad(EX_SOFTWARE, "invalid checksum %s",
			   dcc_type2str_err(type, 0, 0));
		return 0;

	case DCC_CK_ENV_FROM:
	case DCC_CK_FROM:
	case DCC_CK_ENV_TO:
	case DCC_CK_RECEIVED:
	case DCC_CK_MESSAGE_ID:
		dcc_str2ck(g->sum, 0, 0, str);
		break;
	}

	g->type = type;
	g->rpt = rpt;
	g->tgts = DCC_TGTS_INVALID;
	cks->flags |= DCC_CKS_HAVE_SUM;
	return 1;
}



/* make checksum for a locally configured header */
u_char					/* 1=done 0=failed */
dcc_ck_get_sub(DCC_GOT_CKS *cks,
		 const char *hdr,	/* header name, not '\0' terminated */
		 const char *str)	/* header value if not after hdr */
{
	DCC_GOT_SUM *g;
	const DCC_SUB_CK *sck;
	DCC_CK_TYPE type;
	int i;

	/* look for the header name in the list of locally configured headers */
	sck = &sub_cks[0];
	for (i = num_sub_cks; ; ++sck, --i) {
		if (i <= 0)
			return 0;	/* this header is not in the list */
		if (!strncasecmp(hdr, sck->nm, sck->nm_len)
		    && (hdr[sck->nm_len] == '\0'
			|| hdr[sck->nm_len] == ':'))
			break;
	}

	/* Get the header value if the caller did not separate it.
	 * The colon is present if the header field was not separated */
	if (!str)
		str = hdr+sck->nm_len+1;

	/* find a free checksum slot
	 * or a slot already assigned to the header */
	type = DCC_CK_SUB;
	g = &cks->sums[type];
	for (;;) {
		if (type >= DIM(cks->sums))
			return 0;	/* none free */

		if (g->type == DCC_CK_INVALID
		    && (type > DCC_CK_TYPE_LAST
			|| type == DCC_CK_SUB))
			break;		/* found a free slot */

		if (g->type == DCC_CK_SUB
		    && g->hdr == sck->nm)
			break;		/* found previously assigned slot */
		++g;
		++type;
	}

	dcc_str2ck(g->sum, sck->nm, sck->nm_len, str);
	g->type = DCC_CK_SUB;
	g->rpt = 1;
	g->tgts = DCC_TGTS_INVALID;
	g->hdr = sck->nm;
	cks->flags |= DCC_CKS_HAVE_SUM;
	return 1;
}



/* add to the list of locally configured or substitute headers */
u_char
dcc_add_sub_hdr(DCC_EMSG emsg, const char *hdr)
{
	const char *p;
	char c, *q;
	u_int n, len;

	if (num_sub_cks >= DIM(sub_cks)) {
		dcc_pemsg(EX_USAGE, emsg,
			  "too many substitute headers with \"%s\"", hdr);
		return 0;
	}

	p = hdr;
	for (;;) {
		if (*p == '\0')
			break;
		if (*p == ':' && p[1] == '\0') {
			--p;
			break;
		}
		if (*p <= ' ' || *p >= 0x7f || *p == ':') {
			dcc_pemsg(EX_USAGE, emsg,
				  "illegal SMTP field name character in \"%s\"",
				  hdr);
			return 0;
		}
		++p;
	}

	len = p - hdr;
	if (len == 0) {
		dcc_pemsg(EX_USAGE, emsg, "illegal empty field name");
		return 0;
	}

	/* ignore duplicates */
	for (n = 0; n < num_sub_cks; ++n) {
		if (len == sub_cks[n].nm_len
		    && !strncasecmp(hdr, sub_cks[n].nm, len))
			return 1;
	}

	sub_cks[num_sub_cks].nm_len = len;
	q = dcc_malloc(len+1);
	sub_cks[num_sub_cks].nm = q;
	do {
		c = *hdr++;
		*q++ = DCC_TO_LOWER(c);
	} while (--len > 0);
	*q = '\0';
	++num_sub_cks;

	return 1;
}



void
dcc_print_cks(void *arg, const DCC_GOT_CKS *cks, DCC_CKS_WTGTS wtgts,
	      void (out)(void *, const void *, u_int))
{
#define CK_PAT_CK_H	"%25s  %35s"
#define CK_PAT_CK	"%25s: %35s"
#define CK_PAT_SERVER	" %7s"
#define CK_PAT_WLIST	" %5s"
	char white_tgts_buf[12], dcc_tgts_buf[12];
	char type_buf[26], cbuf[DCC_CK2STR_LEN];
	char buf[80];
	const DCC_GOT_SUM *g;
	u_char have_server, have_wlist, headed;
	int inx, i;

	have_server = 0;
	have_wlist = 0;
	for (g = cks->sums, inx = 0; g <= LAST(cks->sums); ++g, ++inx) {
		if (g->type == DCC_CK_INVALID)
			continue;
		if (g->tgts != DCC_TGTS_INVALID)
			have_server = 1;
		if (wtgts[inx] != 0)
			have_wlist = 1;
	}

	headed = 0;
	for (g = cks->sums, inx = 0; g <= LAST(cks->sums); ++g, ++inx) {
		if (g->type == DCC_CK_INVALID)
			continue;

		if (!headed) {
			headed = 1;
			i = snprintf(buf, sizeof(buf), CK_PAT_CK_H,
				     "", "checksum");
			if (i < ISZ(buf)
			    && (have_server || have_wlist))
				i += snprintf(buf+i, sizeof(buf)-i,
					      CK_PAT_SERVER,
					      have_server ? "server" : "");
			if (i < ISZ(buf)
			    && have_wlist)
				i += snprintf(buf+i, sizeof(buf)-i,
					      CK_PAT_WLIST, "wlist");
			if (i >= ISZ(buf)-1)
				i = sizeof(buf)-2;
			buf[i] = '\n';
			buf[++i] = '\0';
			out(arg, buf, i);
		}

		i = snprintf(buf, sizeof(buf), CK_PAT_CK,
			     dcc_type2str(type_buf, sizeof(type_buf),
					  g->type, g->hdr, 0),
			     dcc_ck2str(cbuf, sizeof(cbuf),
					g->type, g->sum));
		if (i < ISZ(buf)
		    && ((g->rpt != 0 && g->tgts != DCC_TGTS_INVALID)
			|| wtgts[inx] != 0))
			i += snprintf(buf+i, sizeof(buf)-i,
				      CK_PAT_SERVER,
				      (g->rpt == 0 || g->tgts==DCC_TGTS_INVALID)
				      ? ""
				      : dcc_cnt2str(dcc_tgts_buf,
						    sizeof(dcc_tgts_buf),
						    g->tgts, 0));
		if (i < ISZ(buf)
			&& wtgts[inx] != 0)
			i += snprintf(buf+i, sizeof(buf)-i,
				      CK_PAT_WLIST,
				      dcc_cnt2str(white_tgts_buf,
						  sizeof(white_tgts_buf),
						  wtgts[inx], 0));
		if (i >= ISZ(buf)-1)
			i = sizeof(buf)-2;
		buf[i] = '\n';
		buf[++i] = '\0';
		out(arg, buf, i);
	}
#undef CK_PAT_CK_H
#undef CK_PAT_CK
#undef CK_PAT_SERVER
#undef CK_PAT_WLIST
}



void
dcc_print_grey(void *arg,		/* for out() */
	       DCC_ASK_GREY_RESULT result,
	       u_int embargo_num,
	       u_char *headed,
	       const char *env_to,
	       const DCC_SUM msg_sum,
	       const DCC_SUM triple_sum,
	       void (out)(void *, const void *, u_int))
{
#define CK_MSG_PAT "%25.*s: %35s "
#define CK_TRIPLE_PAT "                           %35s "
#define CK_HEADING "       "DCC_XHDR_GREY_RECIP"\n"
	const char *msg_pat, *triple_pat;
	char cbuf[DCC_CK2STR_LEN];
	char buf[100];
	int env_to_len, i;

	msg_pat = triple_pat = 0;
	switch (result) {
	case DCC_GREY_ASK_FAIL:
		msg_pat = CK_MSG_PAT"\n";
		triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO_FAIL"\n";
		break;
	case DCC_GREY_ASK_OFF:
		return;
	case DCC_GREY_ASK_EMBARGO:
		msg_pat = CK_MSG_PAT"\n";
		if (embargo_num > 0)
			triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO_NUM"\n";
		else
			triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO"\n";
		break;
	case DCC_GREY_ASK_EMBARGO_END:
		msg_pat = CK_MSG_PAT"\n";
		triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO_ENDED"\n";
		break;
	case DCC_GREY_ASK_PASS:
		msg_pat = CK_MSG_PAT"\n";
		triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO_PASS"\n";
		break;
	case DCC_GREY_ASK_WHITE:
		msg_pat = CK_MSG_PAT"\n";
		triple_pat = CK_TRIPLE_PAT DCC_XHDR_EMBARGO_OK"\n";
		break;
	}

	env_to_len = strlen(env_to);
	if (env_to_len > 1 && env_to[0] == '<' && env_to[env_to_len-1] == '>') {
		++env_to;
		env_to_len -= 2;
	}
	if (!headed || !*headed) {
		if (headed)
			*headed = 1;
		out(arg, CK_HEADING, sizeof(CK_HEADING)-1);
	}

	i = snprintf(buf, sizeof(buf), msg_pat,
		     env_to_len, env_to,
		     dcc_ck2str(cbuf, sizeof(cbuf),
				DCC_CK_GREY_MSG, msg_sum));
	if (i >= ISZ(buf)) {
		i = sizeof(buf)-1;
		buf[i-1] = '\n';
	}
	out(arg, buf, i);

	i = snprintf(buf, sizeof(buf), triple_pat,
		     dcc_ck2str(cbuf, sizeof(cbuf),
				DCC_CK_GREY_TRIPLE, triple_sum),
		     embargo_num);
	if (i >= ISZ(buf)) {
		i = sizeof(buf)-1;
		buf[i-1] = '\n';
	}
	out(arg, buf, i);

	if (!headed)
		out(arg, "\n", 1);

#undef CK_MSG_PAT
#undef CK_TRIPLE_PAT
#undef CK_HEADING
}
