/*
**
** Copyright (C) 1994 Swedish University Network (SUNET)
**
**
** This program is developed by UDAC, Uppsala University by commission
** of the Swedish University Network (SUNET). 
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITTNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
** 
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
**
**                                        Martin.Wendel@udac.uu.se
**                                        Torbjorn.Wictorin@udac.uu.se
**
**                                        UDAC	
**                                        P.O. Box 174
**                                        S-751 04 Uppsala
**                                        Sweden
**
*/


#include "emil.h"
#ifdef SMTP
#define RTYPE(r)    ((r) / 100)
#define RCLASS(r)   (((r) / 10) % 10)
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#ifdef NAMED_BIND
#include <arpa/nameser.h> 
#include <resolv.h>
#endif
#define SMTPBUF 1024
#define smtpout out_fd
#ifndef INADDR_NONE
# define INADDR_NONE  0xffffffff 
#endif
#ifndef SMTPHOSTNAME
#define SMTPHOSTNAME "nohost.nodomain"
#endif
FILE *smtpin;
extern FILE *out_fd;
static char outbuf[SMTPBUF], inbuf[SMTPBUF];
#endif
#ifdef NAMED_BIND
#ifdef HAVE_RES_SEARCH
extern int h_errno;
#endif
#endif
void
make_connection(char *ahost, FILE **in, FILE **out, int usemx)
{
#ifdef SMTP
  register struct hostent *h = (struct hostent *)NULL;
  struct sockaddr_in server;
  int sock = 0;
  int sockt = 0;
  int i;
  char *p, *t;
  char *host;
#ifdef USE_MX
#ifdef HAVE_RES_SEARCH
  struct mxlist *list;
#endif
#endif

  t = NULL;
#ifdef NAMED_BIND
#ifdef HAVE_RES_SEARCH
  _res.options &= (~RES_DNSRCH & 0xffff);
  list = NULL;
#endif
#endif
  host = ahost;
  errno = h_errno = 0;
  if (*host == '[')
    {
      unsigned long i = INADDR_NONE;
      p = host + 1;
      if ((t = index(host, ']')) != NULL)
	{
	  *t = '\0';
	  i = inet_addr(p);
	  *t = ']';
	}
      if (t == NULL || i == INADDR_NONE)
	{
	  /* Invalid server spec */
	  fprintf(stderr, "Emil: Unknown host %s\n", host);
	  exit(EX_NOHOST);
	}
      server.sin_addr.s_addr = i;
    }
 lookup:
  errno = 0;
  if (t == NULL)
    {
#ifdef USE_MX
#ifdef HAVE_RES_SEARCH
      if (usemx)
	/* Look for MX */
	{
	  list = (struct mxlist *)get_mx(host);
	  usemx = 0;
	}
      if (list != NULL)
	{
	  host = list->host;
	  list = list->next;
	}
#endif
#endif
      if ((h = (struct hostent *)gethostbyname(host)) == NULL)
	{
#ifdef USE_MX
#ifdef HAVE_RES_SEARCH	  
	  if (list != NULL)
	    goto lookup;
#endif
#endif
	  if (errno == ETIMEDOUT || h_errno == TRY_AGAIN)
	    exit (EX_TEMPFAIL);
	  if (errno == ECONNREFUSED)
	    exit(EX_TEMPFAIL);
	  /* Failed */
	  fprintf(stderr, "Emil: Host lookup failed.\n");
	  fprintf(stderr, "Emil: Unknown host %s\n", host);
	  exit(EX_NOHOST);
	}
      else
	bcopy((char *)h->h_addr, (char *)&server.sin_addr, h->h_length);
    }
  i = 0;
 again:
  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
      fprintf(stderr, "Emil: Unable to create socket\n");
      exit(EX_TEMPFAIL);
    }
  server.sin_family = AF_INET;
  server.sin_port = htons(25);
  errno = 0;
  if (connect(sock,(struct sockaddr *)&server, sizeof server) < 0)
    {
      close(sock);
#ifdef NAMED_BIND      
#ifdef HAVE_RES_SEARCH
      if (h && h->h_addr_list[i])
	{
	  bcopy(h->h_addr_list[i++],
		(char *)&server.sin_addr, h->h_length);
	  goto again;
	}
      if (list != NULL)
	goto lookup;
#endif
#endif
      switch(errno) {
      case EISCONN:
      case ETIMEDOUT:
      case EINPROGRESS:
      case EALREADY:
      case EADDRINUSE:
      case EHOSTDOWN:
      case ENETDOWN:
      case ENETRESET:
      case ENOBUFS:
      case ECONNREFUSED:
      case ECONNRESET:
      case EHOSTUNREACH:
      case ENETUNREACH:
      case EPERM:
	exit (EX_TEMPFAIL);
      default:
	{
	  fprintf(stderr, "Emil: Unable to connect to %s\n", host);
	  exit(EX_TEMPFAIL);
	}
      }
    }
  *out = fdopen(sock, "a");
  if ((sockt = dup(sock)) == -1)
    {
      fprintf(stderr, "Emil dup(socket) failed.\n");
      exit(EX_OSERR);
    }
  *in = fdopen(sockt, "r");
  if (smtpout == NULL || smtpin == NULL)
    {
      close(sock);
      fprintf(stderr, "Emil: smtp open failed.\n");
      exit(EX_TEMPFAIL);
    }
}

