/* nullpop: A NOOP POP3 Server. Based on:

   virtualmail-pop3d - a POP3 server with virtual domains support
   This code is licensed under the GPL; it has several authors.
   vm-pop3d is based on:
   GNU POP3 - a small, fast, and efficient POP3 daemon
   Copyright (C) 1999 Jakob 'sparky' Kaivo <jkaivo@nodomainname.net>

   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, 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 FITNESS 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.  */


#define _GNU_SOURCE
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <ctype.h>

/* A command that doesn't exist */
#define BAD_COMMAND	"Invalid command"

/* Incorrect number of arguments passed to a command */
#define BAD_ARGS	"Invalid arguments"

/* An action on a message that doesn't exist */
#define NO_MESG		"No such message"

/* A command that is known but not implemented */
#define NOT_IMPL	"Not implemented"

/* The command argument was > 40 characters */
#define TOO_LONG	"Argument too long"

/* Longest legal POP command */
#define POP_MAXCMDLEN	255



/* Maximum length of a hostname (is this defined somewhere else?) */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN        64
#endif

#define OK		0
#define ERR_BAD_ARGS	2
#define ERR_NO_MESG	4
#define ERR_NOT_IMPL	5
#define ERR_BAD_CMD	6
#define ERR_TOO_LONG	8
#define ERR_NO_MEM	9
#define ERR_DEAD_SOCK	10
#define ERR_SIGNAL	11
#define ERR_FILE        12
#define ERR_NO_OFILE    13
#define ERR_TIMEOUT	14

unsigned int timeout;
int ifile;
FILE *ofile;

/* Prints out usage information and exits the program */
void
pop3_usage(char *argv0)
{
  printf("Usage: %s [OPTIONS]\n", argv0);
  printf("Runs the nullpop POP3 daemon.\n\n");
  printf("  -t      sets idle timeout to TIMEOUT seconds\n");
  exit(2);
}

/* This is called if it needs to quit without going to the UPDATE stage.
   This is used for conditions such as out of memory, a broken socket, or
   being killed on a signal */
int
pop3_abquit(int reason)
{
  switch (reason) {
  case ERR_NO_MEM:
    if (NULL != ofile)
      fprintf(ofile, "-ERR Out of memory, quitting\r\n");
    syslog(LOG_ERR, "Out of memory");
    break;
  case ERR_DEAD_SOCK:
    if (NULL != ofile)  /* should this even print to socket? */
      fprintf(ofile, "-ERR Socket closed, quitting\r\n");
    syslog(LOG_ERR, "Socket closed");
    break;
  case ERR_SIGNAL:
    if (NULL != ofile)
      fprintf(ofile, "-ERR Quitting on signal\r\n");
    break;
  case ERR_TIMEOUT:
    if (NULL != ofile)
      fprintf(ofile, "-ERR Session timed out\r\n");
    else
      syslog(LOG_INFO, "Session timed out for no user");
    break;
  case ERR_NO_OFILE:
    syslog(LOG_ERR, "Quitting - couldn't open stream");
    break;
  default:
    if (NULL != ofile)
      fprintf(ofile, "-ERR Quitting (reason unknown)\r\n");
    syslog(LOG_ERR, "Unknown quit");
    break;
  }
  fflush(ofile);
  exit(1);
}

/* Default signal handler to call the pop3_abquit() function */
void
pop3_signal(int signal)
{
  syslog(LOG_ERR, "Quitting on signal: %d", signal);
  pop3_abquit(ERR_SIGNAL);
}


/* Gets a line of input from the client */
char *
pop3_readline(int fd)
{
  fd_set rfds;
  struct timeval tv;
  char buf[1024], *ret = NULL;
  int available;

  FD_ZERO(&rfds);
  FD_SET(fd, &rfds);
  tv.tv_sec = timeout;
  tv.tv_usec = 0;
  memset(buf, '\0', 1024);

  while (strchr(buf, '\n') == NULL) {
    if (timeout > 0) {
      available = select(fd + 1, &rfds, NULL, NULL, &tv);
      if (!available)
	pop3_abquit(ERR_TIMEOUT);
    }
    if (read(fd, buf, 1024) < 1)
      pop3_abquit(ERR_DEAD_SOCK);

    if (ret == NULL) {
      ret = malloc((strlen(buf) + 1) * sizeof(char));
      strcpy(ret, buf);
    } else {
      ret = realloc(ret, (strlen(ret) + strlen(buf) + 1) * sizeof(char));
      strcat(ret, buf);
    }
  }
  return ret;
}

