From: bet@cyclone.sbi.com (Bennett E. Todd)
Newsgroups: comp.lang.perl
Subject: SMTP daemon in perl
Date: 21 Aug 92 03:24:12 GMT
Organization: Salomon Brothers, Inc


A little doodad I whacked out one evening. I've yet to write a real gung-ho
replacement for the local-delivery part of sendmail, so this isn't quite
complete yet, but it was kinda fun.

-Bennett
bet@sbi.com


#!/usr/bin/perl

# Smtpd: SMTP daemon
#
# Install with a line like so in /etc/inetd.conf:
#  smtp stream tcp nowait root .../smtpd %A

# If we print anything, let's make it look like Internet goop.
$\="\r\n"; # output line delimiter


# Save hostname for a zillion messages
$hostname=`hostname`;
chop $hostname;

# Parse arg. The %A above cause $ARGV[0] to be something like 810e5850.2569
$ARGV[0] =~ s/^([0-9a-e][0-9a-e])// || die "500 bad address\r\n221 $hostname stopped";
$o1 = hex($1);
$ARGV[0] =~ s/^([0-9a-e][0-9a-e])// || die "500 bad address\r\n221 $hostname stopped";
$o2 = hex($1);
$ARGV[0] =~ s/^([0-9a-e][0-9a-e])// || die "500 bad address\r\n221 $hostname stopped";
$o3 = hex($1);
$ARGV[0] =~ s/^([0-9a-e][0-9a-e])// || die "500 bad address\r\n221 $hostname stopped";
$o4 = hex($1);
$ARGV[0] =~ s/^\.([0-9][0-9][0-9][0-9])// || die "500 bad address\r\n221 $hostname stopped";
$port = $1;
$via="$o1.$o2.$o3.$o4";

# Try to translate ``via'' IP address into hostname. Don't sweat it if you can't.
open(HOSTS,'</etc/hosts') || die "500 cannot open /etc/hosts\r\n221 $hostname stopped";
lookup: while (<HOSTS>) {
	/^$via[ 	]*([^ 	]*)/ && do { $via=$1; last lookup;};
};
close(HOSTS) || die "500 cannot close /etc/hosts\r\n221 $hostname stopped";

# Suck up passwd file. There are other ways this could be implemented....
open(PASSWD, '</etc/passwd') || die "500 cannot open /etc/passwd\r\n221 $hostname stopped";
while(<PASSWD>) {
	chop;
	($pw_logname, $pw_passwd, $pw_uid, $pw_gid, $pw_gcos, $pw_home, $pw_shell) = split(/:/);
	$pw_passwd{$pw_logname} = $pw_passwd;
	$pw_uid{$pw_logname} = $pw_uid;
	$pw_gid{$pw_logname} = $pw_gid;
	$pw_gcos{$pw_logname} = $pw_gcos;
	$pw_home{$pw_logname} = $pw_home;
	if ($pw_shell eq '') {
		$pw_shell{$pw_logname} = '/bin/sh';
	} else {
		$pw_shell{$pw_logname} = $pw_shell;
	};
};

# Prepare for dialogue.
$|=1;      # Unbuffered writes
$/="\r\n"; # input line delimiter

# Opening greetings
print "220 $hostname Smtpd";

