diff dcclib/ck.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dcclib/ck.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,798 @@
+/* Distributed Checksum Clearinghouse
+ *
+ * compute simple checksums
+ *
+ * Copyright (c) 2008 by Rhyolite Software, LLC
+ *
+ * This agreement is not applicable to any entity which sells anti-spam
+ * solutions to others or provides an anti-spam solution as part of a
+ * security solution sold to other entities, or to a private network
+ * which employs the DCC or uses data provided by operation of the DCC
+ * but does not provide corresponding data to other users.
+ *
+ * Permission to use, copy, modify, and distribute this software without
+ * changes for any purpose with or without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice appear in all
+ * copies and any distributed versions or copies are either unchanged
+ * or not called anything similar to "DCC" or "Distributed Checksum
+ * Clearinghouse".
+ *
+ * Parties not eligible to receive a license under this agreement can
+ * obtain a commercial license to use DCC by contacting Rhyolite Software
+ * at sales@rhyolite.com.
+ *
+ * A commercial license would be for Distributed Checksum and Reputation
+ * Clearinghouse software.  That software includes additional features.  This
+ * free license for Distributed ChecksumClearinghouse Software does not in any
+ * way grant permision to use Distributed Checksum and Reputation Clearinghouse
+ * software
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
+ * 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.3.103-1.90 $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 the 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].rpt2srvr = 1;
+	cks->sums[DCC_CK_IP].tgts = DCC_TGTS_INVALID;
+	dcc_ck_ipv6(cks->sums[DCC_CK_IP].sum, addrp);
+
+	if (&cks->ip_addr != addrp)
+		cks->ip_addr = *addrp;
+}
+
+
+
+void
+dcc_unget_ip_ck(DCC_GOT_CKS *cks)
+{
+	memset(&cks->ip_addr, 0, sizeof(cks->ip_addr));
+	CLR_GOT_SUM(&cks->sums[DCC_CK_IP]);
+	CLR_GOT_SUM(&cks->sums[DCC_CK_IP]);
+}
+
+
+
+/* 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,	/* put checksum here */
+		  const char *str)	/* from this IP address string */
+{
+	DCC_SOCKU su;
+
+	if (!dcc_str2ip(&su, str))
+		return 0;
+
+	if (su.sa.sa_family == AF_INET) {
+		/* treat IPv4 addresses as IPv6 so that everyone computes
+		 * the same checksum */
+		dcc_ipv4toipv6(&cks->ip_addr, su.ipv4.sin_addr);
+	} else {
+		cks->ip_addr = su.ipv6.sin6_addr;
+	}
+
+	dcc_get_ipv6_ck(cks, &cks->ip_addr);
+	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
+	 * do not ignore [.-_] to prevent confusing hostnames */
+	p = cbuf;
+	while ((c = *str++) != '\0' && p <= LAST(cbuf)) {
+		if (DCC_IS_WHITE(c)
+		    || c == '<' || c == '>'
+		    || c == '\'' || c == '"' || c == ',')
+			continue;
+		*p++ = DCC_TO_LOWER(c);
+	}
+	str = cbuf;
+	len = p - str;
+	/* 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,		/* put checksum here */
+	    DCC_CK_TYPES type,
+	    const char *str,		/* checksum of this string */
+	    u_char rpt2srvr)
+{
+	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_G_MSG_R_TOTAL:
+	case DCC_CK_G_TRIPLE_R_BULK:
+		dcc_logbad(EX_SOFTWARE, "invalid checksum %s",
+			   dcc_type2str_err(type, 0, 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->rpt2srvr = rpt2srvr;
+	g->tgts = DCC_TGTS_INVALID;
+	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_TYPES 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_nm == 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->rpt2srvr = 1;
+	g->tgts = DCC_TGTS_INVALID;
+	g->hdr_nm = sck->nm;
+	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;
+}
+
+
+
+static int
+get_received_addr(char addr_buf[INET6_ADDRSTRLEN+2],
+      const char *hdr)			/* *hdr == '[' before the address */
+{
+	int a_len;
+
+
+	a_len = 1+strspn(hdr+1, ".:abcdefABCDEF0123456789");
+	if (a_len <= 6+1 || a_len >= INET6_ADDRSTRLEN+1)
+		return 0;
+	if (hdr[a_len] != ']')
+		return 0;
+
+	/* capture the address
+	 *	include leading '[' in case we later need a host name */
+	memcpy(addr_buf, hdr, a_len);
+	addr_buf[a_len] = '\0';
+
+	return a_len;
+}
+
+
+
+/* find IP address, client host name, and HELO string in a Received:
+ *	    header of forms:
+ *  #1	Received: from helo (hostname [addr] ...
+ *	Received: from helo ([addr] ...
+ *  #2	Received: from hostname [addr] ...
+ *	Received: from  [addr] ...
+ *  #3	Received: from qmailheloandhostname (addr) ...
+ *  #4	Received: from qmailhostname (HELO qmailhelo) (addr) ...
+ *   or	Received: from qmailhostname (HELO qmailhelo) ([addr]) ...
+ *
+ * ignore these forms:
+ *  #5	Received: from localhost by hostname with LMTP
+ *  #6	Received  (qmail 4824 invoked by uid 1000); 8 Nov 2005 12:13:33 -0000
+ *  #7	Received: (qmail 21530 invoked from network); 29 Aug 2005 16:05:04 -0000
+ *  #8	Received: (from user@localhost) by lochost (8.12.10/8.12.10/Submit) ...
+ *  #9  Received: by hostname (Postfix) id ...
+ *
+ * This should be called only with Received: headers that are known to
+ *	have been added by trustworthy code such as the local system
+ *	or an MX secondary.
+ * Return 0 for unknown header, "" if IP address found, or stupid type string
+ */
+const char *
+parse_received(const char *hdr,		/* the null terminated header */
+	       DCC_GOT_CKS *cks,	/* put address checksum here */
+	       char *helo,		/* optionally put HELO value here */
+	       int helo_len,
+	       char *clnt_str, int clnt_str_len,
+	       char *clnt_name, int clnt_name_len)
+{
+	char addr_buf[INET6_ADDRSTRLEN+2];
+	const char *h, *n;
+	int h_len, n_len, a_len;
+	int i;
+
+	/* make the field name optional */
+	if (!CLITCMP(hdr, "Received:"))
+		hdr += LITZ("Received");
+	hdr += strspn(hdr, " \t\r\n");
+
+/* #define DCC_DEBUG_PARSE_RECEIVED */
+#ifdef DCC_DEBUG_PARSE_RECEIVED
+	printf("\n\nReceived: %s\n", hdr);
+#endif
+#define SPAN_ADDR(l,p) (*(p) >= '0' && *(p) <= '9'			\
+			&& ((l) = strspn((p), "0123456789.")) >= 7	\
+			&& (l) < INET_ADDRSTRLEN)
+
+	if (CLITCMP(hdr, "from")) {
+		/* It does not match "Received: from" in #1, #2, #3, and #5
+		 * Recognize #6 and #7 */
+		if (!LITCMP(hdr, "(qmail ")) {
+			hdr += LITZ("(qmail ");
+			i = strspn(hdr, "0123456789");
+			if (i == 0)
+				return 0;
+			hdr += i;
+			if (!LITCMP(hdr, " invoked from network)")
+			    || !LITCMP(hdr, " invoked by uid "))
+				return "qmail";
+			return 0;
+		}
+		/* recognize #8 */
+		if (!LITCMP(hdr, "(from ")) {
+			hdr += LITZ("(from ");
+			hdr = strpbrk(hdr, DCC_WHITESPACE"@");
+			if (!hdr || *hdr != '@')
+				return 0;
+			hdr = strpbrk(hdr, DCC_WHITESPACE")");
+			if (!hdr || *hdr != ')')
+				return 0;
+			hdr += 1+strspn(hdr+1, DCC_WHITESPACE);
+			if (LITCMP(hdr, "by "))
+				return 0;
+			hdr += LITZ("by ");
+			if (strstr(hdr, "/Submit"))
+				return "sendmail Submit";
+			return 0;
+		}
+		/* recognize #9 */
+		if (!LITCMP(hdr, "by ")) {
+			hdr += LITZ("by ");
+			hdr = strpbrk(hdr, DCC_WHITESPACE);
+			if (!hdr)
+				return 0;
+			++hdr;
+			if (!LITCMP(hdr, "(Postfix)"))
+				return "postfix";
+			return 0;
+		}
+		/* unrecognized */
+		return 0;
+	}
+
+	hdr += LITZ("from");
+	i = strspn(hdr, DCC_WHITESPACE);
+	if (i == 0)
+		return 0;
+	hdr += i;
+
+	/* We have "Received: from "
+	 * get the host name or HELO value before '(' or '[' in
+	 * #1, #2, #3, and #5 */
+	h = hdr;
+	hdr = strpbrk(hdr, DCC_WHITESPACE"([");
+	if (!hdr)
+		return 0;		/* unrecognized */
+	h_len = hdr - h;
+	hdr += strspn(hdr, DCC_WHITESPACE);
+
+	if (*hdr == '(') {
+		/* look for client host name of #1
+		 * or IPv4 address of #3
+		 * or HELO value and IPv4 address of #4 */
+		++hdr;
+
+		if (SPAN_ADDR(a_len, hdr)
+		    && hdr[a_len] == ')') {
+			/* we seem to have the IPv4 address of #3 */
+			n = h;
+			n_len = h_len;
+			addr_buf[0] = '[';
+			memcpy(addr_buf+1, hdr, a_len);
+			addr_buf[a_len+1] = '\0';
+
+		} else if (!LITCMP(hdr, "HELO ")
+			   && hdr[LITZ("HELO ")] != '[') {
+			/* we have the #4 qmail HELO form when reverse DNS name
+			 * and helo value differ or unrecognizable */
+			n = h;
+			n_len = h_len;
+			h = hdr + LITZ("HELO ");
+			hdr = strpbrk(h, " \t'\"()[]");
+			if (!hdr)
+				return 0;
+			h_len = hdr - h;
+			if (!h_len
+			    || LITCMP(hdr, ") ("))
+			    return 0;
+			hdr += LITZ(") (");
+			if (SPAN_ADDR(a_len, hdr)
+			    && hdr[a_len] == ')') {
+				addr_buf[0] = '[';
+				memcpy(addr_buf+1, hdr, a_len);
+				addr_buf[a_len+1] = '\0';
+			} else if (hdr[0] == '['
+				   && SPAN_ADDR(a_len, hdr+1)
+				   && hdr[1+a_len] == ']'
+				   && hdr[2+a_len] == ')') {
+				memcpy(addr_buf, hdr, a_len+1);
+				addr_buf[a_len+1] = '\0';
+			} else {
+				return 0;
+			}
+
+		} else {
+			/* it is #1 or unrecognizable */
+			n = hdr;
+			hdr = strpbrk(hdr, DCC_WHITESPACE"[");
+			if (!hdr)
+				return 0;
+			n_len = hdr - n;
+			hdr += strspn(hdr, DCC_WHITESPACE);
+			if (*hdr != '[')
+				return 0;
+			a_len = get_received_addr(addr_buf, hdr);
+			if (!a_len)
+				return 0;
+		}
+
+	} else if (*hdr == '[') {
+		/* format #2; we have possibly null client name and no HELO */
+		n = h;
+		n_len = h_len;
+		h_len = 0;
+		a_len = get_received_addr(addr_buf, hdr);
+		if (!a_len)
+			return 0;
+
+	} else if (!CLITCMP(hdr, "by ")) {
+		/* recognize #5 */
+		hdr += LITZ("by ");
+		n = strchr(hdr, ' ');
+		if (!n || n > hdr+DCC_MAXDOMAINLEN)
+			return 0;
+		if (!CLITCMP(n, " with LMTP"))
+			return "LMTP";	/* stupid type string */
+		return 0;
+
+	} else {
+		return 0;
+	}
+
+	/* it looks ok so send out all the answers
+	 * if the IP address makes sense */
+	if (!dcc_get_str_ip_ck(cks, addr_buf+1))
+		return 0;
+
+	dcc_ipv6tostr(clnt_str, clnt_str_len, &cks->ip_addr);
+
+	if (clnt_name && clnt_name_len) {
+		if (n_len == 0) {
+			/* use address as the client host name */
+			addr_buf[a_len] = ']';
+			n_len = a_len+1;
+			n = addr_buf;
+		}
+		if (clnt_name_len > n_len+1)
+			clnt_name_len = n_len+1;
+		STRLCPY(clnt_name, n, clnt_name_len);
+	}
+
+	if (helo && helo_len) {
+		if (helo_len > h_len+1)
+			helo_len = h_len+1;
+		STRLCPY(helo, h, helo_len);
+	}
+
+#ifdef DCC_DEBUG_PARSE_RECEIVED
+	printf("helo=%s  clnt_str=%s  clnt_name=%s\n",
+	       helo, clnt_str, clnt_name);
+#endif
+
+	return "";
+
+#undef SPAN_ADDR
+}
+
+
+
+u_char					/* 1=found env_From value */
+parse_return_path(const char *hdr, char *buf, int buf_len)
+{
+	int i;
+
+	if (CLITCMP(hdr, "Return-Path:"))
+	    return 0;
+
+	hdr += LITZ("Return-Path:");
+	hdr += strspn(hdr, " \t");
+	i = strlen(hdr);
+	while (i > 0 && (hdr[i-1] == '\r' || hdr[i-1] == '\n'))
+		--i;
+	if (i >= 2 && *hdr == '<' && hdr[i-1] == '>') {
+		++hdr;
+		i -= 2;
+	}
+	if (i >= buf_len-1)
+		i = buf_len-1;
+	if (i <= 0)
+		return 0;
+	memcpy(buf, hdr, i);
+	buf[i] = '\0';
+
+	return 1;
+}
+
+
+
+u_char					/* 1=found env_From value */
+parse_unix_from(const char *hdr, char *buf, int buf_len)
+{
+	const char *p;
+	int i;
+
+	if (strncmp(hdr, "From ", LITZ("From ")))
+		return 0;
+
+	hdr += LITZ("From ");
+	hdr += strspn(hdr, " ");
+	p = strchr(hdr, ' ');
+	if (p == 0)
+		return 0;
+
+	i = p-hdr;
+	if (i >= buf_len)
+		i = buf_len-1;
+	if (i <= 0)
+		return 0;
+	memcpy(buf, hdr, i);
+	buf[i] = '\0';
+	return 1;
+}
+
+
+
+u_char
+parse_mail_host(const char *env_from, char *buf, int buf_len)
+{
+	const char *p, *p2, *p3;
+	int i;
+
+	p = strchr(env_from, '@');
+	if (!p)
+		return 0;
+	p2 = strchr(++p, '>');
+	if (!p2)
+		p2 = p+strlen(p);
+
+	/* do not try to figure out source routes */
+	p3 = strpbrk(p, ";@,");
+	if (p3 && p3 < p2)
+		return 0;
+
+	i = p2-p;
+	if (i >= buf_len)
+		i = buf_len-1;
+	if (i <= 0)
+		return 0;
+	memcpy(buf, p, i);
+	buf[i] = '\0';
+	return 1;
+}
+
+
+
+void
+dcc_print_cks(LOG_WRITE_FNC out, void *arg,
+	      u_char is_spam, DCC_TGTS local_tgts,
+	      const DCC_GOT_CKS *cks, DCC_CKS_WTGTS wtgts,
+	      u_char have_wlist)	/* 1=have whiteclnt result */
+{
+	char tgts_buf[16], type_buf[26], cbuf[DCC_CK2STR_LEN];
+#	define LINE_LEN 81
+	char buf[LINE_LEN*6];
+	const DCC_GOT_SUM *g;
+	u_char have_server, have_thold, headed;
+	DCC_TGTS tgts;
+	int cklen, buflen, inx, i;
+
+	/* decide which column headings are needed */
+	have_server = 0;
+	have_thold = 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;
+		if (cks->tholds_rej[g->type] != DCC_THOLD_UNSET
+		    && g->type != DCC_CK_REP_TOTAL)
+			have_thold = 1;
+	}
+	if (have_wlist)
+		have_thold = 0;
+
+	headed = 0;
+	buflen = 0;
+	for (g = cks->sums, inx = 0; g <= LAST(cks->sums); ++g, ++inx) {
+		if (g->type == DCC_CK_INVALID)
+			continue;
+
+		if (!headed) {
+			headed = 1;
+			dcc_tgts2str(tgts_buf, sizeof(tgts_buf)-LITZ("spam"),
+				     local_tgts, 0);
+			if (is_spam)
+				STRLCAT(tgts_buf, " spam", sizeof(tgts_buf));
+			buflen += snprintf(buf+buflen, sizeof(buf)-buflen,
+					   "                            "
+					   DCC_XHDR_REPORTED" %-15s checksum",
+					   tgts_buf);
+
+			if (have_server || have_wlist || have_thold)
+				buflen += snprintf(buf+buflen,
+						   sizeof(buf)-buflen,
+						   PRINT_CK_PAT_SRVR,
+						   have_server ? "server" : "");
+			if (have_wlist)
+				buflen += snprintf(buf+buflen,
+						   sizeof(buf)-buflen,
+						   PRINT_CK_PAT_WLIST,
+						   "wlist");
+			else if (have_thold)
+				buflen += snprintf(buf+buflen,
+						   sizeof(buf)-buflen,
+						   PRINT_CK_PAT_THOLD,
+						   "thold");
+			if (ISZ(buf)-buflen > 1)
+				buf[buflen++] = '\n';
+
+		} else if (buflen >= ISZ(buf)-LINE_LEN) {
+			out(arg, buf, buflen);
+			buflen = 0;
+		}
+
+		cklen = snprintf(buf+buflen, sizeof(buf)-buflen,
+				 PRINT_CK_PAT_CK,
+				 dcc_type2str(type_buf, sizeof(type_buf),
+					      g->type, g->hdr_nm, 0, 0),
+				 dcc_ck2str(cbuf, sizeof(cbuf),
+					    g->type, g->sum, 0));
+		buflen += cklen;
+
+		if (g->rpt2srvr != 0 && g->tgts != DCC_TGTS_INVALID) {
+			if (buflen < ISZ(buf))
+				buflen += snprintf(buf+buflen,
+						   sizeof(buf)-buflen,
+						   PRINT_CK_PAT_SRVR,
+						   dcc_tgts2str(tgts_buf,
+							sizeof(tgts_buf),
+							g->tgts, 0));
+		} else if (wtgts[inx] != 0
+			   || (have_thold
+			       && cks->tholds_rej[g->type]!=DCC_THOLD_UNSET)) {
+			/* steal space from blank server count for
+			 * long substitute checksums */
+			i = (PRINT_CK_PAT_SRVR_LEN - (cklen - (PRINT_CK_TYPE_LEN
+							+2 +PRINT_CK_SUM_LEN)));
+			if (i > PRINT_CK_PAT_SRVR_LEN)
+				i = PRINT_CK_PAT_SRVR_LEN;
+			if (i > ISZ(buf)-buflen)
+				i = ISZ(buf)-buflen;
+			if (i < 0)
+				i = 0;
+			if (i > ISZ(buf))
+				i = ISZ(buf);
+			while (--i >= 0) {
+				buf[buflen++] = ' ';
+			}
+		}
+
+		if (buflen >= ISZ(buf)) {
+			;
+		} else if (wtgts[inx] != 0) {
+			buflen += snprintf(buf+buflen, sizeof(buf)-buflen,
+					   PRINT_CK_PAT_WLIST,
+					   wtgts[inx] == 0
+					   ? ""
+					   : dcc_tgts2str(tgts_buf,
+							sizeof(tgts_buf),
+							wtgts[inx], 0));
+		} else if (have_thold
+			   && (tgts = cks->tholds_rej[g->type]
+			       ) != DCC_THOLD_UNSET) {
+			buflen += snprintf(buf+buflen, sizeof(buf)-buflen,
+					   PRINT_CK_PAT_THOLD,
+					   dcc_thold2str(tgts_buf,
+							sizeof(tgts_buf),
+							g->type, tgts));
+		}
+
+		if (buflen >= ISZ(buf)-1)
+			buflen = sizeof(buf)-2;
+		buf[buflen] = '\n';
+		buf[++buflen] = '\0';
+	}
+
+	if (buflen != 0)
+		out(arg, buf, buflen);
+
+#undef LINE_LEN
+}