/* @(#) $Header: mail_bbs.c,v 1.22 94/10/06 16:15:30 deyke Exp $ */

/* BBS Mail Delivery Agent */

#include <sys/types.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#include "bbs.h"

#include "global.h"
#include "mbuf.h"
#include "timer.h" /*@*/
#include "transport.h"
#include "mail.h"

struct mesg {
  int state;
#define BBS_OPEN_STATE    0
#define BBS_SID_STATE     1
#define BBS_COMMAND_STATE 2
#define BBS_SUBJECT_STATE 3
#define BBS_SEND_STATE    4
#define BBS_UNLK_STATE    5
#define BBS_QUIT_STATE    6
#define BBS_EOF_STATE     9 /*@*/
  char buf[1024];
  int cnt;
  FILE * fp;
  struct timer timer; /*@*/
  struct mailsys *sp;
  struct transport_cb *tp;
};

static char *Mydomainname;

static void mail_bbs_transaction(struct mesg *mp);
static void mail_bbs_recv_upcall(struct transport_cb *tp, int cnt);
static void mail_bbs_send_upcall(struct transport_cb *tp, int cnt);
static void mail_bbs_state_upcall(struct transport_cb *tp);

/*---------------------------------------------------------------------------*/

/*@*/

static void timer_timeout(void *p)
{
  struct mesg *mp;

  mp = (struct mesg *) p;
  mp->buf[0] = 0;
  mp->cnt = 0;
  mail_bbs_transaction(mp);
}

/*@*/

/*---------------------------------------------------------------------------*/

static void mail_bbs_transaction(struct mesg *mp)
{

  char *p;
  char tmp[1024];
  struct mailjob *jp;
  struct stat statbuf;
  struct tm *tm;

  jp = mp->sp->jobs;
  switch (mp->state) {
  case BBS_OPEN_STATE:
    if (mp->buf[mp->cnt-1] == '>') {
      mp->state = BBS_SID_STATE;
      sprintf(tmp, "%s\n", "[THEBOX-1.8-H$]");
      transport_send(mp->tp, qdata(tmp, strlen(tmp)));
    }
    break;
  case BBS_SID_STATE:
    if (mp->buf[mp->cnt-1] == '>') {
nextjob:
      mp->state = BBS_COMMAND_STATE;
      sprintf(tmp,
	      "S %s @ %s < %s\n",
	      get_user_from_path(jp->to),
	      get_host_from_path(jp->to),
	      get_user_from_path(jp->from));
      for (p = tmp; *p; p++) *p = Xtoupper(*p);
      transport_send(mp->tp, qdata(tmp, strlen(tmp)));
    }
    break;
  case BBS_COMMAND_STATE:
    if (Xtolower(mp->buf[0]) != 'o') {
      strcpy(jp->return_reason, "Mail not accepted");
      mail_return(jp);
      mp->state = BBS_QUIT_STATE;
      transport_close(mp->tp);
      break;
    }
    mp->state = BBS_SUBJECT_STATE;
    sprintf(tmp, "%s\n", *jp->subject ? jp->subject : "no subject");
    transport_send(mp->tp, qdata(tmp, strlen(tmp)));

    /*@*/
    mp->timer.func = timer_timeout;
    mp->timer.arg = mp;
    set_timer(&mp->timer, 5000);
    start_timer(&mp->timer);
    break;
    /*@*/

    /* fall thru */
  case BBS_SUBJECT_STATE:
    if (run_timer(&mp->timer)) break; /*@*/
    if (!stat(jp->cfile, &statbuf) && (mp->fp = fopen(jp->dfile, "r"))) {
      mp->state = BBS_SEND_STATE;
      while (fgets(tmp, sizeof(tmp), mp->fp) && *tmp != '\n') ;
      if (!Mydomainname) {
	sprintf(tmp, "%s.%s", Hostname, MYDOMAIN);
	for (p = tmp; *p; p++) *p = Xtoupper(*p);
	Mydomainname = strdup(tmp);
      }
      tm = gmtime(&statbuf.st_mtime);
      sprintf(tmp,
	      "R:%02d%02d%02d/%02d%02dz @:%s\n",
	      tm->tm_year % 100,
	      tm->tm_mon + 1,
	      tm->tm_mday,
	      tm->tm_hour,
	      tm->tm_min,
	      Mydomainname);
      transport_send(mp->tp, qdata(tmp, strlen(tmp)));
    } else {
      mp->state = BBS_QUIT_STATE;
      transport_close(mp->tp);
    }
    break;
  case BBS_SEND_STATE:
    break;
  case BBS_UNLK_STATE:
    if (mp->buf[mp->cnt-1] == '>') {
      unlink(jp->cfile);
      unlink(jp->dfile);
      if (*jp->xfile) unlink(jp->xfile);
      mp->sp->jobs = jp->next;
      free(jp);
      if ((jp = mp->sp->jobs)) goto nextjob;
      mp->state = BBS_QUIT_STATE;
      transport_close(mp->tp);
    }
    break;
  case BBS_QUIT_STATE:
    break;

  /*@*/
  case BBS_EOF_STATE:
    if (run_timer(&mp->timer)) break;
    transport_send(mp->tp, qdata("\032\n", 2));
    mp->state = BBS_UNLK_STATE;
    break;
  /*@*/

  }
}