# This is the SMTP protocol, deduced by experimenting against a sendmail.
# Sure, I could have busted it up into subroutines. With lables on the blocks
# I think this is just as clear. It is certainly one hell of a big loop, though.
parse_command: while (<STDIN>) {
	chop;chop;
	/^helo *(.*)/i && do {
		$claim=$1;
		print "250 $hostname Hello $claim ($via), pleased to meet you";
		next parse_command;
	};
	/^help$/i && do {
		print '214-Commands:';
		print '214-	HELO	MAIL	RCPT	DATA	RSET';
		print '214-	NOOP	QUIT	HELP	VRFY	EXPN';
		print '214-For more info use "HELP <topic>".';
		print '214-smtp';
		print '214-Report bugs in the implementation to Bent.';
		print '214 End of HELP info';
		next parse_command;
	};
	s/^help *(.*)/\1/i && do {
	  helpswitch: {
		/helo/i && do {
			print '214-HELO <hostname>';
			print '214-	Introduce yourself.  I am a boor, so I really don\'t';
			print '214-	care if you do.';
			last helpswitch;
		};
		/mail/i && do {
			print '214-MAIL FROM: <sender>';
			print '214-	Specifies the sender.';
			last helpswitch;
		};
		/rcpt/i && do {
			print '214-RCPT TO: <recipient>';
			print '214-	Specifies the recipient.  Can be used any number of times.';
			last helpswitch;
		};
		/data/i && do {
			print '214-DATA';
			print '214-	Following text is collected as the message.';
			print '214-	End with a single dot.';
			last helpswitch;
		};
		/rset/i && do {
			print '214-RSET';
			print '214-	Resets the system.';
			last helpswitch;
		};
		/noop/i && do {
			print '214-NOOP';
			print '214-	Do nothing.';
			last helpswitch;
		};
		/quit/i && do {
			print '214-QUIT';
			print '214-	Exit smtpd (SMTP).';
			last helpswitch;
		};
		/help/i && do {
			print '214-HELP [ <topic> ]';
			print '214-	The HELP command gives help info.';
			last helpswitch;
		};
		/vrfy/i && do {
			print '214-VRFY <recipient>';
			print '214-	Not implemented to protocol.  Gives some sexy';
			print '214-	information.';
			last helpswitch;
		};
		/expn/i && do {
			print '214-EXPN <recipient>';
			print '214-	Same as VRFY in this implementation.';
			last helpswitch;
		};
		print '504 HELP topic unknown';
		next parse_command;
	  };
	  print '214 End of HELP info';
	  next parse_command;
	};
	/^mail from: *(.*)/i && do {
		$from=$1;
		print "250 $from... Sender ok";
		next parse_command;
	};
	/^noop/ && do {
		print "200 OK";
		next parse_command;
	};
	/^quit/i && do {
		print "221 $hostname closing connection";
		exit(0);
	};
	/^rset/i && do {
		print '250 Reset state';
		next parse_command;
	};
	s/^(vrfy|expn) *(.*)/\2/i && do {
		s/@$hostname//;
		if ($pw_uid{$_} eq '') {
			print "550 $_... User unknown";
			next parse_command;
		};
		print "250 $pw_gcos{$_} <$_>";
		next parse_command;
	};
	s/^rcpt to: *(.*)/\1/i && do {
		s/@$hostname//;
		if ($pw_uid{$_} eq '') {
			print "550 $_... User unknown";
			next parse_command;
		};
		push(@recipients,$_);
		print "250 $_... Recipient ok";
		next parse_command;
	};
	/^data/i && do {
		if ($from eq '') {
			print '503 Need MAIL command';
			next parse_command;
		};
		if ($#recipients < 0) {
			print '503 Need RCPT (recipient)';
			next parse_command;
		}
		open(BINMAIL,"|/bin/mail @recipients") || die "500 cannot call /bin/mail\r\n221 $hostname stopped";
		print BINMAIL "From $from\n" || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		print BINMAIL "From: $from\n" || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		print BINMAIL "Received-From: $via\n" || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		print BINMAIL "To: @recipients\n" || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		print BINMAIL "\n" || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		print '354 Enter mail, end with "." on a line by itself';
		while (<STDIN>) {
			chop;chop;
			/^\.$/ && do {
				close BINMAIL || die "500 cannot close /bin/mail pipe\r\n221 $hostname stopped";
				print '250 Mail accepted';
				$#recipients = 0;
				$from = '';
				next parse_command;
			};
			print BINMAIL || die "500 cannot send data to /bin/mail\r\n221 $hostname stopped";
		}
		close BINMAIL;
		exit 0;
	};
	print "500 Command unrecognized";
};