/* Distributed Checksum Clearinghouse
 *
 * SMTP reply strings
 *
 * --C-LICENSE--
 * $Revision: 1.36 $
 */


#include "cmn_defs.h"

REPLY_TPLT reject_reply;
REPLY_TPLT reputation_reply;
REPLY_TPLT grey_reply;


/* default SMTP message templates */
typedef struct {
    REPLY_TPLT *reply;
    const char *rcode;			/* default value such as "550" */
    const char *xcode;			/* default value such as "5.7.1" */
    const char *def_pat;		/* default -r pattern */
    const char *log_result;		/* "accept ..." etc. for log */
} REPLY_DEF;

#define	DEF_REJ_MSG "mail %ID from %CIP rejected by DCC"
static REPLY_DEF rej_def = {&reject_reply, DCC_RCODE, DCC_XCODE,
	DEF_REJ_MSG,
	"reject"};

static REPLY_DEF grey_def = {&grey_reply, "452", "4.2.1",
	"mail %ID from %CIP temporary greylist embargoed",
	"temporary greylist embargo"};

static REPLY_DEF rep_def = {&reputation_reply, DCC_RCODE, DCC_XCODE,
	"%ID bulk mail reputation; see https://commercial-dcc.rhyolite.com/cgi-bin/reps.cgi?tgt=%CIP",
	"reject by DCC reputation"};

static REPLY_DEF dnsbl_def = {0, DCC_RCODE, DCC_XCODE,
	DEF_REJ_MSG,
	"reject"};


REPLY_TPLT dcc_fail_reply = {"temporary reject", {REPLY_TPLT_NULL},
	"451", "4.4.0", 0, DCC_XHDR_RESULT_DCC_FAIL};


/* Parse a string into RFC 821 and RFC 2034 codes and a pattern to make a
 * message, or generate the codes for a string that has a message
 * without codes.
 * The string should be something like "5.7.1 550 spammer go home"
 * or "spammer go home". */