/* Takes a string as input and returns either the remainder of the string
   after the first space, or a zero length string if no space */
char *
pop3_args(const char *cmd)
{
  int space = -1, i = 0, len;
  char *buf;

  len = strlen(cmd) + 1;
  buf = malloc(len * sizeof(char));
  if (buf == NULL)
    pop3_abquit(ERR_NO_MEM);

  while (space < 0 && i < len) {
    if (cmd[i] == ' ')
      space = i + 1;
    else if (cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n')
      len = i;
    i++;
  }

  if (space < 0)
    buf[0] = '\0';
  else {
    for (i = space; i < len; i++)
      if (cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n')
	buf[i - space] = '\0';
      else
	buf[i - space] = cmd[i];
  }

  return buf;
}

/* This takes a string and returns the string up to the first space or end of
   the string, whichever occurs first */
char *
pop3_cmd(const char *cmd)
{
  char *buf;
  int i = 0, len;

  len = strlen(cmd) + 1;
  buf = malloc(len * sizeof(char));
  if (buf == NULL)
    pop3_abquit(ERR_NO_MEM);

  for (i = 0; i < len; i++) {
    if (cmd[i] == ' ' || cmd[i] == '\0' || cmd[i] == '\r' || cmd[i] == '\n')
      len = i;
    else
      buf[i] = cmd[i];
  }
  buf[i - 1] = '\0';
  return buf;
}

/* The main part of the daemon. This function reads input from the client and
   executes the proper functions. Also handles the bulk of error reporting. */
int
pop3_mainloop(int infile, int outfile)
{
  int status = OK;
  char *buf, *arg, *cmd;
  struct sockaddr_in name;
  int namelen = sizeof(name);
  char *temp_domain;

  ifile = infile;
  ofile = fdopen(outfile, "w+");
  if (ofile == NULL)
    pop3_abquit(ERR_NO_OFILE);

  if (getpeername(infile,
        (struct sockaddr *) & name, (socklen_t *) & namelen) == 0) {
    if ((temp_domain = (char *) inet_ntoa(name.sin_addr)))
      syslog(LOG_INFO,"Connect from %s", temp_domain);
    else 
      syslog(LOG_INFO, "Connection opened");
  }
  else 
    syslog(LOG_INFO, "Incoming connection opened");

  fflush(ofile);
  fprintf(ofile, "+OK POP3 \r\n");

  for (;;) {
    fflush(ofile);
    status = OK;
    buf = pop3_readline(ifile);
    cmd = pop3_cmd(buf);
    arg = pop3_args(buf);
   
    if (strlen(arg) > POP_MAXCMDLEN || strlen(cmd) > POP_MAXCMDLEN)
      status = ERR_TOO_LONG;
    else if (strlen(cmd) > 4)
      status = ERR_BAD_CMD;
    else if (strncasecmp(cmd, "USER", 4) == 0) {
      fprintf(ofile, "+OK\r\n");
      status = OK;
    } else if (strncasecmp(cmd, "PASS", 4) == 0) {
      fprintf(ofile, "+OK\r\n");
      status = OK;
    } else if (strncasecmp(cmd, "QUIT", 4) == 0) {
      fprintf(ofile, "+OK\r\n");
      fflush(ofile);
      break;
    } else if (strncasecmp(cmd, "AUTH", 4) == 0) {
      status = ERR_NOT_IMPL;
    } else if (strncasecmp(cmd, "STAT", 4) == 0) {
      fprintf(ofile, "+OK %d %d\r\n", 0, 0);
      status = OK;
    } else if (strncasecmp(cmd, "LIST", 4) == 0) {
      if (strchr(arg, ' ') != NULL) {
        status = ERR_BAD_ARGS;
      } else if (strlen(arg) == 0) {
        fprintf(ofile, "+OK\r\n");
        fprintf(ofile, ".\r\n");
        status = OK;
      } else {
        status = ERR_NO_MESG;
      }
    } else if (strncasecmp(cmd, "RETR", 4) == 0) {
      if ((strlen(arg) == 0) || (strchr(arg, ' ') != NULL))
        status = ERR_BAD_ARGS;
      else
        status = ERR_NO_MESG;
    } else if (strncasecmp(cmd, "DELE", 4) == 0) {
      if ((strlen(arg) == 0) || (strchr(arg, ' ') != NULL))
        status = ERR_BAD_ARGS;
      else
        status = ERR_NO_MESG;
    } else if (strncasecmp(cmd, "NOOP", 4) == 0) {
      if (strlen(arg) != 0)
        status = ERR_BAD_ARGS;
      else {
        fprintf(ofile, "+OK\r\n");
        status = OK;
      }
    } else if (strncasecmp(cmd, "RSET", 4) == 0) {
      if (strlen(arg) != 0)
        status = ERR_BAD_ARGS;
      else {
        fprintf(ofile, "+OK\r\n");
        status = OK;
      }
    } else if ((strncasecmp(cmd, "TOP", 3) == 0) && (strlen(cmd) == 3)) {
      status = ERR_NO_MESG;
    } else if (strncasecmp(cmd, "UIDL", 4) == 0) {
      if (strchr(arg, ' ') != NULL)
        status = ERR_BAD_ARGS;
      else 
        status = ERR_NO_MESG;
    } else if (strncasecmp(cmd, "CAPA", 4) == 0) {
      if (strlen(arg) != 0)
        status = ERR_BAD_ARGS;
      else {
        fprintf(ofile, "+OK Capability list follows\r\n");
        fprintf(ofile, "TOP\r\n");
        fprintf(ofile, "USER\r\n");
        fprintf(ofile, "RESP-CODES\r\n");
        fprintf(ofile, "UIDL\r\n");
        fprintf(ofile, ".\r\n");
        status = OK;
      }
    } else {
      status = ERR_BAD_CMD;
    }

    if (status == OK)
      fflush(ofile);
    else if (status == ERR_BAD_ARGS)
      fprintf(ofile, "-ERR " BAD_ARGS "\r\n");
    else if (status == ERR_NO_MESG)
      fprintf(ofile, "-ERR " NO_MESG "\r\n");
    else if (status == ERR_NOT_IMPL)
      fprintf(ofile, "-ERR " NOT_IMPL "\r\n");
    else if (status == ERR_BAD_CMD)
      fprintf(ofile, "-ERR " BAD_COMMAND "\r\n");
    else if (status == ERR_TOO_LONG)
      fprintf(ofile, "-ERR " TOO_LONG "\r\n");

    free(buf);
    free(cmd);
    free(arg);
  }

  fflush(ofile);
  return OK;
}

int
main(int argc, char **argv)
{
  int c = 0;
  timeout = 0;			/* Timeout turned off */

  /* Set the signal handlers */
  signal(SIGINT, pop3_signal);
  signal(SIGQUIT, pop3_signal);
  signal(SIGTERM, pop3_signal);

/* Don't die when a process goes away unexpectedly.
   Ignore write on a pipe with no reader.  */
  signal(SIGPIPE, SIG_IGN);

  while ((c = getopt(argc, argv, "t:") && c)) {
    switch (c) {
    case 't':
      timeout = atoi(optarg);
      break;
    default:
      pop3_usage(argv[0]);
      break;
    }
  }

  /* Set up for syslog */
  openlog("nullpop", LOG_PID, LOG_MAIL);

  pop3_mainloop(fileno(stdin), fileno(stdout));

  /* Close the syslog connection and exit */
  closelog();
  return OK;
}


syntax highlighted by Code2HTML, v. 0.9.1