Commit 0010991c 0010991cc712eb88cdbbe22d9c4021f74a72e4cd by Sergey Poznyakoff

Implemented

1 parent 985fa48d
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
Copyright (C) 1999, 2000, 2001, 2002, 2003,
2004 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
......@@ -15,24 +16,25 @@
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/* First draft by Jeff Bailey based on mbox by Alain Magloire */
/* First draft by Sergey Poznyakoff */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef ENABLE_MAILDIR
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <ctype.h>
#include <dirent.h>
#ifdef WITH_PTHREAD
# ifdef HAVE_PTHREAD_H
......@@ -41,184 +43,597 @@
# endif
#endif
#ifdef HAVE_ALLOCA_H
# include <alloca.h>
#endif
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <mailbox0.h>
#include <registrar0.h>
#include <mailutils/address.h>
#include <mailutils/attribute.h>
#include <mailutils/body.h>
#include <mailutils/debug.h>
#include <mailutils/envelope.h>
#include <mailutils/errno.h>
#include <mailutils/error.h>
#include <mailutils/header.h>
#include <mailutils/locker.h>
#include <mailutils/message.h>
#include <mailutils/mutil.h>
#include <mailutils/observer.h>
#include <mailutils/property.h>
#include <mailutils/stream.h>
#include <mailutils/url.h>
#include <mailutils/observer.h>
#include <mailutils/errno.h>
#include <mailbox0.h>
#include <registrar0.h>
#include <amd.h>
/* Mailbox concrete implementation. */
static int maildir_open __P ((mailbox_t, int));
static int maildir_close __P ((mailbox_t));
static void maildir_destroy __P ((mailbox_t));
static int maildir_get_message __P ((mailbox_t, size_t, message_t *));
/* static int maildir_get_message_by_uid __P ((mailbox_t, size_t, message_t *)); */
static int maildir_append_message __P ((mailbox_t, message_t));
static int maildir_messages_count __P ((mailbox_t, size_t *));
static int maildir_messages_recent __P ((mailbox_t, size_t *));
static int maildir_message_unseen __P ((mailbox_t, size_t *));
static int maildir_expunge __P ((mailbox_t));
static int maildir_save_attributes __P ((mailbox_t));
static int maildir_uidvalidity __P ((mailbox_t, unsigned long *));
static int maildir_uidnext __P ((mailbox_t, size_t *));
static int maildir_scan __P ((mailbox_t, size_t, size_t *));
static int maildir_is_updated __P ((mailbox_t));
static int maildir_get_size __P ((mailbox_t, off_t *));
struct _maildir_message
{
struct _amd_message amd_message;
int newflag;
char *file_name;
};
/* Attribute handling.
FIXME: P (Passed), D (Draft) and F (Flagged) are not handled */
static struct info_map {
char letter;
int flag;
} info_map[] = {
{ 'R', MU_ATTRIBUTE_READ },
{ 'S', MU_ATTRIBUTE_SEEN },
{ 'T', MU_ATTRIBUTE_DELETED },
{ 0 },
};
#define info_map_size (sizeof (info_map) / sizeof (info_map[0]))
int
_mailbox_maildir_init (mailbox_t mailbox)
static int
info_map_letter (int c)
{
struct info_map *p;
if (mailbox == NULL)
return EINVAL;
for (p = info_map; p < info_map + info_map_size; p++)
if (p->letter == c)
return p->flag;
return 0;
}
/* Overloading the defaults. */
mailbox->_destroy = maildir_destroy;
/* NOTE: BUF must be at least 7 bytes long */
static int
flags_to_info (int flags, char *buf)
{
struct info_map *p;
for (p = info_map; p < info_map + info_map_size; p++)
if (p->flag & flags)
*buf++ = p->letter;
*buf = 0;
return 0;
}
mailbox->_open = maildir_open;
mailbox->_close = maildir_close;
static int
info_to_flags (char *buf)
{
int flags = 0;
struct info_map *p;
/* Overloading of the entire mailbox object methods. */
mailbox->_get_message = maildir_get_message;
mailbox->_append_message = maildir_append_message;
mailbox->_messages_count = maildir_messages_count;
mailbox->_messages_recent = maildir_messages_recent;
mailbox->_message_unseen = maildir_message_unseen;
mailbox->_expunge = maildir_expunge;
mailbox->_save_attributes = maildir_save_attributes;
mailbox->_uidvalidity = maildir_uidvalidity;
mailbox->_uidnext = maildir_uidnext;
for (p = info_map; p < info_map + info_map_size; p++)
if (strchr (buf, p->letter))
flags |= p->flag;
return 0;
}
mailbox->_scan = maildir_scan;
mailbox->_is_updated = maildir_is_updated;
static int
maildir_message_cmp (struct _amd_message *a, struct _amd_message *b)
{
unsigned long na = strtoul (((struct _maildir_message *) a)->file_name,
NULL, 10);
unsigned long nb = strtoul (((struct _maildir_message *) b)->file_name,
NULL, 10);
if (na > nb)
return 1;
if (na < nb)
return -1;
return 0;
}
mailbox->_get_size = maildir_get_size;
void
msg_free (struct _amd_message *amsg)
{
struct _maildir_message *mp = (struct _maildir_message *) amsg;
free (mp->file_name);
}
return 0; /* okdoke */
char *
maildir_gethostname ()
{
char hostname[256];
char *hp;
char *p;
size_t s;
if (gethostname (hostname, sizeof hostname) < 0)
strcpy (hostname, "localhost");
for (s = 0, p = hostname; *p; p++)
if (*p == '/' || *p == ':')
s += 4;
if (s)
{
char *q;
hp = malloc (strlen (hostname)) + s + 1;
for (p = hostname, q = hp; *p; p++)
switch (*p)
{
case '/':
memcpy (q, "\\057", 4);
q += 4;
break;
case ':':
memcpy (q, "\\072", 4);
q += 4;
break;
default:
*q++ = *p++;
}
*q = 0;
}
else
hp = strdup (hostname);
return hp;
}
/* Destruct maildir setup */
static void
maildir_destroy (mailbox_t mailbox)
int
read_random (void *buf, size_t size)
{
return;
int rc;
int fd = open ("/dev/urandom", O_RDONLY);
if (fd == -1)
return -1;
rc = read (fd, buf, size);
close (fd);
return rc != size;
}
/* Open the file. For MU_STREAM_READ, the code tries mmap() first and fall
back to normal file. */
static int
maildir_open (mailbox_t mailbox, int flags)
#define PERMS 0700
#define TMPSUF "tmp"
#define CURSUF "cur"
#define NEWSUF "new"
static char *
mkfilename (char *directory, char *suffix, char *name)
{
return -1;
size_t size = strlen (directory) + 1 + strlen (suffix) + 1;
char *tmp;
if (name)
size += 1 + strlen (name);
tmp = malloc (size);
sprintf (tmp, "%s/%s", directory, suffix);
if (name)
{
strcat (tmp, "/");
strcat (tmp, name);
}
return tmp;
}
static int
maildir_close (mailbox_t mailbox)
static char *
mk_info_filename (char *directory, char *suffix, char *name, int flags)
{
return -1;
char fbuf[9];
char *tmp;
int namelen;
size_t size;
tmp = strchr (name, ':');
if (!tmp)
namelen = strlen (name);
else
namelen = tmp - name;
size = strlen (directory)
+ 1 + strlen (suffix)
+ 1 + namelen + 1;
flags_to_info (flags, fbuf);
size += 3 + strlen (fbuf);
tmp = malloc (size);
if (fbuf[0])
sprintf (tmp, "%s/%s/%*.*s:2", directory, suffix, namelen, namelen, name);
else
sprintf (tmp, "%s/%s/%*.*s:2,%s", directory, suffix, namelen, namelen, name, fbuf);
return tmp;
}
/* Cover function that call the real thing, maildir_scan(), with
notification set. */
static int
maildir_scan (mailbox_t mailbox, size_t msgno, size_t *pcount)
char *
maildir_uniq (struct _amd_data *amd, int fd)
{
return 0;
char buffer[PATH_MAX];
int ind = 0;
#define FMT(fmt,val) do {\
ind += snprintf(buffer+ind, sizeof buffer-ind, fmt, val); \
} while (0)
#define PFX(pfx,fmt,val) do {\
if (ind < sizeof buffer-1) {\
buffer[ind++] = pfx;\
FMT(fmt,val);\
}\
} while (0)
#define COPY(s) do {\
char *p; \
for (p = s; ind < sizeof buffer-1 && *p;) \
buffer[ind++] = *p++;\
} while (0);
char *hostname = maildir_gethostname ();
struct timeval tv;
unsigned long n;
struct stat st;
gettimeofday (&tv, NULL);
FMT ("%lu", tv.tv_sec);
COPY (".");
if (read_random (&n, 32))
PFX ('R', "%lX", n);
if (fd > 0 && fstat (fd, &st) == 0)
{
PFX ('I', "%lX", (unsigned long) st.st_ino);
PFX ('V', "%lX", (unsigned long) st.st_dev);
}
PFX ('M', "%lu", tv.tv_usec);
PFX ('P', "%lu", (unsigned long) getpid ());
PFX ('Q', "%lu", (unsigned long) amd->msg_count);
PFX ('.', "%s", hostname);
free (hostname);
buffer[ind] = 0;
return strdup (buffer);
}
/* FIXME: How to handle a shrink ? meaning, the &^$^@%#@^& user start two
browsers and deleted emails in one session. My views is that we should
scream bloody murder and hunt them with a machette. But for now just play
dumb, but maybe the best approach is to pack our things and leave
.i.e exit()/abort(). */
static int
maildir_is_updated (mailbox_t mailbox)
char *
maildir_message_name (struct _amd_message *amsg, int deleted)
{
return -1;
struct _maildir_message *msg = (struct _maildir_message *) amsg;
return mkfilename (amsg->amd->name, msg->newflag ? NEWSUF : CURSUF, msg->file_name);
}
static int
maildir_expunge (mailbox_t mailbox)
static void
maildir_msg_free (struct _amd_message *amsg)
{
return -1;
struct _maildir_message *mp = (struct _maildir_message *) amsg;
free (mp->file_name);
}
static int
maildir_get_size (mailbox_t mailbox, off_t *psize)
/* According to http://www.qmail.org/qmail-manual-html/man5/maildir.html
a file in tmp may be safely removed if it has not been accessed in 36
hours */
static void
maildir_delete_file (char *dirname, char *filename)
{
return -1;
struct stat st;
char *name = mkfilename (dirname, filename, NULL);
if (stat (name, &st) == 0)
{
if (time (NULL) - st.st_atime > 36 * 3600)
remove (name);
}
free (name);
}
static int
maildir_save_attributes (mailbox_t mailbox)
int
maildir_opendir (DIR **dir, char *name, int permissions)
{
return -1;
*dir = opendir (name);
if (!*dir)
{
if (errno == ENOENT)
{
if (mkdir (name, permissions))
return errno;
*dir = opendir (name);
if (!*dir)
return errno;
}
else
return errno;
}
return 0;
}
#define NTRIES 30
/* Delivery to "dir/new" */
static int
maildir_get_message (mailbox_t mailbox, size_t msgno, message_t *pmsg)
maildir_msg_init (struct _amd_data *amd, struct _amd_message *amm)
{
return -1;
struct _maildir_message *msg = (struct _maildir_message *) amm;
char *name;
struct stat st;
int i;
/* chdir (amd->name); FIXME */
name = maildir_uniq (amd, -1);
for (i = 0; i < NTRIES; i++)
{
if (stat (name, &st) < 0 && errno == ENOENT)
{
msg->file_name = name;
return 0;
}
sleep (2);
}
free (name);
return MU_ERR_BAD_RESUMPTION;
}
static int
maildir_append_message (mailbox_t mailbox, message_t msg)
maildir_msg_finish_delivery (struct _amd_data *amd, struct _amd_message *amm)
{
return -1;
struct _maildir_message *msg = (struct _maildir_message *) amm;
char *oldname = mkfilename (amd->name, TMPSUF, msg->file_name);
char *newname = mkfilename (amd->name, NEWSUF, msg->file_name);
unlink (newname);
if (link (oldname, newname))
{
unlink (oldname);
msg->newflag = 1;
}
free (oldname);
free (newname);
return 0;
}
static int
maildir_messages_count (mailbox_t mailbox, size_t *pcount)
/* Maldir scanning */
int
maildir_flush (struct _amd_data *amd)
{
return -1;
int rc;
DIR *dir;
struct dirent *entry;
char *tmpname = malloc (strlen (amd->name) + sizeof TMPSUF);
strcpy (tmpname, amd->name);
strcat (tmpname, TMPSUF);
rc = maildir_opendir (&dir, tmpname, PERMS);
if (rc)
{
free (tmpname);
return rc;
}
while ((entry = readdir (dir)))
{
switch (entry->d_name[0])
{
case '.':
break;
default:
maildir_delete_file (tmpname, entry->d_name);
break;
}
}
free (tmpname);
closedir (dir);
return 0;
}
/* A "recent" message is the one not marked with MU_ATTRIBUTE_SEEN
('O' in the Status header), i.e. a message that is first seen
by the current session (see attributes.h) */
static int
maildir_messages_recent (mailbox_t mailbox, size_t *pcount)
int
maildir_deliver_new (struct _amd_data *amd, DIR *dir)
{
return -1;
struct dirent *entry;
while ((entry = readdir (dir)))
{
char *oldname, *newname;
switch (entry->d_name[0])
{
case '.':
break;
default:
oldname = mkfilename (amd->name, NEWSUF, entry->d_name);
newname = mk_info_filename (amd->name, CURSUF, entry->d_name, 0);
rename (oldname, newname);
free (oldname);
free (newname);
}
}
return 0;
}
/* An "unseen" message is the one that has not been read yet */
static int
maildir_message_unseen (mailbox_t mailbox, size_t *pmsgno)
static struct _maildir_message *
maildir_message_lookup (struct _amd_data *amd, char *file_name)
{
return -1;
struct _maildir_message *msg;
char *p = strchr (file_name, ':');
size_t length = p ? p - file_name : strlen (file_name);
int rc;
for (msg = (struct _maildir_message *) amd->msg_head;
msg
&& strlen (msg->file_name) >= length
&& (rc = memcmp (msg->file_name, file_name, length)) < 0;
msg = (struct _maildir_message *) msg->amd_message.next)
;
return rc == 0 ? msg : NULL;
}
static int
maildir_uidvalidity (mailbox_t mailbox, unsigned long *puidvalidity)
maildir_scan_dir (struct _amd_data *amd, DIR *dir)
{
return -1;
struct _maildir_message *msg;
struct dirent *entry;
while ((entry = readdir (dir)))
{
char *p;
int insert;
switch (entry->d_name[0])
{
case '.':
break;
default:
msg = maildir_message_lookup (amd, entry->d_name);
if (msg)
{
free (msg->file_name);
msg->newflag = 0;
insert = 0;
}
else
{
msg = calloc (1, sizeof(*msg));
insert = 1;
}
msg->file_name = strdup (entry->d_name);
p = strchr (msg->file_name, ':');
if (p && strcmp (p+1, "2,") == 0)
msg->amd_message.attr_flags = info_to_flags (p+3);
else
msg->amd_message.attr_flags = 0;
msg->amd_message.deleted = msg->amd_message.attr_flags & MU_ATTRIBUTE_DELETED;
if (insert)
_amd_message_insert (amd, (struct _amd_message*) msg);
}
}
return 0;
}
static int
maildir_uidnext (mailbox_t mailbox, size_t *puidnext)
maildir_scan0 (mailbox_t mailbox, size_t msgno ARG_UNUSED, size_t *pcount,
int do_notify)
{
return -1;
struct _amd_data *amd = mailbox->data;
DIR *dir;
int status = 0;
char *name;
struct stat st;
if (amd == NULL)
return EINVAL;
monitor_wrlock (mailbox->monitor);
/* 1st phase: Flush tmp/ */
maildir_flush (amd);
/* 2nd phase: Scan and deliver messages from new */
name = mkfilename (amd->name, NEWSUF, NULL);
status = maildir_opendir (&dir, name, PERMS);
if (status == 0)
{
maildir_deliver_new (amd, dir);
closedir (dir);
}
free (name);
name = mkfilename (amd->name, CURSUF, NULL);
/* 3rd phase: Scan cur/ */
status = maildir_opendir (&dir, name, PERMS);
if (status == 0)
{
status = maildir_scan_dir (amd, dir);
closedir (dir);
}
free (name);
if (do_notify)
{
struct _amd_message *mp;
for (mp = amd->msg_head; mp; mp = mp->next)
{
DISPATCH_ADD_MSG(mailbox, amd);
}
}
if (stat (amd->name, &st) == 0)
amd->mtime = st.st_mtime;
if (pcount)
*pcount = amd->msg_count;
/* Reset the uidvalidity. */
if (amd->msg_count > 0)
{
if (amd->uidvalidity == 0)
{
amd->uidvalidity = (unsigned long) time (NULL);
/* FIXME amd->uidnext = amd->msg_count + 1;*/
/* Tell that we have been modified for expunging. */
if (amd->msg_head)
{
amd_message_stream_open (amd->msg_head);
amd_message_stream_close (amd->msg_head);
amd->msg_head->attr_flags |= MU_ATTRIBUTE_MODIFIED;
}
}
}
/* Clean up the things */
amd_cleanup (mailbox);
return status;
}
int
_mailbox_maildir_init (mailbox_t mailbox)
{
int rc;
struct _amd_data *amd;
rc = amd_init_mailbox (mailbox, sizeof (struct _amd_data), &amd);
if (rc)
return rc;
amd->msg_size = sizeof (struct _maildir_message);
amd->msg_free = maildir_msg_free;
amd->msg_init_delivery = maildir_msg_init;
amd->msg_finish_delivery = maildir_msg_finish_delivery;
amd->msg_file_name = maildir_message_name;
amd->scan0 = maildir_scan0;
amd->msg_cmp = maildir_message_cmp;
amd->message_uid = NULL; /* FIXME */
amd->next_uid = NULL; /* FIXME */
/* Set our properties. */
{
property_t property = NULL;
mailbox_get_property (mailbox, &property);
property_set_value (property, "TYPE", "MAILDIR", 1);
}
return 0;
}
#endif
......