void
make_tplt(REPLY_TPLT *tplt,		/* to here */
	  u_char mode,			/* 0=sendmail 1=dccid 2=dnsbl */
	  const char *xcode,		/* default value such as "5.7.1" */
	  const char *rcode,		/* default value such as "550" */
	  const char *arg,		/* using this pattern */
	  const char *log_result)	/* "reject" etc. for log */
{
	const char *p;
	char c, *new_pat;
	char sb[5+1+3+1];
	int p_cnt, i;

	arg += strspn(arg, DCC_WHITESPACE"\"");

	/* Does the message have a valid xcode and rcode?
	 * First check for sendmail.cf ERROR:### and ERROR:D.S.N:### */
	if (mode == 0 && !CLITCMP(arg, "ERROR:")) {
		p = dcc_parse_word(0, sb, sizeof(sb), arg+LITZ("ERROR:"),
				   0, 0, 0);
		if (p) {
			i = strlen(sb);
			if (i == 5+1+3 && sb[5] == ':') {
				memcpy(tplt->xcode, &sb[0], 5);
				memcpy(tplt->rcode, &sb[6], 3);
			} else if (i == 3) {
				BUFCPY(tplt->xcode, xcode);
				memcpy(tplt->rcode, &sb[0], 3);
			} else {
				p = 0;
			}
		}
	} else {
		p = dcc_parse_word(0, tplt->xcode, sizeof(tplt->xcode),
				   arg, 0, 0, 0);
		if (p)
			p = dcc_parse_word(0, tplt->rcode, sizeof(tplt->rcode),
					   p, 0, 0, 0);
	}
	if (!p
	    || tplt->rcode[0] < '4' || tplt->rcode[0] > '5'
	    || tplt->rcode[1] < '0' || tplt->rcode[1] > '9'
	    || tplt->rcode[2] < '0' || tplt->rcode[2] > '9'
	    || tplt->rcode[0] != tplt->xcode[0]
	    || tplt->xcode[1] != '.'
	    || tplt->xcode[2] < '0' || tplt->xcode[2] > '9'
	    || tplt->xcode[3] != '.'
	    || tplt->xcode[4] < '0' || tplt->xcode[4] > '9') {
		BUFCPY(tplt->xcode, xcode);
		BUFCPY(tplt->rcode, rcode);
		p = arg;
	} else {
		p += strspn(p, DCC_WHITESPACE);
	}

	tplt->is_pat = 0;
	p_cnt = 0;
	new_pat = tplt->pat;
	do {
		c = *p++;
		if (c == '\0')
			break;
		if (c == '%') {
			/* parse %x for x=
			 *  ID	    message queue ID
			 *  CIP	    SMTP client IP address
			 *  EMBARGO null or #%d of greylist embargo number
			 *  BTYPE   type of blacklist hit
			 *  BTGT    IP address or name sought in DNSBL
			 *  BPROBE  domain name found in blacklist
			 *  BRESULT value of domain name found in blacklist
			 *
			 *  BT	    obsolete equivalent of BTYPE
			 *  BIP	    obsolete equivalent of BTGT
			 *  s	    obsolete; 1st same as ID; 2nd same as CIP
			 */
			if (new_pat >= LAST(tplt->pat)-2)
				continue;   /* no room for "%s\0" */

			tplt->is_pat = 1;
			*new_pat++ = '%';
			if (mode == 0
			    || p_cnt >= NUM_REPLY_TPLT_ARGS || *p == '%') {
				/* translate %% and bad % to %% */
				++p;

			} else if (!LITCMP(p,"ID")) {
					p += LITZ("ID");
				tplt->args[p_cnt++] = REPLY_TPLT_ID;
				c = 's';
			} else if (!LITCMP(p,"CIP")) {
				p += LITZ("CIP");
				tplt->args[p_cnt++] = REPLY_TPLT_CIP;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BTYPE")) {
				p += LITZ("BTYPE");
				tplt->args[p_cnt++] = REPLY_TPLT_BTYPE;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BTGT")) {
				p += LITZ("BTGT");
				tplt->args[p_cnt++] = REPLY_TPLT_BTGT;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BPROBE")) {
				p += LITZ("BPROBE");
				tplt->args[p_cnt++] = REPLY_TPLT_BPROBE;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BRESULT")) {
				p += LITZ("BRESULT");
				tplt->args[p_cnt++] = REPLY_TPLT_BRESULT;
				c = 's';

			} else if (*p == 's' && p_cnt < 2) {
				/* obsolete
				 * translate 1st "%s" to REPLY_TPLT_ID
				 * 2nd to REPLY_TPLT_CIP */
				++p;
				tplt->args[p_cnt] = (p_cnt == 0
						     ? REPLY_TPLT_ID
						     : REPLY_TPLT_CIP);
				++p_cnt;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BIP")) {
				/* obsolete */
				p += LITZ("BIP");
				tplt->args[p_cnt++] = REPLY_TPLT_BTGT;
				c = 's';
			} else if (mode == 2 && !LITCMP(p,"BT")) {
				/* obsolete */
				p += LITZ("BT");
				tplt->args[p_cnt++] = REPLY_TPLT_BTYPE;
				c = 's';
			}

		} else if (c == '"' && mode == 0) {
			/* Strip double quotes from sendmail rejection
			 * message. Sendmail needs quotes around the message
			 * so it won't convert blanks to dots. */
			continue;

		} else if (c < ' ' || c > '~') {
			/* sendmail does not like control characters in
			 * SMTP status messages */
			c = '.';
		}

		*new_pat++ = c;
	} while (new_pat < LAST(tplt->pat));
	*new_pat = '\0';
	while (p_cnt < NUM_REPLY_TPLT_ARGS)
		tplt->args[p_cnt++] = REPLY_TPLT_NULL;

	tplt->log_result = log_result;
}



