/*****************************************************************************

  POPular -- A POP3 server and proxy for large mail systems

  $Id: pdeliver.c,v 1.17 2001/04/30 12:20:34 sqrt Exp $

  http://www.remote.org/jochen/mail/popular/

******************************************************************************

  Copyright (C) 1999-2001  Jochen Topf <jochen@remote.org>

  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 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA

*****************************************************************************/

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include "popular.h"


int debug;


/*****************************************************************************

  get_dir_size()

  Get size of a directory.

*****************************************************************************/
int
get_dir_size(const char *name, int *num)
{
  DIR *dir;
  struct dirent *de;
  struct stat sb;
  int sum=0;
  char *p, *pp, filename[MAXLEN_MAILFILE+1];

  dir = opendir(name);
  if (!dir) {
    /* XLOG-DOC:ADM:0056:opendir_failed
     * The opendir() system call failed while trying to determine the
     * mailbox size. */
    xlog_printf(xlog_adm, 0x0056, "opendir_failed dir='%s' errno=%d errmsg='%s'", name, errno, strerror(errno));
    exit(RCODE_ERR);
  }

  while ((de = readdir(dir))) {
    int size = 0;

    if (de->d_name[0] == '.') continue;  /* don't count dot files */

    /* try to get message size from file name. The format must be as
       follows: The name must end in a _ (underscore) and the file size. */
    if ((p = strrchr(de->d_name, '_'))) {
      long l=strtol(p+1, &pp, 10);
      if (! *pp) size = (int)l;
    }

    if (size == 0) {
      (void) strlcpy(filename, name, sizeof(filename));
      (void) strlcat(filename, "/", sizeof(filename));
      (void) strlcat(filename, de->d_name, sizeof(filename));
      if (stat(filename, &sb) < 0) {
	/* XLOG-DOC:ADM:0053:stat_failed
	 * The stat() of a mail message file failed. A size of 0 will be
	 * used. The quota can't be calculated accurately. */
        xlog_printf(xlog_adm, 0x0053, "stat_failed file='%s' errno=%d errmsg='%s'", filename, errno, strerror(errno));
      } else {
        size = sb.st_size;
      }
    }
    sum += size;
    (*num)++;
    DEBUG3(DG_MAIN, "get_dir_size", "sum=%d size=%d num=%d", sum, size, *num);
  }

  closedir(dir);
  return sum;
}


/*****************************************************************************

  check_quota()

*****************************************************************************/
void
check_quota(int size, int count)
{
  int s, c=0;

  if ((size == 0) && (count == 0)) return;

  size *= 1024*1024;
  s = get_dir_size("cur", &c) + get_dir_size("new", &c);

  if ((size > 0) && (s >= size)) {
    /* XLOG-DOC:ERR:0057:quota_exceeded
     * The quota on a mailbox was exceeded. No mail will be delivered. */
    xlog_printf(xlog_err, 0x0057, "quota_exceeded size=%d quota=%d", s, size);
    exit(RCODE_QUOTA);
  }

  if ((count > 0) && (c >= count)) {
    /* XLOG-DOC:ERR:0058:quota_exceeded
     * The quota on a mailbox was exceeded. No mail will be delivered. */
    xlog_printf(xlog_err, 0x0058, "quota_exceeded count=%d quota=%d", c, count);
    exit(RCODE_QUOTA);
  }

  DEBUG2(DG_MAIN, "check_quota", "quota_info count=%d size=%d", c, s);

  return;
}


/*****************************************************************************

  write_mail()

  returns size (including CRLF but without . stuffing)

*****************************************************************************/
int
write_mail(FILE *in, FILE *out, mail_format_t format)
{
  char buf[1024+1];
  int n=0, l, contline=0;

  while (fgets(buf, sizeof(buf)-1, in)) {
    if ((format == mfNET) && (buf[0] == '.') &&
                             (buf[1] == '\r') &&
			     (buf[2] == '\n') &&
                             (buf[3] == 0)) break;
    l = strlen(buf);
    if (buf[l-1] == '\n') {
      contline = 0;
    } else {
      if ((format == mfNET) && (contline == 0) && (buf[0] == '.')) {
        if (fputs(buf+1, out) < 0) return -1;
        n += l-1;
      } else {
        if (fputs(buf, out) < 0) return -1;
        n += l;
      }
      contline = 1;
      continue;
    }
   
    switch (format) {
      case mfLF:
        buf[l-1] = '\r';
        buf[l]   = '\n';
        buf[l+1] = '\0';
        if (fputs(buf, out) < 0) return -1;
        n += l+1;
        break;
      case mfCRLF:
        if (fputs(buf, out) < 0) return -1;
        n += l;
        break;
      case mfNET:
        if (buf[0] == '.') {
          if (fputs(buf+1, out) < 0) return -1;
          n += l-1;
        } else {
          if (fputs(buf, out) < 0) return -1;
          n += l;
        }
        break;
      default:
        return -1;
    } 
  }

  return n;
}


