/* 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 #endif #ifdef HAVE_SYS_DIR_H # include #endif #include #include #include #include #include #include #include #include #include #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 * ****************************************************************************/