/*---------------------------------------------------------------------------*/

static void mail_bbs_recv_upcall(struct transport_cb *tp, int cnt)
{

  int c;
  struct mbuf *bp;
  struct mesg *mp;

  mp = (struct mesg *) tp->user;
  mp->sp->state = MS_TALKING;
  transport_recv(tp, &bp, 0);
  while ((c = PULLCHAR(&bp)) != -1)
    if (c == '\n') {
      mp->buf[mp->cnt] = '\0';
      mail_bbs_transaction(mp);
      mp->cnt = 0;
    }
    else if (mp->cnt < sizeof(mp->buf) - 1)
      mp->buf[mp->cnt++] = c;
}

/*---------------------------------------------------------------------------*/

static void mail_bbs_send_upcall(struct transport_cb *tp, int cnt)
{

  char *p;
  int c;
  struct mbuf *bp;
  struct mesg *mp;

  mp = (struct mesg *) tp->user;
  if (mp->state != BBS_SEND_STATE || cnt <= 0) return;
  if (!(bp = alloc_mbuf(cnt))) return;
  p = bp->data;
  c = 0;
  while (p - bp->data < cnt && (c = getc(mp->fp)) != EOF)
    if (c && c != '\004' && c != '\032') *p++ = c;
  if ((bp->cnt = p - bp->data))
    transport_send(tp, bp);
  else
    free_p(bp);
  if (c == EOF) {
    fclose(mp->fp);
    mp->fp = 0;

    /*@*/ /*
    transport_send(mp->tp, qdata("\032\n", 2));
    mp->state = BBS_UNLK_STATE;
    */ /*@*/

    /*@*/
    mp->timer.func = timer_timeout;
    mp->timer.arg = mp;
    set_timer(&mp->timer, 5000);
    start_timer(&mp->timer);
    mp->state = BBS_EOF_STATE;
    /*@*/

  }
}

/*---------------------------------------------------------------------------*/

static void mail_bbs_state_upcall(struct transport_cb *tp)
{
  struct mesg *mp;

  if ((mp = (struct mesg *) tp->user)) {
    if (mp->fp) fclose(mp->fp);
    if (!mp->sp->jobs)
      mp->sp->state = MS_SUCCESS;
    else
      mailer_failed(mp->sp);
    stop_timer(&mp->timer); /*@*/
    free(mp);
  }
  transport_del(tp);
}

/*---------------------------------------------------------------------------*/

void mail_bbs(struct mailsys *sp)
{
  struct mesg *mp;

  mp = (struct mesg *) calloc(1, sizeof(struct mesg));
  mp->sp = sp;
  if ((mp->tp = transport_open(sp->protocol, sp->address, mail_bbs_recv_upcall, mail_bbs_send_upcall, mail_bbs_state_upcall, (char *) mp))) {
    mp->tp->recv_mode = EOL_LF;
    mp->tp->send_mode = strcmp(sp->protocol, "tcp") ? EOL_CR : EOL_CRLF;
    transport_set_timeout(mp->tp, 3600);
  } else {
    mailer_failed(sp);
    free(mp);
  }
}

