/*
elmo - ELectronic Mail Operator
Copyright (C) 2003, 2004 rzyjontko
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; version 2.
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.
----------------------------------------------------------------------
maildir operations
*/
/****************************************************************************
* IMPLEMENTATION HEADERS
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef HAVE_SYS_DIR_H
# include <sys/dir.h>
#endif
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "xmalloc.h"
#include "mail.h"
#include "mlex.h"
#include "file.h"
#include "error.h"
#include "maildir.h"
#include "gettext.h"
#include "hash.h"
#include "misc.h"
#include "bitarray.h"
#include "clock.h"
/****************************************************************************
* IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
****************************************************************************/
#define MAILDIR_CACHE_FILE ".elmomdir.cache"
/****************************************************************************
* IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE DATA
****************************************************************************/
static char *directory = NULL;
static char *directory_cur = NULL;
static char *directory_new = NULL;
/**
* this indicates what was the time of last delivery - it's very important
* to ensure that each file name is unique
*/
static unsigned last_time = 0;
/**
* this is used when reading maildir
*/
static mail_array_t *new_marray = NULL;
static htable_t *file_table = NULL;
/**
* this is used in maildir_refresh to mark the common messages
*/
static bitarray_t *common_messages = NULL;
/****************************************************************************
* INTERFACE DATA
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
****************************************************************************/
static void free_resources (void);
static int dir_is_valid (char *dir);
static void free_list (struct dirent **list, int count);
static int select_fun (const struct dirent *entry);
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTIONS
****************************************************************************/
static unsigned char
char_to_flag (char x)
{
switch (x){
case 'R':
return FLAG_ANSWERED;
break;
case 'T':
return FLAG_TRASHED;
break;
case 'D':
return FLAG_DRAFT;
break;
case 'F':
return FLAG_FLAGGED;
break;
case 'S':
return FLAG_READ;
break;
case 'P':
return FLAG_PASSED;
break;
case 'L':
return FLAG_LEGITIMATE;
break;
}
return 0;
}
static void
free_resources (void)
{
if (directory){
xfree (directory);
directory = NULL;
}
if (directory_cur){
xfree (directory_cur);
directory_cur = NULL;
}
if (directory_new){
xfree (directory_new);
directory_new = NULL;
}
}
static int
dir_is_valid (char *dir)
{
DIR *dp;
dp = opendir (dir);
if (!dp)
return 0;
closedir (dp);
return 1;
}
static void
make_dir_names_from_directory (void)
{
size_t len = strlen (directory);
directory_cur = xmalloc (len + 5);
directory_new = xmalloc (len + 5);
memcpy (directory_cur, directory, len);
memcpy (directory_new, directory, len);
memcpy (directory_cur + len, "/cur", 5);
memcpy (directory_new + len, "/new", 5);
}
static void
make_dir_names (const char *dir)
{
free_resources ();
directory = xstrdup (dir);
make_dir_names_from_directory ();
}
static void
free_list (struct dirent **list, int count)
{
int i;
for (i = 0; i < count; i++)
free (list[i]);
if (count > 0 && list)
free (list);
}
static int
select_fun (const struct dirent *entry)
{
return *entry->d_name != '.';
}
static int
select_old_fun (const struct dirent *entry)
{
char *seek;
if (*entry->d_name == '.')
return 0;
seek = strrchr (entry->d_name, ',');
if (seek == NULL)
return 1;
seek = strchr (seek, 'S');
if (seek == NULL)
return 1;
return 0;
}
static unsigned char
mail_flags (const char *fname, const char *dir, size_t dlen)
{
unsigned char flags = 0;
char *seek;
if (dir[dlen - 1] == 'r')
flags |= FLAG_OLD;
seek = strstr (fname, ":");
while (seek && *seek){
flags |= char_to_flag (*seek);
seek++;
}
return flags;
}
static void
add_mail (const char *fname, const char *dir)
{
int ret;
size_t dname_len = strlen (dir);
char *full_name;
full_name = file_with_dir (dir, fname);
yyin = fopen (full_name, "r");
if (!yyin){
xfree (full_name);
return;
}
ret = mlex_scan_file (0);
if (ret == NEXT_MAIL){
newmail->flags |= mail_flags (fname, dir, dname_len);
newmail->type = BOX_MAILDIR;
newmail->place.file_name = full_name;
newmail->mime->file = full_name;
mail_array_insert (new_marray, newmail);
}
else {
error_ (0, _("%s has bad format"), fname);
xfree (full_name);
}
fclose (yyin);
}
static void
process_list (struct dirent **list, int count, const char *dir, int p)
{
int i;
for (i = 0; i < count; i++){
add_mail (list[i]->d_name, dir);
progress_advance (p, 1);
}
}
static void
process_list_new (struct dirent **list, int count, const char *dir, int base)
{
int i;
for (i = 0; i < count; i++){
if (! bitarray_is_set (common_messages, i + base))
add_mail (list[i]->d_name, dir);
}
}
static void
remove_files (struct dirent **list, int count, const char *dir)
{
int i;
char *fname;
for (i = 0; i < count; i++){
fname = file_with_dir (dir, list[i]->d_name);
unlink (fname);
xfree (fname);
}
}
static void
reset_array (struct dirent **list, int count, int base)
{
int i;
for (i = 0; i < count; i++){
htable_insert (file_table, list[i]->d_name,
(void *) (i + base));
}
}
static int
is_in_array (mail_t *mail)
{
int index;
char *seek;
entry_t *entry;
seek = strrchr (mail->place.file_name, '/');
if (seek)
seek++;
else
seek = mail->place.file_name;
entry = htable_lookup (file_table, seek);
if (entry == NULL)
return 0;
index = (int) entry->content;
bitarray_set (common_messages, index);
return 1;
}
static void
move_to_cur (mail_t *mail)
{
char *fname = mail->place.file_name;
size_t len = strlen (fname);
char *newname = xmalloc (len + 4);
char *seek;
memcpy (newname, fname, len + 1);
seek = strstr (newname, "new");
if (seek == NULL){
xfree (newname);
return;
}
memcpy (seek, "cur", 3);
memcpy (newname + len, ":2,", 4);
if (file_rename (fname, newname)){
error_ (errno, _("couldn't move message, possibly because "
"another mail client is accessing this box "
"in parallel"));
xfree (newname);
return;
}
xfree (fname);
mail->place.file_name = newname;
}
static void
apply_flags (mail_t *mail)
{
int index = 3;
static char flags[12] = ":2,";
int len;
char *seek;
char *fname;
move_to_cur (mail);
if (mail->flags & FLAG_DRAFT){
flags[index] = 'D';
index++;
}
if (mail->flags & FLAG_FLAGGED){
flags[index] = 'F';
index++;
}
if (mail->flags & FLAG_LEGITIMATE){
flags[index] = 'L';
index++;
}
if (mail->flags & FLAG_PASSED){
flags[index] = 'P';
index++;
}
if (mail->flags & FLAG_ANSWERED){
flags[index] = 'R';
index++;
}
if (mail->flags & FLAG_READ){
flags[index] = 'S';
index++;
}
if (mail->flags & FLAG_TRASHED){
flags[index] = 'T';
index++;
}
flags[index] = '\0';
seek = strrchr (mail->place.file_name, ':');
if (seek && strcmp (seek, flags) == 0)
return;
len = (seek) ? (seek - mail->place.file_name)
: strlen (mail->place.file_name);
fname = xmalloc (len + index + 1);
memcpy (fname, mail->place.file_name, len);
memcpy (fname + len, flags, index + 1);
file_rename (mail->place.file_name, fname);
xfree (mail->place.file_name);
mail->place.file_name = fname;
}
static void
destroy_not_flagged (mail_array_t *marray)
{
int i;
mail_t *mail;
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
if (mail->flags & FLAG_FLAGGED)
continue;
mail_destroy (mail, BOX_MAILDIR);
}
}
static void
read_place (memchunk_t *memchunk, mail_t *mail)
{
mail->place.file_name = memchunk_strget (memchunk);
mail->mime->file = mail->place.file_name;
}
static mail_array_t *
read_directory (const char *dir)
{
int p;
int cur_count;
int new_count;
struct dirent **cur_list;
struct dirent **new_list;
mail_array_t *result;
make_dir_names (dir);
if (!dir_is_valid (directory_cur) || !dir_is_valid (directory_new)){
free_resources ();
return NULL;
}
cur_count = scandir (directory_cur, &cur_list, select_fun, alphasort);
new_count = scandir (directory_new, &new_list, select_fun, alphasort);
if (cur_count == -1 || new_count == -1){
free_list (cur_list, cur_count);
free_list (new_list, new_count);
free_resources ();
return NULL;
}
new_marray = mail_array_create_size (cur_count + new_count,
BOX_MAILDIR, dir);
p = progress_setup (cur_count + new_count, _("reading box"));
process_list (cur_list, cur_count, directory_cur, p);
process_list (new_list, new_count, directory_new, p);
progress_close (p);
free_list (cur_list, cur_count);
free_list (new_list, new_count);
result = new_marray;
new_marray = NULL;
return result;
}
static mail_array_t *
read_cache (const char *dir)
{
int i;
char *fname;
FILE *fp;
mail_array_t *marray;
mail_array_t *marray_r;
fname = file_with_dir (dir, MAILDIR_CACHE_FILE);
fp = fopen (fname, "r");
if (file_size(fname) == 0 || fp == NULL){
xfree (fname);
return NULL;
}
marray = mail_array_read_from_file (fp, BOX_MAILDIR, dir, read_place);
if (marray == NULL){
xfree (fname);
return NULL;
}
marray_r = maildir_refresh (marray);
if (marray_r)
mail_array_substitute (marray, marray_r);
for (i = 0; i < marray->count; i++)
marray->array[i].type = BOX_MAILDIR;
xfree (fname);
return marray;
}
/****************************************************************************
* INTERFACE FUNCTIONS
****************************************************************************/
void
maildir_free_resources (void)
{
free_resources ();
}
mail_array_t *
maildir_read_dir (const char *dir)
{
mail_array_t *result;
result = read_cache (dir);
if (result)
return result;
return read_directory (dir);
}
mail_array_t *
maildir_refresh (mail_array_t *marray)
{
int i;
int cur_count;
int new_count;
struct dirent **cur_list;
struct dirent **new_list;
mail_array_t *result;
mail_t *mail;
make_dir_names (marray->path);
/**
* step 1: get the list of files in cur and new
*/
cur_count = scandir (directory_cur, &cur_list, select_fun, alphasort);
new_count = scandir (directory_new, &new_list, select_fun, alphasort);
if (cur_count == -1 || new_count == -1){
free_list (cur_list, cur_count);
free_list (new_list, new_count);
mail_array_destroy (marray);
return NULL;
}
/**
* step 2: create new mail array and a hashing table
*/
file_table = htable_create (misc_logarithm (cur_count + new_count));
new_marray = mail_array_create_size (cur_count + new_count, BOX_MAILDIR,
marray->path);
/**
* step 3: reset marker and put file names into hashing table
*/
common_messages = bitarray_create (cur_count + new_count);
bitarray_zeros (common_messages);
reset_array (cur_list, cur_count, 0);
reset_array (new_list, new_count, cur_count);
/**
* step 4: check the old array if it contains these files
*/
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
if (is_in_array (mail)){
mail->parent = -1;
mail_array_insert (new_marray, mail);
mail->flags |= FLAG_FLAGGED;
}
}
/**
* step 5: read the messages that weren't in the box
*/
process_list_new (cur_list, cur_count, directory_cur, 0);
process_list_new (new_list, new_count, directory_new, cur_count);
/**
* step 6: destroy the messages that weren't flagged
*/
destroy_not_flagged (marray);
/**
* step 7: free resources
*/
free_list (cur_list, cur_count);
free_list (new_list, new_count);
htable_destroy (file_table, NULL);
if (common_messages)
bitarray_destroy (common_messages);
result = new_marray;
file_table = NULL;
new_marray = NULL;
common_messages = NULL;
return result;
}
int
maildir_mail_header (mail_t *mail, char **place)
{
mime_t *mime = mail->mime->mime;
if (file_part (mail->place.file_name, 0, mime->off_start, place)){
*place = NULL;
return 1;
}
return 0;
}
int
maildir_mail_body (mail_t *mail, char **place, mime_t *mime,
const char *fname)
{
if (file_part (fname, mime->off_start, mime->off_end, place)){
*place = NULL;
return 1;
}
return 0;
}
char *
maildir_fetch_single (mail_t *mail)
{
return xstrdup (mail->place.file_name);
}
int
maildir_deliver_to (const char *file, const char *dir)
{
int ret;
int read = 0;
int flen;
int dlen = strlen (dir);
const char *seek = strrchr (file, '/');
char *new_fname = NULL;
char *new_location;
/**
* step 1:
* find the file name
*/
if (seek == NULL)
seek = file;
else {
seek++;
if (strchr (seek, ':'))
read = 1;
}
/**
* step 2:
* check if this message is from maildir
*/
if (! isdigit (*seek)){
new_fname = maildir_valid_file_name ();
flen = strlen (new_fname);
}
else {
flen = strlen (seek);
}
/* dir / new / file \0 */
new_location = xmalloc (dlen + 1 + 3 + 1 + flen + 1);
memcpy (new_location, dir, dlen);
if (read)
memcpy (new_location + dlen, "/cur/", 5);
else
memcpy (new_location + dlen, "/new/", 5);
if (new_fname){
memcpy (new_location + dlen + 5, new_fname, flen + 1);
xfree (new_fname);
}
else {
memcpy (new_location + dlen + 5, seek, flen + 1);
}
ret = file_rename (file, new_location);
xfree (new_location);
return ret;
}
void
maildir_remove (mail_t *mail)
{
unlink (mail->place.file_name);
}
char *
maildir_valid_file_name (void)
{
size_t hlen = 100;
unsigned timestamp = time (NULL);
char *hname = xmalloc (hlen);
char *fname;
while (gethostname (hname, hlen)){
hlen = (hlen + 1) * 2;
hname = xrealloc (hname, hlen);
}
hlen = strlen (hname);
if (timestamp <= last_time){
timestamp = ++last_time;
}
else {
last_time = timestamp;
}
/* time . pid . hostname \0 */
fname = xmalloc (10 + 1 + 10 + 1 + hlen + 1);
sprintf (fname, "%d.%d.%s", timestamp, getpid (), hname);
xfree (hname);
return fname;
}
char *
maildir_fetch_where (const char *dir, int new)
{
char *fname;
char *file = maildir_valid_file_name ();
size_t dlen = strlen (dir);
size_t flen = strlen (file);
/* dir / tmp / file :2,S \0 */
fname = xmalloc (dlen + 1 + 3 + 1 + flen + 4 + 1);
sprintf (fname, "%s/tmp", dir);
if (!dir_is_valid (fname))
if (mkdir (fname, 0700)){
error_ (errno, "%s", fname);
return NULL;
}
if (new)
sprintf (fname, "%s/tmp/%s", dir, file);
else
sprintf (fname, "%s/tmp/%s:2,S", dir, file);
xfree (file);
return fname;
}
int
maildir_create_dir (const char *name)
{
char *dir;
mkdir (name, 0700);
dir = file_with_dir (name, "cur");
if (mkdir (dir, 0700)){
error_ (errno, "%s", dir);
xfree (dir);
return 1;
}
xfree (dir);
dir = file_with_dir (name, "new");
if (mkdir (dir, 0700)){
error_ (errno, "%s", dir);
xfree (dir);
return 1;
}
xfree (dir);
dir = file_with_dir (name, "tmp");
if (mkdir (dir, 0700)){
error_ (errno, "%s", dir);
xfree (dir);
return 1;
}
xfree (dir);
return 0;
}
int
maildir_mail_size (mail_t *mail)
{
struct stat st;
if (stat (mail->place.file_name, &st)){
return -1;
}
return st.st_size;
}
int
maildir_box_mail_count (const char *box, int *unread)
{
char *cur = file_with_dir (box, "cur");
char *new = file_with_dir (box, "new");
struct dirent **cur_list;
struct dirent **new_list;
int cur_count;
int new_count;
int total_count;
cur_count = scandir (cur, &cur_list, select_fun, alphasort);
new_count = scandir (new, &new_list, select_fun, alphasort);
if (cur_count == -1 || new_count == -1){
free_list (cur_list, cur_count);
free_list (new_list, new_count);
xfree (cur);
xfree (new);
if (unread)
*unread = 0;
return -1;
}
free_list (cur_list, cur_count);
free_list (new_list, new_count);
total_count = new_count + cur_count;
cur_count = scandir (cur, &cur_list, select_old_fun, alphasort);
free_list (cur_list, cur_count);
if (unread)
*unread = new_count + cur_count;
xfree (cur);
xfree (new);
return total_count;
}
void
maildir_apply_flag (mail_t *mail)
{
apply_flags (mail);
}
void
maildir_apply_flags (mail_array_t *marray)
{
int i;
mail_t *mail;
for (i = 0; i < marray->count; i++){
mail = mail_array_get (marray, i);
apply_flags (mail);
}
}
void
maildir_dump_place (memchunk_t *memchunk, mail_t *mail)
{
memchunk_strdump (memchunk, mail->place.file_name);
}
char *
maildir_dump_file_name (mail_array_t *marray)
{
return file_with_dir (marray->path, MAILDIR_CACHE_FILE);
}
void
maildir_empty_box (const char *box)
{
char *cur = file_with_dir (box, "cur");
char *new = file_with_dir (box, "new");
struct dirent **cur_list;
struct dirent **new_list;
int cur_count;
int new_count;
cur_count = scandir (cur, &cur_list, select_fun, alphasort);
new_count = scandir (new, &new_list, select_fun, alphasort);
if (cur_count == -1 || new_count == -1){
free_list (cur_list, cur_count);
free_list (new_list, new_count);
xfree (cur);
xfree (new);
return;
}
remove_files (cur_list, cur_count, cur);
remove_files (new_list, new_count, new);
free_list (cur_list, cur_count);
free_list (new_list, new_count);
xfree (cur);
xfree (new);
}
/****************************************************************************
* INTERFACE CLASS BODIES
****************************************************************************/
/****************************************************************************
*
* END MODULE maildir.c
*
****************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1