RCS_ID_C= "$Id: smtpd.c,v 1.7 1994/02/06 10:07:33 gwalter Rel $";
/*
 * smtpd.c --- an example of TCP daemon for AmiTCP/IP
 *
 * $Log: smtpd.c,v $
 * Revision 1.7  1994/02/06  10:07:33  gwalter
 * Changed to use TCPLog from logger.lib
 * Line buffer size increased
 *
 * Revision 1.6  1994/02/01  19:49:35  gwalter
 * Parameters added, tidying
 *
 * Revision 1.5  1994/01/09  14:01:27  gwalter
 * Timeouts, improved logging
 *
 * Revision 1.3  1993/12/30  16:38:18  gwalter
 * Changed to display QUIT command from client.
 *
 * Revision 1.2  1993/12/21  09:44:14  gwalter
 * Reference to pathnames.h removed
 *
 * Revision 1.1  1993/12/21  09:34:12  gwalter
 * Initial revision
 *
 * Revision 1.1  1993/12/05  18:10:02  gwalter
 * Initial revision
 *
 *
 */

#include "smtpd_rev.h"

char ApplicationID[] = "SMTPD";

const char version[] = VERSTAG;

char copyright[] =
  "Copyright © 1993 Graham Walter, <amitcp@gwalter.demon.co.uk>\n"
  "20 Kingstable St, Eton, Berks, England.\n";

#ifdef AMIGA
#if __SASC
#include <proto/socket.h>
#include <proto/dos.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#elif __GNUC__
#include <inline/socket.h>
#include <inline/exec.h>
#else
#include <clib/socket_protos.h>
#endif
#endif /* AMIGA */

#include <errno.h>
#include <netdb.h>

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/syslog.h>

#include <signal.h>

#include <dos/dos.h>
#include <exec/execbase.h>
#include <dos/var.h>
#include <dos/dostags.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <inetdlib.h>

#include <lineread.h>

#include "smtpd.h"

char * gethostname( char * host, long size );

extern struct ExecBase *SysBase;

BPTR Stdin = NULL;
BPTR Stdout = NULL;
BPTR Stderr = NULL;

char ** LocalHostAliases;
char * LocalHost;
char PeerName[MAX_HOSTLEN+1];

char * LogFileName;
char * RMailCommand = RMAIL_TEMPLATE;

int ErrorCount;

void
_STIdosStdio(void)
{
  struct Process *p = (struct Process *)SysBase->ThisTask;

  Stdin = p->pr_CIS;
  Stdout = p->pr_COS;
  Stderr = p->pr_CES ? p->pr_CES : Stdout;
}

/* Handle various mailer errors. */

static char * ApplicationErrorMsgs[] =
{
	"" /*"Network fault"*/,
	MAILERR_TEXT_BAD_RESPONSE,
	MAILERR_TEXT_NOTEMP,
	MAILERR_TEXT_INVALID_COMMAND,
	MAILERR_TEXT_SEND_ERROR,
	MAILERR_TEXT_NO_MEMORY,
	MAILERR_TEXT_EXPECTING_HELO,
	MAILERR_TEXT_TEMPWRITE,
	MAILERR_TEXT_CONNECTION_LOST,
	MAILERR_TEXT_SELECT,
	MAILERR_TEXT_TIMEOUT,
	MAILERR_TEXT_LOCALHOST,
	MAILERR_TEXT_INVALID_ARGUMENTS,
	MAILERR_TEXT_NO_DELIVERY,
	MAILERR_TEXT_NOT_DELIVERED,
	MAILERR_TEXT_COMMAND_REJECT
};

void
MailError(int	Type, char * info )
{
	char * errortext;
	char syserrorbuffer[SHORT_BUFFER_LEN];

	if( Type == 0 )
	{
		char * syserrortext;

		if( errno < __sys_nerr)
			syserrortext = (UBYTE *)__sys_errlist[errno];
		else 
			syserrortext = (UBYTE *)__sys_errlist[0];

		sprintf( syserrorbuffer, "%s %%s", syserrortext );
		errortext = syserrorbuffer;
	}
	else
	{
		if( Type < sizeof( ApplicationErrorMsgs ) / sizeof( ApplicationErrorMsgs[0] ) )
			errortext = ApplicationErrorMsgs[Type];
		else
			errortext = MAILERR_TEXT_UNKNOWN_ERROR;
	}

	TCPLog( LOG_ERR, errortext, (info) ? info: "" );

	if( ++ErrorCount >= MAX_ERRORS )
	{
		TCPLog( LOG_CRIT, MAILERR_TEXT_ABORTING_MAXERRS );

		exit( 20 );
	}
}

