/*
 * $Header: /udir/vixie/src/mail11/d-1.7beta/RCS/smtp-tcp-bsd.c,v 1.2 1991/09/23 01:33:50 vixie Exp $
 * Copyright (c) 1989 Keith Moore
 */
#include <stdio.h>
#include <varargs.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ctype.h>
#include "text.h"
#include "smtp.h"

/*
 * speak to an SMTP protocol server via TCP on top of 4.x BSD sockets.
 */

static int fd;				/* socket to SMTP server */
static FILE *smtp_in;			/* buffered input stream */
static FILE *smtp_out;			/* buffered output stream */

/*
 * Build a single-line reply message to return to caller.
 */

static
struct smtp_reply *
SMTP_MakeReply (va_alist)
va_dcl
{
    static struct smtp_reply reply;
    va_list args;
    char *format;
    char buf[1000];

    va_start (args);
    TEXT_Erase (&reply.msg);
    format = va_arg (args, char *);
    reply.code =
	((format[0] - '0') * 16 + (format[1] - '0')) * 16 + (format[2] - '0');
    vsprintf (buf, format, args);
    TEXT_Append (&reply.msg, buf, strlen (buf));
    va_end (args);
    return &reply;
}

static
struct smtp_reply *
SMTP_Ok ()
{
    return SMTP_MakeReply ("200 done");
}


/*
 * Get a response to the command just issued.
 */
static
struct smtp_reply *
SMTP_GetResponse ()
{
    char c;
    char last_char;
    char cont_flag;
    char *ptr;
    char message[1000];
    static struct smtp_reply reply;
    struct text_line *line;

    reply.code = 0;
    TEXT_Erase (&reply.msg);

    do {
	reply.code = 0;
	ptr = message;
	last_char = '\0';

	*ptr++ = c = getc (smtp_in);
	if (isdigit (c))
	    reply.code = reply.code * 16 + c - '0';
	*ptr++ = c = getc (smtp_in);
	if (isdigit (c))
	    reply.code = reply.code * 16 + c - '0';
	*ptr++ = c = getc (smtp_in);
	if (isdigit (c))
	    reply.code = reply.code * 16 + c - '0';
	*ptr++ = cont_flag = getc (smtp_in);

	do {
	    c = getc (smtp_in);
	    if (c == '\n' && last_char == '\r') {
		TEXT_Append (&reply.msg, message, ptr - message);
		break;
	    }
	    else if (c == EOF)
		return SMTP_MakeReply ("451 Unexpected EOF on SMTP channel");
	    else if (ptr < message + sizeof message)
		*ptr++ = c;
	    last_char = c;
	} while (1);
    } while (cont_flag == '-');
    for (line = reply.msg.head; line; line=line->next)
	DEBUG_Print ("%s\n", line->text);
    return &reply;
}

/*
 * Write a line to the SMTP server.
 * Since this is a TCP connection, we have to convert to NVT notation
 * to maintain transparency.  Also add the leading dot if necessary.
 */
void
SMTP_WriteLine (buf, length)
char *buf;
int length;
{
    if (length > 0 && toascii(*buf) == '.')
	putc ('.', smtp_out);
    while (length > 0) {
	switch (*buf) {
	case '\r':			/* CR => CR NUL */
	    putc ('\r', smtp_out);
	    putc ('\0', smtp_out);
	    break;
	default:
	    putc (*buf & 0x7f, smtp_out);
	    break;
	}
	++buf;
	--length;
    }
    putc ('\r', smtp_out);
    putc ('\n', smtp_out);
    fflush (smtp_out);
}

static
SMTP_Putc (c)
{
    if (c == '\r') {
	putc ('\r', smtp_out);
	putc ('\0', smtp_out);
    }
    else if (c == '\n') {
	putc ('\r', smtp_out);
	putc ('\n', smtp_out);
    }
    else
	putc (c & 0x7f, smtp_out);
}

static
SMTP_DEBUG_Putc (c)
{
    if (c == '\r') {
	putc ('\r', smtp_out);
	putc ('\0', smtp_out);
    }
    else if (c == '\n') {
	putc ('\r', smtp_out);
	putc ('\n', smtp_out);
    }
    else
	putc (c & 0x7f, smtp_out);
    DEBUG_Putc (c);
}

/*
 * Do a subset of printf for the SMTP-TCP channel.  This is intended
 * for use in header generation, etc.  Unlike SMTP_Command, does NOT
 * automatically generate a newline at the end of each line.
 * You have to put \n in the format explicitly.
 *
 * Bug: doesn't check for leading dot.
 */
void
SMTP_Printf (va_alist)
va_dcl
{
    va_list args;
    char *format;
    va_start (args);
    format = va_arg (args, char *);
    vfformat (SMTP_Putc, format, args);
    va_end (args);
}

struct smtp_reply *
SMTP_Open ()
{
    struct hostent *hp;
    struct servent *sp;
    struct sockaddr_in address;

    if ((hp = gethostbyname ("localhost")) == NULL)
	return SMTP_MakeReply ("451 Can't find IP address for \"localhost\"");
    bcopy (hp->h_addr, (char *) &address.sin_addr, hp->h_length);

    if ((sp = getservbyname ("smtp", "tcp")) == NULL)
	return SMTP_MakeReply ("451 Can't find port number for SMTP/TCP");
    address.sin_port = sp->s_port;

    if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == EOF)
	return SMTP_MakeReply ("451 SMTP_Open: Can't create socket: %s",
			       UNIX_ErrorMessage ());
    address.sin_family = AF_INET;

    if (connect (fd, &address, sizeof address) < 0)
	return SMTP_MakeReply ("451 SMTP_Open: Can't connect to socket: %s",
			       UNIX_ErrorMessage ());    
    if ((smtp_in = fdopen (fd, "r")) == NULL) {
	close (fd);
	return SMTP_MakeReply ("451 SMTP_Open: fdopen failed on socket: %s",
			       UNIX_ErrorMessage ());
    }
    if ((smtp_out = fdopen (fd, "w")) == NULL) {
	fclose (smtp_in);
	return SMTP_MakeReply ("451 SMTP_Open: fdopen failed on socket: %s",
			       UNIX_ErrorMessage ());
    }

    return SMTP_GetResponse ();
}

struct smtp_reply *
SMTP_Close ()
{
    if (fclose (smtp_out) < 0) 
	return SMTP_MakeReply ("451 Error in SMTP_Close: %s",
			       UNIX_ErrorMessage ());
    else {
	fclose (smtp_in);
	return SMTP_MakeReply ("221 Service closed.");
    }
}


struct smtp_reply *
SMTP_Command (va_alist)
va_dcl
{
    va_list args;
    char *format;

    va_start (args);
    format = va_arg (args, char *);
    DEBUG_Print (">>> ");
    vfformat (SMTP_DEBUG_Putc, format, args);
    DEBUG_Print ("\n");
    va_end (args);
    putc ('\r', smtp_out);
    putc ('\n', smtp_out);
    fflush (smtp_out);
    return SMTP_GetResponse ();
}
