Logo Search packages:      
Sourcecode: mailfront version File versions  Download package

pop3front-maildir.c

/* pop3front-maildir.c -- POP3 main program
 * Copyright (C) 2005  Bruce Guenter <bruceg@em.ca> or FutureQuest, Inc.
 * Development of this program was sponsored by FutureQuest, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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
 *
 * Contact information:
 * FutureQuest Inc.
 * PO BOX 623127
 * Oviedo FL 32762-3127 USA
 * http://www.FutureQuest.net/
 * ossi@FutureQuest.net
 */
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sysdeps.h>
#include <iobuf/iobuf.h>
#include <msg/msg.h>
#include <str/str.h>
#include "pop3.h"

typedef struct
{
  const char* filename;
  unsigned long size;
  int read;
  int deleted;
} msg;

#define MSG_DELETED ((unsigned long)-1)

const char program[] = "pop3front-maildir";
const int authenticating = 0;

static str msg_filenames;
static msg* msgs;

static long max_count;
static long max_new_count;
static long max_cur_count;

static long new_count;
static long cur_count;
static long del_count;
static long msg_count;
static long msg_bytes;
static long del_bytes;
static str tmp;

static long scan_dir(const char* subdir, str* list, long* countptr, long max)
{
  DIR* dir;
  direntry* entry;
  struct stat s;
  long count;
  
  if ((dir = opendir(subdir)) == 0) return 0;
  count = 0;
  while (!(max_count > 0 && msg_count >= max_count) &&
       !(max > 0 && count >= max) &&
       (entry = readdir(dir)) != 0) {
    if (entry->d_name[0] == '.') continue;
    if (!str_copys(&tmp, subdir)) return 0;
    if (!str_catc(&tmp, '/')) return 0;
    if (!str_cats(&tmp, entry->d_name)) return 0;
    if (stat(tmp.s, &s) == -1) continue;
    if (!S_ISREG(s.st_mode)) continue;
    if (!str_cat(list, &tmp)) return 0;
    if (!str_catc(list, 0)) return 0;
    msg_bytes += s.st_size;
    ++count;
    ++msg_count;
  }
  closedir(dir);
  *countptr = count;
  return 1;
}

static void make_msg(msg* m, const char* filename)
{
  struct stat s;
  const char* c;
  m->filename = filename;
  if (stat(filename, &s) == -1)
    m->size = 0, m->deleted = 1;
  else
    m->size = s.st_size, m->deleted = 0;
  m->read = 0;
  if ((c = strchr(filename, ':')) != 0)
    if (c[1] == '2' && c[2] == ',')
      if (strchr(c+3, 'S') != 0)
      m->read = 1;
}

static int fn_compare(const str_sortentry* a, const str_sortentry* b)
{
  if (a->str[4] < '0'
      || a->str[4] > '9'
      || b->str[4] < '0'
      || b->str[4] > '9')
    return strcmp(a->str+4, b->str+4);
  else {
    int at = atoi(a->str+4);
    int bt = atoi(b->str+4);
    return at - bt;
  }
}

static int scan_maildir(void)
{
  long pos;
  long i;

  msg_bytes = 0;
  if (!str_truncate(&msg_filenames, 0)) return 0;
  msg_count = 0;
  if (!scan_dir("cur", &msg_filenames, &cur_count, max_cur_count)) return 0;
  if (!scan_dir("new", &msg_filenames, &new_count, max_new_count)) return 0;
  if (!str_sort(&msg_filenames, 0, -1, fn_compare)) return 0;

  del_count = 0;
  del_bytes = 0;
  
  if (msgs != 0) free(msgs);
  if ((msgs = malloc(msg_count * sizeof msgs[0])) == 0) return 0;

  for (i = pos = 0; i < msg_count; pos += strlen(msg_filenames.s+pos)+1, ++i)
    make_msg(&msgs[i], msg_filenames.s + pos);

  return 1;
}

static int msgnum_check(long i)
{
  if (i > msg_count)
    respond("-ERR Message number out of range");
  else if (msgs[i-1].deleted)
    respond("-ERR Message was deleted");
  else
    return 1;
  return 0;
}

