#ifndef __lint
static const char rcsid[] = "@(#) $Header: pop2d.c,v 1.22 94/10/09 08:23:09 deyke Exp $";
#endif

/* pop2d - Post Office Protocol server */

/*

   This server implements the POP2 protocol described in RFC937.
   It should work with any system derived from Unix 4.2/4.3 BSD.
   It adds a small feature to the standard protocol:

   The non-standard command "save" is supported. If a "save"
   command is issued, any message deleted from the user's mail
   spool file is saved in the "save" file (by default this is
   named by appending ".bak" to the spool file).

   To install this do the following:

   1) Add a line to inetd.conf, as follows:

      pop2 stream tcp nowait root /etc/pop2d pop2d

   2) Add a line to /etc/services, as follows

      pop2 109/tcp

      Dont forget to remake maps if YP is running

*/

#include <sys/types.h>

#include <stdio.h>

#include <ctype.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#ifdef ibm032
#include <sys/fcntl.h>
#include <sys/file.h>
#define SEEK_SET L_SET
#endif

#include "configure.h"
#include "lockfile.h"

#define null    0
#define helo    1
#define fold    2
#define raed    3
#define retr    4
#define acks    5
#define ackd    6
#define nack    7
#define quit    8
#define save    9
#define NKEYS   9

static const struct key_word {
  const char *key;
  int val;
  int args;
} keytab[] = {
  { "null", null, 0 },
  { "helo", helo, 2 },
  { "fold", fold, 1 },
  { "read", raed, 1 },
  { "retr", retr, 0 },
  { "acks", acks, 0 },
  { "ackd", ackd, 0 },
  { "nack", nack, 0 },
  { "quit", quit, 0 },
  { "save", save, 1 }   /* Non standard */
};

#define AUTH    0
#define MBOX    1
#define ITEM    2
#define NEXT    3
#define DONE    4
#define ERRO    5

static const int state_tab[4][NKEYS] = {

/*             helo  fold  read  retr  acks  ackd  nack  quit  save  */
/*-------------------------------------------------------------------*/
/* AUTH: */  { MBOX, ERRO, ERRO, ERRO, ERRO, ERRO, ERRO, DONE, ERRO },
/* MBOX: */  { ERRO, MBOX, ITEM, ERRO, ERRO, ERRO, ERRO, DONE, MBOX },
/* ITEM: */  { ERRO, MBOX, ITEM, NEXT, ERRO, ERRO, ERRO, DONE, MBOX },
/* NEXT: */  { ERRO, ERRO, ERRO, ERRO, ITEM, ITEM, ITEM, DONE, ERRO }

};

/*

		State Diagram corresponding to the state transition table
		represented by the variable state_tab[][]

			     |
			     | helo
			     |
			    \|/
		       --------------      quit           -----------
	      ------->|     MBOX     |-----------------> |    DONE   |
       fold  |         --------------                     -----------
    or save  |         |   |      /|\                         /|\
	      ---------    |       |                           |
			   |       |                           |
		      read |       | fold or save              |
			  \|/      |                           |
		       --------------      quit                |
	      ------->|     ITEM     |-------------------------
       read  |         --------------                          |
	     |         |   |      /|\                          |
	      ---------    |       | ack(s/d)                  |
		      retr |       |                           |
			  \|/      |                           |
		       --------------      quit                |
		      |     NEXT     |-------------------------
		       --------------

						 --------------
						|     ERRO     |
						 --------------

*/

#define MAXMSGS         1000
#define SAVEFILE        ".bak"

struct message {
  long headp;
  int deleted;
  int lines;
};

static FILE *fsave;
static FILE *ftemp;
static char *mdir;
static char line[1024];
static char mailbox[1024];
static char mailtmp[1024];
static char myargv[4][1024];
static const char tmplate[] = "/tmp/popXXXXX";
static int cur_msg;
static int cur_msg_len;
static int msg_cnt;
static int myargc;
static int nlines;
static int nmsg;
static int opened;
static struct key_word key;
static struct message msg[MAXMSGS+2];
static struct passwd *pwd;

static int parse(void);
static int check_user(const char *user, const char *passwd);
static int lexical(void);
static int whatkey(char *s);
static int openit(void);
static void closeit(void);
static int msglen(int n);
static void outmsg(int n);
static int my_gets(char *s);
static int openbk(const char *savefile);

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