/*****************************************************************************

  print_usage()

*****************************************************************************/
void
print_usage(char *prg)
{
  printf("Usage: %s [OPTION] ... [MAILBOX]\n", prg);
  printf("Deliver mail from stdin into a mailbox.\n");
  printf("Options:\n");
  printf("  -f, --format=FORMAT    format of mail data (default: `LF')\n");
  printf("  -i, --id=ID            id for logging\n");
  printf("  -l, --logfile=FILE     name of log file (default: `%s')\n", PDELIVER_LOG_FILE);
  printf("  -m, --mailboxdir=DIR   set mailbox directory (default: `%s')\n", POPDIR);
  printf("  -q, --quota=QUOTA      max size of mailbox in MB (default: no limit)\n");
  printf("  -Q, --maxnum=NUM       max number of mails in mailbox (default: no limit)\n");
  printf("  -d, --debug            enable debug logging\n");
  printf("      --help             print this help message\n");
  printf("      --version          print version information\n");
  printf("\n");
  printf("Formats:\n");
  printf("  LF     line ends are LF (UNIX format)\n");
  printf("  CRLF   line ends are CRLF\n");
  printf("  NET    line ends are CRLF and leading dots (.) are quoted\n");
  printf("         (SMTP/POP wire format)\n");
  printf("\n");
}