/* Get the date, and format it for RFC822. */

char *
GetDate( void )
{
	time_t		clock;

	clock = time(NULL);

	return ctime(&clock);
}

char **
gethostent( void )
{
	char ** nameblock;
	char ** alias;
	char local[STD_BUFFER_LEN];
	struct hostent * hostent;
	int i;

	if( nameblock = calloc( MAX_ALIASES+2, sizeof( char * ) ) )
	{
		gethostname( local, sizeof( local ) );

		if( hostent = gethostbyname( local ) )
		{
			nameblock[0] = hostent->h_name;
			
			for( i = 1, alias = hostent->h_aliases; *alias; alias++ )
			{
				nameblock[i++] = strdup( *alias );

				if( i > MAX_ALIASES )
					break;
			}
		}
		else
		{
			free( nameblock );
			nameblock = NULL;
		}
	}
	else
		MailError( MAILERR_NO_MEMORY, NULL );

	if( ! nameblock )
		MailError( MAILERR_LOCALHOST, NULL );

	return( nameblock );
}

int
StripCRLF( char * buffer )
{
	int len;

	len = strlen( buffer );
	
	if( len && ( buffer[len-1] == '\n' ) )
		buffer[--len] = '\0';

	if( len && ( buffer[len-1] == '\r' ) )
		buffer[--len] = '\0';

	return( len );
}

/* Get a line from a socket */

int
GetLine( int s, char * buffer, struct LineRead * rl )
{
static	int len = 0;
	int nfds;
	fd_set readfds;
	fd_set excepfds;
static	struct timeval timeout = { TIMEOUT, 0 };

	for( ;; )
	{
		if( ! len )
		{
			FD_ZERO( &readfds );
			FD_SET(s, &readfds );

			FD_ZERO( &excepfds );
			FD_SET(s, &excepfds );

			if( (nfds = select(s + 1, &readfds, NULL, &excepfds, &timeout )) < 0)
			{
        			MailError( MAILERR_SELECT, "GetLine" );
        			return( -1 );
	        	}

			if( ! nfds )
			{
				MailError( MAILERR_TIMEOUT, "GetLine" );
				return( -1 );
			}
		}

		if( ( len = lineRead( rl ) ) != 0 )
			break;

		if( ! rl->rl_Line )
		{
			return( -1 );	/* EOF */
		}

	}

  	if( len <= 0 )
  	{
      		MailError( 0, "GetLine" );
      		return( -1 );
      	}

	strcpy( buffer, rl->rl_Line );

	return( StripCRLF( buffer ) );
}

/* Simply send some text down the socket; check the write succeeds, but don't
check for any response. */

int
SendText(int s, char *Text)
{
	if( send( s, Text, strlen(Text), 0 ) != strlen(Text) )
	{
		MailError( 0, "SendText" );
		return( 0 );
	}

	return 1;
}

int
isCommand( char * buffer, char * Command )
{
	int len = strlen( Command );
	int Status = FALSE;

	if( ( strnicmp( buffer, Command, len ) == 0 )
	 && ( ( isspace( buffer[len] ) )
	   || ( buffer[len] == '\0' ) ) )
	{
		Status = TRUE;
	}

	return( Status );
}

