/* command-line smtp submission
 * Andreas van Cranenburgh <0440949>, Apr 19 22:58:23 CEST 2007
 *
 * Usage:
 *   smtp from to host
 *
 * Tested to compile on OpenBSD and GNU/Linux.
 * For educational purposes only, not spam spam spam spam spam spam. */
 
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <netinet/in.h>
#define SMTP_PORT 25
#define BUFLENGTH 1024

void sock_send(char cmdStr[], int smtp_socket);
void sock_recv(char cmdStr[], int smtp_socket);
int readlinecr(char buffer[], int buflength);

int main(int argc, char * argv[]) {
	struct	hostent *smtp_host;
	struct	sockaddr_in server;
	struct  tm *tm;
	time_t	thetime;
	int	smtp_socket, a;
	char	cmdStr[BUFLENGTH], host[256], date[256];
	
	if (argc != 4) {
		printf("incorrect number of arguments. Usage:\nsmtp from-addr to-addr host\n");
		return EXIT_FAILURE;
	}

	/* resolve address */
	smtp_host = gethostbyname(argv[3]);
	if (smtp_host == NULL) {
		perror("can't resolve smtp-host");
		return EXIT_FAILURE;
	}
	
	/* create socket */
	smtp_socket = socket(AF_INET, SOCK_STREAM, 0);
		if (smtp_socket == -1) {
			perror("Create socket");
			return EXIT_FAILURE;
		}
	/* copy IP address to the sockaddr_in structure */
	memcpy(&server.sin_addr, smtp_host->h_addr_list[0], smtp_host->h_length);
	server.sin_family = AF_INET;
	server.sin_port = htons(SMTP_PORT);
	
	/* make connection */	
	a = connect(smtp_socket, (struct sockaddr *)&server, sizeof(server));
	if (a) {
		perror("error connecting.");
		return EXIT_FAILURE;
	}

	/* expect 220 banner */
	sock_recv(cmdStr, smtp_socket);

	/* try to find our own FQDN (oh well, it should be one anyway..) */
	a = gethostname(host, (int)sizeof(host));
	if (a != 0 || strcasecmp("localhost", host)) {
		perror("gethostname() failed or hostname is \"localhost\"");
		return EXIT_FAILURE;
	}
	
	/* say hello */	
	sprintf(cmdStr, "EHLO %s\r\n", host);
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	sprintf(cmdStr, "MAIL FROM:<%s>\r\n", argv[1]); 
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	sprintf(cmdStr, "RCPT TO:<%s>\r\n", argv[2]); 
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	sprintf(cmdStr, "DATA\r\n"); 
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	/* lousy attempt at RFC 2822 */
	sprintf(cmdStr, "From: %s\r\n", argv[1]);
	sock_send(cmdStr, smtp_socket);
	sprintf(cmdStr, "To: %s\r\n", argv[2]);
	sock_send(cmdStr, smtp_socket);
	/* sprintf(cmdStr, "Subject: %s\r\n", argv[3]);
	sock_send(cmdStr, smtp_socket); */
	// RFC 2822 date format: Wed, 18 Apr 2007 01:01:49 +0200
	thetime = time(NULL);
	tm = localtime(&thetime);	
	a = strftime(date, 255, "%a, %d %b %Y %T %z", tm);
	if (a == 0) {
		perror("couldn't get current time");
		exit(EXIT_FAILURE);
	}
	sprintf(cmdStr, "Date: %s\r\n\r\n", date);
	sock_send(cmdStr, smtp_socket);
 	
	/* read from stdin and send to smtp connection */
	do {	
		a = readlinecr(cmdStr, BUFLENGTH);
		sock_send(cmdStr, smtp_socket);
	} while (a != EOF);

	sprintf(cmdStr, "\r\n.\r\n"); 
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	sprintf(cmdStr, "QUIT\r\n"); 
	sock_send(cmdStr, smtp_socket);
	sock_recv(cmdStr, smtp_socket);
	
	close(smtp_socket);
	return 0;
}

void sock_send(char cmdStr[], int smtp_socket) {
	int cmdStrLen, a;
	
	cmdStrLen = strlen(cmdStr);	
	
	a = write(smtp_socket, cmdStr, cmdStrLen);
	if (a != cmdStrLen) {
		perror("error: different number of bytes were sent than expected");
		exit(EXIT_FAILURE);
	}
	printf("sent %d %s", a, cmdStr);
}
void sock_recv(char cmdStr[], int smtp_socket) {
	int a;
	a = recv(smtp_socket, cmdStr, 255, 0);
	if (a == -1) {
		perror("error reading from socket");
		exit(EXIT_FAILURE);
	}
	cmdStr[a] = 0; /* make sure it's null terminated */

	/* check if there was either a transient or permanent failure */
	if (cmdStr[0] == '4') {
		printf("transient failure: %s", cmdStr);
		sprintf(cmdStr, "QUIT\r\n"); 
		sock_send(cmdStr, smtp_socket);
		exit(EXIT_FAILURE);
	}
	if (cmdStr[0] == '5') {
		printf("permanent failure: %s", cmdStr);
		sprintf(cmdStr, "QUIT\r\n"); 
		sock_send(cmdStr, smtp_socket);
		exit(EXIT_FAILURE);
	}
	printf("received %d %s", a, cmdStr);
}

int readlinecr(char buffer[], int buflength) {
	/* read some characters from stdin until buffer is full or 
 	 * EOF is reached. Meanvhile, convert LF to CRLF,
 	 * and escape lines with a single dot. */
	int a;
	for (a =0; a < buflength - 1; a++) {
		/* leave room for null termination as well
		 * as a CR in the unlikely case where the
		 * last character happens to be a LF. */
		buffer[a] = getchar();
		if (buffer[a] == EOF) {
			buffer[a] = 0;
			return EOF;
		}
		/* do the LF -> CRLF magic */
		if (buffer[a] == '\n' && a > 0 &&
		    buffer[a - 1] != '\r') {
			buffer[a] = '\r';
			a++;
			buffer[a] = '\n';
		}
		/* escape <CRLF>.<CRLF>:
.
to avoid the above being a problem... */
		if (buffer[a] == '\n' && a > 4 &&
		    buffer[a - 2] == '.' &&
		    buffer[a - 3] == '\n') {
			buffer[a - 1] = '.';
			buffer[a] = '\r';
			a++;
			buffer[a] = '\n';
		
		}
	}
	buffer[a] = 0;
	return 0;
}