static long msgnum(const str* arg)
{
  long i;
  char* end;
  if ((i = strtol(arg->s, &end, 10)) <= 0 || *end != 0)
    respond(err_syntax);
  else if (msgnum_check(i))
    return i;
  return 0;
}

static void dump_msg(long num, long bodylines)
{
  ibuf in;
  static char buf[4096];
  int in_header;        /* True until a blank line is seen */
  int sol;              /* True if at start of line */
  
  if (!msgnum_check(num)) return;

  if (!ibuf_open(&in, msgs[num-1].filename, 0))
    return respond("-ERR Could not open that message");
  respond(ok);

  sol = in_header = 1;
  while (ibuf_read(&in, buf, sizeof buf) || in.count) {
    const char* ptr = buf;
    const char* end = buf + in.count;
    while (ptr < end) {
      const char* lfptr;
      if (sol) {
      if (!in_header)
        if (--bodylines < 0) break;
      if (*ptr == '.')
        obuf_putc(&outbuf, '.');
      }
      if ((lfptr = memchr(ptr, LF, end-ptr)) == 0) {
      obuf_write(&outbuf, ptr, end-ptr);
      ptr = end;
      sol = 0;
      }
      else {
      if (in_header && lfptr == ptr)
        in_header = 0;
      obuf_write(&outbuf, ptr, lfptr-ptr);
      obuf_puts(&outbuf, CRLF);
      ptr = lfptr + 1;
      sol = 1;
      }
    }
  }
  ibuf_close(&in);
  obuf_puts(&outbuf, CRLF);
  respond(".");
}

/* Mark a maildir filename with the named flag */
static int add_flag(str* fn, char flag)
{
  int c;
  /* If the filename has no flags, append them */
  if ((c = str_findfirst(fn, ':')) == -1) {
    if (!str_cats(fn, ":2,")) return 0;
  }
  else {
    /* If it has a colon (start of flags), see if they are a type we
     * recognize, and bail out if they aren't */
    if (fn->s[c+1] != '2' || fn->s[c+2] != ',') return 1;
    /* Scan through the flag characters and return success
     * if the message is already marked with the flag */
    if (strchr(fn->s+c+3, flag) != 0) return 1;
  }
  return str_catc(fn, flag);
}

/* Commands ******************************************************************/
static void cmd_dele(const str* arg)
{
  long i;
  if ((i = msgnum(arg)) == 0) return;
  --i;
  del_bytes += msgs[i].size;
  del_count++;
  msgs[i].deleted = 1;
  respond(ok);
}

static void cmd_last(void)
{
  long last;
  long i;
  for (last = i = 0; i < msg_count; i++)
    if (msgs[i].read)
      last = i + 1;
  if (!str_copys(&tmp, "+OK ") ||
      !str_cati(&tmp, last))
    respond(err_internal);
  respond(tmp.s);
}

static void cmd_list(void)
{
  long i;
  respond(ok);
  for (i = 0; i < msg_count; i++) {
    if (!msgs[i].deleted) {
      obuf_putu(&outbuf, i+1);
      obuf_putc(&outbuf, SPACE);
      obuf_putu(&outbuf, msgs[i].size);
      obuf_puts(&outbuf, CRLF);
    }
  }
  respond(".");
}

static void cmd_list_one(const str* arg)
{
  long i;
  if ((i = msgnum(arg)) == 0) return;
  if (!str_copys(&tmp, "+OK ") ||
      !str_catu(&tmp, i) ||
      !str_catc(&tmp, SPACE) ||
      !str_catu(&tmp, msgs[i-1].size))
    respond(err_internal);
  else
    respond(tmp.s);
}

static void cmd_noop(void)
{
  respond(ok);
}

static void cmd_quit(void)
{
  long i;
  for (i = 0; i < msg_count; i++) {
    const char* fn = msgs[i].filename;
    if (msgs[i].deleted)
      unlink(fn);
    /* Logic: 
     * 1. move all messages into "cur"
     * 2. tag all read messages without flags with a read flag (:2,S)
     * Note: no real opportunity to report errors,
     * so just continue when we hit one.
     */
    else {
      if (!str_copys(&tmp, "cur/")) continue;
      if (!str_cats(&tmp, fn+4)) continue;
      if (msgs[i].read && !add_flag(&tmp, 'S')) continue;
      rename(fn, tmp.s);
    }
  }
  respond(ok);
  exit(0);
}

