view src/dccifd.cpp @ 407:29d54e7028f6 stable-6-0-54

document dmarc vs dnsbl dkim/spf; switch to . rather than " " for dkim impossible signer
author Carl Byington <carl@five-ten-sg.com>
date Thu, 30 Mar 2017 10:26:30 -0700
parents df7dc6b2b153
children 5209e92b4885
line wrap: on
line source

/*

Copyright (c) 2007 Carl Byington - 510 Software Group, released under
the GPL version 3 or any later version at your choice available at
http://www.gnu.org/licenses/gpl-3.0.txt

*/

#include "includes.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

// needed for socket io
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>


const  int	maxlen = 1000;			// used for snprintf buffers
extern int	NULL_SOCKET;
const  char *options = "header\n";


////////////////////////////////////////////////
// helper to convert syslog control chars
//
string escaper(string v);
string escaper(string v)
{
	size_t n = v.length();
	char buf[n+1];
	strncpy(buf, v.c_str(), n);
	for (size_t i=0; i<n; i++) {
		if (buf[i] == '\r') buf[i] = 'r';
		if (buf[i] == '\n') buf[i] = 'n';
		if ((unsigned char)(buf[i]) < ' '){
			buf[i] = '.';
		}
	}
	return string(buf, n);
}


DccInterface::DccInterface(const char *port_, mlfiPriv *priv_, int ip, const char *helo_, const char *from)
{
	err 			= false;
	first_recipient = true;
	first_header	= true;
	priv			= priv_;
	ip4 			= ip;
	helo			= helo_;
	envfrom 		= from;
	dccifd_port 	= port_;
	dccifd_socket	= NULL_SOCKET;
}


DccInterface::~DccInterface()
{
	my_disconnect();
}


void DccInterface::mlfi_envrcpt(SMFICTX *ctx, const char *envrcpt, bool grey)
{
	if (first_recipient) {
		first_recipient = false;
		char adr[sizeof "255.255.255.255   "];
		adr[0] = '\0';
		inet_ntop(AF_INET, (const u_char *)&ip4, adr, sizeof(adr));
		// Validated sending site's address
		const char *rdns = getmacro(ctx, "_", "");
		char buf[maxlen+1];
		if (*rdns == '[') rdns = "";
		else {
			int n = 0;
			while ((n < maxlen) && rdns[n] && (rdns[n] != ' ')) n++;
			strncpy(buf, rdns, n);
			buf[n] = '\0';
			rdns = buf;
		}
		output(options);
		output(adr);		output("\r");
		output(rdns);		output("\n");
		output(helo);		output("\n");
		output(envfrom);	output("\n");
	}
	output(envrcpt);
	if (grey) output("\r\n");
	else	  output("\rdnsblnogrey\n");
}


void DccInterface::mlfi_header(SMFICTX *ctx, const char *headerf, const char *headerv)
{
	if (dccifd_socket == NULL_SOCKET) Connect();
    if (err) return;
	if ((dccifd_socket != NULL_SOCKET) && (!dccifd_input.empty())) {
		output(dccifd_input);
		dccifd_input = "";
	}

	if (first_header) {
		output("\n");
		first_header = false;
	}

	output(headerf);
	output(": ");
	output(headerv);
	output("\r\n");
}


void DccInterface::mlfi_eoh()
{
	output("\r\n");
}


void DccInterface::mlfi_body(const u_char *bodyp, size_t bodylen)
{
	output((const char *)bodyp, bodylen);
}