int StoreRecipient( char * to, char * from )
{
	char * cp;
	char * host;
	char ** alias;
	int length;

	if( ! ( cp = strchr( from, ':' ) ) )
		return( FALSE );

	from = cp + 1;

	if( *from == '<' )
	{
		if( ! ( cp = strchr( from, '>' ) ) )
			return( FALSE );

		from ++;
		length = cp - from;
	}
	else
	{
		length = stcarg( from, " \r\n" );
	}

	memcpy( to, from, length );
	to[length] = '\0';

	if( strchr( to, '%' ) )			/* need to forward ? */
		return( FALSE );

	if( ! ( cp = strchr( to, '@' ) ) )	/* no host specified */
		return( TRUE );

	host = cp + 1;
	length = strlen( host );
	*cp = '\0';

	for( alias = LocalHostAliases; *alias; alias++ )
	{
		if( stricmp( host, *alias ) == 0 ) /* this host */
			return( TRUE );

		if( ( strnicmp( host, *alias, length ) == 0 )
		 && ( (*alias)[length] == '.' ) )
		 	return( TRUE );		   /* valid abbreviation */
	}

	return( FALSE );
}

int
LogMail( ULONG fh )
{
	ULONG logfh;
static  buffer[STD_BUFFER_LEN];
	int len;

	if( ! (logfh = Open( LogFileName, MODE_READWRITE ) ) )
	{
		TCPLog( LOG_WARNING, MAILERR_TEXT_LOG_OPEN_FAIL );
		return( 1 );
	}

	Seek( logfh, 0, OFFSET_END );

	Seek( fh, 0, OFFSET_BEGINNING );

	while( len = Read( fh, buffer, sizeof( buffer ) ) )
	{
		Write( logfh, buffer, len );
	}

	Write( logfh, "\n", 1 );

	Close( logfh );

	return( 0 );
}

int
PassMailToRecipients( int s, struct LineRead * rl, char * From, ULONG rfh )
{
	char * tfile;
	ULONG tfh;
	char * line;
	char * reply;
	char buffer[STD_BUFFER_LEN];
	char Recipient[STD_BUFFER_LEN];
	long len;
	int SentCount;
	int status = 0;

	if( ! rfh )
	{
		SendText( s, SMTP_RESPONSE_NO_RECIPIENTS );

		TCPLog( LOG_WARNING, MAILERR_TEXT_NO_RECIPIENTS );

		return( 0 );
	}

	tfile = tmpnam( NULL );
	if( ! ( tfh = Open( tfile, MODE_NEWFILE ) ) )
	{
	      MailError( MAILERR_NOTEMP, "Data" );

	      return( 1 );
	}

	strcpy( buffer, GetDate() );

	FPrintf( tfh, "From %s %s", From, buffer ); 

	FPrintf( tfh, "Return-Path: <%s>\n", From );

	FPrintf( tfh, "Received: ") ;
	FPrintf( tfh, "from %s ", PeerName );
	FPrintf( tfh, "by %s with SMTP\n\tid AA%05ld; %s",
			LocalHost, 1, buffer );


	if( ! SendText(s, SMTP_RESPONSE_RECEIVING ) )
	{
	      Close( tfh );
	      return( 1 );
	}

	for(;;)
	{
		if( ( len = GetLine( s, buffer, rl ) ) < 0 )
		{
			MailError( 0, "Getting Mail Item Data" );
			Close( tfh );
			return( 1 );
		}

		line = buffer;

		if( line[0] == '.' )
		{
			if( line[1] == '\0' )
			{
				break;
			}

			line++;
		}

		FPrintf( tfh, "%s\n", line );
	}

	Flush( tfh );

	if( LogFileName )
		LogMail( tfh );

	Close( tfh );

	Seek( rfh, 0, OFFSET_BEGINNING );

	SentCount = 0;

	while( status == 0 
	    && ( FGets( rfh, Recipient, sizeof( Recipient ) - 1 ) > 0 ) )
	{
		StripCRLF( Recipient );

		sprintf( buffer, RMailCommand, tfile, Recipient );
		if( SystemTags( buffer, TAG_DONE ) == 0 )
			SentCount++;
		else
		   	MailError( MAILERR_NO_DELIVERY, Recipient );
	}

	if( SentCount )
	{
		DeleteFile( tfile );

		reply = SMTP_RESPONSE_OK;
	}
	else
	{
		MailError( MAILERR_NOT_DELIVERED, tfile );

		reply = SMTP_RESPONSE_NO_DELIVERY;
	}

	if( ! SendText(s, reply ) )
		return( 1 );

	return( 0 );
}

