diff src/context.cpp @ 90:962a1f8f1d9f stable-5-4

add verify statement to verify addresses with better mx host
author carl
date Sun, 18 Sep 2005 10:19:58 -0700
parents 7245c45cef7a
children ca46fafc6621
line wrap: on
line diff
--- a/src/context.cpp	Sun Aug 07 11:26:37 2005 -0700
+++ b/src/context.cpp	Sun Sep 18 10:19:58 2005 -0700
@@ -8,6 +8,16 @@
 
 #include "includes.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>
+
 static char* context_version="$Id$";
 
 char *token_black;
@@ -40,10 +50,290 @@
 char *token_substitute;
 char *token_tld;
 char *token_unknown;
+char *token_verify;
 char *token_white;
 
+char *token_myhostname;
+char myhostname[HOST_NAME_MAX+1];
+
+verify_map	verifiers;
 string_set all_strings; 	// owns all the strings, only modified by the config loader thread
-const int maxlen = 1000;
+const int maxlen = 1000;	// used for snprintf buffers
+const int maxage = 120; 	// smtp verify sockets older than this are ancient
+extern int	  NULL_SOCKET;
+extern time_t ERROR_SOCKET_TIME;	// number of seconds between attempts to open a socket an smtp host for address verification
+
+
+int SMTP::writer() {
+	// log("writer() sees buffer with %s", buffer);
+	// log("writer() sees error %d", (int)error);
+	int rs = 0;
+	if (!error) {
+		int len = strlen(buffer);
+		while (rs < len) {
+			int ws = write(fd, buffer+rs, len-rs);
+			if (ws > 0) {
+				rs += ws;
+			}
+			else {
+				// peer closed the socket!
+				rs = 0;
+				error = true;
+				break;
+			}
+		}
+	}
+	return rs;
+}
+
+
+int SMTP::reader() {
+	// read some bytes terminated by lf or end of buffer.
+	// we may have a multi line response or part thereof in the buffer.
+	// log("reader() sees error %d", (int)error);
+	if (error) return 0;
+	int len = maxlen-1; // room for null terminator
+	while (pending < len) {
+		int ws = read(fd, buffer+pending, len-pending);
+		if (ws > 0) {
+			pending += ws;
+			if (buffer[pending-1] == '\n') break;
+		}
+		else {
+			// peer closed the socket!
+			pending = 0;
+			error = true;
+			break;
+		}
+	}
+	buffer[pending] = '\0';
+	// log("reader() sees buffer with %s", buffer);
+	return pending;
+}
+
+
+int SMTP::read_line() {
+	char *lf = strchr(buffer, '\n');
+	if (!lf) {
+		reader();  // get a lf
+		lf = strchr(buffer, '\n');
+		if (!lf) lf = buffer + pending - 1;
+	}
+	return (lf-buffer)+1;	// number of bytes in this line
+}
+
+
+int SMTP::flush_line(int r) {
+	if (pending > r) memmove(buffer, buffer+r, pending-r);
+	pending -= r;
+}
+
+
+int SMTP::read_response() {
+	pending = 0;
+	buffer[pending] = '\0';
+	while (true) {
+		int r = read_line();
+		// log("read_response() sees line with %s", buffer);
+		// log("read_response() sees line length %d", r);
+		if (r == 0) return 0;	// failed to read any bytes
+		if ((r > 4) && (buffer[3] == '-')) {
+			flush_line(r);
+			continue;
+		}
+		return atoi(buffer);
+	}
+	return 0;
+}
+
+
+int SMTP::cmd(char *c) {
+	if (c) {
+		init();
+		append(c);
+	}
+	append("\r\n");
+	writer();
+	return read_response();
+}
+
+
+int SMTP::helo() {
+	if (read_response() != 220) return 0;
+	init();
+	append("HELO ");
+	append(token_myhostname);
+	return cmd(NULL);
+}
+
+
+int SMTP::rset() {
+	return cmd("RSET");
+}
+
+
+int SMTP::from(char *f) {
+	init();
+	append("MAIL FROM:<");
+	append(f);
+	append(">");
+	return cmd(NULL);
+}
+
+
+int SMTP::rcpt(char *t) {
+	init();
+	append("RCPT TO:<");
+	append(t);
+	append(">");
+	return cmd(NULL);
+}
+
+
+int SMTP::quit() {
+	int rc = cmd("QUIT");
+	shutdown(fd, SHUT_RDWR);
+	close(fd);
+	return rc;
+}
+
+
+//	void SMTP::log(char *m, int v) {
+//		char buf[maxlen];
+//		snprintf(buf, maxlen, m, v);
+//		my_syslog(buf);
+//	}
+//
+//
+//	void SMTP::log(char *m, char *v) {
+//		char buf[maxlen];
+//		snprintf(buf, maxlen, m, v);
+//		my_syslog(buf);
+//	}
+//
+//
+VERIFY::VERIFY(char *h) {
+	host	 = h;
+	last_err = 0;
+	pthread_mutex_init(&mutex, 0);
+}
+
+
+void VERIFY::closer() {
+	bool ok = true;
+	while (ok) {
+		int fd = 0;
+		pthread_mutex_lock(&mutex);
+			if (sockets.empty()) {
+				ok = false;
+			}
+			else {
+				time_t t = times.front();
+				time_t now = time(NULL);
+				if ((now - t) > maxage) {
+					// this socket is ancient, remove it
+					fd = sockets.front();
+					times.pop_front();
+					sockets.pop_front();
+				}
+				else {
+					ok = false;
+				}
+			}
+		pthread_mutex_unlock(&mutex);
+		if (fd) {
+			SMTP s(fd);
+			s.quit();	// closes the fd
+			// s.log("closer() closes ancient %d", fd);
+		}
+	}
+}
+
+
+int  VERIFY::get_socket() {
+	int sock = NULL_SOCKET;
+	pthread_mutex_lock(&mutex);
+		if (!sockets.empty()) {
+			sock = sockets.front();
+			times.pop_front();
+			sockets.pop_front();
+			// SMTP::log("get_socket() %d from cache", sock);
+		}
+	pthread_mutex_unlock(&mutex);
+
+	if (sock == NULL_SOCKET) {
+		time_t now = time(NULL);
+		if ((now - last_err) > ERROR_SOCKET_TIME) {
+			// nothing recent, maybe this time it will work
+			hostent *h = gethostbyname(host);
+			if (h) {
+				sockaddr_in server;
+				server.sin_family = h->h_addrtype;
+				server.sin_port   = htons(25);
+				memcpy(&server.sin_addr, h->h_addr_list[0], h->h_length);
+				sock = socket(PF_INET, SOCK_STREAM, 0);
+				if (sock != NULL_SOCKET) {
+					bool rc = (connect(sock, (sockaddr *)&server, sizeof(server)) == 0);
+					if (!rc) {
+						shutdown(sock, SHUT_RDWR);
+						close(sock);
+						sock = NULL_SOCKET;
+						last_err = now;
+					}
+				}
+				else last_err = now;
+			}
+			else last_err = now;
+		}
+		if (sock != NULL_SOCKET) {
+			SMTP s(sock);
+			if (s.helo() != 250) {
+				put_socket(sock, true);
+				sock = NULL_SOCKET;
+			}
+		}
+	}
+	return sock;
+}
+
+
+void VERIFY::put_socket(int fd, bool err) {
+	if (err) {
+		// SMTP::log("put_socket() %d with error, close it", fd);
+		shutdown(fd, SHUT_RDWR);
+		close(fd);
+		last_err = time(NULL);
+	}
+	else {
+		// SMTP::log("put_socket() %d", fd);
+		pthread_mutex_lock(&mutex);
+			time_t now = time(NULL);
+			times.push_back(now);
+			sockets.push_back(fd);
+		pthread_mutex_unlock(&mutex);
+	}
+}
+
+
+bool VERIFY::ok(char *from, char *to) {
+	if (host == token_myhostname) return true;
+	int fd = get_socket();
+	if (fd == NULL_SOCKET) return true; // cannot verify right now, we have socket errors
+	SMTP s(fd);
+	s.rset();
+	int rc;
+	rc = s.from(from);
+	// s.log("verify::ok from sees %d", rc);
+	if (rc != 250) {
+		put_socket(fd, s.err());
+		return (rc >= 500) ? false : true;
+	}
+	rc = s.rcpt(to);
+	// s.log("verify::ok rcpt sees %d", rc);
+	put_socket(fd, s.err());
+	return (rc >= 500) ? false : true;
+}
+
 
 DNSBL::DNSBL(char *n, char *s, char *m) {
 	name	= n;
@@ -155,6 +445,7 @@
 CONTEXT::CONTEXT(CONTEXTP parent_, char *name_) {
 	parent				= parent_;
 	name				= name_;
+	verify_host 		= NULL;
 	env_from_default	= (parent) ? token_inherit : token_unknown;
 	content_filtering	= (parent) ? parent->content_filtering : false;
 	content_suffix		= NULL;
@@ -203,6 +494,26 @@
 }
 
 
+VERIFYP CONTEXT::find_verify(char *to) {
+	if (verify_host && (verify_host != token_myhostname) && cover_env_to(to)) {
+		verify_map::iterator i = verifiers.find(verify_host);
+		if (i == verifiers.end()) {
+			if (debug_syslog) {
+				char buf[maxlen];
+				snprintf(buf, maxlen, "cannot find struc for %s", verify_host);
+				my_syslog(buf);
+			}
+			return NULL;
+		}
+		VERIFYP v = (*i).second;
+
+		return v;
+	}
+	else if (parent) return parent->find_verify(to);
+	else return NULL;
+}
+
+
 char *CONTEXT::find_from(char *from) {
 	char *rc = token_inherit;
 	string_map::iterator i = env_from.find(from);
@@ -389,6 +700,10 @@
 	}
 	printf("%s     }; \n", indent);
 
+	if (verify_host) {
+		printf("%s     verify %s; \n", indent, verify_host);
+	}
+
 	for (context_map::iterator i=children.begin(); i!=children.end(); i++) {
 		CONTEXTP c = (*i).second;
 		c->dump(level+1);
@@ -682,6 +997,17 @@
 
 ////////////////////////////////////////////////
 //
+bool parse_verify(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_verify(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+	char *host = tok.next();
+	if (!tsa(tok, token_semi)) return false;
+	me.set_verify(host);
+	add_verify_host(host);
+}
+
+
+////////////////////////////////////////////////
+//
 bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me);
 bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
 	char *st = tok.next();
@@ -782,6 +1108,9 @@
 		else if (have == token_envto) {
 			if (!parse_envto(tok, dc, *con)) return false;
 		}
+		else if (have == token_verify) {
+			if (!parse_verify(tok, dc, *con)) return false;
+		}
 		else if (have == token_envfrom) {
 			if (!parse_envfrom(tok, dc, *con)) return false;
 		}
@@ -827,6 +1156,33 @@
 
 
 ////////////////////////////////////////////////
+// setup a new smtp verify host
+//
+void add_verify_host(char *host) {
+	verify_map::iterator i = verifiers.find(host);
+	if (i == verifiers.end()) {
+		VERIFYP v = new VERIFY(host);
+		verifiers[host] = v;
+	}
+}
+
+
+////////////////////////////////////////////////
+// thread to check for verify hosts with old sockets that we can close
+//
+void* verify_closer(void *arg) {
+	while (true) {
+		sleep(maxage);
+		for (verify_map::iterator i=verifiers.begin(); i!=verifiers.end(); i++) {
+			VERIFYP v = (*i).second;
+			v->closer();
+		}
+	}
+	return NULL;
+}
+
+
+////////////////////////////////////////////////
 // init the tokens
 //
 void token_init() {
@@ -860,5 +1216,12 @@
 	token_substitute = register_string("substitute");
 	token_tld		 = register_string("tld");
 	token_unknown	 = register_string("unknown");
+	token_verify	 = register_string("verify");
 	token_white 	 = register_string("white");
+
+	if (gethostname(myhostname, HOST_NAME_MAX+1) != 0) {
+		strncpy(myhostname, "localhost", HOST_NAME_MAX+1);
 }
+	myhostname[HOST_NAME_MAX] = '\0'; // ensure null termination
+	token_myhostname = register_string(myhostname);
+}