int main(int argc, char **argv)
{
  mdir = (argc >= 2) ? argv[1] : MAIL_DIR;
  if (*mdir) {
    gethostname(line, sizeof(line));
    printf("+ POP2 %s\r\n", line);
    while (parse() > 0) ;
    if (*mailtmp) unlink(mailtmp);
  } else
    printf("- Cannot find mail directory\r\n");
  return 0;
}

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

/* Main parser - guides us through states and checks protocol compliance */

static int parse(void)
{

  char savebox[1024];
  int next_state;
  int token;
  static int cur_state;

  alarm(6 * 3600);
  token = lexical();
  if (token < 0) return (-1);
  next_state = state_tab[cur_state][token-1];
  switch (next_state) {
  case MBOX:
    switch (token) {
    case helo:
      if (check_user(myargv[1], myargv[2])) {
	printf("- Logon incorrect\r\n");
	return (-1);
      }
      strcpy(mailbox, mdir);
      strcat(mailbox, "/");
      strcat(mailbox, myargv[1]);
      msg_cnt = openit();
      printf("#%d messages in %s\r\n", msg_cnt, mailbox);
      break;
    case fold:
      strcpy(mailbox, myargv[1]);
      msg_cnt = openit();
      printf("#%d messages in %s\r\n", msg_cnt, mailbox);
      break;
    case save:
      if (myargc >= 1 && strlen(myargv[1]))
	strcpy(savebox, myargv[1]);
      else {
	strcpy(savebox, mailbox);
	strcat(savebox, SAVEFILE);
      }
      if (openbk(savebox))
	printf("+ Saving to %s\r\n", savebox);
      else
	printf("- Can't access file %s\r\n", savebox);
      break;
    }
    break;
  case ITEM:
    if (!opened) {
      printf("- No open mailbox\r\n");
      return (-1);
    }
    switch (token) {
    case raed:
      if (myargc) cur_msg = atoi(myargv[1]);
      if (cur_msg < 1) {
	printf("- Illegal message number\r\n");
	return (-1);
      }
      break;
    case ackd:
      msg[cur_msg].deleted = 1;
    case acks:
      cur_msg++;
    case nack:
      break;
    }
    if ((cur_msg_len = msglen(cur_msg)))
      printf("=%d characters in message %d\r\n", cur_msg_len, cur_msg);
    else
      printf("=0 no more messages\r\n");
    break;
  case NEXT:
    if (!cur_msg_len) {
      printf("- Zero length message\r\n");
      return (-1);
    }
    outmsg(cur_msg);
    break;
  case DONE:
    printf("+ OK, bye, bye\r\n");
    closeit();
    return 0;
  case ERRO:
    printf("- Syntax error\r\n");
    return (-1);
  }
  cur_state = next_state;
  return 1;
}

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

#ifdef __386BSD__

#define crypt(key, salt) (key)

#else

char *crypt();

#endif

/* Check userid and password */

static int check_user(const char *user, const char *passwd)
{

  char *namep;
  char salt[3];

  pwd = getpwnam(user);
  if (!pwd) return (-1);
  salt[0] = pwd->pw_passwd[0];
  salt[1] = pwd->pw_passwd[1];
  salt[2] = 0;
  namep = crypt((char *) passwd, salt);
  if (strcmp(namep, pwd->pw_passwd)) return (-1);
  setgid(pwd->pw_gid);
  initgroups((char *) user, pwd->pw_gid);
  setuid(pwd->pw_uid);
  chdir(pwd->pw_dir);
  umask(077);
  return 0;
}

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

/* Perform lexical analysis on incomming stuff */

static int lexical(void)
{

  char inpline[1024];
  int i;

  for (i = 0; i < 3; i++) myargv[i][0] = '\0';
  myargc = -1;
  while (myargc < 0) {
    if (my_gets(inpline) < 0) return (-1);
    myargc = sscanf(inpline, "%s%s%s", myargv[0], myargv[1], myargv[2]);
    myargc--;
  }
  key = keytab[whatkey(myargv[0])];
  if (key.val == null) {
    printf("- Invalid command '%s'\r\n", myargv[0]);
    return (-1);
  }
  if (key.args && ((key.args - myargc) > 1)) {
    printf("- Not enough arguments for '%s'\r\n", myargv[0]);
    return (-1);
  }
  return key.val;
}

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

/* Now determine which keyword we've got... */