int ReadMailItem( int s, struct LineRead * rl )
{
	char * From;
	char * cp;
	long len;
	char * buffer;
	char Recipient[STD_BUFFER_LEN] = "";
	char rfname[L_tmpnam];
	ULONG rfh = NULL;
	int status = 0;

	if( ! ( buffer = malloc( MAX_LINE_LENGTH ) ) )
	{
		return( 1 );
	}

	strcpy( buffer, rl->rl_Line + strlen( "MAIL FROM:" ) );
	if( buffer[0] == '<' )
	{
		if( cp = strchr( buffer, '>' ) )
			*cp = '\0';
		cp = buffer + 1;
	}
	else cp = buffer;

	StripCRLF( buffer );

	From = strdup( cp );

	if( ! SendText( s, SMTP_RESPONSE_OK ) )
	{
		free( buffer );
		free( From );
		return( 1 );
	}

	for(;;)
	{
		if( ( len = GetLine( s, buffer, rl ) ) < 0 )
		{
			status = 1;
			break;
		}

		if( isCommand( buffer, SMTP_COMMAND_RCPT ) )
		{
			char * reply;

			if( StoreRecipient( Recipient, buffer ) )
			{
			   reply = SMTP_RESPONSE_OK;

			   if( ! rfh )
			   {
				if( ! ( rfh = Open( tmpnam( rfname ), MODE_NEWFILE ) ) )
				{
					MailError( MAILERR_NOTEMP, "Recipients" );
					status = 1;
					break;
				}
			   }

			   if( FPrintf( rfh, "%s\n", Recipient ) <= 0 )
			   {
				MailError( MAILERR_TEMPWRITE, "Recipients (w)" );
				reply = SMTP_RESPONSE_NOWRITE_RECIPIENT;
			   }
			}
			else
				reply = SMTP_RESPONSE_WHOTHEHECK;

			if( reply[0] != '2' )
				MailError( MAILERR_COMMAND_REJECT, buffer );

			if( ! SendText(s, reply ) )
			{
			      status = 1;
			      break;
			}
		}
		else if( isCommand( buffer, SMTP_COMMAND_RSET ) )
		{
			TCPLog( LOG_INFO, buffer );

			if( ! SendText(s, SMTP_RESPONSE_OK ) )
			{
			      status = 1;
			      break;
			}

			break;
		}
		else if( isCommand( buffer, SMTP_COMMAND_NOOP ) )
		{
			if( ! SendText(s, SMTP_RESPONSE_OK ) )
			{
			      status = 1;
			      break;
			}
		}
		else if( isCommand( buffer, SMTP_COMMAND_DATA ) )
		{
			if( ! rfh )
			{
				SendText( s, SMTP_RESPONSE_NO_RECIPIENTS );

				TCPLog( LOG_WARNING, MAILERR_TEXT_NO_RECIPIENTS );

				break;
			}

			Flush( rfh );

			status = PassMailToRecipients( s, rl, From, rfh );

			break;
		}
		else if( isCommand( buffer, SMTP_COMMAND_QUIT ) )
		{
			TCPLog( LOG_INFO, buffer );

			sprintf( buffer, SMTP_RESPONSE_CLOSING, LocalHost );

  			SendText( s, buffer );

      			return( 1 );
		}
		else
		{
			MailError( MAILERR_INVALID_COMMAND, buffer );
			status = 1;
      			break;
		}
	}

	if( rfh )
	{
		Close( rfh );
		DeleteFile( rfname );
	}

	free( buffer );
	free( From );
	return( status );
}