void
smtp_open(char *host)
{
  int r;
#ifdef HAVE_RES_SEARCH
#ifdef USE_MX
  make_connection(host, &smtpin, &smtpout, 1);
#else
  make_connection(host, &smtpin, &smtpout, 0);
#endif
#endif
  if ((r = smtp_get()) < 0 || RTYPE(r) != 2)
    {
      fprintf(stderr, "Emil: %s greeted us with %d\n", host, r);
      exit(EX_TEMPFAIL);
    }
  if (hostname == NULL)
    hostname = NEWSTR(SMTPHOSTNAME);
  sprintf(outbuf,  "HELO %s\r\n", hostname);
  if (fputs(outbuf, smtpout) == EOF)
    {
      fprintf(stderr, "Emil: smtp error on write\n");
      exit(EX_TEMPFAIL);
    }
  fflush(smtpout);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: %s", outbuf);
#endif
  
  if ((r = smtp_get()) < 0 || RTYPE(r) != 2)
    {
      fprintf(stderr, "Emil: %s said %d\n", host, r);
      exit(EX_TEMPFAIL);
    }
  sprintf(outbuf, "MAIL FROM: %s\r\n", sender);
  if (fputs(outbuf, smtpout) == EOF)
    {
      fprintf(stderr, "Emil: smtp error on write\n");
      exit(EX_TEMPFAIL);
    }
  fflush(smtpout);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: %s", outbuf);
#endif
  r = smtp_get();
  if (r != 250)
    {
      fprintf(stderr, "Emil: sender %s not accepted by %s", sender, host);
      exit(EX_NOUSER);
    }
  
  sprintf(outbuf, "RCPT TO: %s\r\n", recipient);
  if (fputs(outbuf, smtpout) == EOF)
    {
      fprintf(stderr, "Emil: smtp error on write\n");
      exit(EX_TEMPFAIL);
    }
  fflush(smtpout);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: %s", outbuf);
#endif
  r = smtp_get();
  if (r != 250)
    {
      fprintf(stderr, "Emil: recipient %s not accepted by %s\n", recipient, host);
      exit(EX_NOUSER);
    }
  
  sprintf(outbuf, "DATA\r\n");
  if (fputs(outbuf, smtpout) == EOF)
    {
      fprintf(stderr, "Emil: smtp error on write\n");
      exit(EX_TEMPFAIL);
    }
  fflush(smtpout);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: DATA\r\n");
#endif
  r = smtp_get();
  if (r != 250 && r != 354)
    {
      fprintf(stderr, "Emil: data transfer to %s failed\n", host);
      exit(EX_NOUSER);
    }
#endif
}

void
smtp_close(char *host)
{
  int r;
  sprintf(outbuf, "\r\n.\r\n");
  fputs(outbuf, smtpout);
  fflush(smtpout);
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: .\r\n");
#endif
  r = smtp_get();
  if (r != 250)
    {
      fprintf(stderr, "Emil: %s said %d after data transer completed\n", host, r);
      exit(EX_TEMPFAIL);
    }      
  sprintf(outbuf, "QUIT\r\n");
#ifdef DEBUG
  if (edebug)
    fprintf(stderr, "SMTP write: QUIT\r\n");
#endif
  fputs(outbuf, smtpout);
  fflush(smtpout);
  r = smtp_get();
}

int
smtp_get()
{
  int i;
  for(;;)
    {
      if ((fgets(inbuf, SMTPBUF, smtpin)) == NULL)
	{
	  fprintf(stderr, "Emil SMTP: Error on read.\n");
	  exit(EX_TEMPFAIL);
	}
#ifdef DEBUG
      if (edebug)
	fprintf(stderr, "SMTP read:%s:", inbuf);
#endif
      if (inbuf[0] == '4' || inbuf[0] == '5')
	{
	  /* Serious error */
	  exit(EX_NOUSER);
	}
      i = atoi(inbuf);
      if (inbuf[3] == '-')
	continue;
      if (i < 100)
	continue;
      return(i);
    }
}

