/* 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 (msg_count == 0) return 1;
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;
}
syntax highlighted by Code2HTML, v. 0.9.1