static int whatkey(char *s)
{
  int i;

  for (i = 0; i < 4; i++)
    if (s[i] >= 'A' && s[i] <= 'Z') s[i] = tolower(s[i]);
  for (i = NKEYS; i > 0; i--)
    if (!strncmp(keytab[i].key, s, 4)) return keytab[i].val;
  return null;
}

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

/* Open the specified mailbox */

static int openit(void)
{

  FILE * fmail;
  struct stat statb;

  if (opened) closeit();
  nmsg = 0;
  nlines = 0;
  strcpy(mailtmp, tmplate);
  mktemp(mailtmp);
  unlink(mailtmp);
  if (!(ftemp = fopen(mailtmp, "w+"))) return 0;
  if (!(fmail = fopen(mailbox, "r+"))) {
    fclose(ftemp);
    unlink(mailtmp);
    return 0;
  }
  lock_fd(fileno(fmail), 0);
  if (fstat(fileno(fmail), &statb) || statb.st_size == 0) {
    fclose(ftemp);
    unlink(mailtmp);
    fclose(fmail);
    return 0;
  }
  while (fgets(line, sizeof(line), fmail)) {
    if (strncmp("From ", line, 5) == 0) {
      msg[nmsg++].lines = nlines;
      nlines = 0;
      msg[nmsg].headp = ftell(fmail) - strlen(line);
    } else
      nlines++;
    fputs(line, ftemp);
  }
  msg[nmsg].lines = nlines;
  msg[nmsg+1].headp = ftell(fmail);
  fclose(fmail);
  cur_msg = 1;
  opened = 1;
  return nmsg;
}

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

static void closeit(void)
{

  FILE * fmail;
  int changed;
  int i;
  int j;
  struct stat statb;

  if (!opened) return;
  changed = 0;
  for (i = 1; i <= nmsg; i++)
    if (msg[i].deleted) {
      changed = 1;
      break;
    }
  if (changed) {
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    if (!(fmail = fopen(mailbox, "r+"))) return;
    lock_fd(fileno(fmail), 0);
    fstat(fileno(fmail), &statb);
    if (statb.st_size != msg[nmsg+1].headp) {
      fseek(fmail, msg[nmsg+1].headp, SEEK_SET);
      fseek(ftemp, msg[nmsg+1].headp, SEEK_SET);
      nlines = 0;
      while (fgets(line, sizeof(line), fmail)) {
	nlines++;
	fputs(line, ftemp);
      }
      nmsg++;
      msg[nmsg].lines = nlines - 1;
      msg[nmsg+1].headp = ftell(ftemp);
    }
#if 0
    ftruncate(fileno(fmail), 0L);
#else
    close(creat(mailbox, 0660));
#endif
    rewind(fmail);
    for (i = 1; i <= nmsg; i++) {
      if (!msg[i].deleted || fsave) {
	fseek(ftemp, msg[i].headp, SEEK_SET);
	for (j = 0; j <= msg[i].lines; j++) {
	  fgets(line, sizeof(line), ftemp);
	  fputs(line, msg[i].deleted ? fsave : fmail);
	}
      }
    }
    fclose(fmail);
  }
  fclose(ftemp);
  unlink(mailtmp);
  opened = 0;
}

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

/* Compute a message's length. <lf> counts as <cr><lf> */

static int msglen(int n)
{
  struct message *m;

  if (n > nmsg || msg[n].deleted) return 0;
  m = msg + n;
  fseek(ftemp, m->headp, SEEK_SET);
  fgets(line, sizeof(line), ftemp);
  return (msg[n+1].headp - m->headp) - strlen(line) + m->lines;
}

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

/*
 * Output msg to primary, convert to net standard ascii.
 * We are already positioned at the first line.
 */

static void outmsg(int n)
{
  int i;

  for (i = msg[n].lines; i > 0; i--) {
    fgets(line, sizeof(line), ftemp);
    line[strlen(line)-1] = '\0';
    printf("%s\r\n", line);
  }
}

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

/* Read an entire line from the network */

static int my_gets(char *s)
{
  int c;

  fflush(stdout);
  while ((c = getchar()) != '\n') {
    if (ferror(stdin) || feof(stdin)) return (-1);
    if (c != '\r') *s++ = c;
  }
  *s = '\0';
  return 0;
}

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

/* Open a backup file for deleted messages */

static int openbk(const char *savefile)
{
  if (fsave) fclose(fsave);
  if ((fsave = fopen(savefile, "a"))) return 1;
  return 0;
}