/*****************************************************************************

  main

*****************************************************************************/
int
main(int argc, char *argv[])
{
  int fd, c, size;
  int quota_count, quota_size;
  char *logfile, *mbdir, *mailbox, *id;
  mail_format_t format;
  char tmpbuf[MAXLEN_MAILFILE+1];
  char newbuf[MAXLEN_MAILFILE+1];
  FILE *tmpfile;
  char *prgname = argv[0];

  static struct option long_options[] = {
    { "help",          no_argument,       NULL, 0 },
    { "version",       no_argument,       NULL, 0 },
    { "format",        required_argument, NULL, 'f' },
    { "id",            required_argument, NULL, 'i' },
    { "logfile",       required_argument, NULL, 'l' },
    { "mailboxdir",    required_argument, NULL, 'm' },
    { "quota",         required_argument, NULL, 'q' },
    { "maxnum",        required_argument, NULL, 'Q' },
    { "debug",         no_argument,       NULL, 'd' },
    { NULL,            0,                 NULL, 0 }
  };


  /***************************************************************************
    Defaults for parameters
  ***************************************************************************/
  mbdir       = POPDIR;
  logfile     = PDELIVER_LOG_FILE;
  id          = NULL;
  format      = mfLF;
  quota_size  = 0;
  quota_count = 0;
  debug       = 0;


  /***************************************************************************
    Parse command line options
  ***************************************************************************/
  while (1) {
    int option_index = 0;
    c = getopt_long(argc, argv, "f:i:l:m:q:Q:d", long_options, &option_index);

    if (c == -1) break;

    switch (c) {
      case 0:
        if (! strcmp(long_options[option_index].name, "version")) {
          printf("pdeliver - POPular POP server suite %s\n", VERSION);
          exit(RCODE_OK);
        } else if (! strcmp(long_options[option_index].name, "help")) {
          print_usage(prgname);
          exit(RCODE_OK);
        } else {
          fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name);
          exit(RCODE_INTERNAL);
        }
        break;
      case 'q':         /* quota mailbox size */
	quota_size = get_int(optarg, 0, 10000, -1, -2, 0);
        if (quota_size < 0) {
          fprintf(stderr, "%s: quota must be between 1 an 10000 MByte or 0 for unlimited.\n", prgname);
          exit(RCODE_CMDLINE);
        }
        break;
      case 'Q':         /* quota mail number */
	quota_count = get_int(optarg, 0, 10000, -1, -2, 0);
        if (quota_count < 0) {
          fprintf(stderr, "%s: max number of mails must be between 1 an 10000 or 0 for unlimited.\n", prgname);
          exit(RCODE_CMDLINE);
        }
        break;
      case 'f':         /* format */
        if (!strcmp(optarg, "CRLF")) format = mfCRLF;
        else if (!strcmp(optarg, "LF")) format = mfLF;
        else if (!strcmp(optarg, "NET")) format = mfNET;
        else {
          fprintf(stderr, "%s: unknown format: `%s'\n", prgname, optarg);
          exit(RCODE_CMDLINE);
        }
        break;
      case 'i':         /* id */
        id = optarg;
        break;
      case 'l':         /* logfile */
        logfile = optarg;
        break;
      case 'm':
        mbdir = optarg;
        break;
      case 'd':
        debug = DG_ALL;
        break;
      default:
        fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
        exit(RCODE_CMDLINE);
    }
  }

  if (optind == argc-1) {
    mailbox = argv[optind];
  } else if (optind < argc) {
    fprintf(stderr, "%s: extra arguments.\nTry `%s --help' for more information.\n", prgname, prgname);
    exit(RCODE_CMDLINE);
  } else {
    fprintf(stderr, "%s: mailbox argument missing.\nTry `%s --help' for more information.\n", prgname, prgname);
    exit(RCODE_CMDLINE);
  }

  
  /***************************************************************************
    Start logging.
  ***************************************************************************/
  xlog_open(PDELIVER_PRG_NAME, logfile, PDELIVER_LOG_MODE);
  if (id) xlog_set_id(id);


  /***************************************************************************
    Prepare mailbox directory and check quota.
  ***************************************************************************/
  if (chdir(mbdir) != 0) {
    /* XLOG-DOC:SOS:0059:mailboxdir_error
     * Pdeliver can't change into the mailbox directory. */
    xlog_printf(xlog_sos, 0x0059, "mailboxdir_error dir='%s' errno=%d errmsg='%s'", mbdir, errno, strerror(errno));
    exit(RCODE_ERR);
  }
  if (! mailbox_prepdir(mailbox)) {
    exit(RCODE_ERR);
  }
  check_quota(quota_size, quota_count);


  /***************************************************************************
    Create filename for tmp file, open it and write mail into it.
  ***************************************************************************/
  snprintf(tmpbuf, sizeof(tmpbuf), "tmp/%lu.%d", time(NULL), getpid());
  tmpbuf[sizeof(tmpbuf)-1] = 0;
  
  if ((fd = open(tmpbuf, O_WRONLY|O_CREAT|O_EXCL, MAILFILEMODE)) < 0) {
    /* XLOG-DOC:ADM:005a:open_tmpfile_failed
     * Opening the tmp file for a new mail message failed. */
    xlog_printf(xlog_adm, 0x005a, "open_tmpfile_failed file='%s' errno=%d errmsg='%s'", tmpbuf, errno, strerror(errno));
    exit(RCODE_ERR);
  }
  if ((tmpfile = fdopen(fd, "w")) == NULL) {
    /* XLOG-DOC:ADM:005b:fdopen_failed
     * fdopen() on new tmp file for mail message failed. */
    xlog_printf(xlog_adm, 0x005b, "fdopen_failed errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }
  if ((size = write_mail(stdin, tmpfile, format)) < 0) {
    /* XLOG-DOC:ADM:005c:write_mail_failed
     * A write to the tmp file for a new mail message failed. */
    xlog_printf(xlog_adm, 0x005c, "write_mail_failed errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }
  if (fclose(tmpfile) != 0) {
    /* XLOG-DOC:ADM:005d:close_failed
     * The close on the tmp file for a new mail message failed. */
    xlog_printf(xlog_adm, 0x005d, "close_failed errno=%d errmsg='%s'", errno, strerror(errno));
    exit(RCODE_ERR);
  }


  /***************************************************************************
    Create filename for new file and rename mail file.
  ***************************************************************************/
#ifdef STORE_MAIL_SIZE_IN_NAME
  snprintf(newbuf, sizeof(newbuf), "new/%s_%d", tmpbuf+4, size);
#else
  strlcpy(newbuf, tmpbuf, sizeof(newbuf));
  newbuf[0] = 'n'; newbuf[1] = 'e'; newbuf[2] = 'w';
#endif
  if (rename(tmpbuf, newbuf) != 0) {
    /* XLOG-DOC:ADM:0054:rename_failed
     * After the mail message has been written to the 'tmp' directory, it
     * can't be renamed to the 'new' directory. */
    xlog_printf(xlog_adm, 0x0054, "rename_failed filename='%s' errno=%d errmsg='%s'", tmpbuf, errno, strerror(errno));
    exit(RCODE_ERR);
  }

  /* XLOG-DOC:INF:0055:delivered
   * A mail has been delivered successfully. */
  xlog_printf(xlog_inf, 0x0055, "delivered box='%s' size=%d", mailbox, size);

  exit(RCODE_OK);
}


/** THE END *****************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1