static void cmd_rset(void)
{
  if (!scan_maildir()) {
    respond(err_internal);
    exit(1);
  }
  respond(ok);
}

static void cmd_stat(void)
{
  if (!str_copys(&tmp, "+OK ") ||
      !str_catu(&tmp, msg_count - del_count) ||
      !str_catc(&tmp, SPACE) ||
      !str_catu(&tmp, msg_bytes - del_bytes))
    respond(err_internal);
  else
    respond(tmp.s);
}

static void cmd_top(const str* arg)
{
  long num;
  long lines;
  char* end;

  if ((num = strtol(arg->s, &end, 10)) <= 0) return respond(err_syntax);
  while (*end == SPACE) ++end;
  if (*end == 0) {
    msgs[num-1].read = 1;
    return dump_msg(num, LONG_MAX);
  }
  if ((lines = strtol(end, &end, 10)) < 0 || *end != 0)
    return respond(err_syntax);
  dump_msg(num, lines);
}

static unsigned long uidl_len(const char* filename)
{
  const char* end;
  unsigned long len;
  len = ((end = strchr(filename, ':')) != 0)
    ? (unsigned long)(end - filename)
    : strlen(filename);
  if (len > 70)
    len = 70;
  return len;
}

static void cmd_uidl(void)
{
  long i;
  respond(ok);
  for (i = 0; i < msg_count; i++) {
    msg* m = &msgs[i];
    if (!m->deleted) {
      const char* fn = m->filename + 4;
      obuf_putu(&outbuf, i+1);
      obuf_putc(&outbuf, SPACE);
      obuf_write(&outbuf, fn, uidl_len(fn));
      obuf_puts(&outbuf, CRLF);
    }
  }
  respond(".");
}

static void cmd_uidl_one(const str* arg)
{
  long i;
  const char* fn;
  if ((i = msgnum(arg)) == 0) return;
  fn = msgs[i-1].filename + 4;
  if (!str_copys(&tmp, "+OK ") ||
      !str_catu(&tmp, i) ||
      !str_catc(&tmp, SPACE) ||
      !str_catb(&tmp, fn, uidl_len(fn)))
    respond(err_internal);
  else
    respond(tmp.s);
}

command commands[] = {
  { "DELE", 0,        cmd_dele,     0 },
  { "LAST", cmd_last, 0,            0 },
  { "LIST", cmd_list, cmd_list_one, 0 },
  { "NOOP", cmd_noop, 0,            0 },
  { "QUIT", cmd_quit, 0,            0 },
  { "RETR", 0,        cmd_top,      0 },
  { "RSET", cmd_rset, 0,            0 },
  { "STAT", cmd_stat, 0,            0 },
  { "TOP",  0,        cmd_top,      0 },
  { "UIDL", cmd_uidl, cmd_uidl_one, 0 },
  { 0,      0,        0,            0 }
};

extern void report_io_bytes(void);

int startup(int argc, char* argv[])
{
  const char* env;
  if (argc > 2) {
    msg3("usage: ", program, " [default-maildir]");
    return 0;
  }

  if ((env = getenv("MAX_MESSAGES")) != 0)     max_count = atol(env);
  if ((env = getenv("MAX_CUR_MESSAGES")) != 0) max_cur_count = atol(env);
  if ((env = getenv("MAX_NEW_MESSAGES")) != 0) max_new_count = atol(env);
  
  if ((env = getenv("MAILBOX")) == 0) {
    if (argc < 2) {
      error1("Mailbox not specified");
      return 0;
    }
    env = argv[1];
  }
  if (chdir(env) == -1) {
    respond("-ERR Could not chdir to maildir");
    return 0;
  }
  if (!scan_maildir()) {
    respond("-ERR Could not access maildir");
    return 0;
  }
  atexit(report_io_bytes);
  return 1;
}

Generated by  Doxygen 1.6.0   Back to index