void DccInterface::mlfi_eom(bool &grey, int &bulk)
{
	// AnAnX-DCC-Rhyolite-Metrics: ns.five-ten-sg.com 104; Body=2 Fuz1=2nn

	close_output(); // signal EOF to DccInterface
	input();		// read what the dcc has to say about this message
	my_syslog(priv, "dcc returned " + escaper(dccifd_output));
	grey = false;
	bulk = 0;
	const int n = dccifd_output.length();
	char buf[n+1];
	strncpy(buf, dccifd_output.c_str(), n);
	buf[n] = '\0';

	int newlines = 0;
	int j, i = 0;
	while ((i<n) && (newlines < 2)) {
		switch (buf[i++]) {
			case 'G' :
				grey = true;
				break;
			case '\n' :
				newlines++;
			default : ;
		}
	}

	// skip up to and including ;
	while ((i<n) && (buf[i++] != ';'));

	// convert to lower, = to space, ctrl-chars to space
	for (j=i; j<n; j++) {
		buf[j] = tolower(buf[j]);
		if (buf[j] == '=') buf[j] = ' ';
		if (buf[j] <  ' ') buf[j] = ' ';
	}

	while (i<n) {
		// skip leading blanks
		while ((i<n) && (buf[i] == ' ')) i++;

		// find blank terminator
		for (j=i; (j<n) && (buf[j] != ' '); j++);

		// find anything?
		if (j > i) {
			// process this token
			buf[j] = '\0';
			//my_syslog(priv, string("dccifd token") + (buf+i));
			if (strcmp(buf+i, "bulk") == 0)           bulk = dccbulk;
			else if (strcmp(buf+i, "many") == 0)      bulk = dccbulk;
			else if (strcmp(buf+i, "whitelist") == 0) bulk = 0;
			else if (isdigit(buf[i])) {
				int b = atoi(buf+i);
				if (b > bulk) bulk = b;
			}
			// skip this token
			i = j+1;
		}
	}
	//char buff[maxlen];
	//snprintf(buff, sizeof(buff), "dccifd found grey %s bulk %d", ((grey) ? "yes" : "no"), bulk);
	//my_syslog(priv, buff);
}


void DccInterface::my_disconnect()
{
	if (dccifd_socket != NULL_SOCKET) {
		shutdown(dccifd_socket, SHUT_RDWR);
		close(dccifd_socket);
		dccifd_socket = NULL_SOCKET;
	}
}


void DccInterface::Connect()
{
	if (err) return;

	sockaddr_un server;
	memset(&server, '\0', sizeof(server));
	server.sun_family = AF_UNIX;
	strncpy(server.sun_path, dccifd_port, sizeof(server.sun_path)-1);
	dccifd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
	if (dccifd_socket != NULL_SOCKET) {
		bool rc = (connect(dccifd_socket, (sockaddr *)&server, sizeof(server)) == 0);
		if (!rc) {
			my_disconnect();
			err = true;
		}
	}
}


size_t DccInterface::my_write(const char *buf, size_t len) {
	if (err) return 0;

	size_t rs = 0;
	while (len) {
		ssize_t ws = write(dccifd_socket, buf, len);
		if (ws > 0) {
			rs	+= ws;
			len -= ws;
			buf += ws;
		}
		else {
			// error or peer closed the socket!
			rs = 0;
			err = true;
			break;
		}
	}
	return rs;
}

size_t DccInterface::my_read(char *buf, size_t len) {
	if (err) return 0;

	size_t rs = 0;
	while (len) {
		ssize_t ws = read(dccifd_socket, buf, len);
		if (ws > 0) {
			rs	+= ws;
			len -= ws;
			buf += ws;
		}
		else if (ws < 0) {
			// read error
			rs = 0;
			err = true;
			break;
		}
		else {
			// peer closed the socket, end of file
			break;
		}
	}
	return rs;
}

void DccInterface::output(const char* buffer, size_t size)
{
	if (err) return;

	// buffer it if not connected yet
	if (dccifd_socket == NULL_SOCKET) {
		//my_syslog(priv, string("dcc buffered ") + escaper(string(buffer, size)));
		dccifd_input.append(buffer, size);
		return;
	}

	// write it if we are connected
	//my_syslog(priv, string("dcc write ") + escaper(string(buffer, size)));
	my_write(buffer, size);
}


void DccInterface::output(const char* buffer)
{
	output(buffer, strlen(buffer));
}


void DccInterface::output(string buffer)
{
	output(buffer.c_str(), buffer.size());
}


void DccInterface::close_output()
{
	if (dccifd_socket != NULL_SOCKET) {
		shutdown(dccifd_socket, SHUT_WR);
	}
}


void DccInterface::input()
{
	if ((dccifd_socket == NULL_SOCKET) || err) return;
	char buf[maxlen];
	int rs;
	while ((rs = my_read(buf, maxlen))) {
		//my_syslog(priv, string("dcc read ") + escaper(string(buf, rs)));
		dccifd_output.append(buf, rs);
	}
	my_disconnect();
}


const char *DccInterface::getmacro(SMFICTX *ctx, const char *macro, const char *def)
{
	const char *rc = smfi_getsymval(ctx, (char*)macro);
	if (!rc) rc = def;
	return rc;
}