diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /contrib/carmel/c-client | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'contrib/carmel/c-client')
-rw-r--r-- | contrib/carmel/c-client/Makefile.patch | 49 | ||||
-rw-r--r-- | contrib/carmel/c-client/bzk2cml.c | 103 | ||||
-rw-r--r-- | contrib/carmel/c-client/carmel-purge.sh | 89 | ||||
-rw-r--r-- | contrib/carmel/c-client/carmel.c | 1232 | ||||
-rw-r--r-- | contrib/carmel/c-client/carmel.h | 77 | ||||
-rw-r--r-- | contrib/carmel/c-client/carmel2.c | 4412 | ||||
-rw-r--r-- | contrib/carmel/c-client/carmel2.h | 200 |
7 files changed, 6162 insertions, 0 deletions
diff --git a/contrib/carmel/c-client/Makefile.patch b/contrib/carmel/c-client/Makefile.patch new file mode 100644 index 00000000..9134d31a --- /dev/null +++ b/contrib/carmel/c-client/Makefile.patch @@ -0,0 +1,49 @@ +*** Makefile Tue Aug 23 16:42:37 1994 +--- Makefile.new Wed Aug 31 18:40:39 1994 +*************** +*** 31,43 **** + # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + ARCHIVE=c-client.a + ARRC=ar rc + BINARIES=mail.o bezerk.o mtx.o tenex2.o mbox.o mh.o mmdf.o imap2.o pop3.o \ + news.o nntpcunx.o phile.o dummy.o smtp.o nntp.o rfc822.o misc.o \ +! osdep.o sm_unix.o + CFLAGS=$(EXTRACFLAGS) +! DEFAULTDRIVERS=imap nntp pop3 mh mtx tenex mmdf bezerk news phile dummy + LDFLAGS=$(EXTRALDFLAGS) + LN=ln -s + MAKE=make +--- 31,44 ---- + # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ++ EXTRACFLAGS=-DBWC + ARCHIVE=c-client.a + ARRC=ar rc + BINARIES=mail.o bezerk.o mtx.o tenex2.o mbox.o mh.o mmdf.o imap2.o pop3.o \ + news.o nntpcunx.o phile.o dummy.o smtp.o nntp.o rfc822.o misc.o \ +! osdep.o sm_unix.o carmel.o carmel2.o + CFLAGS=$(EXTRACFLAGS) +! DEFAULTDRIVERS=imap carmel carmel2 nntp pop3 mh mtx tenex mmdf bezerk news phile dummy + LDFLAGS=$(EXTRALDFLAGS) + LN=ln -s + MAKE=make +*************** +*** 207,213 **** + + ult: # Ultrix + $(MAKE) mtest OS=$@ EXTRADRIVERS="$(EXTRADRIVERS)" \ +! STDPROTO=bezerkproto \ + CFLAGS="-g3 -O2 -Olimit 800 -Dconst= $(EXTRACFLAGS)" \ + LDFLAGS="-lauth $(EXTRALDFLAGS)" + +--- 208,214 ---- + + ult: # Ultrix + $(MAKE) mtest OS=$@ EXTRADRIVERS="$(EXTRADRIVERS)" \ +! STDPROTO=carmelproto \ + CFLAGS="-g3 -O2 -Olimit 800 -Dconst= $(EXTRACFLAGS)" \ + LDFLAGS="-lauth $(EXTRALDFLAGS)" + diff --git a/contrib/carmel/c-client/bzk2cml.c b/contrib/carmel/c-client/bzk2cml.c new file mode 100644 index 00000000..fcff6438 --- /dev/null +++ b/contrib/carmel/c-client/bzk2cml.c @@ -0,0 +1,103 @@ +#include <stdio.h> +#include <ctype.h> +#include <pwd.h> +#include <sys/dir.h> +#include "mail.h" +#include "osdep.h" +#include "carmel2.h" +#include "carmel.h" + +char *last_path(), *cpystr(); +void carmel2_bezerk_mail(); +extern DRIVER carmeldriver; + +extern char carmel_20k_buf[20000]; + + +main(argc, argv) + int argc; + char **argv; +{ + char carmel_folder[500], *bezerk_folder; + MAILSTREAM *stream; + + if(argc <= 1) { + fprintf(stderr, "Usage: bzk2cml <folder>\n"); + exit(-1); + } + + for(argv++; *argv != NULL; argv++) { + bezerk_folder = last_path(*argv); /* Only last component of path */ + sprintf(carmel_folder, "#carmel#%s", bezerk_folder); + + if(carmel_create(NULL, carmel_folder) == 0) { + continue; + } + + + stream = (MAILSTREAM *)fs_get(sizeof(MAILSTREAM)); + stream->dtb = &carmeldriver; + stream->mailbox = cpystr(carmel_folder); + stream->local = NULL; + + if(carmel_open(stream) == NULL) { + continue; + } + + if(carmel2_lock(stream, stream->mailbox, WRITE_LOCK) < 0) { + fprintf(stderr, "Carmel folder %s locked\n", carmel_folder); + carmel_close(stream); + continue; + } + + carmel2_spool_mail(stream, *argv, stream->mailbox, 0); + carmel2_unlock(stream, stream->mailbox, WRITE_LOCK); + fprintf(stderr, "Folder \"%s\" copied to \"%s\"\n", *argv, + carmel_folder); + carmel_close(stream); + } +} + + + + + + + + +char *last_path(path) + char *path; +{ + char *p; + + p = path + strlen(path) - 1; + + while(p > path && *p != '/') + p--; + + if(*p == '/') + p++; + return(p); +} + +void +mm_log(mess, code) + char *mess; + int code; +{ + fprintf(stderr, "%s\n", mess); +} + +void +mm_fatal() +{ + abort(); +} + +void mm_mailbox() {} +void mm_critical() {} +void mm_nocritical() {} +void mm_searched() {} +void mm_exists() {} +void mm_expunged() {} + diff --git a/contrib/carmel/c-client/carmel-purge.sh b/contrib/carmel/c-client/carmel-purge.sh new file mode 100644 index 00000000..87d35ceb --- /dev/null +++ b/contrib/carmel/c-client/carmel-purge.sh @@ -0,0 +1,89 @@ +#!/bin/csh -f +# vmail.purge - zaps all unindexed mail messages. +# +# The_Epoch vick created this. +# 1988 Nov 10 Thu 12:00 bob added code to allow a pathname for $1. +# 1988 Nov 14 Mon 10:14 bob rewrote the code that was breaking inside `quotes`. +# 1988 Nov 15 Tue 16:44 bob added code to track spurious .vmail files +# 1988 Nov 16 Wed 10:44 bob redirected error messages to stderr. +# 1989 Feb 14 Tue 13:37 bob add exceptions for corrupt indexes +# 1991 Sun Nov 24 10:30 Shoa add removal of RECOVER index. +# 1992 Oct 21 Wed 17:01 bob check for Pine read lock files, and abort. + +# +# Usage: vmail.purge [ username | somepath/username/.vmail ] + + +set pname = $0 +set pname = ${pname:t} # name of this script for error messages + +set indexed = /tmp/VMprg_idx.$$ # a list of indexed messages +set messages = /tmp/VMprg_msg.$$ # a list of all messages +set unindexed = /tmp/VMprg_Csh.$$ # script to remove unindexed messages + +if ( 1 <= $#argv ) then + if ( ".vmail" == $1:t ) then # assume /u/dp/bob/.vmail + set vpath = $1:h # /u/dp/bob + setenv USER $vpath:t # bob + else + setenv USER $1 + endif +endif + +if ( ! -d ~$USER/.vmail ) then + echo -n "${pname}: " + if ( 1 <= $#argv ) then + sh -c "echo 1>&2 ${pname}: invalid argument $1" + else + sh -c "echo 1>&2 ${pname}: $USER has no .vmail directory" + endif + exit 1 +endif + +cd ~$USER/.vmail + +if (!( -d index && -d msg)) then + sh -c "echo 1>&2 ${pname}: $cwd is missing required components" + exit 1 +endif +# +# check for Pine inbox read lock file +if ( -e index/.MAIL.rl ) then + sh -c "echo 1>&2 ${pname}: $cwd is now running Pine" + exit 1 +endif +# +# +#remove the temporary RECOVER index *must* be done before deletion of messages +# start. +if ( -e index/RECOVER ) then + rm index/RECOVER + if ( -e ~$USER/.inda ) rm ~$USER/.inda + if ( -e ~$USER/.indf ) rm ~$USER/.indf +endif +# +# +# create the shell script +# +awk '$1 ~ /^[0-9]/ {print substr($1,1,6)}' index/* | sort | uniq > $indexed +if ( $status ) then + sh -c "echo 1>&2 ${pname}: some index probably corrupt" + exit 1 +endif + +ls msg | awk '{ print substr($1,1,6) }' > $messages +comm -23 $messages $indexed | awk '{ print "rm -f " $1 " " $1 ".wid" }' > $unindexed + +# provide verbose statistics +# +echo $USER " total:" `wc -l < $messages` " indexed:" `wc -l < $indexed`\ + " purging: " `wc -l < $unindexed` + +# do the work +# +cd msg +csh -f $unindexed + +# cleanup +# +rm $messages $indexed $unindexed diff --git a/contrib/carmel/c-client/carmel.c b/contrib/carmel/c-client/carmel.c new file mode 100644 index 00000000..5265097d --- /dev/null +++ b/contrib/carmel/c-client/carmel.c @@ -0,0 +1,1232 @@ +/*---------------------------------------------------------------------- + + T H E C A R M E L M A I L 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 "Carmel" mail format is cutsom mail file format that has messages +saved in individual files and an index file that references them. It +has also been called the "pod" or "vmail" format. This is probably not +of interest to anyone away from it's origin, though the Carmel2 format +might be. + +This driver reads and writes the format in conjunction with the carmel2 +driver. The carmel2 file driver does most of the work as most of the +operations are done on the created auxiliary carmel2 index and the +carmel index is updated when the mail file is closed. + + ----------------------------------------------------------------------*/ + + +#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 "carmel.h" +#include "rfc822.h" +#include "misc.h" + + +/* Driver dispatch used by MAIL. The carmel 2driver shares most of + its data structures and functions with the carmel driver + */ + +DRIVER carmeldriver = { + "carmel", + (DRIVER *) NIL, /* next driver */ + carmel_valid, /* mailbox is valid for us */ + carmel_parameters, /* Set parameters */ + carmel_find, /* find mailboxes */ + carmel_find_bboards, /* find bboards */ + carmel_find_all, /* find all mailboxes */ + carmel_find_bboards, /* find all the bboards ; just a noop here */ + carmel_subscribe, /* subscribe to mailbox */ + carmel_unsubscribe, /* unsubscribe from mailbox */ + carmel_subscribe_bboard, /* subscribe to bboard */ + carmel_unsubscribe_bboard, /* unsubscribe from bboard */ + carmel_create, /* create mailbox */ + carmel_delete, /* delete mailbox */ + carmel_rename, /* rename mailbox */ + carmel_open, /* open mailbox */ + carmel_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 */ + carmel_check, /* check for new messages */ + carmel_expunge, /* expunge deleted messages */ + carmel2_copy, /* copy messages to another mailbox */ + carmel2_move, /* move messages to another mailbox */ + carmel_append, /* Append message to a mailbox */ + carmel2_gc, /* garbage collect stream */ +}; + +MAILSTREAM carmelproto ={&carmeldriver}; /* HACK for default_driver in pine.c*/ + +#ifdef ANSI +static int carmel_sift_files(struct direct *); +static void carmel_recreate_index(MAILSTREAM *); +static char *carmel_calc_paths(int, char *, int); +static int vmail2carmel(char *, char *, MAILSTREAM *, char *, char *); +static int write_carmel_index(FILE *, MESSAGECACHE *, ENVELOPE *); +static int carmel_reset_index_list(); +static void carmel_kill_locks(char *); +#else +static int carmel_sift_files(); +static void carmel_recreate_index(); +static char *carmel_calc_paths(); +static int vmail2carmel(); +static int write_carmel_index(); +static int carmel_reset_index_list(); +static void carmel_kill_locks(); +#endif + + + +extern char carmel_20k_buf[20000], carmel_path_buf[], carmel_error_buf[]; + +static int disk_setup_checked = 0; + +/*---------------------------------------------------------------------- + Carmel mail validate mailbox + +Args: name -- name to check + +Returns: our driver if name is valid, otherwise calls valid in next driver + ---*/ + +DRIVER *carmel_valid (name) + char *name; +{ + return(carmel_isvalid(name) ? &carmeldriver : NIL); +} + + + +/*---------------------------------------------------------------------- + Check and see if a named mailbox is a valid carmel mail index + +Args: name -- name or path of mail file. It is a FQN, e.g. #carmel#sent-mail + +Returns: 0 if it is not a valid mailbox + 1 if it is. + + A carmel index must be a regular file, readable, and have the second word in +the file be "index", upper or lower case. + ----*/ +int +carmel_isvalid (name) + char *name; +{ + struct stat sbuf; + int fd; + char *p, *carmel_index_file; + struct carmel_mb_name *parsed_name; + + if(!disk_setup_checked){ + disk_setup_checked = 1; + carmel_init(NULL); + } + + /* Don't accept plain "inbox". If we do then the carmel driver + takes over all other drivers linked after it. This way the + inbox-path in Pine can be set to #carmel#MAIL to make the + inbox the carmel one + */ + + parsed_name = carmel_parse_mb_name(name, '\0'); + if(parsed_name == NULL) + return(0); + + carmel_free_mb_name(parsed_name); + + carmel_index_file = carmel_calc_paths(CalcPathCarmelIndex, 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 name matches and it doesn't exist, it's OK */ + + 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); + carmel_20k_buf[199] = '\0'; + + close(fd); + + for(p = carmel_20k_buf; *p && !isspace(*p); p++); /* frist word */ + for(; *p && isspace(*p); p++); /* space */ + lcase(p); /* lower case the "index" */ + if(!*p || strncmp(p, "index", 5)) + return(0); + + return(1); +} + + + + +/*---------------------------------------------------------------------- + + ----*/ +void * +carmel_parameters(function, value) + long function; + char *value; +{} + + + +/*---------------------------------------------------------------------- + This is used by scandir to determine which files in the directory + are treated as mail files + ----*/ +static char *sift_pattern = NULL; +static int +carmel_sift_files(dir) + struct direct *dir; +{ + if(dir->d_name[0] == '.') + return(0); + else if(strcmp(dir->d_name, "MAIL") == 0) + /* Never return the inbox. "INBOX" is always included by default */ + return(0); + else if(pmatch(dir->d_name, sift_pattern)) + return(1); + else + return(0); +} + +int alphasort(); + +/* ---------------------------------------------------------------------- + Carmel find list of mail boxes + Args: mail stream + pattern to search + + This scans the ".vmail/index" directory for a list of folders + (indexes in vmail terms). + +BUG -- doesn't really match patterns (yet) + ----*/ +void +carmel_find(stream, pat) + MAILSTREAM *stream; + char *pat; +{ + char tmp[MAILTMPLEN]; + struct direct **namelist, **n; + int num; + struct carmel_mb_name *parsed_name; + struct passwd *pw; + + parsed_name = carmel_parse_mb_name(pat, '\0'); + + if(parsed_name == NULL) + return; + + if(parsed_name->user == NULL) { + sprintf(tmp, "%s/%s", myhomedir(), CARMEL_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, CARMEL_INDEX_DIR); + } + + sift_pattern = parsed_name->mailbox; + + num = scandir(tmp, &namelist, carmel_sift_files, alphasort); + + if(num <= 0) { +/* 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 for find in the carmel driver; there is no +difference between subscribed and unsubscribed mailboxes + ----*/ +void +carmel_find_all (stream, pat) + MAILSTREAM *stream; + char *pat; +{ + carmel_find(stream, pat); +} + + +/*---------------------------------------------------------------------- + Find bulliten boards is a no-op for carmel, there are none (yet) + */ +void +carmel_find_bboards (stream,pat) + MAILSTREAM *stream; + char *pat; +{ + /* Always a no-op */ +} + + +/*---------------------------------------------------------------------- + No subscription management for the carmel format. Probably should work + with the .mailbox list format, but that's probably implemented in mail.c + ----*/ +long carmel_subscribe() {} +long carmel_unsubscribe() {} +long carmel_subscribe_bboard() {} +long carmel_unsubscribe_bboard() {} + + + +/*---------------------------------------------------------------------- + Create a carmel folder + +Args: stream -- Unused here + mailbox -- the FQN of mailbox to create + +Returns: T on success, NIL on failure. + +An error message is logged on failure. + ----*/ +long +carmel_create(stream, mailbox) + MAILSTREAM *stream; + char *mailbox; +{ + char *carmel_index_file, *carmel2_index_file; + struct stat sb; + FILE *f; + + carmel_index_file = carmel_calc_paths(CalcPathCarmelIndex, mailbox, 0); + + if(carmel_index_file == NULL) + return(NIL); + + /*--- Make sure it doesn't already exist ---*/ + if(stat(carmel_index_file, &sb) >= 0) + return(NIL); + + /*--- The carmel index ----*/ + f = fopen(carmel_index_file, "w"); + if(f == NULL) + goto bomb; + + if(fprintf(f, "%-13.13s index.....\n",carmel_pretty_mailbox(mailbox))==EOF) + goto bomb; + + if(fclose(f) == EOF) + goto bomb; + + /*--- The carmel2 index ----*/ + carmel2_index_file = carmel_calc_paths(CalcPathCarmel2Index, mailbox,0); + f = fopen(carmel2_index_file, "w"); + if(f == NULL) + goto bomb; + + if(fprintf(f, "\254\312--CARMEL-MAIL-FILE-INDEX--\n") == EOF) + goto bomb; + + if(fclose(f) == EOF) + goto bomb; + + + carmel_kill_locks(mailbox); /* Get rid of any left over locks */ + carmel_reset_index_list(); + return(T); + + bomb: + unlink(carmel_calc_paths(CalcPathCarmelIndex, mailbox, 0)); + sprintf(carmel_error_buf, "Error creating mailbox %s: %s", + carmel_pretty_mailbox(mailbox), strerror(errno)); + mm_log(carmel_error_buf, ERROR); + return(NIL); +} + + + +/*---------------------------------------------------------------------- + Delete a carmel mailbox, and it's carmel2 index + +Args: stream -- unsed here + mailbox -- FQN of mailbox to delete + +Returns: NIL -- if delete fails + T -- if successful + ----*/ +long +carmel_delete(stream, mailbox) + MAILSTREAM *stream; + char *mailbox; +{ + char *carmel_index_file, *carmel2_index_file; + + carmel_index_file = carmel_calc_paths(CalcPathCarmelIndex, mailbox, 0); + if(carmel_index_file == NULL) + return(NIL); + + if(unlink(carmel_index_file) < 0) { + sprintf(carmel_error_buf, "Error deleting mailbox %s: %s", + carmel_pretty_mailbox(mailbox), strerror(errno)); + mm_log(carmel_error_buf, ERROR); + return(NIL); + } + + /*------ Second the carmel2 index, quietly -----*/ + carmel2_index_file = carmel_calc_paths(CalcPathCarmel2Index, mailbox, 0); + + unlink(carmel2_index_file); + + carmel_kill_locks(mailbox); /* Make sure all locks are clear */ + carmel_reset_index_list(); + return(T); +} + + + +/*---------------------------------------------------------------------- + Rename a carmel index, and its carmel2 index + +Args: stream -- unused + orig -- FQN of original mailbox + new -- FQN of new name + +Returns: NIL if rename failed, T if it succeeded. + +BUG: theoretically this could rename a folder from one user name +to another. Either that should be disallowed here, or code to make +it work should be written. + ----*/ +long +carmel_rename(stream, orig, new) + MAILSTREAM *stream; + char *orig; + char *new; +{ + char path_buf[CARMEL_PATHBUF_SIZE], *new_path, *orig_index_file; + + /*---- First the Carmel index -----*/ + orig_index_file = carmel_calc_paths(CalcPathCarmelIndex, orig, 0); + if(orig_index_file == NULL) + return(NIL); + strcpy(path_buf, orig_index_file); + new_path = carmel_calc_paths(CalcPathCarmelIndex, new, 0); + if(new_path == NULL) + return(NIL); + + if(rename(path_buf, new_path) < 0) { + sprintf(carmel_error_buf, "Error renaming mailbox %s: %s", + carmel_pretty_mailbox(orig), strerror(errno)); + mm_log(carmel_error_buf, ERROR); + return(NIL); + } + + /*----- Next the Carmel index, quietly ------*/ + strcpy(path_buf, carmel_calc_paths(CalcPathCarmel2Index, orig, 0)); + new_path = carmel_calc_paths(CalcPathCarmel2Index, new, 0); + + rename(path_buf, new_path); + + carmel_reset_index_list(); + return(T); +} + + + +/*---------------------------------------------------------------------- + Carmel mail open + + Args: stream + + Returns: stream on success, NIL on failure. + +The complex set of comparison determine if we get it for read, write or not +at all. + +The folder will be open for write if: + - It is not locked + - The carmel index is writeable + - The carmel index is writeable + - The carmel index is openable and not corrupt + +The folder open will fail if: + - The carmel index does exist + - The carmel carmel index is missing + AND the folder is locked or unwritable + +The folder is opened readonly if: + - It is locked + - The carmel index is not writable + - The carmel index is not writable + + ----*/ + +MAILSTREAM * +carmel_open(stream) + MAILSTREAM *stream; +{ + char carmel_index_path[CARMEL_PATHBUF_SIZE]; + char carmel2_index_path[CARMEL_PATHBUF_SIZE]; + char tmp[MAILTMPLEN], tmp2[MAILTMPLEN]; + struct stat carmel_stat, carmel2_stat; + int carmel2_up_to_date; + + if(!stream) + return(&carmelproto); /* Must be a OP_PROTOTYPE call */ + + mailcache = carmel2_cache; + + /* close old file if stream being recycled */ + if (LOCAL) { + carmel_close (stream); /* dump and save the changes */ + stream->dtb = &carmeldriver; /* reattach this driver */ + mail_free_cache (stream); /* clean up cache */ + } + + /* Allocate local stream */ + stream->local = fs_get (sizeof (CARMEL2LOCAL)); + LOCAL->carmel = 1; + 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; + LOCAL->dirty = NIL; + stream->msgno = -1; + stream->env = NULL; + stream->body = NULL; + stream->scache = 1; + LOCAL->calc_paths = carmel_calc_paths; + LOCAL->aux_copy = carmel_copy; + LOCAL->index_stream = NULL; + LOCAL->new_file_on_copy = 0; + + /*------ Figure out the file paths -------*/ + if(carmel_calc_paths(CalcPathCarmelIndex,stream->mailbox,0) == NULL) + return(NULL); + strcpy(carmel_index_path, + carmel_calc_paths(CalcPathCarmelIndex,stream->mailbox,0)); + strcpy(carmel2_index_path, + carmel_calc_paths(CalcPathCarmel2Index,stream->mailbox,0)); + + /*------ Does the CARMEL index exist? Fail if not ------*/ + if(stat(carmel_index_path, &carmel_stat) < 0) { + sprintf(carmel_error_buf, "Can't open mailbox: %s", strerror(errno)); + mm_log(carmel_error_buf, ERROR); + return(NULL); /* FAIL, no carmel index */ + } + + /*----- Determine if carmel index is up to date -----*/ + if(stat(carmel2_index_path, &carmel2_stat) >= 0 && + carmel2_stat.st_mtime >= carmel_stat.st_mtime) + carmel2_up_to_date = 1; + else + carmel2_up_to_date = 0; + + /*------ Do we have write access to the Carmel mail index? ----*/ + if(access(carmel2_index_path, W_OK) != 0 && errno != ENOENT) + stream->readonly = 1; /* fail later if dir index is uncreatable */ + + /*------ If CARMEL index R/O and Carmel out of date then fail -----*/ + if(stream->readonly && !carmel2_up_to_date) { + mm_log("Can't open mailbox; Unable to write carmel index", ERROR); + return(NULL); + } + + /*-------- Case for R/O stream or failed lock ---------*/ + if(stream->readonly || + carmel2_lock(stream->local, stream->mailbox, READ_LOCK)< 0){ + /* If carmel is out of date here that's OK, we just will see old data*/ + if(access(carmel_index_path, R_OK) == 0) { + stream->readonly = 1; + goto open_it; + } else { + /*-- Can't lock, and there's no carmel index, best to fail -*/ + mm_log("Can't open mailbox, can't lock to create carmel index", + ERROR); + return(NULL); + } + } + /* Index is now read locked */ + + /*---- Got an up to date carmel index and write access too ----*/ + if(carmel2_up_to_date) + if(access(carmel2_index_path, W_OK) == 0) { + goto open_it; + } else { + stream->readonly = 1; + goto open_it; + } + + /*---- If needed recreation of carmel index fails, go readonly -----*/ + if(vmail2carmel(carmel_index_path, carmel2_index_path, stream, + stream->mailbox) < 0) { + carmel2_unlock(stream->local, stream->mailbox, READ_LOCK); + stream->readonly = 1; + } + + open_it: + /*-- carmel_open2 shouldn't fail after all this check, but just in case -*/ + if(carmel_open2(stream, carmel2_index_path) < 0) { + /* carmel_open2 will take care of the error message */ + carmel2_unlock(stream->local, stream->mailbox, READ_LOCK); + if (LOCAL->msg_buf) fs_give ((void **) &LOCAL->msg_buf); + /* nuke the local data */ + fs_give ((void **) &stream->local); + return(NULL); + } + + if(stream->readonly) + mm_log("Mailbox opened READONLY", WARN); + + mail_exists (stream,stream->nmsgs); + mail_recent (stream,stream->recent); + + return(stream); +} + + + +/*---------------------------------------------------------------------- + Create a carmel2 index for a carmel index + + Args: folder -- the full UNIX path name existing carmel folder + carmel_folder -- the full UNIX path name of carmel index to create + stream -- MAILSTREAM to operate on + + Returns: 0 if all is OK + -1 if it failed + + This reads the Carmel index, and the headers of the carmel messages to +construct entries for a carmel index. + ----*/ +static +vmail2carmel(folder, carmel2_folder, stream, mailbox) + MAILSTREAM *stream; + char *folder, *carmel2_folder; + char *mailbox; +{ + FILE *carmel_stream; + FILE *carmel2_stream; + ENVELOPE *e; + BODY *b; + MESSAGECACHE mc; + char pathbuf[CARMEL_PATHBUF_SIZE], *message, *p, *save_subject; + struct stat st; + STRING string_struct; + + sprintf(pathbuf, "Recreating Carmel2 index for \"%s\"...", + carmel_pretty_mailbox(mailbox)); + mm_log(pathbuf, WARN); + + mm_critical(stream); + if(carmel2_lock(stream->local, mailbox, WRITE_LOCK) < 0) + return(-1); + + carmel_stream = fopen(folder,"r"); + if(carmel_stream == NULL) { + carmel2_unlock(stream->local, mailbox, WRITE_LOCK); + mm_nocritical(stream); + return(-1); + } + + carmel2_stream = fopen(carmel2_folder, "w"); + if(carmel2_stream == NULL) { + carmel2_unlock(stream->local, mailbox, WRITE_LOCK); + mm_nocritical(stream); + return(-1); + } + + fprintf(carmel2_stream, "\254\312--CARMEL-MAIL-FILE-INDEX--\n"); + + /* The first .... line */ + fgets(carmel_20k_buf, sizeof(carmel_20k_buf), carmel_stream); + + /*---- Loop through all entries in carmel index -----*/ + while(fgets(carmel_20k_buf,sizeof(carmel_20k_buf),carmel_stream) != NULL){ + + mc.data1 = 0L; + mc.data2 = atol(carmel_20k_buf); /* The file name/number */ + mc.deleted = 0; + mc.seen = 1; + mc.flagged = 0; + mc.answered = 0; + mc.recent = 0; + mc.user_flags = 0; + + strcpy(pathbuf,carmel_calc_paths(CalcPathCarmel2Data, + mailbox, mc.data2)); + + if(stat(pathbuf, &st) >= 0 || + stat(strcat(pathbuf, ".wid"), &st) >= 0) { + save_subject = cpystr(carmel_20k_buf + 27); + save_subject[strlen(save_subject) - 1] = '\0'; + mc.rfc822_size = st.st_size; + message = carmel2_readmsg(stream, 1, 0, mc.data2); + INIT(&string_struct, mail_string, (void *)"", 0); + rfc822_parse_msg(&e, &b, message, strlen(message), &string_struct, + mylocalhost(), carmel_20k_buf); + carmel2_parse_bezerk_status(&mc, message); + carmel2_rfc822_date(&mc, message); + if(e->subject == NULL || + strncmp(save_subject,e->subject,strlen(save_subject))){ + if(e->subject != NULL) + fs_give((void **)(&(e->subject))); + if(strlen(save_subject)) + e->subject = cpystr(save_subject); + else + e->subject = NULL; + } + } else { + /* Data file is missing; copy record as best we can */ + carmel_20k_buf[strlen(carmel_20k_buf) - 1] = '\0'; + e = mail_newenvelope(); + e->subject = cpystr(carmel_20k_buf+27); + e->from = mail_newaddr(); + e->from->mailbox = cpystr(carmel_20k_buf+17); + for(p = e->from->mailbox; *p && !isspace(*p); p++); + *p = '\0'; + mc.hours = 1; + mc.minutes = 0; + mc.seconds = 0; + mc.zoccident = 0; + mc.zhours = 0; + mc.zminutes = 0; + mc.day = 1; + mc.month = 1; + mc.year = 1; + } + carmel2_write_index(e, &mc, carmel2_stream); + mail_free_envelope(&e); + } + + fclose(carmel_stream); + fclose(carmel2_stream); + + carmel2_unlock(stream->local, mailbox, WRITE_LOCK); + mm_nocritical(stream); + + mm_log("Recreating Carmel2 index.....Done", WARN); + + return(1); +} + + + +#define TESTING /* Make a copy of carmel index before rewriting */ + +/*----------------------------------------------------------------------` + Close a carmel mail folder + +Args: stream -- Mail stream to close + +This will cause the carmel index to be recreated + + ----*/ +void +carmel_close(stream) + MAILSTREAM *stream; +{ + if (LOCAL && LOCAL->index_stream != NULL) { + /* only if a file is open */ + if(!stream->readonly) { + carmel_check (stream); /* dump final checkpoint */ + carmel2_unlock(stream->local, stream->mailbox, READ_LOCK); + } + + fclose(LOCAL->index_stream); + if (LOCAL->msg_buf) fs_give ((void **) &LOCAL->msg_buf); + if(LOCAL->stdio_buf) + fs_give ((void **) &LOCAL->stdio_buf); + fs_give ((void **) &stream->local); + + } + stream->dtb = NIL; /* log out the DTB */ +} + + + +/*---------------------------------------------------------------------- + Check for new mail and write out the indexes as a checkpoint + +Args -- The stream to checkpoint + ----*/ +void +carmel_check(stream) + MAILSTREAM *stream; +{ + if(carmel2_check2(stream)) + carmel_recreate_index(stream); /* only if carmel2 index changed */ +} + + + + +/*---------------------------------------------------------------------- + Expunge the mail stream + +Args -- The stream to checkpoint + +Here we expunge the carmel2 index and then recreate the carmel index from +it. + ----*/ +void +carmel_expunge(stream) + MAILSTREAM *stream; +{ + carmel2_expunge(stream); + carmel_recreate_index(stream); +} + + + +/*---------------------------------------------------------------------- + Rewrite the carmel index from the carmel2 index + +Args: -- stream to be recreated + +BUG: could probably use some error checking here + ----*/ +static void +carmel_recreate_index(stream) + MAILSTREAM *stream; +{ +#ifdef TESTING + char copy1[CARMEL_PATHBUF_SIZE]; + char copy2[CARMEL_PATHBUF_SIZE]; +#define MAXBACKUPS 4 + int nback; +#endif + long msgno; + MESSAGECACHE *mc; + ENVELOPE *envelope; + FILE *carmel_index; + struct stat sb; + char *cm_index_file; + struct timeval tp[2]; + + mm_critical(stream); + + /*---- calculate the carmel index path -----*/ + strcpy(carmel_path_buf, + (*(LOCAL->calc_paths))(CalcPathCarmelIndex, stream->mailbox, 0)); + + /*---- Get the first index line out of old index ----*/ + *carmel_20k_buf = '\0'; + carmel_index = fopen(carmel_path_buf, "r"); + if(carmel_index != NULL) { + fgets(carmel_20k_buf,sizeof(carmel_20k_buf), carmel_index); + fclose(carmel_index); + } + +#ifdef TESTING + /*--- rotate Backup copies of the index --- crude*/ + for (nback = MAXBACKUPS; 1 < nback; --nback) { + strcpy(copy2, + (*(LOCAL->calc_paths))(CalcPathCarmelBackup, stream->mailbox, nback)); + strcpy(copy1, + (*(LOCAL->calc_paths))(CalcPathCarmelBackup, stream->mailbox, nback-1)); + unlink(copy2); + link(copy1,copy2); + } + unlink(copy1); + link(carmel_path_buf, copy1); + unlink(carmel_path_buf); +#endif + + /*---- Reopen the carmel index for write, truncating it ------*/ + carmel_index = fopen(carmel_path_buf, "w"); + + if(carmel_index == NULL) + goto bomb; + + if(fputs(carmel_20k_buf, carmel_index) == EOF) + goto bomb; + + /*---- Loop through all the message the stream has ----*/ + for(msgno = 1; msgno <= stream->nmsgs; msgno++) { + mc = MC(msgno); + envelope = carmel2_fetchstructure(stream, msgno, NULL); + if(write_carmel_index(carmel_index, mc, envelope) < 0) + goto bomb; + } + if(fclose(carmel_index) == EOF) + goto bomb; + + /*---- Get mod time of carmel index ---*/ + cm_index_file = (*(LOCAL->calc_paths))(CalcPathCarmelIndex, + stream->mailbox, 0); + if(stat(cm_index_file, &sb) >= 0) { + tp[0].tv_sec = sb.st_atime; + tp[0].tv_usec = 0; + tp[1].tv_sec = sb.st_mtime; + tp[1].tv_usec = 0; + + /*---- Set Carmel index to have same mod time ---*/ + utimes(stream->mailbox, tp); + } + mm_nocritical(stream); + +#ifdef CHATTY + sprintf(carmel_error_buf, "Rewrote Carmel index \"%s\" with %d messages", + carmel_pretty_mailbox(stream->mailbox), msgno-1); + mm_log(carmel_error_buf, WARN); +#endif + + return; + + bomb: + mm_nocritical(stream); + sprintf(carmel_error_buf, "Error recreating carmel index: %s", + strerror(errno)); + mm_log(carmel_error_buf, ERROR); +} + + + +/*---------------------------------------------------------------------- + Do all the file name generation for the carmel driver + +Args: operation -- The type of calculation to perform + mailbox -- The canonical mailbox name + data_file_num -- For calculating the name of a file storing a message + +Returns: string with the path name in static storage or NULL on error + +The Path passed in will be in the format #carmel#user-id#folder or + #carmel#folder + +The only that error occurs is in looking up the user-id. A error is logged +when that happens. + ----*/ +static char * +carmel_calc_paths(operation, mailbox, data_file_num) + char *mailbox; + int data_file_num, operation; +{ + static char path[CARMEL_PATHBUF_SIZE]; + char *home_dir, *end_user_name; + struct passwd *pw; + struct carmel_mb_name *parsed_name; + + parsed_name = carmel_parse_mb_name(mailbox, '\0'); + + 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(NULL); + } + home_dir = pw->pw_dir; + } else { + home_dir = myhomedir(); + } + mailbox = parsed_name->mailbox; + + if(strucmp2(mailbox, "inbox") == 0) /* Map "inbox" into "MAIL" */ + mailbox = "MAIL"; + + + switch(operation) { + + case CalcPathCarmelIndex: + sprintf(path, "%s/%s/%s", home_dir, CARMEL_INDEX_DIR, mailbox); + break; + + case CalcPathCarmelBackup: + sprintf(path, "%s/%s/.%s.COD-FUR-%d", home_dir, CARMEL_INDEX_DIR, + mailbox, data_file_num); + break; + + case CalcPathCarmel2Data: + sprintf(path, "%s/%s/%d", home_dir, CARMEL_MSG_DIR, data_file_num); + break; + + case CalcPathCarmel2Index: + sprintf(path, "%s/%s/.%s.cx", home_dir, CARMEL_INDEX_DIR, mailbox); + break; + + case CalcPathCarmel2MAXNAME: + sprintf(path, "%s/%s", home_dir, CARMEL_MAXFILE); + break; + + case CalcPathCarmel2WriteLock: + sprintf(path, "%s/%s/.%s.wl", home_dir, CARMEL_INDEX_DIR, mailbox); + break; + + case CalcPathCarmel2ReadLock: + sprintf(path, "%s/%s/.%s.rl", home_dir, CARMEL_INDEX_DIR, mailbox); + break; + } + +/* sprintf(carmel_debug, "CARMEL: calc_paths returning \"%s\"\n", path); + mm_dlog(carmel_debug); */ + + carmel_free_mb_name(parsed_name); + + return(path); +} + + + +/*---------------------------------------------------------------------- + Copy carmel messages, this is the auxiliary copy, called from carmel2_copy + +Args: local -- Fake CARMELLOCAL structure + mailbox -- Destination mailbox, in FQN format, e.g. #carmel#fooo + e -- envelope + mc -- MESSAGECACHE entry + +Retuns: -1 on failure + ----*/ +long +carmel_copy(local, mailbox, e, mc) + CARMEL2LOCAL *local; + char *mailbox; + ENVELOPE *e; + MESSAGECACHE *mc; +{ + FILE *carmel_index; + int new; + struct stat sb; + char *carmel_index_file; + + carmel_index_file = (*(local->calc_paths))(CalcPathCarmelIndex, mailbox,0); + if(carmel_index_file == NULL) + return(-1); + + if(stat(carmel_index_file, &sb) < 0) + new = 1; + else + new = 0; + + carmel_index = fopen(carmel_index_file, "a+"); + if(carmel_index == NULL) + return(-1); + + if(new) + fprintf(carmel_index, "%-13.13s index.....\n", + carmel_pretty_mailbox(mailbox)); + + if(write_carmel_index(carmel_index, mc, e) < 0) { + fclose(carmel_index); + return(-1); + } + + if(fclose(carmel_index) == EOF) + return(-1); + + return(0); +} + + + +/*---------------------------------------------------------------------- + Make sure all the directories are there and writable for carmel + +Args: folders_dir - suggested directory, ignored by carmel + +Result: returns 0 if OK, -1 if not + ----*/ +carmel_init(folders_dir) + char *folders_dir; +{ + char *p; + FILE *mail_file; + struct stat sb; + + /*----- The ~/.vmail directory ----*/ + sprintf(carmel_path_buf, "%s/%s", myhomedir(), CARMEL_DIR); + carmel2_check_dir(carmel_path_buf); + + /*---- The ~/.vmail/.MAXNAME file ------*/ + sprintf(carmel_path_buf,"%s/%s", myhomedir(), CARMEL_MAXFILE); + if(stat(carmel_path_buf, &sb) < 0) { + mail_file = fopen(carmel_path_buf, "w"); + if(mail_file == NULL) { + mm_log("Error creating .MAXNAME file", ERROR); + } else { + fprintf(mail_file, "100000"); + fclose(mail_file); + } + } + + /*----- The ~/.vmail/index directory -----*/ + sprintf(carmel_path_buf,"%s/%s",myhomedir(),CARMEL_INDEX_DIR); + carmel2_check_dir(carmel_path_buf); + + /*----- The ~/.vmail/msg directory -----/* + sprintf(carmel_path_buf,"%s/%s",myhomedir(),CARMEL_MSG_DIR); + carmel2_check_dir(carmel_path_buf); + + /*----- The ~/.vmail/MAIL file -----*/ + sprintf(carmel_path_buf, "%s/%s/MAIL", myhomedir(), CARMEL_INDEX_DIR); + if(stat(carmel_path_buf, &sb) < 0) { + mail_file = fopen(carmel_path_buf, "w"); + if(mail_file == NULL) { + mm_log("Error creating \"MAIL\" folder", WARN); + } else { + fprintf(mail_file, "MAIL index.....\n"); + fclose(mail_file); + } + } + + return(0); +} + + + +/*---------------------------------------------------------------------- + Write an entry into a carmel index + +Args: file -- The open file stream + mc -- the MESSAGECACHE + envelope -- The envelope to write + +Returns: 0 if all is OK, -1 if not + ----*/ +static +write_carmel_index(file, mc, envelope) + FILE *file; + MESSAGECACHE *mc; + ENVELOPE *envelope; +{ + if(fprintf(file, "%6d %02d %-.3s %2d %-9.9s %-.50s\n", + mc->data2, mc->day, + month_abbrev2(mc->month), + (mc->year + 1969) % 100, + envelope != NULL && envelope->from != NULL && + envelope->from->mailbox != NULL ? + envelope->from->mailbox : "", + envelope != NULL && envelope->subject != NULL ? + envelope->subject : "") == EOF) + return(-1); + else + return(0); +} + + + +/*---------------------------------------------------------------------- + Append a message to a carmel mailbox + +Args: stream -- a suggested stream, ignored here + mailbox -- FQN of mailbox to append message to + message -- Text string of message + +Returns: T on success, NIL on failure + ----*/ +long +carmel_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 = carmel_calc_paths; + local.carmel = 1; + local.aux_copy = carmel_copy; + + + return(carmel2_append2(stream, &local, mailbox, flags, date, message)); +} + + + + + +/*---------------------------------------------------------------------- + This really just removes the file Carmel uses for the list of + folders, because Carmel will just recreate it from the indexes themselves. + ----*/ +static +carmel_reset_index_list() +{ + sprintf(carmel_path_buf, "%s/.inda", myhomedir()); + unlink(carmel_path_buf); + + sprintf(carmel_path_buf, "%s/.indf", myhomedir()); + unlink(carmel_path_buf); +} + + + +/*---------------------------------------------------------------------- + Used to make sure there are no old locks of any sort left around + when a folder is deleted or when one is created. + --*/ +static void +carmel_kill_locks(mailbox) + char *mailbox; +{ + unlink(carmel_calc_paths(CalcPathCarmel2WriteLock, mailbox, 0)); + unlink(carmel_calc_paths(CalcPathCarmel2ReadLock, mailbox, 0)); + unlink(carmel_calc_paths(CalcPathCarmel2Expunge, mailbox, 0)); +} diff --git a/contrib/carmel/c-client/carmel.h b/contrib/carmel/c-client/carmel.h new file mode 100644 index 00000000..ffdabaaa --- /dev/null +++ b/contrib/carmel/c-client/carmel.h @@ -0,0 +1,77 @@ +/*---------------------------------------------------------------------- + + T H E C A R M E L M A I L 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 + + ----------------------------------------------------------------------*/ + + +/* Most of the real stuff is defined in carmel2.h */ + +/* Function prototypes */ + +#ifdef ANSI +DRIVER *carmel_valid(char *); +int carmel_isvalid(char *); +void *carmel_parameters(); +char *carmel_file (char *, char *); +void carmel_find(MAILSTREAM *, char *); +void carmel_find_all(MAILSTREAM *, char *); +void carmel_find_bboards(MAILSTREAM *, char *); +long carmel_subscribe(); +long carmel_unsubscribe(); +long carmel_subscribe_bboard(); +long carmel_unsubscribe_bboard(); +long carmel_create(MAILSTREAM *, char *); +long carmel_delete(MAILSTREAM *, char *); +long carmel_rename(MAILSTREAM *, char *, char *); +MAILSTREAM *carmel_open(MAILSTREAM *); +void carmel_close(MAILSTREAM *); +void carmel_check(MAILSTREAM *); +void carmel_void(MAILSTREAM *); +long carmel_copy(CARMEL2LOCAL *, char *, ENVELOPE *, MESSAGECACHE *); +int carmel_init(char *); +long carmel_append(MAILSTREAM *, char *, char *, char *, STRING *); +#else +DRIVER *carmel_valid(); +int carmel_isvalid(); +void *carmel_parameters(); +char *carmel_file (); +void carmel_find(); +void carmel_find_all(); +void carmel_find_bboards(); +long carmel_subscribe(); +long carmel_unsubscribe(); +long carmel_subscribe_bboard(); +long carmel_unsubscribe_bboard(); +long carmel_create(); +long carmel_delete(); +long carmel_rename(); +MAILSTREAM *carmel_open(); +void carmel_close(); +void carmel_check(); +void carmel_expunge(); +long carmel_copy(); +int carmel_init(); +long carmel_append(); +#endif + + +/* These are all relative to the users home directory */ +#define CARMEL_INDEX_DIR ".vmail/index" +#define CARMEL_DIR ".vmail" +#define CARMEL_MSG_DIR ".vmail/msg" +#define CARMEL_MAXFILE ".vmail/.MAXNAME" + +/* operations for carmel_calc_path -- must mesh with operations for + carmel2_calc_path in carmel2.h + */ +#define CalcPathCarmelIndex 100 +#define CalcPathCarmelBackup 101 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, §ion, 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, §ion,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 + diff --git a/contrib/carmel/c-client/carmel2.h b/contrib/carmel/c-client/carmel2.h new file mode 100644 index 00000000..550a4bf3 --- /dev/null +++ b/contrib/carmel/c-client/carmel2.h @@ -0,0 +1,200 @@ +/*---------------------------------------------------------------------- + + T H E C A R M E L 2 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 + ----------------------------------------------------------------------*/ + +/* Command bits from carmel2_getflags() */ + +#define fSEEN 1 +#define fDELETED 2 +#define fFLAGGED 4 +#define fANSWERED 8 +#define fRECENT 16 + +/* Kinds of locks for carmel2_lock() */ + +#define READ_LOCK 0 +#define WRITE_LOCK 1 + + +/* Carmel2 I/O stream local data */ + +typedef struct _local2 { + MESSAGECACHE **mc_blocks; + long cache_size; + FILE *index_stream; + char *stdio_buf; + char *msg_buf; + unsigned long msg_buf_size; + unsigned long msg_buf_text_offset; + char *msg_buf_text_start; + char *buffered_file; + long read_lock_mod_time, write_lock_mod_time; + long index_size; + unsigned int dirty:1; + unsigned int carmel:1; /* It's a carmel file instead of carmel2 */ + unsigned int buffered_header_and_text:1; + unsigned int new_file_on_copy:1; + char *(*calc_paths)(); + long (*aux_copy)(); +} CARMEL2LOCAL; + + +struct carmel_mb_name { + char version[2]; /* \0 for version 1, ASCII digit and \0 for other */ + char *user; /* String of userid for other user names */ + char *mailbox; /* Mailbox name */ +}; + + +#define MC(x) (&(LOCAL->mc_blocks[(x) >> 8][(x) & 0xff])) +#define LOCAL ((CARMEL2LOCAL *) stream->local) +#define CARMEL_MAXMESSAGESIZE (20000000) /* 20Mb */ +#define CARMEL_MAX_HEADER (64000) /* 64K for DOS (someday?) */ +#define CARMEL_PATHBUF_SIZE (1024) +#define CARMEL2_INDEX_BUF_SIZE (20000) /* Size for carmel2 index FILE buf */ +#define CARMEL_NAME_CHAR ('#') /* Separator for carmel names */ +#define CARMEL_NAME_PREFIX "#carmel" /* Prefix for all mailbox names */ + + +/* Kinds of paths that for carmel_calc_path */ + +#define CalcPathCarmel2Index 1 +#define CalcPathCarmel2Data 2 +#define CalcPathCarmel2MAXNAME 3 +#define CalcPathCarmel2WriteLock 4 +#define CalcPathCarmel2ReadLock 5 +#define CalcPathCarmel2Expunge 6 + + +/* These are all relative to the users home directory */ + +#define CARMEL2_INDEX_DIR "Carmel2Mail" +#define CARMEL2_DIR "Carmel2Mail" +#define CARMEL2_MSG_DIR "Carmel2Mail/.Messages" +#define CARMEL2_MAXFILE "Carmel2Mail/.MAXNAME" + + +#define BEZERKLOCKTIMEOUT 5 + +/* Function prototypes */ + +#ifdef ANSI +DRIVER *carmel2_valid(char *); +int carmel2_isvalid(char *); +char *carmel2_file(char *, char *); +void *carmel2_parameters(); +void carmel2_find(MAILSTREAM *, char *); +void carmel2_find_bboards(MAILSTREAM *, char *); +void carmel2_find_all(MAILSTREAM *, char *); +void carmel2_find_all_bboards(MAILSTREAM *, char *); +long carmel2_subscribe(); +long carmel2_unsubscribe(); +long carmel2_subscribe_bboard(); +long carmel2_unsubscribe_bboard(); +long carmel2_create(MAILSTREAM *, char *); +long carmel2_delete(MAILSTREAM *, char *); +long carmel2_rename(MAILSTREAM *, char *, char *); +MAILSTREAM *carmel2_open(MAILSTREAM *); +int carmel2_open2(MAILSTREAM *, char *); +void carmel2_close(MAILSTREAM *); +void carmel2_fetchfast(MAILSTREAM *, char *); +void carmel2_fetchflags(MAILSTREAM *, char *); +ENVELOPE *carmel2_fetchstructure(MAILSTREAM *, long, BODY **); +char *carmel2_fetchheader(MAILSTREAM *, long); +char *carmel2_fetchtext(MAILSTREAM *, long); +char *carmel2_fetchbody(MAILSTREAM *, long, char *, unsigned long *); +void carmel2_setflag(MAILSTREAM *, char *, char *); +void carmel2_clearflag(MAILSTREAM *, char *, char *); +void carmel2_search(MAILSTREAM *, char *); +long carmel2_ping(MAILSTREAM *); +void carmel2_check(MAILSTREAM *); +void carmel2_expunge(MAILSTREAM *); +long carmel2_copy(MAILSTREAM *, char *, char *); +long carmel2_move(MAILSTREAM *, char *, char *); +void carmel2_gc(MAILSTREAM *, long); +void *carmel2_cache(MAILSTREAM *, long, long); +long carmel2_append(MAILSTREAM *, char *, STRING *); +int carmel2_write_index(ENVELOPE *, MESSAGECACHE *, FILE *); +char *carmel2_readmsg(MAILSTREAM *, int, long, int); +int carmel2_lock(CARMEL2LOCAL *, char *, int); +void carmel2_unlock(CARMEL2LOCAL *, char *, int); +int carmel2_update_lock(CARMEL2LOCAL *, char *, int); +int carmel2_check_dir(char *); +void carmel2_parse_bezerk_status(MESSAGECACHE *, char *); +int carmel2_append2(MAILSTREAM *, CARMEL2LOCAL *, char *, char *, + char *, STRING *); +char *month_abbrev2(int); +void carmel2_rfc822_date(MESSAGECACHE *, char *); +char *carmel_pretty_mailbox(char *); +struct carmel_mb_name * + carmel_parse_mb_name(char *, char); +void carmel_free_mb_name(struct carmel_mb_name *); +int strucmp2(char *, char *); +int struncmp2(char *, char *, int); + +#else /* ANSI */ + + +DRIVER *carmel2_valid(); +int carmel2_isvalid(); +char *carmel2_file(); +void *carmel2_parameters(); +void carmel2_find(); +void carmel2_find_bboards(); +void carmel2_find_all(); +void carmel2_find_all_bboards(); +long carmel2_subscribe(); +long carmel2_unsubscribe(); +long carmel2_subscribe_bboard(); +long carmel2_unsubscribe_bboard(); +long carmel2_create(); +long carmel2_delete(); +long carmel2_rename(); +MAILSTREAM *carmel2_open(); +int carmel2_open2(); +void carmel2_close(); +void carmel2_fetchfast(); +void carmel2_fetchflags(); +ENVELOPE *carmel2_fetchstructure(); +char *carmel2_fetchheader(); +char *carmel2_fetchtext(); +char *carmel2_fetchbody(); +void carmel2_setflag(); +void carmel2_clearflag(); +void carmel2_search(); +long carmel2_ping(); +void carmel2_check(); +void carmel2_expunge(); +long carmel2_copy(); +long carmel2_move(); +void carmel2_gc(); +void *carmel2_cache(); +long carmel2_append(); +int carmel2_write_index(); +char *carmel2_readmsg(); +int carmel2_lock(); +void carmel2_unlock(); +int carmel2_update_lock(); +int carmel2_check_dir(); +void carmel2_parse_bezerk_status(); +int carmel2_append2(); +char *month_abbrev2(); +void carmel2_rfc822_date(); +char *carmel_pretty_mailbox(); +struct carmel_mb_name * + carmel_parse_mb_name(); +void carmel_free_mb_name(); +int strucmp2(); +int struncmp2(); + +#endif /* ANSI */ |