static inline void
finish_reply(const REPLY_DEF *def, const char *pat)
{
	make_tplt(def->reply, 1, def->xcode, def->rcode, pat,
		  def->log_result);
}



void
finish_replies(void)
{
	/* make SMTP status strings for cases not set with -rPATTERN */
	if (reject_reply.rcode[0] == '\0')
		finish_reply(&rej_def, rej_def.def_pat);
	if (reputation_reply.rcode[0] == '\0')
		finish_reply(&rep_def, rep_def.def_pat);
	if (grey_reply.rcode[0] == '\0')
		finish_reply(&grey_def, grey_def.def_pat);
}



/* parse another "-r pattern" argument */
void
parse_reply_arg(const char *arg)
{
	/* a blank string implies the default */
	arg += strspn(arg, DCC_WHITESPACE);

	/* each -r finishes the next SMTP rejection message */

	if (reject_reply.rcode[0] == '\0') {
		if (*arg == '\0')
			finish_reply(&rej_def, rej_def.def_pat);
		else
			finish_reply(&rej_def, arg);
		return;
	}

	if (grey_reply.rcode[0] == '\0') {
		if (*arg == '\0') {
			finish_reply(&grey_def, grey_def.def_pat);
			return;
		}
		finish_reply(&grey_def, arg);
		if (grey_reply.rcode[0] != '4'
		    || grey_reply.xcode[0] != '4') {
			dcc_error_msg("invalid greylist message: %s", arg);
			finish_reply(&grey_def, grey_def.def_pat);
		}
		return;
	}

	if (reputation_reply.rcode[0] == '\0') {
		if (*arg == '\0')
			finish_reply(&rep_def, rep_def.def_pat);
		else
			finish_reply(&rep_def, arg);
		return;
	}

	dcc_error_msg("more than 3 -r settings");
}



/* set up the DNSBL SMTP error message template for all threads based on
 *	a -B arg.  Allow % patterns and so forth */
const REPLY_TPLT *
dnsbl_parse_reply(const char *pat)
{
	REPLY_TPLT *tplt;

	tplt = malloc(sizeof(REPLY_TPLT));
	memset(tplt, 0, sizeof(REPLY_TPLT));
	make_tplt(tplt, 2, dnsbl_def.xcode, dnsbl_def.rcode, pat,
		  dnsbl_def.log_result);
	return tplt;
}



void
make_reply(REPLY_STRS *strs, const REPLY_TPLT *tplt,
	   const CMN_WORK *cwp, const DNSBL_HGROUP *hg)
{
	strs->rcode = tplt->rcode;
	strs->xcode = tplt->xcode;
	if (!tplt->is_pat) {
		strs->str = tplt->pat;
	} else {
		const char *args[NUM_REPLY_TPLT_ARGS];
		int i;

		memset(args, 0, sizeof(args));
		for (i = 0; i < NUM_REPLY_TPLT_ARGS; ++i) {
			switch (tplt->args[i]) {
			case REPLY_TPLT_NULL:
				args[i] = "";
				break;
			case REPLY_TPLT_ID:
				args[i] = cwp->id;
				break;
			case REPLY_TPLT_CIP:
				args[i] = dcc_trim_ffff(cwp->clnt_str);
				break;
			case REPLY_TPLT_BTYPE:
				args[i] = (hg && hg->btype) ? hg->btype : "";
				break;
			case REPLY_TPLT_BTGT:
				args[i] = hg ? hg->tgt.c : "";
				break;
			case REPLY_TPLT_BPROBE:
				args[i] = hg ? hg->probe.c : "";
				break;
			case REPLY_TPLT_BRESULT:
				args[i] = hg ? hg->ip : "";
				break;
			}
		}
		snprintf(strs->str_buf, sizeof(strs->str_buf), tplt->pat,
			 args[0], args[1], args[2], args[3], args[4], args[5]);
		strs->str = strs->str_buf;
	}

	strs->log_result = tplt->log_result;
}
