summaryrefslogtreecommitdiff
path: root/contrib/carmel/c-client/carmel2.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/carmel/c-client/carmel2.c')
-rw-r--r--contrib/carmel/c-client/carmel2.c4412
1 files changed, 4412 insertions, 0 deletions
diff --git a/contrib/carmel/c-client/carmel2.c b/contrib/carmel/c-client/carmel2.c
new file mode 100644
index 00000000..c56f8900
--- /dev/null
+++ b/contrib/carmel/c-client/carmel2.c
@@ -0,0 +1,4412 @@
+#define TESTING
+/*----------------------------------------------------------------------
+
+ T H E C A R M E L M A I L F I L E D R I V E R
+
+ Author(s): Laurence Lundblade
+ Baha'i World Centre
+ Data Processing
+ Haifa, Israel
+ Internet: lgl@cac.washington.edu or laurence@bwc.org
+ September 1992
+
+ Last edited: Aug 31, 1994
+
+The Carmel2 mail file stores messages in individual files and
+implements folders or mailboxes with index files that contain
+references to the files a nd a full c-client envelope in an easily
+parsed form. It was written as a needed part of the pod mail file
+driver with hopes that it might be useful otherwise some day. It has
+only been run with the carmel driver.
+
+Advantages over Berkeley format and driver:
+ + Opening mail folder is very fast
+ + Expunge is fast
+ + Check point is very fast
+ + Memory usage is much lower
+ + Search of message headers is fast
+Disadvantages:
+ - Fetching a large message is slow
+ - Searching the message bodies is slow
+ - Sorting the mailbox is slow
+
+ Index File Format
+ -----------------
+
+The first line of the file is always:
+ "\254\312--CARMEL2-MAIL-FILE-INDEX--\n"
+
+It is followed by index entries which are of the format:
+---START-OF-MESSAGE---\007\001nnnnnnnnnn
+Ufrads______________________________
+Zxxxxxx
+D..... <Many fields here, labeled by the first letter in the line>
+ <Fields may be repeated>
+
+
+The index is almost an ASCII file. With the first version of this
+driver it is not advisable to edit this file, lest the byte counts get
+disrupted. In the future it will be editable. The file starts with
+two binary bytes so the file(1) utility will report it as "data" to
+discourage would be hackers from messing with it. The ^G is also in
+each index entry for the same reason. If you are reading this source you
+probably know enough to edit the index file without destroying it.
+Other parts of the format are designed for the easiest possible
+parsing. The idea was to have a file format that was reasonable to
+fiddle for debugging, to discourage inexperienced folks for fiddling
+it and to be efficient for use by the program.
+
+
+ Routines and data structures
+ ----------------------------
+
+C-CLIENT INTERFACE FUCTIONS
+ carmel2_valid - check to see if a mailbox is valid for carmel2 mail files
+ carmel2_isvalid - actual work of checking
+ carmel2_find - generate list of carmel2 mailboxes
+ carmel2_sift_files - select mailboxes out of list, used with scandir
+ carmel2_find_bboars - dummy routine, doesn't do anything
+
+ carmel2_open - initial phase of opening a mailbox
+ carmel2_open2 - real work of opening mailbox, shared with carmel driver
+ carmel2_close - close a mail stream
+
+ carmel2_fetchfast - fetch "fast" message info, noop for this driver
+ carmel2_fetchflags - fetch the flags, a noop for this driver
+ carmel2_fetchstructure - fetch and envelope and possibly body
+
+ carmel2_fetchheader - fetch the text header of the message
+ carmel2_fetchtext - fetch the text of the message (no header included)
+ carmel2_fetchbody - fetch the text of a body part of a message
+
+ carmel2_setflag - Set a flag for a message sequence
+ carmel2_clearflag - Clear a flag for a message sequence
+
+ carmel2_search - Invoke the search facilities
+
+ carmel2_ping - Check for new mail and see if still alive
+ carmel2_check - Checkpoint the message statuses to the disk
+ carmel2_expunge - Delete all the messages marked for delete
+
+ carmel2_copy - Copy a message to another mailbox
+ carmel2_move - Move a message to another mailbox
+
+ carmel_gc - Garbage collection, a noop for this driver
+ carmel_cache - The required c-client cache handler, doesn't do much
+
+ carmel2_append - Append a message to a mail folder
+
+SUPPORT FUNCTIONS
+ carmel_bodystruct - Fetch the body structure for a carmel message
+ carmel_parse_address - Parse the address out of a carmel index
+ carmel_parse_addr_field - Parse individual address field out of carmel index
+ carmel2_write_index - Write an entry into a carmel index
+ carmel2_index_address - Write an address into a carmel index
+
+ carmel_readmsg - Read a message file into memory, header text or both
+ carmel_spool_mail - Get new mail out of spoold mail file
+ carmel_copy_msg_file - Make copy of messsage file when copying (unused)
+
+ carmel2_lock - Lock a carmel index for read or write
+ carmel2_unlock - Unlock a carmel index
+ carmel2_update_lock - Touch lock mod time, marking it as active
+ carmel2_bezerk_lock - Lock the spool mail file Berkeley style
+ carmel2_bezerk_unlock - Unlock the spool mail file
+
+ carmel2_check_dir - Check that directory exists and is writeable
+ carmel2_new_data_file - Get file number for new message file
+ carmel2_calc_paths - Calculate path names for carmel driver
+
+ carmel_parse_bezerk_status - Parse the "Status: OR" field in mail headers
+ carmel2_getflags - Turn the named flags into a bit mask
+ carmel_new_mc - Get pointer to new MESSAGECACHE, allocating if needed
+
+ carmel_append2 - The real work of append a message to a mailbox
+
+ carmel2_search-* - A bunch of search support routines
+
+ month_abbrev2 - Returns three letter month abbreviation given a name
+ carmel2_rfc822_date - Parse a date string, into MESSAGECACHE structure
+ carmel2_date2string - Generate a date string from MESSAGECACHE
+ carmel2_parse_date - Parse date out of a carmel index
+ next_num - Called to parse dates
+
+ strucmp2 - Case insensitive strcmp()
+ struncmp2 - Case insensitive strncmp()
+
+ ----------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <netdb.h>
+#include <errno.h>
+extern int errno; /* just in case */
+#include "mail.h"
+#include "osdep.h"
+#include <sys/dir.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include "carmel2.h"
+#include "rfc822.h"
+#include "misc.h"
+
+
+char *strindex2(), *strchr();
+
+#ifdef ANSI
+static int carmel2_sift_files(struct direct *);
+static BODY *carmel2_bodystruct(MAILSTREAM *, MESSAGECACHE *);
+static ADDRESS *carmel2_parse_address(char *, ADDRESS *, char *);
+static char *carmel2_parse_addr_field(char **);
+static int carmel2_index_address(ADDRESS *, int , FILE *);
+static int carmel2_parse_mail(MAILSTREAM *, long);
+void carmel2_spool_mail(MAILSTREAM *, char *, char *, int);
+static int carmel2_copy_msg_file(MAILSTREAM *, int, char *);
+static int carmel2_bezerk_lock(char *, char *);
+static void carmel2_bezerk_unlock(int, char *);
+static int carmel2_new_data_file(CARMEL2LOCAL *, char *);
+static char *carmel2_calc_paths(int, char *, int);
+static short carmel2_getflags(MAILSTREAM *, char *);
+static MESSAGECACHE *carmel2_new_mc(MAILSTREAM *, int);
+static char carmel2_search_all(MAILSTREAM *, long, char *, long);
+static char carmel2_search_answered(MAILSTREAM *, long, char *, long);
+static char carmel2_search_deleted(MAILSTREAM *, long, char *, long);
+static char carmel2_search_flagged(MAILSTREAM *, long, char *, long);
+static char carmel2_search_keyword(MAILSTREAM *, long, char *, long);
+static char carmel2_search_new(MAILSTREAM *, long, char *, long);
+static char carmel2_search_old(MAILSTREAM *, long, char *, long);
+static char carmel2_search_recent(MAILSTREAM *, long, char *, long);
+static char carmel2_search_seen(MAILSTREAM *, long, char *, long);
+static char carmel2_search_unanswered(MAILSTREAM *, long, char *, long);
+static char carmel2_search_undeleted(MAILSTREAM *, long, char *, long);
+static char carmel2_search_unflagged(MAILSTREAM *, long, char *, long);
+static char carmel2_search_unkeyword(MAILSTREAM *, long, char *, long);
+static char carmel2_search_unseen(MAILSTREAM *, long, char *, long);
+static char carmel2_search_before(MAILSTREAM *, long, char *, long);
+static char carmel2_search_on(MAILSTREAM *, long, char *, long);
+static char carmel2_search_since(MAILSTREAM *, long, char *, long);
+static unsigned long carmel2_msgdate(MAILSTREAM *, long);
+static char carmel2_search_body(MAILSTREAM *, long, char *, long);
+static char carmel2_search_subject(MAILSTREAM *, long, char *, long);
+static char carmel2_search_text(MAILSTREAM *, long, char *, long);
+static char carmel2_search_bcc(MAILSTREAM *, long, char *, long);
+static char carmel2_search_cc(MAILSTREAM *, long, char *, long);
+static char carmel2_search_from(MAILSTREAM *, long, char *, long);
+static char carmel2_search_to(MAILSTREAM *, long, char *, long);
+typedef char (*search_t) ();
+static search_t carmel2_search_date(search_t, long *);
+static search_t carmel2_search_flag(search_t, char **);
+static search_t carmel2_search_string(search_t, char **, long *);
+static void carmel2_date2string(char *, MESSAGECACHE *);
+static void carmel2_parse_date(MESSAGECACHE *, char *);
+static int next_num(char **);
+#else
+static int carmel2_sift_files();
+static BODY *carmel2_bodystruct();
+static ADDRESS *carmel2_parse_address();
+static char *carmel2_parse_addr_field();
+static int carmel2_index_address();
+static int carmel2_parse_mail();
+void carmel2_spool_mail();
+static int carmel2_copy_msg_file();
+static int carmel2_bezerk_lock();
+static void carmel2_bezerk_unlock();
+static int carmel2_new_data_file();
+static char *carmel2_calc_paths();
+static short carmel2_getflags();
+static MESSAGECACHE *carmel2_new_mc();
+static char carmel2_search_all();
+static char carmel2_search_answered();
+static char carmel2_search_deleted();
+static char carmel2_search_flagged();
+static char carmel2_search_keyword();
+static char carmel2_search_new();
+static char carmel2_search_old();
+static char carmel2_search_recent();
+static char carmel2_search_seen();
+static char carmel2_search_unanswered();
+static char carmel2_search_undeleted();
+static char carmel2_search_unflagged();
+static char carmel2_search_unkeyword();
+static char carmel2_search_unseen();
+static char carmel2_search_before();
+static char carmel2_search_on();
+static char carmel2_search_since();
+static unsigned long carmel2_msgdate();
+static char carmel2_search_body();
+static char carmel2_search_subject();
+static char carmel2_search_text();
+static char carmel2_search_bcc();
+static char carmel2_search_cc();
+static char carmel2_search_from();
+static char carmel2_search_to();
+typedef char (*search_t) ();
+static search_t carmel2_search_date();
+static search_t carmel2_search_flag();
+static search_t carmel2_search_string();
+static void carmel2_date2string();
+static void carmel2_parse_date();
+static int next_num();
+#endif
+
+
+/*------ Driver dispatch used by MAIL -------*/
+
+DRIVER carmel2driver = {
+ "carmel2",
+ (DRIVER *) NIL, /* next driver */
+ carmel2_valid, /* mailbox is valid for us */
+ carmel2_parameters, /* manipulate parameters */
+ carmel2_find, /* find mailboxes */
+ carmel2_find_bboards, /* find bboards */
+ carmel2_find_all, /* find all mailboxes */
+ carmel2_find_bboards, /* find all bboards ; just a noop here */
+ carmel2_subscribe, /* subscribe to mailbox */
+ carmel2_unsubscribe, /* unsubscribe from mailbox */
+ carmel2_subscribe_bboard, /* subscribe to bboard */
+ carmel2_unsubscribe_bboard, /* unsubscribe from bboard */
+ carmel2_create,
+ carmel2_delete,
+ carmel2_rename,
+ carmel2_open, /* open mailbox */
+ carmel2_close, /* close mailbox */
+ carmel2_fetchfast, /* fetch message "fast" attributes */
+ carmel2_fetchflags, /* fetch message flags */
+ carmel2_fetchstructure, /* fetch message envelopes */
+ carmel2_fetchheader, /* fetch message header only */
+ carmel2_fetchtext, /* fetch message body only */
+ carmel2_fetchbody, /* fetch message body section */
+ carmel2_setflag, /* set message flag */
+ carmel2_clearflag, /* clear message flag */
+ carmel2_search, /* search for message based on criteria */
+ carmel2_ping, /* ping mailbox to see if still alive */
+ carmel2_check, /* check for new messages */
+ carmel2_expunge, /* expunge deleted messages */
+ carmel2_copy, /* copy messages to another mailbox */
+ carmel2_move, /* move messages to another mailbox */
+ carmel2_append, /* Append message to a mailbox */
+ carmel2_gc, /* garbage collect stream */
+};
+
+MAILSTREAM carmel2proto ={&carmel2driver};/*HACK for default_driver in pine.c*/
+
+/*-- Some string constants used in carmel2 indexes --*/
+char *carmel2_s_o_m = "---START-OF-MESSAGE---";
+int carmel2_s_o_m_len = 22;
+char *carmel2_s_o_f = "\254\312--CARMEL2-MAIL-FILE-INDEX--\n";
+
+
+
+/*-- Buffers used for various reasons, also used by carmel driver --*/
+char carmel_20k_buf[20000], carmel_path_buf[CARMEL_PATHBUF_SIZE],
+ carmel_error_buf[200];
+
+
+/*----------------------------------------------------------------------
+ Carmel mail validate mailbox
+
+Args: - mailbox name
+
+Returns: our driver if name is valid, otherwise calls valid in next driver
+ ---*/
+
+DRIVER *carmel2_valid (name)
+ char *name;
+{
+ return (carmel2_isvalid (name) ? &carmel2driver : NIL);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Open the mailbox and see if it's the correct format
+
+Args: name -- name of the mailbox, not fully qualified path
+
+Returns: 0 if is is not valid, 1 if it is valid
+
+The file must be a regular file and start with the string in the
+variable carmel2_s_o_f. It has a magic number of sorts
+ ----*/
+int
+carmel2_isvalid (name)
+ char *name;
+{
+ char *carmel_index_file;
+ struct stat sbuf;
+ int fd;
+ struct carmel_mb_name *parsed_name;
+
+ /*---- Must match FQN name of carmel mailboxes ----*/
+ parsed_name = carmel_parse_mb_name(name, '2');
+ if(parsed_name == NULL)
+ return;
+ carmel_free_mb_name(parsed_name);
+
+ carmel_index_file = carmel2_calc_paths(CalcPathCarmel2Index, name, 0);
+ if(carmel_index_file == NULL)
+ return(0); /* Will get two error messages here, one from dummy driver */
+
+ if(stat(carmel_index_file, &sbuf) < 0)
+ return(1); /* If the names match and it doesn't exist, it's valid */
+
+ if(!(sbuf.st_mode & S_IFREG))
+ return(0);
+
+ fd = open(carmel_index_file, O_RDONLY);
+ if(fd < 0)
+ return(0);
+
+ if(read(fd, carmel_20k_buf, 200) <= 0)
+ return(0);
+
+ close(fd);
+
+ if(strncmp(carmel_20k_buf, carmel2_s_o_f, strlen(carmel2_s_o_f)))
+ return(0);
+
+ return(1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Set parameters for the carmel driver.
+
+ Currently does nothing
+ ----*/
+void *
+carmel2_parameters()
+{
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail find list of mailboxes
+
+Args: stream -- mail stream to find mailboxes for
+ pat -- wildcard pattern to match (currently unused)
+
+Returns nothing, the results are passed back by calls to mm_log
+
+This needs testing
+ ----*/
+
+void
+carmel2_find (stream, pat)
+ MAILSTREAM *stream;
+ char *pat;
+{
+ char tmp[MAILTMPLEN];
+ struct direct **namelist, **n;
+ int num;
+ extern int alphasort();
+ struct carmel_mb_name *parsed_name;
+ struct passwd *pw;
+
+ parsed_name = carmel_parse_mb_name(pat, '2');
+ if(parsed_name == NULL)
+ return;
+
+ if(parsed_name->user == NULL) {
+ sprintf(tmp, "%s/%s", myhomedir(), CARMEL2_INDEX_DIR);
+ } else {
+ pw = getpwnam(parsed_name->user);
+ if(pw == NULL) {
+ sprintf(carmel_error_buf,
+ "Error accessing mailbox \"%s\". No such user name \"%s\"\n",
+ pat, parsed_name->user);
+ mm_log(carmel_error_buf, ERROR);
+ return;
+ }
+ sprintf(tmp, "%s/%s", pw->pw_dir, CARMEL2_INDEX_DIR);
+ }
+
+ sprintf(tmp, "%s/%s", myhomedir(), CARMEL2_INDEX_DIR);
+
+ num = scandir(tmp, &namelist, carmel2_sift_files, alphasort);
+
+ if(num <= 0) {
+/* Seems this error message should not be logged
+ sprintf(carmel_error_buf, "Error finding mailboxes \"%s\": %s",
+ pat, strerror(errno));
+ mm_log(carmel_error_buf, ERROR); */
+ return;
+ }
+
+ for(n = namelist; num > 0; num--, n++) {
+ if(parsed_name->user == NULL) {
+ sprintf(tmp, "%s%s%c%s", CARMEL_NAME_PREFIX, parsed_name->version,
+ CARMEL_NAME_CHAR, (*n)->d_name);
+ } else {
+ sprintf(tmp, "%s%s%c%s%c%s", CARMEL_NAME_PREFIX,
+ parsed_name->version, CARMEL_NAME_CHAR,
+ parsed_name->user, CARMEL_NAME_CHAR,
+ (*n)->d_name);
+ }
+ mm_mailbox(tmp);
+ free(*n);
+ }
+ free(namelist);
+ carmel_free_mb_name(parsed_name);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Find_all is the same as find for the carmel2 format -- no notion
+ of subscribed and unsubscribed
+ ---*/
+void
+carmel2_find_all (stream, pat)
+ MAILSTREAM *stream;
+ char *pat;
+{
+ carmel2_find(stream, pat);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail find list of bboards; always NULL, no bboards
+ ----*/
+void carmel2_find_bboards (stream,pat)
+ MAILSTREAM *stream;
+ char *pat;
+{
+ /* Always a no-op, Carmel2 file format doesn't do news */
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail find list of bboards; always NULL, no bboards
+ ----*/
+void carmel2_find_all_bboards (stream,pat)
+ MAILSTREAM *stream;
+ char *pat;
+{
+ /* Always a no-op, Carmel2 file format doesn't do news */
+}
+
+
+
+/*----------------------------------------------------------------------
+ This is used by scandir to determine which files in the directory
+ are treated as mail files
+ ----*/
+static int
+carmel2_sift_files(dir)
+ struct direct *dir;
+{
+ if(dir->d_name[0] == '.')
+ return(0);
+ else
+ return(1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Dummy subscribe/unsubscribe routines.
+ Carmel driver does support this. Maybe it should support the UNIX
+ sm sometime
+ ----*/
+long carmel2_subscribe() {}
+long carmel2_unsubscribe() {}
+long carmel2_subscribe_bboard() {}
+long carmel2_unsubscribe_bboard() {}
+
+
+
+/*----------------------------------------------------------------------
+ ----*/
+long
+carmel2_create(stream, mailbox)
+ MAILSTREAM *stream;
+ char *mailbox;
+{
+}
+
+
+
+/*----------------------------------------------------------------------
+ ----*/
+long
+carmel2_delete(stream, mailbox)
+ MAILSTREAM *stream;
+ char *mailbox;
+{
+}
+
+
+
+/*----------------------------------------------------------------------
+ ----*/
+long
+carmel2_rename(stream, orig_mailbox, new_mailbox)
+ MAILSTREAM *stream;
+ char *orig_mailbox, *new_mailbox;
+{
+}
+
+
+/*----------------------------------------------------------------------
+ Open a carmel2 mail folder/stream
+
+Args: stream -- stream to by fully opened
+
+Returns: it's argument or NULL of the open fails
+
+This needs testing and more code, see pod_open for examples
+ ----*/
+
+MAILSTREAM *
+carmel2_open (stream)
+ MAILSTREAM *stream;
+{
+ char tmp[MAILTMPLEN];
+ struct hostent *host_name;
+
+ if(!stream) return &carmel2proto; /* Must be a OP_PROTOTYPE call */
+
+ /* close old file if stream being recycled */
+ if (LOCAL) {
+ carmel2_close (stream); /* dump and save the changes */
+ stream->dtb = &carmel2driver; /* reattach this driver */
+ mail_free_cache (stream); /* clean up cache */
+ }
+
+ mailcache = carmel2_cache;
+
+ /* Allocate local stream */
+ stream->local = fs_get (sizeof (CARMEL2LOCAL));
+
+ stream->readonly = 1; /* Read-only till more work is done */
+
+ LOCAL->msg_buf = NULL;
+ LOCAL->msg_buf_size = 0;
+ LOCAL->buffered_file = NULL;
+ LOCAL->msg_buf_text_start = NULL;
+ LOCAL->msg_buf_text_offset = 0;
+ LOCAL->stdio_buf = NULL;
+ stream->msgno = -1;
+ stream->env = NULL;
+ stream->body = NULL;
+ stream->scache = 1;
+ LOCAL->calc_paths = carmel2_calc_paths;
+ LOCAL->aux_copy = NULL;
+ LOCAL->new_file_on_copy = 1;
+
+ if(carmel_open2(stream,
+ (*(LOCAL->calc_paths))(CalcPathCarmel2Index, stream->mailbox,0)) < 0)
+ return(NULL);
+
+ mail_exists (stream,stream->nmsgs);
+ mail_recent (stream,stream->recent);
+
+ return(stream);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Do the real work of opening a Carmel folder.
+
+Args: stream -- The mail stream being opened
+ file -- Path name of the actual Carmel index file
+
+Returns: -1 if the open fails
+ 0 if it succeeds
+
+This is shared between the Carmel driver and the Pod driver.
+
+Here, the status, size and date (fast info) of a message entry
+is read out of the index file into the MESSAGECACHE structures.
+To make the reading efficient these items are at the beginning
+of each entry and there is a byte offset to the next entry.
+If the byte offset is wrong (as detected by looking for the
+start of message string) then the index is read line by line
+until it synchs up. This allows manual editing of the index.
+However, the first two lines of an index entry cannot be
+edited because mail_check() writes them in place. If these
+lines have been edited it is detected here and the folder is
+deemed corrupt.
+ ---*/
+carmel_open2(stream, file)
+ MAILSTREAM *stream;
+ char *file;
+{
+ long file_pos, recent;
+ MESSAGECACHE *mc;
+ struct stat sb;
+
+ if(stat(file, &sb) < 0) {
+ sprintf(carmel_error_buf, "Can't open mailbox: %s", strerror(errno));
+ mm_log(carmel_error_buf, ERROR);
+ return(-1);
+ }
+
+ LOCAL->index_stream = fopen(file, stream->readonly ? "r" : "r+");
+ LOCAL->stdio_buf = fs_get(CARMEL2_INDEX_BUF_SIZE);
+ setbuffer(LOCAL->index_stream, LOCAL->stdio_buf, CARMEL2_INDEX_BUF_SIZE);
+ if(LOCAL->index_stream == NULL) {
+ sprintf(carmel_error_buf, "Can't open mailbox: %s", strerror(errno));
+ mm_log(carmel_error_buf, ERROR);
+ return(-1);
+ }
+
+ recent = 0;
+ stream->nmsgs = 0;
+ LOCAL->cache_size = 0;
+ LOCAL->mc_blocks = NULL;
+ LOCAL->index_size = sb.st_size;
+
+ /*---- Read line with magic number, which we already checked ------*/
+ if(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),LOCAL->index_stream)==NULL){
+ fclose(LOCAL->index_stream);
+ mm_log("Mailbox is corrupt. Open failed", ERROR);
+ return(-1);
+ }
+
+ file_pos = ftell(LOCAL->index_stream);
+ if(carmel2_parse_mail(stream, file_pos) < 0) {
+ fclose(LOCAL->index_stream);
+ mm_log("Mailbox is corrupt. Open failed", ERROR);
+ return(-1);
+ }
+
+ /* Bug, this doesn't really do the recent correctly */
+
+ stream->recent = recent;
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail close
+
+Args: stream -- stream to close
+
+ ----*/
+
+void
+carmel2_close (stream)
+ MAILSTREAM *stream;
+{
+ if (LOCAL) { /* only if a file is open */
+ carmel2_check (stream); /* dump final checkpoint */
+ if(LOCAL->msg_buf)
+ fs_give ((void **) &LOCAL->msg_buf);
+ if(LOCAL->stdio_buf)
+ fs_give ((void **) &LOCAL->stdio_buf);
+ /* nuke the local data */
+ fs_give ((void **) &stream->local);
+ stream->dtb = NIL; /* log out the DTB */
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail fetch fast information.
+
+This is a no-op because the data is always available as it is read in to
+the message cache blocks when the folder is opened
+----*/
+void
+carmel2_fetchfast (stream, sequence)
+ MAILSTREAM *stream;
+ char *sequence;
+{
+ return;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail fetch flags.
+
+This is a no-op because the data is always available as it is read in to
+the message cache blocks when the folder is opened
+----*/
+void
+carmel2_fetchflags(stream, sequence)
+ MAILSTREAM *stream;
+ char *sequence;
+{
+ return; /* no-op for local mail */
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail fetch message structure
+ Args: stream -- stream to get structure for
+ msgno -- Message number to fetch data for
+ body -- Pointer to place to return body strucuture, may be NULL
+
+If the request is the for the same msgno as last time, the saved copy
+of the envelope and/or body structure is returned.
+
+To get the envelope the Carmel index file itself must be read and parsed,
+but this is fast because it is easy to parse (by design) and the amount of
+data is small.
+
+To get the body, the whole message is read into memory and then parsed
+by routines called from here. Doing this for a large message can be slow,
+so it is best not to request the body if it is not needed. (body == NULL)
+ ----*/
+
+ENVELOPE *
+carmel2_fetchstructure (stream, msgno, body)
+ MAILSTREAM *stream;
+ long msgno;
+ BODY **body;
+{
+ MESSAGECACHE *mc;
+ ENVELOPE **env;
+ BODY **b;
+
+ env = &stream->env;
+ b = &stream->body;
+
+ if (msgno != stream->msgno){
+ /* flush old poop if a different message */
+ mail_free_envelope (env);
+ mail_free_body (b);
+ }
+ stream->msgno = msgno;
+
+ mc = MC(msgno);
+
+ if(*env == NULL) {
+ *env = mail_newenvelope();
+
+ fseek(LOCAL->index_stream, mc->data1, 0);
+ fgets(carmel_20k_buf, sizeof(carmel_20k_buf), LOCAL->index_stream);
+ if(strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len))
+ return(NULL); /* Oh ooo */
+
+ carmel2_date2string(carmel_20k_buf, mc);
+ (*env)->date = cpystr(carmel_20k_buf);
+
+ while(fgets(carmel_20k_buf, sizeof(carmel_20k_buf),
+ LOCAL->index_stream) != NULL &&
+ strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len)) {
+ carmel_20k_buf[strlen(carmel_20k_buf) - 1] = '\0';
+ switch(*carmel_20k_buf) {
+ case 'F':
+ (*env)->from = carmel2_parse_address(carmel_20k_buf,
+ (*env)->from,
+ mylocalhost());
+ break;
+ case 'T':
+ (*env)->to = carmel2_parse_address(carmel_20k_buf,
+ (*env)->to,
+ mylocalhost());
+ break;
+ case 'C':
+ (*env)->cc = carmel2_parse_address(carmel_20k_buf,
+ (*env)->cc,
+ mylocalhost());
+ break;
+ case 'B':
+ (*env)->bcc = carmel2_parse_address(carmel_20k_buf,
+ (*env)->bcc,
+ mylocalhost());
+ break;
+ case 'E':
+ (*env)->sender = carmel2_parse_address(carmel_20k_buf,
+ (*env)->sender,
+ mylocalhost());
+ break;
+ case 'R':
+ (*env)->reply_to = carmel2_parse_address(carmel_20k_buf,
+ (*env)->reply_to,
+ mylocalhost());
+ break;
+ case 'H':
+ (*env)->return_path =carmel2_parse_address(carmel_20k_buf,
+ (*env)->return_path,
+ mylocalhost());
+ break;
+ case 'J':
+ (*env)->subject = cpystr(carmel_20k_buf+1);
+ break;
+ case 'I':
+ (*env)->message_id = cpystr(carmel_20k_buf+1);
+ break;
+ case 'L':
+ (*env)->in_reply_to = cpystr(carmel_20k_buf+1);
+ break;
+ case 'N':
+ (*env)->newsgroups = cpystr(carmel_20k_buf+1);
+ break;
+ case 'r':
+ (*env)->remail = cpystr(carmel_20k_buf+1);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if(body != NULL) {
+ if(*b == NULL) {
+ /* Have to fetch the body structure too */
+ *b = carmel2_bodystruct(stream, mc);
+ }
+ *body = *b;
+ }
+
+ return(*env); /* return the envelope */
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail fetch message header
+
+Args: stream --
+ msgno
+
+Returns: pointer to text of mail header. Returned string is null terminated
+ and consists of internel storage. It will be valid till the next
+ call to mail_fetchheader() or mail_fetchtext(). An empty
+ string is returned if the fetch fails.
+ ----*/
+
+char *
+carmel2_fetchheader (stream, msgno)
+ MAILSTREAM *stream;
+ long msgno;
+{
+ char *header;
+ MESSAGECACHE *mc;
+
+ mc = MC(msgno);
+ header = carmel2_readmsg(stream, 1, 0, mc->data2);
+
+ return(header == NULL ? "" : header);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel mail fetch message text (only)
+
+Args: stream --
+ msgno
+
+Returns: pointer to text of mail message. Returned string is null terminated
+ and consists of internel storage. It will be valid till the next
+ call to mail_fetchheader() or mail_fetchtext(). An empty
+ string is returned if the fetch fails.
+ ----*/
+
+char *
+carmel2_fetchtext(stream, msgno)
+ MAILSTREAM *stream;
+ long msgno;
+{
+ MESSAGECACHE *mc;
+ char *m;
+
+ mc = MC(msgno);
+
+ if (!mc->seen) { /* if message not seen before */
+ mc->seen = T; /* mark as seen */
+ LOCAL->dirty = T; /* and that stream is now dirty */
+ }
+
+ m = carmel2_readmsg(stream, 0, mc->rfc822_size, mc->data2);
+
+ return(m);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Fetch MIME body parts
+
+Args: stream
+ msgno
+ section -- string section number spec. i.e. "1.3.4"
+ len -- pointer to return len in
+
+Returns: The contents of the body part, or NULL
+ ---*/
+
+char *
+carmel2_fetchbody (stream, msgno, section, len)
+ MAILSTREAM *stream;
+ long msgno;
+ char *section;
+ unsigned long *len;
+{
+ char *base;
+ BODY *b;
+ PART *part;
+ int part_num;
+ long offset;
+ MESSAGECACHE *mc;
+
+ if (carmel2_fetchstructure(stream, msgno, &b) == NULL || b == NULL)
+ return(NULL);
+
+ if(section == NULL || *section == '\0')
+ return(NULL);
+
+ part_num = strtol(section, &section, 10);
+ if(part_num <= 0)
+ return(NULL);
+
+ base = carmel2_fetchtext(stream, msgno);
+ if(base == NULL)
+ base = "";
+ offset = 0;
+
+ do { /* until find desired body part */
+ if (b->type == TYPEMULTIPART) {
+ part = b->contents.part; /* yes, find desired part */
+ while (--part_num && (part = part->next));
+ if (!part)
+ return (NIL); /* bad specifier */
+
+ /* Found part, get ready to go further */
+ b = &part->body;
+ if(b->type == TYPEMULTIPART && *section == '\0')
+ return(NULL); /* Ran out of section numbers, needed more */
+
+ offset = part->offset; /* and its offset */
+
+ } else if (part_num != 1) {
+ return NIL; /* Not Multipart, better be part 1 */
+ }
+
+ /* need to go down further? */
+
+ if(*section == 0) {
+ break;
+ } else {
+ switch (b->type) {
+ case TYPEMESSAGE: /* embedded message, calculate new base */
+ offset = b->contents.msg.offset;
+ b = b->contents.msg.body;
+ /* got its body, drop into multipart case*/
+
+ case TYPEMULTIPART: /* multipart, get next section */
+ if ((*section++ == '.') &&
+ (part_num = strtol (section, &section,10)) > 0)
+ break; /* Found the part */
+
+ default: /* bogus subpart specification */
+ return(NULL);
+ }
+ }
+ } while (part_num);
+
+ mc = MC(msgno);
+ /* lose if body bogus */
+ if ((!b) || b->type == TYPEMULTIPART)
+ return(NULL);
+
+ if (!mc->seen) { /* if message not seen before */
+ mc->seen = T; /* mark as seen */
+ LOCAL->dirty = T; /* and that stream is now dirty */
+ }
+
+ *len = b->size.ibytes;
+ return(base + offset);
+}
+
+
+
+/*----------------------------------------------------------------------
+ * Carmel mail set flag
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ */
+
+void
+carmel2_setflag (stream,sequence,flag)
+ MAILSTREAM *stream;
+ char *sequence;
+ char *flag;
+{
+ MESSAGECACHE *elt;
+ long i;
+ short f = carmel2_getflags (stream,flag);
+ if (!f) return; /* no-op if no flags to modify */
+ /* get sequence and loop on it */
+ if (mail_sequence (stream,sequence))
+ for (i = 1; i <= stream->nmsgs; i++){
+ elt = MC(i);
+ if (elt->sequence) {
+ /* set all requested flags */
+ if (f&fSEEN) elt->seen = T;
+ if (f&fDELETED) elt->deleted = T;
+ if (f&fFLAGGED) elt->flagged = T;
+ if (f&fANSWERED) elt->answered = T;
+ /* Could be more creative about keeping track to see if something
+ really changed */
+ LOCAL->dirty = T;
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ * Carmel mail clear flag
+ * Accepts: MAIL stream
+ * sequence
+ * flag(s)
+ */
+
+void
+carmel2_clearflag (stream,sequence,flag)
+ MAILSTREAM *stream;
+ char *sequence;
+ char *flag;
+{
+ MESSAGECACHE *elt;
+ long i = stream->nmsgs;
+ short f = carmel2_getflags (stream,flag);
+ if (!f) return; /* no-op if no flags to modify */
+ /* get sequence and loop on it */
+ if (mail_sequence (stream,sequence))
+ for(i = 1; i <= stream->nmsgs; i++) {
+ elt = MC(i);
+ if (elt->sequence) {
+ /* clear all requested flags */
+ if (f&fSEEN) elt->seen = NIL;
+ if (f&fDELETED) elt->deleted = NIL;
+ if (f&fFLAGGED) elt->flagged = NIL;
+ if (f&fANSWERED) elt->answered = NIL;
+ /* clearing either seen or deleted does this */
+ /* Could be more creative about keeping track to see if something
+ really changed */
+ LOCAL->dirty = T; /* mark stream as dirty */
+ }
+ }
+}
+
+
+
+/* Carmel mail search for messages
+ * Accepts: MAIL stream
+ * search criteria
+ */
+
+void
+carmel2_search (stream,criteria)
+ MAILSTREAM *stream;
+ char *criteria;
+{
+ long i,n;
+ char *d;
+ search_t f;
+
+ /* initially all searched */
+ for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
+ /* get first criterion */
+ if (criteria && (criteria = strtok (criteria," "))) {
+ /* for each criterion */
+ for (; criteria; (criteria = strtok (NIL," "))) {
+ f = NIL; d = NIL; n = 0; /* init then scan the criterion */
+ switch (*ucase (criteria)) {
+ case 'A': /* possible ALL, ANSWERED */
+ if (!strcmp (criteria+1,"LL")) f = carmel2_search_all;
+ else if (!strcmp (criteria+1,"NSWERED")) f = carmel2_search_answered;
+ break;
+ case 'B': /* possible BCC, BEFORE, BODY */
+ if (!strcmp (criteria+1,"CC"))
+ f = carmel2_search_string (carmel2_search_bcc,&d,&n);
+ else if (!strcmp (criteria+1,"EFORE"))
+ f = carmel2_search_date (carmel2_search_before,&n);
+ else if (!strcmp (criteria+1,"ODY"))
+ f = carmel2_search_string (carmel2_search_body,&d,&n);
+ break;
+ case 'C': /* possible CC */
+ if (!strcmp (criteria+1,"C"))
+ f = carmel2_search_string (carmel2_search_cc,&d,&n);
+ break;
+ case 'D': /* possible DELETED */
+ if (!strcmp (criteria+1,"ELETED")) f = carmel2_search_deleted;
+ break;
+ case 'F': /* possible FLAGGED, FROM */
+ if (!strcmp (criteria+1,"LAGGED")) f = carmel2_search_flagged;
+ else if (!strcmp (criteria+1,"ROM"))
+ f = carmel2_search_string (carmel2_search_from,&d,&n);
+ break;
+ case 'K': /* possible KEYWORD */
+ if (!strcmp (criteria+1,"EYWORD"))
+ f = carmel2_search_flag (carmel2_search_keyword,&d);
+ break;
+ case 'N': /* possible NEW */
+ if (!strcmp (criteria+1,"EW")) f = carmel2_search_new;
+ break;
+
+ case 'O': /* possible OLD, ON */
+ if (!strcmp (criteria+1,"LD")) f = carmel2_search_old;
+ else if (!strcmp (criteria+1,"N"))
+ f = carmel2_search_date (carmel2_search_on,&n);
+ break;
+ case 'R': /* possible RECENT */
+ if (!strcmp (criteria+1,"ECENT")) f = carmel2_search_recent;
+ break;
+ case 'S': /* possible SEEN, SINCE, SUBJECT */
+ if (!strcmp (criteria+1,"EEN")) f = carmel2_search_seen;
+ else if (!strcmp (criteria+1,"INCE"))
+ f = carmel2_search_date (carmel2_search_since,&n);
+ else if (!strcmp (criteria+1,"UBJECT"))
+ f = carmel2_search_string (carmel2_search_subject,&d,&n);
+ break;
+ case 'T': /* possible TEXT, TO */
+ if (!strcmp (criteria+1,"EXT"))
+ f = carmel2_search_string (carmel2_search_text,&d,&n);
+ else if (!strcmp (criteria+1,"O"))
+ f = carmel2_search_string (carmel2_search_to,&d,&n);
+ break;
+ case 'U': /* possible UN* */
+ if (criteria[1] == 'N') {
+ if (!strcmp (criteria+2,"ANSWERED")) f = carmel2_search_unanswered;
+ else if (!strcmp (criteria+2,"DELETED")) f = carmel2_search_undeleted;
+ else if (!strcmp (criteria+2,"FLAGGED")) f = carmel2_search_unflagged;
+ else if (!strcmp (criteria+2,"KEYWORD"))
+ f = carmel2_search_flag (carmel2_search_unkeyword,&d);
+ else if (!strcmp (criteria+2,"SEEN")) f = carmel2_search_unseen;
+ }
+ break;
+ default: /* we will barf below */
+ break;
+ }
+ if (!f) { /* if can't determine any criteria */
+ sprintf(carmel_error_buf,"Unknown search criterion: %s",criteria);
+ mm_log (carmel_error_buf,ERROR);
+ return;
+ }
+ /* run the search criterion */
+ for (i = 1; i <= stream->nmsgs; ++i)
+ if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
+ mail_elt (stream,i)->searched = NIL;
+ }
+ /* report search results to main program */
+ for (i = 1; i <= stream->nmsgs; ++i)
+ if (mail_elt (stream,i)->searched) mail_searched (stream,i);
+ }
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ * Carmel mail ping mailbox
+ * Accepts: MAIL stream
+ * Returns: T if stream alive, else NIL
+ */
+
+long
+carmel2_ping (stream)
+ MAILSTREAM *stream;
+{
+ struct stat sb;
+ char path[1000], *mailfile;
+#ifdef TESTING
+ char debug_buf[1000];
+#endif
+
+ if(!stream->readonly &&
+ carmel2_update_lock(stream->local, stream->mailbox, READ_LOCK) < 0) {
+ /* Yuck! Someone messed up our lock file, this stream is hosed */
+ mm_log("Mailbox updated unexpectedly! Mailbox closed", ERROR);
+ stream->readonly = 1;
+ return NIL;
+ }
+
+ /*--- First check to see if the Carmel index grew ----*/
+ /* BUG, will this really work on NFS since the file is held open? */
+ stat((*LOCAL->calc_paths)(CalcPathCarmel2Index, stream->mailbox, 0), &sb);
+ if(sb.st_size > LOCAL->index_size) {
+#ifdef TESTING
+ mm_log("!!!!! Carmel index grew", NIL);
+#endif
+ /* Pull in the new mail.... */
+#ifdef MAY_NEED_FOR_NFS
+ /*---- First close and reopen the mail file -----*/
+ fclose(LOCAL->index_stream);
+ LOCAL->index_stream = fopen((*LOCAL->calc_paths)(CalcPathCarmel2Index,
+ stream->mailbox, 0),
+ stream->readonly ? "r" : "r+");
+ if(LOCAL->index_stream == NULL) {
+ mm_log("Mailbox stolen. Mailbox closed", ERROR);
+ stream->readonly = 1;
+ return NIL;
+ }
+#endif
+ if(carmel2_parse_mail(stream, LOCAL->index_size) < 0) {
+ mm_log("Mailbox corrupted. Mailbox closed", ERROR);
+ stream->readonly = 1;
+ return NIL;
+ }
+ LOCAL->index_size = sb.st_size;
+ }
+
+ if(sb.st_size < LOCAL->index_size) {
+ /* Something bad happened, mail box shrunk on us, shutdown */
+ stream->readonly = 1;
+ mm_log("Mailbox shrank unexpectedly! Mailbox closed", ERROR);
+ return NIL;
+ }
+
+#ifdef TESTING
+ sprintf(debug_buf, "!!!!! Mailbox:\"%s\" pretty_mailbox:\"%s\"",
+ stream->mailbox, carmel_pretty_mailbox(stream->mailbox));
+ mm_log(debug_buf, NIL);
+ sprintf(debug_buf, "!!!! Readonly:%d, Carmel:%d", stream->readonly,
+ LOCAL->carmel);
+ mm_log(debug_buf, NIL);
+#endif
+
+ if(!stream->readonly &&
+ ((LOCAL->carmel &&
+ strcmp(carmel_pretty_mailbox(stream->mailbox), "MAIL") == 0) ||
+ strucmp2(carmel_pretty_mailbox(stream->mailbox), "inbox") == 0)) {
+ /* If it's the inbox we've got to check the spool file */
+ mailfile = getenv("MAIL") == NULL ? MAILFILE : getenv("MAIL");
+ sprintf(path, mailfile, myhomedir());
+#ifdef TESTING
+ sprintf(debug_buf, "!!!!! Checking spool mail\"%s\"", path);
+ mm_log(debug_buf, NIL);
+#endif
+ if(stat(path, &sb) < 0)
+ return(T); /* No new mail...*/
+ if(sb.st_size > 0) {
+ mm_critical(stream);
+ if(carmel2_lock(stream->local, stream->mailbox, WRITE_LOCK) < 0) {
+ mm_nocritical(stream);
+ return(T);
+ }
+ mm_log("!!!! Inbox locked, sucking in mail", NIL);
+ carmel2_spool_mail(stream, path, stream->mailbox, 1); /*go get it*/
+ carmel2_unlock(stream->local, stream->mailbox, WRITE_LOCK);
+ mm_nocritical(stream);
+ stat((*LOCAL->calc_paths)(CalcPathCarmel2Index, stream->mailbox,0),
+ &sb);
+ LOCAL->index_size = sb.st_size;
+ }
+ }
+
+ return T;
+}
+
+
+
+
+
+/*----------------------------------------------------------------------
+ This checks for new mail and checkpoints the mail file
+
+Args -- The stream to check point
+
+ ----*/
+void
+carmel2_check(stream)
+ MAILSTREAM *stream;
+{
+ (void)carmel2_check2(stream);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Do actual work of a check on carmel2 index, returning status
+
+Returns: 0 if no checkpoint was done, 1 if it was done.
+ ----*/
+carmel2_check2(stream)
+ MAILSTREAM *stream;
+{
+ int msgno;
+ MESSAGECACHE *mc;
+
+ if(stream->readonly || LOCAL == NULL)
+ return(0); /* Nothing to do in readonly or closed case */
+
+ carmel2_ping(stream); /* check for new mail (Ping always checks) */
+
+ if(!LOCAL->dirty)
+ return(0); /* Nothing to do */
+
+ mm_critical(stream);
+ if(carmel2_lock(stream->local, stream->mailbox, WRITE_LOCK) < 0) {
+ mm_nocritical(stream);
+ return(0); /* Couldn't get a write lock, nothing we can do */
+ }
+
+ for(msgno = 1; msgno <= stream->nmsgs; msgno++) {
+ mc = MC(msgno);
+ fseek(LOCAL->index_stream, mc->data1 + carmel2_s_o_m_len + 13, 0);
+ if(fprintf(LOCAL->index_stream,
+ "U%c%c%c%c%c____________________________\n",
+ mc->flagged ? 'F' : 'f',
+ mc->recent ? 'R' : 'r',
+ mc->answered ? 'A' : 'a',
+ mc->deleted ? 'D' : 'd',
+ mc->seen ? 'S' : 's') == EOF) {
+ /* BUG .. Need some error check here */
+ }
+ }
+ fflush(LOCAL->index_stream);
+
+ carmel2_unlock(stream->local, stream->mailbox, WRITE_LOCK);
+ mm_nocritical(stream);
+ mm_log("Check completed", NIL);
+ return(1);
+}
+
+
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail expunge mailbox
+
+Args: stream -- mail stream to expunge
+
+ ----*/
+
+void carmel2_expunge (stream)
+ MAILSTREAM *stream;
+{
+ char *index_file;
+ long msgno, new_msgno;
+ FILE *new_index;
+ MESSAGECACHE *mc, *new_mc;
+ ENVELOPE *envelope;
+ int save_e;
+ struct stat sb;
+
+ if (stream->readonly) {
+ if(!stream->silent)
+ mm_log ("Expunge ignored on readonly mailbox",NIL);
+ return;
+ }
+
+ mm_critical(stream);
+ carmel2_lock(stream->local, stream->mailbox, WRITE_LOCK);
+
+ strcpy(carmel_path_buf,
+ (*LOCAL->calc_paths)(CalcPathCarmel2Expunge, stream->mailbox, 0));
+
+ new_index = fopen(carmel_path_buf, "w");
+ if(new_index == NULL) {
+ goto fail;
+ }
+ if(fprintf(new_index, carmel2_s_o_f) == EOF)
+ goto fail;
+
+ new_msgno = 1;
+ for(msgno = 1; msgno <= stream->nmsgs; msgno++) {
+ mc = MC(msgno);
+ if(mc->deleted) {
+ mm_expunged(stream, new_msgno);
+ continue;
+ }
+
+ if(msgno != new_msgno) {
+ new_mc = MC(new_msgno);
+ *new_mc = *mc;
+ new_mc->msgno = new_msgno;
+ mc = new_mc;
+ }
+
+ envelope = carmel2_fetchstructure(stream, msgno, NULL);
+ if(envelope == NULL)
+ goto fail;
+
+ /* get this after envelope to offset is still valid in old file */
+ mc->data1 = ftell(new_index);
+
+ if(carmel2_write_index(envelope, mc, new_index) < 0)
+ goto fail;
+ new_msgno++;
+ }
+
+ index_file = (*LOCAL->calc_paths)(CalcPathCarmel2Index, stream->mailbox, 0);
+
+ /*--- Close it to make sure bits are really on disk across NFS. ---*/
+ if(fclose(new_index) != EOF) {
+ /*--- copy of index was a success, rename it ---*/
+ unlink(index_file);
+ link(carmel_path_buf, index_file);
+
+ /*--- Save the mail index size ----*/
+ stat(index_file, &sb);
+ LOCAL->index_size = sb.st_size;
+
+ stream->nmsgs = new_msgno - 1;
+ mm_exists(stream, stream->nmsgs);
+
+ fclose(LOCAL->index_stream);
+ LOCAL->index_stream = fopen(index_file, "r+");
+ }
+ unlink(carmel_path_buf);
+ carmel2_unlock(stream->local, stream->mailbox, WRITE_LOCK);
+ mm_nocritical(stream);
+ return;
+
+ fail:
+ save_e = errno;
+ if(new_index != NULL)
+ fclose(new_index);
+ unlink(carmel_path_buf);
+ sprintf(carmel_error_buf, "Expunge failed: %s", strerror(save_e));
+ mm_log(carmel_error_buf, WARN);
+ carmel2_unlock(stream->local, stream->mailbox, WRITE_LOCK);
+ mm_nocritical(stream);
+ return;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail copy message(s)
+
+ Args: stream - mail stream
+ sequence - message sequence
+ mailbox - destination mailbox, FQN
+
+ Returns: T if copy successful, else NIL
+ ----*/
+long
+carmel2_copy(stream, sequence, mailbox)
+ MAILSTREAM *stream;
+ char *sequence;
+ char *mailbox;
+{
+ ENVELOPE *e;
+ MESSAGECACHE *mc, new_mc;
+ long msgno, file_no, file_pos;
+ int fail;
+ char *file_name, *line;
+ FILE *dest_stream;
+ struct stat sb;
+
+ if (!mail_sequence (stream,sequence)) /* get sequence to do */
+ return(NIL);
+
+ /*--- Get the file open (creating if needed) first ----*/
+ file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Index, mailbox, 0, 0);
+ if(file_name == NULL)
+ return(NIL);
+
+ if(stat(file_name, &sb) < 0) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy", NIL);
+ return(NIL);
+ }
+
+ dest_stream = fopen(file_name, "a+");
+ if(dest_stream == NULL) {
+ sprintf(carmel_error_buf, "Can't copy message to %s: %s",
+ mailbox, strerror(errno));
+ mm_log(carmel_error_buf, ERROR);
+ return(NIL);
+ }
+
+ mm_critical(stream);
+
+ if(carmel2_lock(stream->local, mailbox, WRITE_LOCK) < 0) {
+ mm_nocritical(stream);
+ sprintf(carmel_error_buf,
+ "Mailbox %s locked, can't copy messages to it",
+ mailbox);
+ mm_log(carmel_error_buf, ERROR);
+ fclose(dest_stream);
+ return(NIL);
+ }
+
+
+ /*--- Get the destination Carmel index open ----*/
+ file_pos = ftell(dest_stream);
+ fail = 0;
+
+
+ for(msgno = 1; msgno <= stream->nmsgs; msgno++) {
+ mc = MC(msgno);
+ if(!mc->sequence)
+ continue;
+
+ new_mc = *mc;
+
+ if(LOCAL->new_file_on_copy) {
+ new_mc.data2 = carmel2_copy_msg_file(stream, mc->data2, mailbox);
+ if((long)new_mc.data2 < 0) {
+ fail = 1;
+ break;
+ }
+ }
+
+ e = carmel2_fetchstructure(stream, msgno, NULL);
+ if(carmel2_write_index(e, &new_mc, dest_stream) < 0) {
+ fail = 1;
+ break;
+ }
+
+ if(LOCAL->carmel && LOCAL->aux_copy != NULL) {
+ if((*LOCAL->aux_copy)(stream->local, mailbox, e, &new_mc)) {
+ fail = 1;
+ break;
+ }
+ }
+ }
+
+ if(fail) {
+ ftruncate(fileno(dest_stream), file_pos);
+ }
+
+ fclose(dest_stream);
+
+ carmel2_unlock(stream->local, mailbox, WRITE_LOCK);
+
+ mm_nocritical(stream);
+
+ return(fail ? NIL : T);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Carmel2 mail move message(s)
+
+
+ Returns: T if move successful, else NIL
+ ----*/
+
+long
+carmel2_move (stream,sequence,mailbox)
+ MAILSTREAM *stream;
+ char *sequence;
+ char *mailbox;
+{
+ long i;
+ MESSAGECACHE *elt;
+
+ if (!(mail_sequence (stream,sequence) &&
+ carmel2_copy (stream,sequence,mailbox))) return NIL;
+ /* delete all requested messages */
+ for (i = 1; i <= stream->nmsgs; i++)
+ elt = MC(i);
+ if (elt->sequence) {
+ elt->deleted = T; /* mark message deleted */
+ LOCAL->dirty = T; /* mark mailbox as dirty */
+ }
+ return T;
+}
+
+
+
+/*----------------------------------------------------------------------
+ * Carmel2 garbage collect stream
+ * Accepts: Mail stream
+ * garbage collection flags
+ */
+
+void carmel2_gc (stream, gcflags)
+ MAILSTREAM *stream;
+ long gcflags;
+{
+ /* No garbage collection in Carmel2 driver, not much to collect */
+}
+
+
+
+/*----------------------------------------------------------------------
+ Handle MessageCache for carmel2 mail driver
+
+Args: stream --
+ msgno -- message number
+ op -- operation code
+
+The carmel2 format keeps MESSAGECACHE entries in core for all messages
+in the open mail folder so there isn't any cache flushing and rereading
+that has to go on.
+ ----*/
+void *
+carmel2_cache(stream, msgno, op)
+ MAILSTREAM *stream;
+ long msgno;
+ long op;
+{
+ /* It's a carmel driver if first 6 letters of name are carmel */
+ if(stream->dtb == NULL)
+ return(0);
+
+ if(struncmp2(stream->dtb->name, "carmel", 6) == 0) {
+ if(op == CH_MAKEELT)
+ return(MC(msgno));
+ else
+ return(0);
+ }
+
+ /* Not a carmel2 or carmel driver, call the standard function. This works
+ as long as there is only one other driver since we know it must be
+ mm_cache().
+ */
+ return(mm_cache(stream, msgno, op));
+}
+
+
+
+/*----------------------------------------------------------------------
+ Append a message to a mailbox
+
+Args: mailbox -- The message to append the mail folder too
+ message -- Message header and text in rfc822 \r\n format to append
+
+Returns: T if all went OK, NIL if not
+ ----*/
+long
+carmel2_append(stream, mailbox, flags, date, message)
+ MAILSTREAM *stream;
+ char *mailbox, *flags, *date;
+ STRING *message;
+{
+ CARMEL2LOCAL local;
+
+ /*---- A fake local data structure to pass to other functions---*/
+ local.calc_paths = carmel2_calc_paths;
+ local.carmel = 0;
+ local.aux_copy = NULL;
+
+ return(carmel2_append2(stream, &local, mailbox, flags, date, message));
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Fetch the body structure for a camel message
+
+Args: stream -- MAILSTREAM
+ mc -- The incore message cache entry for the message
+
+Returns: body structure
+
+Note, the envelope that results from the parse here is ignored. Only
+the envelope from the index is actually used in the Carmel2 format.
+ ----*/
+static BODY *
+carmel2_bodystruct(stream, mc)
+ MESSAGECACHE *mc;
+ MAILSTREAM *stream;
+{
+ char *header, *text, *file, *p;
+ ENVELOPE *e_place_holder;
+ BODY *b;
+ STRING string_struct;
+
+ header = carmel2_readmsg(stream, 1, mc->rfc822_size, mc->data2);
+ if(header == NULL)
+ return(NULL);
+
+ text = carmel2_readmsg(stream, 0, mc->rfc822_size, mc->data2);
+ if(text == NULL)
+ return(NULL);
+
+ INIT(&string_struct, mail_string, (void *)text, strlen(text));
+ rfc822_parse_msg(&e_place_holder, &b, header, strlen(header),
+ &string_struct, mylocalhost(), carmel_20k_buf);
+
+ mail_free_envelope(&e_place_holder);
+
+#ifdef BWC
+ /* If there's no content type in the header and it's the carmel
+ driver at the BWC set the type X-BWC-Glyph
+ */
+ for(p = header; *p; p++)
+ if(*p=='\n' && (*(p+1)=='C' || *(p+1)=='c') &&
+ struncmp2(p+1,"content-type:", 13) == 0)
+ break;
+
+ if(!*p && LOCAL->carmel && /* Carmel, not Carmel2 */
+ b->type == TYPETEXT && /* Type text */
+ (b->subtype == NULL || strcmp(b->subtype,"PLAIN") == 0) &&
+ ((int)mc->year) + 1969 < 1994) {
+ /* Most mail in a pod mail store is in the BWC-Glyph character
+ set, but there is no tag in the data on disk, so we fake it here.
+ We expect after 1994 all mail generated in BWC-Glyph format
+ will have proper tags!
+ */
+ b->subtype = cpystr("X-BWC-Glyph");
+ }
+#endif
+
+ return(b);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Parse an address in a Carmel2 format mail index
+
+Args: line -- The line from the index to parse
+ addr -- The address list to add to (if there is one)
+
+Returns: address list
+ ----*/
+static ADDRESS *
+carmel2_parse_address(line, addr, localhost)
+ char *line, *localhost;
+ ADDRESS *addr;
+{
+ ADDRESS *a;
+
+ if(addr == NULL) {
+ addr = mail_newaddr();
+ a = addr;
+ } else {
+ for(a = addr; a!= NULL && a->next != NULL; a = a->next);
+ a->next = mail_newaddr();
+ a = a->next;
+ }
+
+ line++; /* Skip the tag chacater */
+ a->personal = carmel2_parse_addr_field(&line);
+ a->mailbox = carmel2_parse_addr_field(&line);
+ a->host = carmel2_parse_addr_field(&line);
+ a->adl = carmel2_parse_addr_field(&line);
+/* if(a->host == NULL)
+ a->host = cpystr(localhost); */ /* host can't be NULL */
+ /* Yes it can for Internet group syntax */
+ return(addr);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Parse the next address field out of a carmel address index entry
+
+Args: string -- pointer to pointer to string
+
+Returns: field in malloced string or NULL
+
+Input string is a bunch of substrings separated by ^A. This function scans
+for the next ^A or end of string, cuts it out and returns it. The original
+strings passed in is mangled
+----*/
+static char *
+carmel2_parse_addr_field(string)
+ char **string;
+{
+ char *p, end, *start;
+
+ start = p = *string;
+ while(*p > '\001') /* Find end of sub string or string */
+ p++;
+ if((p - *string) == 0) {
+ if(*p) p++;
+ *string = p;
+ return(NULL); /* If nothing found return nothing */
+ }
+ end = *p; /* Save terminating character (^A or \0) */
+ *p = '\0';
+ if(end) /* If not end of string, get ready for next call */
+ p++;
+ *string = p; /* Return pointer to next substring */
+ return(cpystr(start));
+}
+
+
+
+
+/*----------------------------------------------------------------------
+ Write an entry into a carmel2 index
+
+Args: e -- Envelope
+ mc -- Message Cache element
+ stream -- File stream to write to
+
+Returns: 0 if OK, -1 if failed
+----*/
+carmel2_write_index(e, mc, stream)
+ ENVELOPE *e;
+ MESSAGECACHE *mc;
+ FILE *stream;
+{
+ long f_start, f_end;
+
+ f_start = ftell(stream);
+
+ if(fprintf(stream, "%s\007\001xxxxxxxxxx\n", carmel2_s_o_m) == EOF)
+ goto blah;
+ if(fprintf(stream, "U%c%c%c%c%c____________________________\n",
+ mc->flagged ? 'F' : 'f',
+ mc->recent ? 'R' : 'r',
+ mc->answered ? 'A' : 'a',
+ mc->deleted ? 'D' : 'd',
+ mc->seen ? 'S' : 's') == EOF)
+ goto blah;
+ if(fprintf(stream, "Z%d\n", mc->rfc822_size) == EOF)
+ goto blah;
+ if(fprintf(stream, "D%d\001%d\001%d\001%d\001%d\001%d\001%d\001%d\n",
+ mc->year+1969, mc->month, mc->day, mc->hours, mc->minutes,
+ mc->seconds, mc->zhours * (mc->zoccident ? 1 : -1),
+ mc->zminutes) == EOF)
+ goto blah;
+ if(fprintf(stream, "Svmail\n") == EOF)
+ goto blah;
+ if(fprintf(stream, "P%d\n",mc->data2) == EOF)
+ goto blah;
+ if(carmel2_index_address(e->to, 'T', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->from, 'F', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->cc, 'C', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->bcc, 'B', stream) < 0)
+ goto blah;
+#ifdef HAVE_RESENT
+ if(carmel2_index_address(e->resent_to, 't', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->resent_from, 'f', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->resent_cc, 'c', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->resent_bcc, 'b', stream) < 0)
+ goto blah;
+#endif
+ if(carmel2_index_address(e->return_path, 'H', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->sender, 'E', stream) < 0)
+ goto blah;
+ if(carmel2_index_address(e->reply_to, 'R', stream) < 0)
+ goto blah;
+ if(e->in_reply_to != NULL)
+ if(fprintf(stream, "L%s\n", e->in_reply_to) == EOF)
+ goto blah;
+ if(e->remail != NULL)
+ if(fprintf(stream, "r%s\n", e->remail) == EOF)
+ goto blah;
+ if(e->message_id != NULL)
+ if(fprintf(stream, "I%s\n", e->message_id) == EOF)
+ goto blah;
+ if(e->newsgroups != NULL)
+ if(fprintf(stream, "N%s\n", e->newsgroups) == EOF)
+ goto blah;
+ if(e->subject != NULL)
+ if(fprintf(stream, "J%s\n", e->subject) == EOF)
+ goto blah;
+
+ /*--- figure out and write the offset ---*/
+ f_end = ftell(stream);
+ if(fseek(stream, f_start, 0) < 0)
+ goto blah;
+ if(fprintf(stream, "%s\007\001%10d\n", carmel2_s_o_m, f_end - f_start)==EOF)
+ goto blah;
+ if(fseek(stream, f_end, 0) < 0)
+ goto blah;
+
+ return(0);
+
+ blah:
+ /* Index entry is a bit of a mess now. Maybe we should try to truncate */
+ return(-1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Write an address entry into a carmel2 index
+
+Args: addr -- addresslist
+ field -- Field character specifier
+ stream -- File stream to write to
+
+Returns 0 if OK, -1 on error writing
+ ---*/
+static
+carmel2_index_address(addr, field, stream)
+ ADDRESS *addr;
+ int field;
+ FILE *stream;
+{
+ ADDRESS *a;
+
+ for(a = addr; a != NULL; a = a->next) {
+ if(fprintf(stream, "%c%s\001%s\001%s\001%s\n",
+ field,
+ a->personal == NULL ? "" : a->personal,
+ a->mailbox == NULL ? "" : a->mailbox,
+ a->host == NULL ? "" : a->host,
+ a->adl == NULL ? "" : a->adl) == EOF)
+ return(-1);
+ }
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Real work of reading mail data files
+
+Args: stream
+ header_only -- return header if set, text if not
+ file_size -- The file size if known (saves a stat)
+ file -- name of the file to read
+
+Returns buffer with text stored in internel buffer. The Carmel2 format
+buffers the text of the current message and header in an internal
+buffer. The buffer never shrinks and is expanded as needed, up to a
+maximum. The text in the buffer is in CRLF format and is read in line
+by line using stdio. It is believed this will be efficient on whatever
+machine it is running on and will not use too much memory. (There's
+some extra memory used for buffering in stdio.) If a request is made
+first for only the header, then only the header will be read in. This
+is a big efficiency win when the file is large and only the header is
+needed. (In the Carmel2 format the header is genera lly not used, and
+when it is it is with the body to do a MIME parse, but the pod format
+does read the headers in to create the Carmel2 index.) An estimate is
+made of the size needed to expand the file to convert the line ends
+from LF to CRLF. The estimate alloca tes 10% extra space. If it
+doesn't fit, the whole buffer will be expanded by 50% and the whole
+read done over. When the header is read in a 30K buffer is allocated
+initially (if the buffer is smaller than that initially). The 50%
+increase is applied to the buffer when reading only the header.
+ ----*/
+
+char *
+carmel2_readmsg(stream, header_only, file_size, file_num)
+ MAILSTREAM *stream;
+ int header_only;
+ int file_num;
+ long file_size;
+{
+ FILE *msg_stream;
+ char *p, *p_end, *file_name;
+ int past_header, not_eof;
+ long max_read;
+ struct stat st;
+#define DEBUGDEBUG 1
+#ifdef DEBUGDEBUG
+ char debug_buf[500];
+#endif
+
+ file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Data,
+ stream->mailbox, file_num);
+ if(file_name == NULL)
+ return(NULL); /* Just in case; should never be invalid if open */
+#ifdef DEBUGDEBUG
+ sprintf(debug_buf, "READ RQ:\"%s\" HV:\"%s\" RQ_HD:%d HV_TXT:%d\n",
+ file_name,
+ LOCAL->buffered_file == NULL ? "" : LOCAL->buffered_file,
+ header_only, LOCAL->buffered_header_and_text);
+ mm_log(debug_buf, NIL);
+#endif
+
+ /*---- Check out what we have read already -----*/
+ if(LOCAL->buffered_file != NULL &&
+ strcmp(LOCAL->buffered_file, file_name) == 0) {
+ /* The file is the same. Now have we read in the part
+ that is wanted? If so return it.
+ */
+ if(header_only || LOCAL->buffered_header_and_text) {
+ if(header_only) {
+#ifdef DEBUGDEBUG
+ mm_log("....Returning buffered header\n", NIL);
+#endif
+ return(LOCAL->msg_buf);
+ } else {
+#ifdef DEBUGDEBUG
+ mm_log("....Returning buffered text\n", NIL);
+#endif
+ return(LOCAL->msg_buf_text_start);
+ }
+ }
+ } else {
+ /*-- Different file than last time, reset a few things --*/
+ LOCAL->buffered_header_and_text = 0;
+ LOCAL->msg_buf_text_offset = 0L;
+ if(LOCAL->buffered_file != NULL)
+ fs_give((void **)&(LOCAL->buffered_file));
+ }
+
+#ifdef DEBUGDEBUG
+ mm_log("....Reading file\n", NIL);
+#endif
+
+ /*----- Open the file ------*/
+ /* Would actually be more efficient not to use stdio here */
+ msg_stream = fopen(file_name, "r");
+ if(msg_stream == NULL) {
+ strcat(file_name, ".wid");
+ msg_stream = fopen(file_name, "r");
+ if(msg_stream == NULL)
+ return(NULL);
+ }
+
+ /*---- Check the size of the file ----*/
+ if(file_size == 0 && stat(file_name, &st) >= 0)
+ file_size = st.st_size;
+
+
+ /* ------Pick an amount to read -------
+ Assume the header is less than MAX_HEADER. We don't want to
+ allocate buffer for the whole message if we are just getting
+ the header.
+ */
+ max_read = (file_size * 11) / 10;
+ past_header = 0;
+ p = LOCAL->msg_buf;
+ if(header_only) {
+ max_read = min(max_read, CARMEL_MAX_HEADER);
+ } else if(LOCAL->msg_buf_text_offset > 0) {
+ past_header = 1;
+ p = LOCAL->msg_buf_text_start;
+ fseek(msg_stream, LOCAL->msg_buf_text_offset, 0);
+ }
+ if(max_read > CARMEL_MAXMESSAGESIZE)
+ max_read = CARMEL_MAXMESSAGESIZE;
+ if(max_read == 0)
+ max_read = 1; /* Forces buffer allocation below */
+
+
+ /*----- Loop (re)reading the whole file 'till it fits ---*/
+ /* This will fit the first time for all but the
+ strangest cases.
+ */
+ do {
+ /*---- Make sure buffer is the right size ----*/
+ if(LOCAL->msg_buf_size < max_read) {
+ /* Buffer not big, enough start whole read over */
+ if(LOCAL->msg_buf != NULL)
+ fs_give((void **)&(LOCAL->msg_buf));
+ LOCAL->msg_buf = fs_get(max_read);
+ LOCAL->msg_buf_size = max_read;
+ fseek(msg_stream, 0, 0);
+ past_header = 0;
+ p = LOCAL->msg_buf;
+ }
+
+ p_end = LOCAL->msg_buf + LOCAL->msg_buf_size - 3;
+
+ while(p < p_end && (not_eof =(fgets(p, p_end-p, msg_stream) != NULL))){
+ if(*p == '\n' && !past_header) {
+ *p++ = '\r';
+ *p++ = '\n';
+ *p++ = '\0';
+ past_header = 1;
+ LOCAL->msg_buf_text_offset = ftell(msg_stream);
+ LOCAL->msg_buf_text_start = p;
+ if(header_only)
+ goto done;
+ else
+ continue;
+ }
+ p += strlen(p) - 1;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ *p = '\0';
+ if(!not_eof)
+ goto done;
+
+ /* If we're here, the buffer wasn't big enough, which
+ is due to a message with most lines less than 10 characters
+ (the 10% addition for turning LF to CRLF wasn't enough).
+ Increase it by 50% and try again, till we hit
+ the largest message we can read
+ */
+ max_read = min(CARMEL_MAXMESSAGESIZE, (max_read * 15) / 10);
+ fseek(msg_stream, 0, 0);
+ } while (1);
+ done:
+ if(p == p_end)
+ mm_log("Message truncated. It's is too large", WARN);
+
+ fclose(msg_stream);
+
+ /*---- Save the name of the file buffered file ----*/
+ LOCAL->buffered_file = cpystr(file_name);
+ LOCAL->buffered_header_and_text |= !header_only;
+
+ return(header_only ? LOCAL->msg_buf : LOCAL->msg_buf_text_start);
+}
+
+
+/*----------------------------------------------------------------------
+ Parse basic/quick entries in a Carmel mailbox
+
+Args: stream -- stream for mailbox
+ file_pos -- File position in the index to start parsing at
+
+Returns: 0 if parse succeeds
+ -1 if it fails
+ ----*/
+static int
+carmel2_parse_mail(stream, file_pos)
+ MAILSTREAM *stream;
+ long file_pos;
+{
+ MESSAGECACHE *mc;
+ long nmsgs, next, last_line_read;
+ int found;
+
+ nmsgs = stream->nmsgs;
+
+ /*---- Get the start-of-message record ------*/
+ fseek(LOCAL->index_stream, file_pos, 0);
+ if(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),LOCAL->index_stream)==NULL)
+ goto done_reading;
+ if(strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len) != 0)
+ goto bomb;
+
+ while(1) {
+ if(strlen(carmel_20k_buf) != carmel2_s_o_m_len + 13)
+ goto bomb;
+
+ nmsgs++;
+ next = atol(carmel_20k_buf+24);
+ mc = carmel2_new_mc(stream, nmsgs);
+ mc->data1 = file_pos;
+
+ /*-- Get the status line, It must be the first line in the entry ----*/
+ if(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),
+ LOCAL->index_stream) == NULL)
+ goto done_reading;
+ if(*carmel_20k_buf != 'U' || strlen(carmel_20k_buf) != 35)
+ goto bomb; /* Invalid format! */
+ mc->flagged = carmel_20k_buf[1] == 'F';
+ mc->answered = carmel_20k_buf[3] == 'A';
+ mc->deleted = carmel_20k_buf[4] == 'D';
+ mc->seen = carmel_20k_buf[5] == 'S';
+ mc->recent = 0;
+
+ /*---- Get the rest of the other interesting entries -----*/
+ found = 0;
+ while(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),
+ LOCAL->index_stream) != NULL &&
+ found < 3 &&
+ strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len))
+ if (*carmel_20k_buf == 'Z') {
+ mc->rfc822_size = atol(carmel_20k_buf+1);
+ found++;
+ } else if(*carmel_20k_buf == 'D') {
+ carmel2_parse_date(mc, carmel_20k_buf+1);
+ found++;
+ } else if(*carmel_20k_buf == 'P') {
+ mc->data2 = atoi(carmel_20k_buf+1);
+ found++;
+ }
+
+ /*-------- Now find the next index entry ---------*/
+ last_line_read = ftell(LOCAL->index_stream);
+ file_pos += next;
+ fseek(LOCAL->index_stream, file_pos, 0); /* try the offset first */
+ if(fgets(carmel_20k_buf, sizeof(carmel_20k_buf),
+ LOCAL->index_stream) == NULL)
+ break;
+ if(strncmp(carmel_20k_buf, carmel2_s_o_m, carmel2_s_o_m_len) != 0){
+ /*-- Didn't match what was seeked to, back off and read lines --*/
+ fseek(LOCAL->index_stream, last_line_read, 0);
+ do {
+ file_pos = ftell(LOCAL->index_stream);
+ if(fgets(carmel_20k_buf, sizeof(carmel_20k_buf),
+ LOCAL->index_stream) == NULL)
+ goto done_reading;
+ }while(strncmp(carmel_20k_buf,carmel2_s_o_m,carmel2_s_o_m_len)!=0);
+ }
+ }
+ done_reading:
+ if(stream->nmsgs != nmsgs) {
+ stream->nmsgs = nmsgs;
+ mm_exists(stream, nmsgs);
+ }
+ return(0);
+
+ bomb:
+ return(-1);
+}
+
+
+
+/* This killer macro is from bezerk.h. It's only needed at sites that don't
+ * escape "From " lines with ">From " unless absolutely necessary (The UW).
+ */
+
+/* Validate line known to start with ``F''
+ * Accepts: pointer to candidate string to validate as a From header
+ * return pointer to end of date/time field
+ * return pointer to offset from t of time (hours of ``mmm dd hh:mm'')
+ * return pointer to offset from t of time zone (if non-zero)
+ * Returns: T if valid From string, t,ti,zn set; else NIL
+ */
+
+#define VALID(s,x,ti,zn) \
+ (s[1] == 'r') && (s[2] == 'o') && (s[3] == 'm') && (s[4] == ' ') && \
+ (x = strchr (s+5,'\n')) && \
+ ((x-s < 41) || ((ti = ((x[-2] == ' ') ? -14 : (x[-3] == ' ') ? -15 : \
+ (x[-4] == ' ') ? -16 : (x[-5] == ' ') ? -17 : \
+ (x[-6] == ' ') ? -18 : (x[-7] == ' ') ? -19 : \
+ (x[-8] == ' ') ? -20 : (x[-9] == ' ') ? -21 : \
+ (x[-10]== ' ') ? -22 : (x[-11]== ' ') ? -23 : 0)) && \
+ (x[ti] == ' ') && (x[ti+1] == 'r') && (x[ti+2] == 'e') && \
+ (x[ti+3] == 'm') && (x[ti+4] == 'o') && (x[ti+5] == 't') && \
+ (x[ti+6] == 'e') && (x[ti+7] == ' ') && (x[ti+8] == 'f') && \
+ (x[ti+9] == 'r') && (x[ti+10]== 'o') && (x[ti+11]== 'm') && \
+ (x += ti)) || T) && \
+ (x-s >= 27) && \
+ ((x[ti = -5] == ' ') ? ((x[-8] == ':') ? !(zn = 0) : \
+ ((x[ti = zn = -9] == ' ') || \
+ ((x[ti = zn = -11] == ' ') && \
+ ((x[-10] == '+') || (x[-10] == '-'))))) : \
+ ((x[zn = -4] == ' ') ? (x[ti = -9] == ' ') : \
+ ((x[zn = -6] == ' ') && ((x[-5] == '+') || (x[-5] == '-')) && \
+ (x[ti = -11] == ' ')))) && \
+ (x[ti - 3] == ':') && (x[ti -= ((x[ti - 6] == ':') ? 9 : 6)] == ' ') && \
+ (x[ti - 3] == ' ') && (x[ti - 7] == ' ') && (x[ti - 11] == ' ')
+
+
+/* You are not expected to understand this macro, but read the next page if
+ * you are not faint of heart.
+ *
+ * Known formats to the VALID macro are:
+ * From user Wed Dec 2 05:53 1992
+ * BSD From user Wed Dec 2 05:53:22 1992
+ * SysV From user Wed Dec 2 05:53 PST 1992
+ * rn From user Wed Dec 2 05:53:22 PST 1992
+ * From user Wed Dec 2 05:53 -0700 1992
+ * From user Wed Dec 2 05:53:22 -0700 1992
+ * From user Wed Dec 2 05:53 1992 PST
+ * From user Wed Dec 2 05:53:22 1992 PST
+ * From user Wed Dec 2 05:53 1992 -0700
+ * Solaris From user Wed Dec 2 05:53:22 1992 -0700
+ *
+ * Plus all of the above with `` remote from xxx'' after it. Thank you very
+ * much, smail and Solaris, for making my life considerably more complicated.
+ */
+
+/*
+ * What? You want to understand the VALID macro anyway? Alright, since you
+ * insist. Actually, it isn't really all that difficult, provided that you
+ * take it step by step.
+ *
+ * Line 1 Validates that the 2-5th characters are ``rom ''.
+ * Line 2 Sets x to point to the end of the line.
+ * Lines 3-12 First checks to see if the line is at least 41 characters long.
+ * If so, it scans backwards up to 10 characters (the UUCP system
+ * name length limit due to old versions of UNIX) to find a space.
+ * If one is found, it backs up 12 characters from that point, and
+ * sees if the string from there is `` remote from''. If so, it
+ * sets x to that position. The ``|| T'' is there so the parse
+ * will still continue.
+ * Line 13 Makes sure that there are at least 27 characters in the line.
+ * Lines 14-17 Checks if the date/time ends with the year. If so, It sees if
+ * there is a colon 3 characters further back; this would mean
+ * that there is no timezone field and zn is set to 0 and ti is
+ * left in front of the year. If not, then it expects there to
+ * either to be a space four characters back for a three-letter
+ * timezone, or a space six characters back followed by a + or -
+ * for a numeric timezone. If a timezone is found, both zn and
+ * ti are the offset of the space immediately before it.
+ * Lines 18-20 Are the failure case for a date/time not ending with a year in
+ * line 14. If there is a space four characters back, it is a
+ * three-letter timezone; there must be a space for the year nine
+ * characters back. Otherwise, there must be a space six
+ * characters back and a + or - five characters back to indicate a
+ * numeric timezone and a space eleven characters back to indicate
+ * a year. zn and ti are set appropriately.
+ * Line 21 Make sure that the string before ti is of the form hh:mm or
+ * hh:mm:ss. There must be a colon three characters back, and a
+ * space six or nine characters back (depending upon whether or
+ * not the character six characters back is a colon). ti is set
+ * to be the offset of the space immediately before the time.
+ * Line 22 Make sure the string before ti is of the form www mmm dd.
+ * There must be a space three characters back (in front of the
+ * day), one seven characters back (in front of the month), and
+ * one eleven characters back (in front of the day of week).
+ *
+ * Why a macro? It gets invoked a *lot* in a tight loop. On some of the
+ * newer pipelined machines it is faster being open-coded than it would be if
+ * subroutines are called.
+ *
+ * Why does it scan backwards from the end of the line, instead of doing the
+ * much easier forward scan? There is no deterministic way to parse the
+ * ``user'' field, because it may contain unquoted spaces! Yes, I tested it to
+ * see if unquoted spaces were possible. They are, and I've encountered enough
+ * evil mail to be totally unwilling to trust that ``it will never happen''.
+ */
+
+
+/*----------------------------------------------------------------------
+ Get the new mail out of the spooled mail file
+
+ Args: stream -- The inbox stream to add mail too
+ spool -- The path name of the spool file
+ mailbox -- Name user sees for this, used only for error reporting
+
+ Result:
+
+ - Lock the spool mail file with bezerk_lock
+ - Get the carmel2 index open and remember where we started in it
+ - Make buffer big enough for biggest header we'll mess with
+ - Loop reading all the message out of the spool file:
+ - Get a new data file for the message and open it
+ - Read the header of the message into the big buffer while...
+ - writing the message into the new data file
+ - finish writing the text of the message into the data file
+ - If all went well bump the message count and say it exists
+ - Parse the header of the message to get an envelope, date and size
+ - Write an entry into the carmel2 index
+ - Unlink and create (to zero) the spool file and unlock it
+
+The carmel inbox should be locked when this is called and mm_critical
+should be called around this function.
+ ----*/
+void
+carmel2_spool_mail(stream, spool, mailbox, clear_spool_file)
+ MAILSTREAM *stream;
+ char *spool, *mailbox;
+ int clear_spool_file;
+{
+ char *p, *eof, *from_p;
+ int n, size, fd, in_header, from_i1, from_i2;
+ long file_pos, index_file_pos, byte_count, start_of_append;
+ FILE *spool_stream, *data_stream;
+ ENVELOPE *envelope;
+ BODY *b;
+ MESSAGECACHE *mc;
+ STRING string_struct;
+#ifdef BWC
+ int is_wide;
+#endif
+
+ /*--- Get the locks set and files open-----*/
+ fd = carmel2_bezerk_lock(spool, mailbox);
+ if(fd < 0) {
+ return;
+ }
+ spool_stream = fdopen(fd, "r");
+ fseek(LOCAL->index_stream, 0L, 2);
+ start_of_append = ftell(LOCAL->index_stream);
+
+ /*--- Make buffer big enough for largest allowable header ----*/
+ if(LOCAL->msg_buf_size < CARMEL_MAX_HEADER) {
+ if(LOCAL->msg_buf != NULL)
+ fs_give((void **)&(LOCAL->msg_buf));
+ LOCAL->msg_buf_size = CARMEL_MAX_HEADER;
+ LOCAL->msg_buf = fs_get(CARMEL_MAX_HEADER);
+ }
+ LOCAL->buffered_header_and_text = 0;
+ LOCAL->msg_buf_text_offset = 0L;
+ if(LOCAL->buffered_file != NULL)
+ fs_give((void **)&(LOCAL->buffered_file));
+
+
+ /*---- Read (and ignore) the first line with the "From " in it ----*/
+ eof = fgets(carmel_20k_buf, sizeof(carmel_20k_buf), spool_stream);
+
+ /*----- Loop getting messages ----*/
+ while(eof != NULL) {
+
+ /*----- get a data file for the new message ----*/
+ n = carmel2_new_data_file(stream->local, stream->mailbox);
+ data_stream = fopen((*LOCAL->calc_paths)(CalcPathCarmel2Data,
+ stream->mailbox, n),"w");
+ if(data_stream == NULL)
+ goto backout;
+ file_pos = ftell(spool_stream);
+ p = LOCAL->msg_buf;
+ in_header = 1;
+ byte_count = 0L;
+#ifdef BWC
+ is_wide = 0;
+#endif
+
+
+ /*--------------------------------------------------------------------
+ Read the message in line by line, writing it out to the
+ new data file. Also acculamuate a copy of the header in
+ a buffer for parsing
+ ---*/
+ eof = fgets(carmel_20k_buf, sizeof(carmel_20k_buf), spool_stream);
+ while(eof != NULL){
+ if(VALID(carmel_20k_buf, from_p, from_i1, from_i2))
+ break;
+
+ if(in_header) {
+#ifdef BWC
+ is_wide |= carmel_match_glyph_wide(carmel_20k_buf);
+#endif
+ if(*carmel_20k_buf == '\n') {
+ /* Encountered first blank line, end of header */
+ in_header = 0;
+ *p = '\0';
+ } else {
+ if(p - LOCAL->msg_buf + strlen(carmel_20k_buf) >
+ LOCAL->msg_buf_size){
+ /* out of room in buffer, end it */
+ in_header = 0;
+ *p = '\0';
+ } else {
+ strcpy(p, carmel_20k_buf);
+ p +=strlen(p);
+ }
+ }
+ }
+
+ /*----- Write the message into the file -----*/
+ byte_count += strlen(carmel_20k_buf);
+ if(carmel_20k_buf[0] == '>' && carmel_20k_buf[1] == 'F' &&
+ carmel_20k_buf[2] == 'r' && carmel_20k_buf[3] == 'o' &&
+ carmel_20k_buf[4] == 'm' && carmel_20k_buf[5] == ' ') {
+ if(fputs(carmel_20k_buf + 1, data_stream) == EOF)
+ goto backout;
+ byte_count -= 1;
+ } else {
+ if(fputs(carmel_20k_buf, data_stream) == EOF)
+ goto backout;
+ }
+ eof = fgets(carmel_20k_buf, sizeof(carmel_20k_buf), spool_stream);
+ }
+ fclose(data_stream);
+#ifdef BWC
+ if(is_wide) {
+ sprintf(carmel_path_buf, "%s.wid",
+ (*LOCAL->calc_paths)(CalcPathCarmel2Data,stream->mailbox,n)
+ );
+ rename((*LOCAL->calc_paths)(CalcPathCarmel2Data,stream->mailbox,n),
+ carmel_path_buf);
+ }
+#endif
+
+ /*---- get a new MESSAGECACHE to store it in -----*/
+ mc = carmel2_new_mc(stream, stream->nmsgs + 1);
+
+ /*------ Parse the message header ------*/
+ INIT(&string_struct, mail_string, (void *)"", 0);
+ rfc822_parse_msg(&envelope, &b, LOCAL->msg_buf, strlen(LOCAL->msg_buf),
+ &string_struct, mylocalhost(), carmel_20k_buf);
+ carmel2_parse_bezerk_status(mc, LOCAL->msg_buf);
+ carmel2_rfc822_date(mc, LOCAL->msg_buf);
+ mc->rfc822_size = byte_count;
+ mc->data2 = n;
+ mc->data1 = ftell(LOCAL->index_stream);
+ mc->recent = 1;
+
+ /*----- Now add the message to the Carmel2 index ------*/
+ if(carmel2_write_index(envelope, mc, LOCAL->index_stream) < 0)
+ goto backout;
+
+ /*----- Write message into auxiliary index (plain carmel) ----*/
+ if(LOCAL->carmel && LOCAL->aux_copy != NULL) {
+ if((*LOCAL->aux_copy)(stream->local, mailbox, envelope, mc)) {
+ /* BUG - this error may leave things half done, but will
+ only result in duplicated mail */
+ goto backout;
+ }
+ }
+
+ /*---- All went well, let the user know -----*/
+ stream->nmsgs++;
+ mm_exists(stream, stream->nmsgs);
+
+ mail_free_envelope(&envelope);
+ }
+
+ fflush(LOCAL->index_stream); /* Force new index entries onto disk */
+ fclose(spool_stream);
+ if(clear_spool_file) {
+ unlink(spool);
+ close(creat(spool, 0600));
+ }
+ carmel2_bezerk_unlock(fd, spool);
+ return;
+
+ backout:
+ sprintf(carmel_error_buf, "Error incorporating new mail into \"%s\": %s",
+ carmel_parse_mb_name(mailbox,'\0')->mailbox, strerror(errno));
+ /* bug in above call to parse_mb -- should have version passed in */
+ mm_log(carmel_error_buf, ERROR);
+ fflush(LOCAL->index_stream);
+ ftruncate(fileno(LOCAL->index_stream), start_of_append);
+ carmel2_bezerk_unlock(fd, spool);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Copy the actual data file when copying a message
+
+Returns: -1 for failure
+ otherwise the number of the new file,
+
+ ----*/
+static
+carmel2_copy_msg_file(stream, orig_file_number, new_mailbox)
+ MAILSTREAM *stream;
+ int orig_file_number;
+ char *new_mailbox;
+{
+ char *file_name;
+ int wide, e, new_file_num, n, orig_fd, new_fd;
+
+ /*---- Open the orginal file ----*/
+ wide = 0;
+ file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Data,
+ new_mailbox,orig_file_number);
+ if(file_name == NULL)
+ return(-1);
+
+ orig_fd = open(file_name, O_RDONLY);
+ if(orig_fd < 0 && LOCAL->carmel) {
+ strcat(file_name, ".wid");
+ orig_fd = open(file_name, O_RDONLY);
+ if(orig_fd < 0)
+ goto bomb;
+ wide = 1;
+ } else {
+ goto bomb;
+ }
+
+ /*----- Open and create the new file ------*/
+ new_file_num = carmel2_new_data_file(stream->local, new_mailbox);
+ if(new_file_num < 0)
+ goto bomb;
+ file_name = (*LOCAL->calc_paths)(CalcPathCarmel2Data,
+ new_mailbox, new_file_num);
+ if(wide)
+ strcat(file_name, ".wid");
+ new_fd = open(file_name, O_WRONLY | O_CREAT, 0600);
+ if(new_fd < 0) {
+ goto bomb;
+ }
+
+ /*---- Copy the bits ------*/
+ e = 0;
+ while((n = read(orig_fd, carmel_20k_buf, sizeof(carmel_20k_buf))) >0) {
+ if(write(new_fd, carmel_20k_buf, n) < 0) {
+ e = errno;
+ break;
+ }
+ }
+
+ /*---- Close the streams and handle any errors ----*/
+ close(orig_fd);
+ close(new_fd);
+
+ if(e == 0)
+ return(new_file_num); /* All is OK */
+
+ /*--- something didn't go right ---*/
+ bomb:
+ unlink(file_name);
+ sprintf(carmel_error_buf, "Error copying message to %s: %s",
+ new_mailbox, strerror(errno));
+ mm_log(carmel_error_buf, ERROR);
+ return(-1);
+}
+
+
+
+
+
+
+/*----------------------------------------------------------------------
+ Locking for Carmel and Pod formats
+
+Args: stream -- Mail stream we're operating on
+ file -- Mail file name
+ write -- Flag indicating we want write access
+
+Retuns: -1 if lock fails, 0 if it succeeds
+
+- There are two locks. Plain locks and write locks. They are created
+ about the same way, but have different names. The time out on the write
+ locks is much shorter, because it should never be held for very long.
+
+- Hitching (links in file system) post locking is used so it will work
+ across NFS. Flock() could be used as it has two advantages. First it
+ is more efficient, second the locks will disolve automatically when the
+ process dies. The efficiency is not of great concern, and the
+ process should not (theoretically) die unless it crashes due to a bug
+ or it is abnormally terminated. The advantage of locking across NFS
+ is considered greater than the advantages of flock().
+
+- The mod time of the lock file is updated everytime mail_check()
+ or mail_ping() is called and the mod time on the lock file is recorded.
+ This is so it can be determined that the lock file is current.
+
+- When a read lock file over 30 or a write lock over 5 minutes old is
+ encountered it is assumed the lock is old and should be overridden
+ (because the process crashed or was killed).
+
+- Everytime the mod time on the lock file is updated (on calls to
+ mail_check() and mail_ping()), the mod time of the lock file is
+ checked against the record of what it was last set to. If the mod times
+ don't match the lock has been broken and overridden. Then the original
+ Pine should go into read-only mode.... This is only likely to happen if
+ someone suspends (^Z's) the process for more than 30 minutes, and another
+ process is started.
+ ----*/
+int
+carmel2_lock(local, file, write)
+ CARMEL2LOCAL *local;
+ char *file;
+ int write;
+{
+ char *hitch, lock[CARMEL_PATHBUF_SIZE], error_mess[200], *l_path;
+ struct stat sb;
+ int n, link_result, hitch_fd, timer_set, l;
+ long override, timer;
+
+ /* Set the length of time for override. It is 5 minutes for a
+ write lock (no process should have it for that long) and
+ 30 minutes for a read lock, that's 30 minutes since the
+ lock file was last touched. */
+ override = 60 * (write ? 5 : 30);
+ timer = -1;
+
+ /*----- Get the lock file and hitch names -----*/
+ l_path = (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
+ CalcPathCarmel2ReadLock,
+ file, 0);
+ if(l_path == NULL)
+ return(-1);
+ strcpy(lock, l_path);
+ /* copy lock file into bufferl call it hitch, unique thing added below */
+ hitch = carmel_path_buf;
+ hitch = strcpy(hitch, lock);
+ l = strlen(hitch);
+
+ do {
+ /*--- First create hitching post ----*/
+ for(n = time(0) % 6400; ; n += 10007) {
+ /* Get random file name, that's not too long */
+ sprintf(hitch + l, "_%c%c", '0' + (n % 80) , '0' + (n/80));
+ if(stat(hitch, &sb) < 0)
+ break; /* Got a name that doesn't exist */
+ }
+ hitch_fd = open(hitch, O_CREAT, 0666);
+ if(hitch_fd < 0) {
+ sprintf(error_mess, "Error creating lock file \"%s\": %s",
+ hitch, strerror(errno));
+ mm_log(error_mess, WARN);
+ return(-1);
+ }
+ close(hitch_fd);
+
+ /*----- Got a hitching post, now try link -----*/
+ link_result = link(hitch, lock);
+ stat(lock, &sb);
+ unlink(hitch);
+ if(link_result == 0 && sb.st_nlink == 2) {
+ /*------ Got the lock! ------*/
+ stat(lock, &sb);
+ if(write)
+ local->write_lock_mod_time = sb.st_mtime;
+ else
+ local->read_lock_mod_time = sb.st_mtime;
+ return(0);
+ }
+
+ /*----- Check and override if lock is too old -----*/
+ if(sb.st_mtime + override < time(0)) {
+ unlink(lock); /* Lock is old, override it */
+ timer = 100; /* Get us around the loop again */
+ continue;
+ } else {
+ if(timer < 0) /* timer not set */
+ timer = sb.st_mtime + override - time(0);
+ }
+
+ /*----- Will user wait till time for override? -----*/
+ if(!write || timer > 5 * 60) {
+ return(-1); /* Not more that 5 minutes */
+ }
+
+ /*----- Try again, and tell user we're trying -------*/
+ if(!(timer % 15)) {
+ sprintf(error_mess,
+ "Please wait. Mailbox %s is locked for %d more seconds",
+ file, timer);
+ mm_log(error_mess, WARN);
+ }
+ timer--;
+ sleep(1);
+ } while(timer > 0);
+
+ return(-1);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Unlock a carmel mail stream
+
+Args: stream -- The mailstream that is locked
+ mailbox -- FQN of mailbox to lock ( e.g. #carmel#foo )
+ write -- flag to set if it is a write lock
+
+Nothing is returned
+ ----*/
+void
+carmel2_unlock(local, mailbox, write)
+ CARMEL2LOCAL *local;
+ char *mailbox;
+ int write;
+{
+ char lock[CARMEL_PATHBUF_SIZE];
+ struct stat sb;
+
+ strcpy(lock, (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
+ CalcPathCarmel2ReadLock,
+ mailbox, 0));
+
+ if(stat(lock, &sb) < 0)
+ return; /* Hmmm... lock already broken */
+
+ if(sb.st_mtime !=
+ (write ? local->write_lock_mod_time : local->read_lock_mod_time))
+ return; /* Hmmm... not our lock */
+
+ unlink(lock);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Keep the mod date on the lock file fresh
+
+Args: stream --
+ file -- the name of the mailbox the lock is for
+ write -- set if this is a write lock
+
+Returns: 0 if update was OK
+ -1 if something bad happened, like the lock was stolen
+ ----*/
+static int
+carmel2_update_lock(local, file, write)
+ CARMEL2LOCAL *local;
+ char *file;
+ int write;
+{
+ char lock[CARMEL_PATHBUF_SIZE];
+ struct timeval tp[2];
+ struct stat sb;
+
+ strcpy(lock, (*local->calc_paths)(write ? CalcPathCarmel2WriteLock:
+ CalcPathCarmel2ReadLock,
+ file, 0));
+
+ if(stat(lock, &sb) < 0) {
+ /* Lock file stolen, oh oh */
+ return(-1);
+ }
+
+ if(sb.st_mtime !=
+ (write ? local->write_lock_mod_time : local->read_lock_mod_time)) {
+ /* Not our lock anymore , oh oh */
+ return(-1);
+ }
+
+ gettimeofday (&tp[0],NIL); /* set atime to now */
+ gettimeofday (&tp[1],NIL); /* set mtime to now */
+ utimes(lock, tp);
+
+ if(write)
+ local->write_lock_mod_time = tp[1].tv_sec;
+ else
+ local->read_lock_mod_time = tp[1].tv_sec;
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Berkeley open and lock mailbox
+
+This is mostly ripped off from the Bezerk driver
+ ----*/
+
+static int
+carmel2_bezerk_lock (spool, file)
+ char *spool, *file;
+{
+ int fd,ld,j;
+ int i = BEZERKLOCKTIMEOUT * 60 - 1;
+ struct timeval tp;
+ struct stat sb;
+ char *hitch, *lock;
+
+ lock = carmel_path_buf;
+ sprintf(lock, "%s.lock", spool);
+ do { /* until OK or out of tries */
+ gettimeofday (&tp,NIL); /* get the time now */
+#ifdef NFSKLUDGE
+ /* SUN-OS had an NFS, As kludgy as an albatross;
+ * And everywhere that it was installed, It was a total loss. -- MRC 9/25/91
+ */
+ /* build hitching post file name */
+ hitch = carmel_20k_buf;
+ sprintf(hitch, "%s.%d.%d.",carmel_path_buf,time (0),getpid ());
+ j = strlen (hitch); /* append local host name */
+ gethostname (hitch + j,(MAILTMPLEN - j) - 1);
+ /* try to get hitching-post file */
+ if ((ld = open (hitch, O_WRONLY|O_CREAT|O_EXCL,0666)) < 0) {
+ /* prot fail & non-ex, don't use lock files */
+ if ((errno == EACCES) && (stat (hitch, &sb))) *lock = '\0';
+ else { /* otherwise something strange is going on */
+ sprintf (carmel_20k_buf,"Error creating %s: %s",lock,strerror (errno));
+ mm_log (carmel_20k_buf, WARN); /* this is probably not good */
+ /* don't use lock files if not one of these */
+ if ((errno != EEXIST) && (errno != EACCES)) *lock = '\0';
+ }
+ }
+ else { /* got a hitching-post */
+ chmod (hitch,0666); /* make sure others can break the lock */
+ close (ld); /* close the hitching-post */
+ link (hitch,lock); /* tie hitching-post to lock, ignore failure */
+ stat (hitch, &sb); /* get its data */
+ unlink (hitch); /* flush hitching post */
+ /* If link count .ne. 2, hitch failed. Set ld to -1 as if open() failed
+ so we try again. If extant lock file and time now is .gt. file time
+ plus timeout interval, flush the lock so can win next time around. */
+ if ((ld = (sb.st_nlink != 2) ? -1 : 0) && (!stat (lock,&sb)) &&
+ (tp.tv_sec > sb.st_ctime + BEZERKLOCKTIMEOUT * 60)) unlink (lock);
+ }
+
+#else
+ /* This works on modern Unix systems which are not afflicted with NFS mail.
+ * "Modern" means that O_EXCL works. I think that NFS mail is a terrible
+ * idea -- that's what IMAP is for -- but some people insist upon losing...
+ */
+ /* try to get the lock */
+ if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,0666)) < 0) switch (errno) {
+ case EEXIST: /* if extant and old, grab it for ourselves */
+ if ((!stat (lock,&sb)) && tp.tv_sec > sb.st_ctime + LOCKTIMEOUT * 60)
+ ld = open (lock,O_WRONLY|O_CREAT,0666);
+ break;
+ case EACCES: /* protection fail, ignore if non-ex or old */
+ if (stat (lock,&sb) || (tp.tv_sec > sb.st_ctime + LOCKTIMEOUT * 60))
+ *lock = '\0'; /* assume no world write mail spool dir */
+ break;
+ default: /* some other failure */
+ sprintf (tmp,"Error creating %s: %s",lock,strerror (errno));
+ mm_log (tmp,WARN); /* this is probably not good */
+ *lock = '\0'; /* don't use lock files */
+ break;
+ }
+ if (ld >= 0) { /* if made a lock file */
+ chmod (tmp,0666); /* make sure others can break the lock */
+ close (ld); /* close the lock file */
+ }
+#endif
+ if ((ld < 0) && *lock) { /* if failed to make lock file and retry OK */
+ if (!(i%15)) {
+ sprintf (carmel_20k_buf,"Mailbox %s is locked, will override in %d seconds...",
+ file,i);
+ mm_log (carmel_20k_buf, WARN);
+ }
+ sleep (1); /* wait 1 second before next try */
+ }
+ } while (i-- && ld < 0 && *lock);
+ /* open file */
+ if ((fd = open (spool, O_RDONLY)) >= 0) flock (fd, LOCK_SH);
+ else { /* open failed */
+ j = errno; /* preserve error code */
+ if (*lock) unlink (lock); /* flush the lock file if any */
+ errno = j; /* restore error code */
+ }
+ return(fd);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Berkeley unlock and close mailbox
+ ----*/
+static void
+carmel2_bezerk_unlock (fd, spool)
+ int fd;
+ char *spool;
+{
+ sprintf(carmel_path_buf, "%s.lock", spool);
+
+ flock (fd, LOCK_UN); /* release flock'ers */
+ close (fd); /* close the file */
+ /* flush the lock file if any */
+ if (*carmel_path_buf) unlink (carmel_path_buf);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Make sure directory exists and is writable
+
+Args: dir - directory to check, should be full path
+
+Result: returns -1 if not OK along with mm_logging a message
+ 0 if OK
+----*/
+
+carmel2_check_dir(dir)
+ char *dir;
+{
+ struct stat sb;
+ char error_mess[150];
+
+ if(stat(dir, &sb) < 0) {
+ if(mkdir(dir, 0700) < 0) {
+ sprintf(error_mess, "Error creating directory %-.30s %s",
+ dir, strerror(errno));
+ mm_log(error_mess, WARN);
+ return(-1);
+ }
+ } else if(!(sb.st_mode & S_IFDIR)) {
+ sprintf(error_mess, "Warning: %s is not a directory",dir);
+ mm_log(error_mess, WARN);
+ return(-1);
+
+ } else if(access(dir, W_OK) != 0) {
+ sprintf(error_mess, "Warning: unable to write to %-.30s %s",
+ dir, strerror(errno));
+ mm_log(error_mess, WARN);
+ return(-1);
+ }
+ return(0);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Return the number for the next message file
+
+Args: stream -- the Mail stream for the new data file
+ mailbox -- The FQN of mailbox data file is for
+
+Returns: file number or -1
+ ----*/
+static
+carmel2_new_data_file(local, mailbox)
+ CARMEL2LOCAL *local;
+ char *mailbox;
+{
+ char *path, num_buf[50], e_buf[100];
+ int fd, num, bytes_read;
+
+ /*---- Get the full path of the .MAXNAME file for index ----*/
+ path = (*local->calc_paths)(CalcPathCarmel2MAXNAME, mailbox, 0);
+ if(path == NULL)
+ return(-1);
+
+ fd = open(path, O_RDWR|O_CREAT, 0666);
+ if(fd < 0) {
+ sprintf(e_buf, "Error getting next number of mail file: %s",
+ strerror(errno));
+ mm_log(e_buf, ERROR);
+ return(-1);
+ }
+
+ bytes_read = read(fd, num_buf, sizeof(num_buf));
+ if(bytes_read < 6) {
+ num = 100000;
+ } else {
+ num = atoi(num_buf) + 1;
+ if(num >= 999999)
+ num = 100000;
+ }
+ sprintf(num_buf, "%-6d\n", num);
+ lseek(fd, 0, 0);
+ write(fd, num_buf, strlen(num_buf));
+ close(fd);
+ return(num);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Do all the file name generation for carmel2 driver
+
+ The mailbox passed in is in either the format: #carmel2#folder or
+ #carmel2#user%folder
+This generates that absolute paths for an index, or a data file or
+the .MAXNAME file.
+
+Bug: This is untested!
+ ----*/
+static char *
+carmel2_calc_paths(operation, mailbox, data_file_num)
+ int operation;
+ char *mailbox;
+ int data_file_num;
+{
+ static char path[CARMEL_PATHBUF_SIZE], num[20];
+ char *p, *home_dir;
+ struct carmel_mb_name *parsed_name;
+ struct passwd *pw;
+
+ parsed_name = carmel_parse_mb_name(mailbox,'2');
+
+ if(parsed_name == NULL) {
+ mm_log("Internal error (bug). Invalid Carmel folder name",ERROR);
+ return(NULL);
+ }
+
+
+ if(parsed_name->user != NULL) {
+ /*---- has a user in mailbox name -----*/
+ pw = getpwnam(parsed_name->user);
+ if(pw == NULL) {
+ sprintf(carmel_error_buf,
+ "Error accessing mailbox \"%s\". No such user name \"%s\"\n",
+ mailbox, parsed_name->user);
+ mm_log(carmel_error_buf, ERROR);
+ carmel_free_mb_name(parsed_name);
+ return(0);
+ }
+ home_dir = pw->pw_dir;
+ } else {
+ home_dir = myhomedir();
+ }
+ mailbox = parsed_name->mailbox;
+
+ switch(operation) {
+
+ case CalcPathCarmel2Data:
+ sprintf(path, "%s/%s/%d", home_dir, CARMEL2_MSG_DIR, data_file_num);
+
+
+ case CalcPathCarmel2Index:
+ sprintf(path, "%s/%s/%s", home_dir, CARMEL2_INDEX_DIR, mailbox);
+ break;
+
+ case CalcPathCarmel2MAXNAME:
+ sprintf(path, "%s/%s", home_dir, CARMEL2_MAXFILE);
+ break;
+
+ case CalcPathCarmel2WriteLock:
+ sprintf(path, "%s/%s/.%s.wl", home_dir, CARMEL2_INDEX_DIR, mailbox);
+ break;
+
+ case CalcPathCarmel2ReadLock:
+ sprintf(path, "%s/%s/.%s.rl", home_dir, CARMEL2_INDEX_DIR, mailbox);
+ break;
+ }
+
+ carmel_free_mb_name(parsed_name);
+ return(path);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Find and parse the status line in a mail header
+
+Args: mc -- the message cache where status is to be stored
+ header -- the message header to parsen
+ ----*/
+void
+carmel2_parse_bezerk_status(mc, header)
+ MESSAGECACHE *mc;
+ char *header;
+{
+ register char *p;
+ for(p = header; *p; p++) {
+ if(*p != '\n')
+ continue;
+ p++;
+ if(*p != 'S' && *p != 'X')
+ continue;
+ if(strncmp(p, "Status: ", 8) == 0 || strncmp(p,"X-Status: ",10)== 0) {
+ mc->recent = 1;
+ for(p += *p == 'X' ? 10: 8; *p && *p != '\n'; p++)
+ switch(*p) {
+ case 'R': mc->seen = 1; break;
+ case 'O': mc->recent = 0; break;
+ case 'D': mc->deleted = 1; break;
+ case 'F': mc->flagged = 1; break;
+ case 'A': mc->flagged = 1; break;
+ }
+ }
+ }
+}
+
+
+
+/*----------------------------------------------------------------------
+ Turn a string of describing flags into a bit mask
+
+Args: stream -- mail stream, unused
+ flag -- string flag list
+
+Returns: returns short with bits set; bits are defined in carmel2.h
+ ----*/
+static short
+carmel2_getflags (stream, flag)
+ MAILSTREAM *stream;
+ char *flag;
+{
+ char *t, tmp[100];
+ short f = 0;
+ short i,j;
+ if (flag && *flag) { /* no-op if no flag string */
+ /* check if a list and make sure valid */
+ if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
+ mm_log ("Bad flag list",ERROR);
+ return NIL;
+ }
+ /* copy the flag string w/o list construct */
+ strncpy (tmp,flag+i,(j = strlen (flag) - (2*i)));
+ tmp[j] = '\0';
+ t = ucase (tmp); /* uppercase only from now on */
+
+ while (*t) { /* parse the flags */
+ if (*t == '\\') { /* system flag? */
+ switch (*++t) { /* dispatch based on first character */
+ case 'S': /* possible \Seen flag */
+ if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
+ t += 4; /* skip past flag name */
+ break;
+ case 'D': /* possible \Deleted flag */
+ if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
+ t[5] == 'E' && t[6] == 'D') i = fDELETED;
+ t += 7; /* skip past flag name */
+ break;
+ case 'F': /* possible \Flagged flag */
+ if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
+ t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
+ t += 7; /* skip past flag name */
+ break;
+ case 'A': /* possible \Answered flag */
+ if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
+ t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
+ t += 8; /* skip past flag name */
+ break;
+ case 'R': /* possible \Answered flag */
+ if (t[1] == 'E' && t[2] == 'C' && t[3] == 'E' && t[4] == 'N' &&
+ t[5] == 'T') i = fRECENT;
+ t += 6; /* skip past flag name */
+ break;
+ default: /* unknown */
+ i = 0;
+ break;
+ }
+ /* add flag to flags list */
+ if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
+ else { /* bitch about bogus flag */
+ mm_log ("Unknown system flag",ERROR);
+ return NIL;
+ }
+ }
+ else { /* no user flags yet */
+ mm_log ("Unknown flag",ERROR);
+ return NIL;
+ }
+ }
+ }
+ return f;
+}
+
+
+/*----------------------------------------------------------------------
+ Get a pointer to a MESSAGECACHE entry, allocating if needed
+
+Args: stream -- message stream
+ num -- Message number to allocate on for
+
+Returns: The MESSAGECACHE entry
+
+The message caches are allocated in blocks of 256 to save space taken up by
+pointers in a linked list and allocation overhead. The mc_blocks
+data structure is an array that points to each block. The macro MC()
+defined in carmel.h returns a pointer to a MESSAGECACHE given
+a message number. This function here can be called when a MESSAGECACHE
+is needed to a message number that might be new. It allocates a new
+block if needed and clears the MESSAGECACHE returned.
+
+The MESSAGECACHE entries are currently about 28 bytes which implies 28Kb
+must be used per 1000 messages. If memory is limited this driver will be
+limited in the number of messages it can handle, and the limit is due to
+the fact that these MESSAGECACHEs must be in core.
+
+It might be possible to reduce the size of each entry by a few bytes if
+the message numbers were reduced to a short, and the mostly unused keywords
+were removed. It would also be nice to add a day of the week (3 bits)
+ ----*/
+static MESSAGECACHE *
+carmel2_new_mc(stream, num)
+ MAILSTREAM *stream;
+ int num;
+{
+ MESSAGECACHE *mc;
+
+ /* Make sure we've got a cache_entry */
+ if(num >= LOCAL->cache_size) {
+ if(LOCAL->mc_blocks == NULL)
+ LOCAL->mc_blocks=(MESSAGECACHE **)fs_get(sizeof(MESSAGECACHE *));
+ else
+ fs_resize((void **)&(LOCAL->mc_blocks), ((num >>8) + 1) *
+ sizeof(MESSAGECACHE *));
+ LOCAL->mc_blocks[num >> 8] = (MESSAGECACHE *)
+ fs_get(256 * sizeof(MESSAGECACHE));
+ LOCAL->cache_size = ((num >> 8) + 1) * 256;
+ }
+
+ mc = MC(num);
+
+ mc->user_flags = 0;
+ mc->lockcount = 0;
+ mc->seen = 0;
+ mc->deleted = 0;
+ mc->flagged = 0;
+ mc->answered = 0;
+ mc->recent = 0;
+ mc->searched = 0;
+ mc->sequence = 0;
+ mc->spare = 0;
+ mc->spare2 = 0;
+ mc->spare3 = 0;
+ mc->msgno = num;
+
+ /* Don't set the date, the size and the extra data,
+ assume they will be set
+ */
+ return(mc);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Do the real work of appending a message to a mailbox
+
+Args: local -- The carmel2 local data structure, (a some what fake incomplete
+ one, set up for the purposes here)
+ mailbox -- Name of mailbox to append to
+ message -- The rfc822 format of the message with \r\n's
+
+Returns: T if all went OK, NIL if it did not
+
+ - Make sure index exists or can be created
+ - lock index for write
+ - get a data file name
+ - Put the text in the file
+ - Parse the string to get envelope and message cache
+ - Write the entry into the index
+ - Unlock the index
+
+BUG: This needs some locking and some error messages
+
+ ----*/
+carmel2_append2(stream, local, mailbox, flags, date, message)
+ char *mailbox, *flags, *date;
+ CARMEL2LOCAL *local;
+ STRING *message;
+ MAILSTREAM *stream;
+{
+ char *index_name, *data_name, *p, c, *header_string;
+ ENVELOPE *envelope;
+ BODY *b;
+ MESSAGECACHE mc;
+ FILE *index_file, *data_file;
+ struct stat sb;
+ int last_was_crlf, saved_errno;
+ STRING string_struct;
+ long size;
+ short flagbits;
+
+ /*------ Lock the mailbox for write ------*/
+ if(carmel2_lock(local, mailbox, WRITE_LOCK) < 0) {
+ sprintf(carmel_error_buf,
+ "Mailbox \"%s\" is locked. Can't append to it.",
+ mailbox);
+ mm_log(carmel_error_buf, ERROR);
+ return(NIL);
+ }
+
+ /*----- Get the file name of carmel2 index -----*/
+ index_name = (*local->calc_paths)(CalcPathCarmel2Index, mailbox, 0);
+ if(index_name == NULL) {
+ saved_errno = 0;
+ goto bomb;
+ }
+
+ /*------ See if it exists already or not ------*/
+ if(stat(index_name, &sb) < 0) {
+ mm_notify (stream,"[TRYCREATE] Must create mailbox before copy", NIL);
+ carmel2_unlock(local, mailbox, WRITE_LOCK);
+ return(NIL);
+ }
+
+ index_file = fopen(index_name, "a");
+ if(index_file == NULL)
+ goto bomb;
+
+ mc.data2 = carmel2_new_data_file(local, mailbox);
+
+ flagbits = carmel2_getflags(NULL, flags);
+
+ if(flagbits & fSEEN) mc.seen = T;
+ if(flagbits & fDELETED) mc.deleted = T;
+ if(flagbits & fFLAGGED) mc.flagged = T;
+ if(flagbits & fANSWERED) mc.answered = T;
+ if(flagbits & fRECENT) mc.recent = T;
+ mc.user_flags = 0;
+
+ /*----- Open the data file -----*/
+ data_name = (*local->calc_paths)(CalcPathCarmel2Data, mailbox, mc.data2);
+ if(data_name == NULL) {
+ errno = 0; /* Don't generate an error message at all */
+ goto bomb;
+ }
+ data_file = fopen(data_name, "w");
+ if(data_file == NULL)
+ goto bomb;
+
+ /*--- accumulate header as we go for later parsing ---*/
+ header_string = carmel_20k_buf;
+
+ /*------ Write the message to the file, and get header in a string -----*/
+ for(size = SIZE(message); size > 0; size--){
+ c = SNX(message);
+ if(c == '\r' && size > 1) {
+ /* Turn CRLF into LF for UNIX */
+ c = SNX(message);
+ size--;
+ if(c != '\n') {
+ if(fputc('\r', data_file) < 0 || fputc(c, data_file) < 0)
+ goto bomb;
+ if(header_string != NULL) {
+ *header_string++ = '\r';
+ *header_string++ = c;
+ }
+ } else {
+ if(fputc('\n', data_file) < 0)
+ goto bomb;
+ if(header_string != NULL) {
+ if(last_was_crlf) {
+ *header_string = '\0';
+ header_string = NULL;
+ } else {
+ *header_string++ = '\r';
+ *header_string++ = '\n';
+ }
+ }
+ last_was_crlf = 1;
+ }
+ } else {
+ if(fputc(c, data_file) == EOF)
+ goto bomb;
+ if(header_string != NULL)
+ *header_string++ = c;
+ last_was_crlf = 0;
+ }
+ }
+ if(fclose(data_file) == EOF)
+ goto bomb;
+ data_file = NULL;
+
+
+ /*----Get the size that we actually wrote -----*/
+ stat(data_name, &sb);
+ mc.rfc822_size = sb.st_size;
+
+ /* This blows the nice tight memory usage for the carmel2 driver :-( */
+ header_string = cpystr(carmel_20k_buf);
+
+#ifdef BWC
+ /*--- For MIME type x-bwc-glyph-wide, store in a nnnn.wid file ----*/
+ for(p = header_string; *p; p++) {
+ if((p == header_string && carmel_match_glyph_wide(p)) ||
+ (*p == '\r' && *(p+1) == '\n' && carmel_match_glyph_wide(p+2))) {
+ sprintf(carmel_path_buf, "%s.wid", data_name);
+ rename(data_name, carmel_path_buf);
+ break;
+ }
+ }
+#endif
+
+ /*------ Parse the message to get envelope and message cache ----*/
+ INIT(&string_struct, mail_string, (void *)"", 0);
+ rfc822_parse_msg(&envelope, &b, header_string, strlen(header_string),
+ &string_struct, mylocalhost(), carmel_20k_buf);
+ carmel2_parse_bezerk_status(&mc, header_string);
+ carmel2_rfc822_date(&mc, header_string);
+
+ /*------ Write the entry into the index ------*/
+ if(carmel2_write_index(envelope, &mc, index_file) < 0)
+ goto bomb;
+
+ if(local->aux_copy != NULL) /* Write carmel index if needed */
+ (*local->aux_copy)(local, mailbox, envelope, &mc);
+
+ mail_free_envelope(&envelope);
+ fs_give((void **)&header_string);
+
+ if(fclose(index_file) == EOF)
+ goto bomb;
+ carmel2_unlock(local, mailbox, WRITE_LOCK);
+ return(T);
+
+ bomb:
+ saved_errno = errno;
+ if(index_file != NULL) {
+ fclose(index_file);
+ }
+ if(data_file != NULL) {
+ fclose(data_file);
+ unlink(data_name);
+ }
+ carmel2_unlock(local, mailbox, WRITE_LOCK);
+ if(saved_errno != 0) {
+ sprintf(carmel_error_buf,"Message append failed: %s",
+ strerror(saved_errno));
+ mm_log(carmel_error_buf, ERROR);
+ }
+ return(NIL);
+}
+
+
+
+
+/* Search support routines
+ * Accepts: MAIL stream
+ * message number
+ * pointer to additional data
+ * pointer to temporary buffer
+ * Returns: T if search matches, else NIL
+ */
+
+static char
+carmel2_search_all (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return T; /* ALL always succeeds */
+}
+
+
+static char
+carmel2_search_answered (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->answered ? T : NIL;
+}
+
+
+static char
+carmel2_search_deleted (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->deleted ? T : NIL;
+}
+
+
+static char
+carmel2_search_flagged (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->flagged ? T : NIL;
+}
+
+
+static char
+carmel2_search_keyword (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return NIL; /* keywords not supported yet */
+}
+
+
+static char
+carmel2_search_new (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ MESSAGECACHE *elt = MC(msgno);
+ return (elt->recent && !elt->seen) ? T : NIL;
+}
+
+static char
+carmel2_search_old (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->recent ? NIL : T;
+}
+
+
+static char
+carmel2_search_recent (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->recent ? T : NIL;
+}
+
+
+static char
+carmel2_search_seen (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->seen ? T : NIL;
+}
+
+
+static char
+carmel2_search_unanswered (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->answered ? NIL : T;
+}
+
+
+static char
+carmel2_search_undeleted (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->deleted ? NIL : T;
+}
+
+
+static char
+carmel2_search_unflagged (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->flagged ? NIL : T;
+}
+
+
+static char
+carmel2_search_unkeyword (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return T; /* keywords not supported yet */
+}
+
+
+static char
+carmel2_search_unseen (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return MC(msgno)->seen ? NIL : T;
+}
+
+static char
+carmel2_search_before (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return (char) (carmel2_msgdate (stream,msgno) < n);
+}
+
+
+static char
+carmel2_search_on (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ return (char) (carmel2_msgdate (stream,msgno) == n);
+}
+
+
+static char
+carmel2_search_since (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ /* everybody interprets "since" as .GE. */
+ return (char) (carmel2_msgdate (stream,msgno) >= n);
+}
+
+
+static unsigned long
+carmel2_msgdate (stream,msgno)
+ MAILSTREAM *stream;
+ long msgno;
+{
+ struct stat sbuf;
+ struct tm *tm;
+ MESSAGECACHE *elt = MC(msgno);
+
+ return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
+}
+
+/*----------------------------------------------------------------------
+ Search only the body of the text.
+ BUG, probably need to unencode before searching
+ ---*/
+static char
+carmel2_search_body (stream,msgno, pat, pat_len)
+ MAILSTREAM *stream;
+ long msgno,pat_len;
+ char *pat;
+{
+ char *t = carmel2_fetchtext(stream, msgno);
+ return (t && search (t,strlen(t), pat, pat_len));
+}
+
+
+/*----------------------------------------------------------------------
+ Search the subject field
+ ----*/
+static char
+carmel2_search_subject (stream,msgno, pat, pat_len)
+ MAILSTREAM *stream;
+ long msgno, pat_len;
+ char *pat;
+{
+ char *t = carmel2_fetchstructure (stream,msgno,NULL)->subject;
+ return t ? search (t, strlen(t), pat, pat_len) : NIL;
+}
+
+
+/*----------------------------------------------------------------------
+ Search the full header and body text of the message
+ ---*/
+static char
+carmel2_search_text (stream, msgno, pat, pat_len)
+ MAILSTREAM *stream;
+ long msgno, pat_len;
+ char *pat;
+{
+ char *t = carmel2_fetchheader(stream,msgno);
+ return (t && search(t, strlen(t), pat, pat_len)) ||
+ carmel2_search_body(stream,msgno, pat, pat_len);
+}
+
+
+/*----------------------------------------------------------------------
+ Search the Bcc field
+ ---*/
+static char
+carmel2_search_bcc (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ carmel_20k_buf[0] = '\0';
+ /* get text for address */
+ rfc822_write_address (carmel_20k_buf,
+ carmel2_fetchstructure (stream,msgno,NULL)->bcc);
+ return search (carmel_20k_buf, strlen(carmel_20k_buf),d,n);
+}
+
+
+static char
+carmel2_search_cc (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ carmel_20k_buf[0] = '\0';
+ /* get text for address */
+ rfc822_write_address (carmel_20k_buf,
+ carmel2_fetchstructure (stream, msgno, NULL)->cc);
+ return search (carmel_20k_buf, strlen(carmel_20k_buf),d,n);
+}
+
+
+static char
+carmel2_search_from (stream,msgno,d,n)
+ MAILSTREAM *stream;
+ long msgno;
+ char *d;
+ long n;
+{
+ carmel_20k_buf[0] = '\0';
+ /* get text for address */
+ rfc822_write_address (carmel_20k_buf,
+ carmel2_fetchstructure (stream,msgno,NULL)->from);
+ return search (carmel_20k_buf, strlen(carmel_20k_buf),d,n);
+}
+
+
+static char
+carmel2_search_to (stream,msgno, pat, pat_len)
+ MAILSTREAM *stream;
+ long msgno;
+ char *pat;
+ long pat_len;
+{
+ carmel_20k_buf[0] = '\0';
+ /* get text for address */
+ rfc822_write_address (carmel_20k_buf,
+ carmel2_fetchstructure (stream, msgno, NULL)->to);
+ return(search(carmel_20k_buf,strlen(carmel_20k_buf), pat, pat_len));
+}
+
+/* Search parsers */
+
+
+/* Parse a date
+ * Accepts: function to return
+ * pointer to date integer to return
+ * Returns: function to return
+ */
+
+static search_t
+carmel2_search_date (f, n)
+ search_t f;
+ long *n;
+{
+ long i;
+ char *s;
+ MESSAGECACHE elt;
+ /* parse the date and return fn if OK */
+ return (carmel2_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
+ (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
+}
+
+/* Parse a flag
+ * Accepts: function to return
+ * pointer to string to return
+ * Returns: function to return
+ */
+
+static search_t
+carmel2_search_flag (f,d)
+ search_t f;
+ char **d;
+{
+ /* get a keyword, return if OK */
+ return (*d = strtok (NIL," ")) ? f : NIL;
+}
+
+
+/* Parse a string
+ * Accepts: function to return
+ * pointer to string to return
+ * pointer to string length to return
+ * Returns: function to return
+ */
+
+static search_t
+carmel2_search_string (f,d,n)
+ search_t f;
+ char **d;
+ long *n;
+{
+ char *c = strtok (NIL,""); /* remainder of criteria */
+ if (c) { /* better be an argument */
+ switch (*c) { /* see what the argument is */
+ case '\0': /* catch bogons */
+ case ' ':
+ return NIL;
+ case '"': /* quoted string */
+ if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
+ return NIL;
+ break;
+ case '{': /* literal string */
+ *n = strtol (c+1,&c,10); /* get its length */
+ if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
+ *n > strlen (*d = c)) return NIL;
+ c[*n] = '\255'; /* write new delimiter */
+ strtok (c,"\255"); /* reset the strtok mechanism */
+ break;
+ default: /* atomic string */
+ *n = strlen (*d = strtok (c," "));
+ break;
+ }
+ return f;
+ }
+ else return NIL;
+}
+
+
+
+
+
+
+/*----------------------------------------------------------------------
+ Some stuff to help out with the date parsing
+ ---*/
+char *xdays2[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL};
+
+char *
+month_abbrev2(month_num)
+ int month_num;
+{
+ static char *xmonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
+ if(month_num < 1 || month_num > 12)
+ return("xxx");
+ return(xmonths[month_num - 1]);
+}
+
+struct time_zone_names {
+ char *name;
+ int hours;
+ int minutes;
+} tz_names[] = {
+ {"GMT", 0, 0},
+ {"PST", -8, 0},
+ {"PDT", -7, 0},
+ {"MST", -7, 0},
+ {"MDT", -6, 0},
+ {"CST", -6, 0},
+ {"CDT", -5, 0},
+ {"EST", -5, 0},
+ {"EDT", -4, 0},
+ {"JST", 9, 0},
+ {"IST", 2, 0},
+ {"IDT", 3, 0},
+ {NULL, 0, 0}};
+
+/*----------------------------------------------------------------------
+ Parse a date string into into a structure
+
+Args: mc -- mesage cache to with structure to receive data
+ given_date -- full header with date string somewhere to be found
+
+This parses a number of date formats and produces a cannonical date in
+a structure. The basic format is:
+
+ dd mm yy hh:mm:ss.t tz
+
+It will also handle:
+ ww dd mm yy hh:mm:ss.t tz mm dd yy hh:mm:ss.t tz
+ ww dd mm hh:mm:ss.t yy tz mm dd hh:mm:ss.t yy tz
+
+It knows many abbreviations for timezones, but is not complete.
+In general absolute time zones in hours +/- GMT are best.
+ ----*/
+void
+carmel2_rfc822_date(mc, given_date)
+ char *given_date;
+ MESSAGECACHE *mc;
+{
+ char *p, **i, *q;
+ int month, year;
+ struct time_zone_names *tz;
+
+ mc->seconds = 0;
+ mc->minutes = 0;
+ mc->hours = 30;
+ mc->day = 0;
+ mc->month = 0;
+ mc->year = 0;
+ mc->zhours = 0;
+ mc->zminutes = 0;
+
+ if(given_date == NULL)
+ return;
+
+ p = given_date;
+
+ if(*p != 'D' && strncmp(p, "Date:",5))
+ while(*p) {
+ if(*p == '\n' && *(p+1) == 'D' && strncmp(p+1, "Date:", 5) == 0)
+ break;
+ p++;
+ }
+ if(!*p)
+ return;
+
+ p += 6; /* Skip "\nDate: " */
+ while(isspace(*p))
+ p++;
+
+ /* Start with month, weekday or day ? */
+ for(i = xdays2; *i != NULL; i++)
+ if(struncmp2(p, *i, 3) == 0) /* Match first 3 letters */
+ break;
+ if(*i != NULL) {
+ /* Started with week day .. buz over it*/
+ while(*p && !isspace(*p) && *p != ',')
+ p++;
+ while(*p && (isspace(*p) || *p == ','))
+ p++;
+ }
+ if(isdigit(*p)) {
+ mc->day = atoi(p);
+ while(*p && isdigit(*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace(*p)))
+ p++;
+ }
+ for(month = 1; month <= 12; month++)
+ if(struncmp2(p, month_abbrev2(month), 3) == 0)
+ break;
+ if(month < 13) {
+ mc->month = month;
+
+ }
+ /* Move over month, (or whatever is there) */
+ while(*p && !isspace(*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace(*p) || *p == ',' || *p == '-'))
+ p++;
+
+ /* Check again for day */
+ if(isdigit(*p) && mc->day == -1) {
+ mc->day = atoi(p);
+ while(*p && isdigit(*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace(*p)))
+ p++;
+ }
+
+ /*-- Check for time --*/
+ for(q = p; *q && isdigit(*q); q++);
+ if(*q == ':') {
+ /* It's the time (out of place) */
+ mc->hours = atoi(p);
+ while(*p && *p != ':' && !isspace(*p))
+ p++;
+ if(*p == ':') {
+ mc->minutes = atoi(p);
+ while(*p && *p != ':' && !isspace(*p))
+ p++;
+ if(*p == ':') {
+ mc->seconds = atoi(p);
+ while(*p && !isspace(*p))
+ p++;
+ }
+ }
+ while(*p && isspace(*p))
+ p++;
+ }
+
+ /* Get the year 0-50 is 2000-2050; 50-100 is 1950-1999 and
+ 101-9999 is 101-9999 */
+ if(isdigit(*p)) {
+ year = atoi(p);
+ if(year < 50)
+ year += 2000;
+ else if(year < 100)
+ year += 1900;
+ mc->year = year - 1969;
+ while(*p && isdigit(*p))
+ p++;
+ while(*p && (*p == '-' || *p == ',' || isspace(*p)))
+ p++;
+ } else {
+ /* Something wierd, skip it and try to resynch */
+ while(*p && !isspace(*p) && *p != ',' && *p != '-')
+ p++;
+ while(*p && (isspace(*p) || *p == ',' || *p == '-'))
+ p++;
+ }
+
+ /*-- Now get hours minutes, seconds and ignore tenths --*/
+ for(q = p; *q && isdigit(*q); q++);
+ if(*q == ':' && mc->hours == 30) {
+ mc->hours = atoi(p);
+ while(*p && *p != ':' && !isspace(*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ mc->minutes = atoi(p);
+ while(*p && *p != ':' && !isspace(*p))
+ p++;
+ if(*p == ':') {
+ p++;
+ mc->seconds = atoi(p);
+ while(*p && !isspace(*p) && *p != '+' && *p != '-')
+ p++;
+ }
+ }
+ }
+ while(*p && isspace(*p))
+ p++;
+
+
+ /*-- The time zone --*/
+ if(*p) {
+ if(*p == '+' || *p == '-') {
+ char tmp[3];
+ mc->zoccident = (*p == '+' ? 1 : 0);
+ p++;
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ mc->zhours = atoi(tmp);
+ tmp[0] = *p++;
+ tmp[1] = *p++;
+ tmp[2] = '\0';
+ mc->zminutes *= atoi(tmp);
+ } else {
+ for(tz = tz_names; tz->name != NULL; tz++) {
+ if(struncmp2(p, tz->name, strlen(tz->name)) ==0) {
+ if(tz->hours >= 0) {
+ mc->zhours = tz->hours;
+ mc->zoccident = 1;
+ } else {
+ mc->zhours = -tz->hours;
+ mc->zoccident = 0;
+ }
+ mc->zminutes = tz->minutes;
+ break;
+ }
+ }
+ }
+ }
+ if(mc->hours == 30)
+ mc->hours = 0;
+}
+
+
+
+/*----------------------------------------------------------------------
+ Print the date from the MESSAGECACHE into the string
+ ----*/
+static void
+carmel2_date2string(string, mc)
+ char *string;
+ MESSAGECACHE *mc;
+{
+ sprintf(string, "%d %s %d %d:%02d:%02d %s%04d",
+ mc->day, month_abbrev2(mc->month), 1969+mc->year,
+ mc->hours, mc->minutes, mc->seconds, mc->zoccident ? "-" : "",
+ mc->zhours * 100 + mc->zminutes);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Read the date into a structure from line in Carmel index
+
+ Args: mc -- The structure to contain the date (and other things)
+ string -- String to be parsed. Format is:
+ "yyyy^Amm^Add^Ahh^Amm^Ass^Azh^Azm"
+
+ ----*/
+static void
+carmel2_parse_date(mc, string)
+ MESSAGECACHE *mc;
+ char *string;
+{
+ int n;
+ mc->year = next_num(&string) - 1969;
+ mc->month = next_num(&string);
+ mc->day = next_num(&string);
+ mc->hours = next_num(&string);
+ mc->minutes = next_num(&string);
+ mc->seconds = next_num(&string);
+
+ n = next_num(&string);
+ if(n < 0) {
+ mc->zoccident = 0;
+ mc->zhours = -n;
+ } else {
+ mc->zoccident = 1;
+ mc->zhours = n;
+ }
+ mc->zminutes = next_num(&string);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Read the next number out of string and return it, advancing the string
+ ----*/
+static
+next_num(string)
+ char **string;
+{
+ int n;
+ char *p;
+
+ if(string == NULL)
+ return(0);
+
+ p = *string;
+ n = atoi(p);
+ while(*p > '\001')
+ p++;
+ if(*p)
+ *string = p+1;
+ else
+ *string = NULL;
+ return(n);
+}
+
+
+/*----------------------------------------------------------------------
+ Take a (slightly ugly) FQ mailbox and and return the prettier
+ last part of it
+ ----*/
+char *
+carmel_pretty_mailbox(mailbox)
+ char *mailbox;
+{
+ char *pretty_mb;
+
+ for(pretty_mb = mailbox + strlen(mailbox) - 1;
+ *pretty_mb != '#' && pretty_mb > mailbox;
+ pretty_mb--)
+ ;
+ if(*pretty_mb == '#')
+ pretty_mb++;
+ return(pretty_mb);
+}
+
+/*----------------------------------------------------------------------
+ Parse a carmel mailbox name into its parts
+
+Args: fq_name: The name to parse
+ given_version: The version that must match; currently either \0 or '2'
+
+Returns: NULL if not a valid carmel name, version of name and given_version
+ do not match, or a malloced structure if it is.
+ ----*/
+struct carmel_mb_name *
+carmel_parse_mb_name(fq_name, given_version)
+ char *fq_name;
+ char given_version;
+{
+ char *p, *q, version[2];
+ struct carmel_mb_name *parsed_name;
+
+ if(struncmp2(fq_name, CARMEL_NAME_PREFIX, strlen(CARMEL_NAME_PREFIX))!= 0){
+ return(0); /* BUG -- we won't work with non-FQN names for now */
+ p = fq_name;
+ version[0] = given_version;
+ version[1] = '\0';
+ } else {
+ if(fq_name[7] == CARMEL_NAME_CHAR) {
+ version[0] = '\0';
+ p = fq_name + 8;
+ } else if(fq_name[8] == CARMEL_NAME_CHAR) {
+ version[0] = fq_name[7];
+ version[1] = '\0';
+ p = fq_name + 9;
+ } else {
+ return(NULL);
+ }
+ }
+
+ if(given_version != version[0])
+ return(NULL);
+
+ parsed_name=(struct carmel_mb_name *)fs_get(sizeof(struct carmel_mb_name));
+ parsed_name->version[0] = version[0];
+ parsed_name->version[1] = version[1];
+
+ /*---- Find second # if there is one ---*/
+ for(q = p; *q && *q != CARMEL_NAME_CHAR; q++);
+
+ if(*q == CARMEL_NAME_CHAR) {
+ /*----- There is a user name -----*/
+ parsed_name->user = fs_get((q - p) + 1);
+ strncpy(parsed_name->user, p, q - p);
+ parsed_name->user[q - p] = '\0';
+ p = q + 1;
+ } else {
+ parsed_name->user = NULL;
+ }
+
+ parsed_name->mailbox = cpystr(p);
+
+ return(parsed_name);
+}
+
+
+void
+carmel_free_mb_name(mb_name)
+ struct carmel_mb_name *mb_name;
+{
+ if(mb_name->user != NULL)
+ fs_give((void **)(&(mb_name->user)));
+ fs_give((void **)(&(mb_name->mailbox)));
+ fs_give((void **)&mb_name);
+}
+
+
+
+
+
+
+
+
+
+
+/*--------------------------------------------------
+ A case insensitive strcmp()
+
+ Args: o, r -- The two strings to compare
+
+ Result: integer indicating which is greater
+ ---*/
+strucmp2(o, r)
+ register char *r, *o;
+{
+ if(r == NULL && o == NULL)
+ return(0);
+ if(o == NULL)
+ return(1);
+ if(r == NULL)
+ return(-1);
+
+ while(*o && *r && (isupper(*o) ? tolower(*o) : *o) ==
+ (isupper(*r) ? tolower(*r) : *r)) {
+ o++, r++;
+ }
+ return((isupper(*o) ? tolower(*o): *o)-(isupper(*r) ? tolower(*r) : *r));
+}
+
+
+
+/*----------------------------------------------------------------------
+ A case insensitive strncmp()
+
+ Args: o, r -- The two strings to compare
+ n -- length to stop comparing strings at
+
+ Result: integer indicating which is greater
+
+ ----*/
+struncmp2(o, r, n)
+ register char *r, *o;
+ register int n;
+{
+ if(r == NULL && o == NULL)
+ return(0);
+ if(o == NULL)
+ return(1);
+ if(r == NULL)
+ return(-1);
+
+ n--;
+ while(n && *o && *r &&
+ (isupper(*o)? tolower(*o): *o) == (isupper(*r)? tolower(*r): *r)) {
+ o++; r++; n--;
+ }
+ return((isupper(*o)? tolower(*o): *o) - (isupper(*r)? tolower(*r): *r));
+}
+
+/*----------------------------------------------------------------------
+ A replacement for strchr or index ...
+
+ ....so we don't have to worry if it's there or not. We bring our own.
+If we really care about efficiency and think the local one is more
+efficient the local one can be used, but most of the things that take
+a long time are in the c-client and not in pine.
+ ----*/
+char *
+strindex2(buffer, ch)
+ char *buffer;
+ int ch;
+{
+ /** Returns a pointer to the first occurance of the character
+ 'ch' in the specified string or NULL if it doesn't occur **/
+
+ do
+ if(*buffer == ch)
+ return(buffer);
+ while (*buffer++ != '\0');
+
+ return(NULL);
+}
+
+
+/*======================================================================
+ */
+
+xopen(file, mode)
+ char *file, *mode;
+{}
+
+
+xclose(stream)
+ FILE *stream;
+{}
+
+xread() {}
+
+xwrite() {}
+
+xlseek() {}
+
+#ifdef BWC
+carmel_match_glyph_wide(string)
+ char *string;
+{
+ extern char *ptspecials;
+ char *s;
+
+ if(struncmp2(string, "content-type", 12))
+ return(0); /* Nope */
+ string += 12;
+ while(*string && isspace(*string)) string++;
+ if(*string != ':')
+ return(0);
+ string++;
+ while(*string && isspace(*string)) string++;
+ s = string;
+ string = rfc822_parse_word(string, ptspecials);
+ if(string == NULL)
+ return(0);
+ if(struncmp2(s, "text", 4))
+ return(0);
+ while(*string && isspace(*string)) string++;
+ if(*string != '/')
+ return;
+ string++;
+ while(*string && isspace(*string)) string++;
+ s = string;
+ string = rfc822_parse_word(string, ptspecials);
+ if(string == NULL)
+ return(0);
+ if(struncmp2(s, "x-bwc-glyph-wide", 16))
+ return(0);
+ return(1);
+}
+#endif
+