int
main(int argc, char **argv)
{
	long len;
	int s = server_socket;

#ifdef LOGGING
#include <netinet/in.h>
	struct sockaddr_in sin;
	int sval;
#endif
	char buffer[STD_BUFFER_LEN];
	struct LineRead * rl;

	struct RDArgs	*Args;
	long		ArgRes[ARGCOUNT];
	register int	i = 0;

	for( i = 0; i < ARGCOUNT; ArgRes[i++] = NULL )
		;

	/* Look at command line arguments. */
#ifdef DEBUG
	TCPLog( LOG_DEBUG, "starting, %ld arguments", argc );
	if( argc )
		TCPLog( LOG_DEBUG, "1st argument:%s", argv[0] );
#endif

	if(!(Args = ReadArgs((UBYTE *)SMTPD_TEMPLATE, ArgRes, NULL)))
	{
		MailError( MAILERR_INVALID_ARGUMENTS, NULL ); 
		return( 20 );
	}

	if( ArgRes[ARG_LOGFILE] )
	{
		LogFileName = strdup( (char*)(ArgRes[ARG_LOGFILE]) );
#ifdef DEBUG
		TCPLog( LOG_DEBUG, "LogFile set to %s", LogFileName );
#endif
	}

	if( ArgRes[ARG_RMAIL] )
	{
		RMailCommand = strdup( (char*)(ArgRes[ARG_RMAIL]) );
#ifdef DEBUG
		TCPLog( LOG_DEBUG, "RMailCommand set to %s", RMailCommand );
#endif
	}

	FreeArgs(Args);

	if ( s == -1 )
	{
#ifdef STANDALONE
		struct sockaddr_in sin;

		s = serveraccept( "smtp", &sin );
		if ( s != -1 )
		{
			FPrintf( Stderr, "Accepted a connection from %s, port %ld\n",
			inet_ntoa( sin.sin_addr ), sin.sin_port );
		} else
#endif
			return 1;
	}

	if( ! ( LocalHostAliases = gethostent( ) ) )
		return( 1 );

	LocalHost = LocalHostAliases[0];

#ifdef LOGGING			/* unused for now */
	sval = sizeof( sin );
	if ( getpeername( 0, &sin, &sval ) < 0 )
		fatal( "getpeername" );
#endif

	if( ! ( rl = ( struct LineRead * )AllocMem( sizeof ( *rl ), 0 ) ) )
	{
		MailError( MAILERR_NO_MEMORY, NULL );
		return( 1 );
	}

	initLineRead( rl, s, /*LF_REQLF*/ 1, RL_BUFSIZE );

	sprintf( buffer, SMTP_RESPONSE_READY, LocalHost );

	if( ! SendText( s, buffer ) )
		return( 1 );

	if( ( ( len = GetLine( s, buffer, rl ) ) <= strlen( SMTP_COMMAND_HELO ) + 1 )
	 || ( ! isCommand( buffer, SMTP_COMMAND_HELO ) ) )
	{
		MailError( MAILERR_EXPECTING_HELO, buffer );
		return( 1 );
	}

	TCPLog( LOG_INFO, buffer );

	strcpy( PeerName, buffer + strlen( SMTP_COMMAND_HELO ) + 1 );

	sprintf( buffer, SMTP_RESPONSE_OK_SUBS, LocalHost );

	if( ! SendText( s, buffer ) )
		return( 1 );

  for(;;)
  {
	if( ( len = GetLine( s, buffer, rl ) ) < 0 )
	{
		MailError( MAILERR_CONNECTION_LOST, NULL );

		return( 1 );
	}

	if( isCommand( buffer, SMTP_COMMAND_QUIT ) )
	{
		TCPLog( LOG_INFO, buffer );

		sprintf( buffer, SMTP_RESPONSE_CLOSING, LocalHost );
  		if( ! SendText( s, buffer ) )
  		{
      			return( 1 );
  		}

		break;
	}
	else if( isCommand( buffer, SMTP_COMMAND_NOOP ) )
	{
		if( ! SendText(s, SMTP_RESPONSE_OK ) )
		{
		      return( 1 );
		}
	}
	else if( isCommand( buffer, SMTP_COMMAND_RSET ) )
	{
		if( ! SendText(s, SMTP_RESPONSE_OK ) )
		{
		      return( 1 );
		}
	}
	else if( isCommand( buffer, SMTP_COMMAND_MAIL ) )
	{
		TCPLog( LOG_INFO, buffer );

		if( ReadMailItem( s, rl ) )
			return( 1 );
	}
	else
	{
		MailError( MAILERR_INVALID_COMMAND, buffer );

		if( ! SendText(s, SMTP_RESPONSE_PARDON ) )
		{
		      return( 1 );
		}
	}

  }

#if 0
  TCPLog( LOG_DEBUG, "Terminating normally" );
#endif

  return 0;
}
