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 | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'contrib')
42 files changed, 11763 insertions, 0 deletions
diff --git a/contrib/bitmaps/pine.icon b/contrib/bitmaps/pine.icon new file mode 100644 index 00000000..e5cd051b --- /dev/null +++ b/contrib/bitmaps/pine.icon @@ -0,0 +1,43 @@ +#define icon_width 60 +#define icon_height 60 +static char icon_bits[] = { + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xe3, 0x50, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x50, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1c, 0xe8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xd8, + 0x02, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xe8, 0x05, 0x00, 0x00, 0x00, + 0x80, 0x71, 0x00, 0x5c, 0x05, 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0xf4, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6a, 0x05, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xf4, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xfa, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x76, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + 0x07, 0x00, 0x18, 0x07, 0x00, 0x00, 0x80, 0xec, 0x0a, 0x00, 0xe0, 0x01, + 0x00, 0x00, 0xe0, 0xff, 0x13, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x30, 0xfb, + 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7c, 0x35, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xef, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xfd, + 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfe, 0x45, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe0, 0xff, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xf9, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0xfe, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0xf7, 0xce, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, 0xee, + 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xf9, 0xc9, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x7c, 0xfe, 0x1f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xff, + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xf7, 0xbf, 0x07, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0xed, 0xf7, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xf3, + 0xcf, 0xf0, 0x01, 0x00, 0x00, 0x00, 0xfc, 0xff, 0x7c, 0x01, 0x00, 0x00, + 0x00, 0x00, 0xef, 0xf8, 0xe7, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x33, 0xff, + 0xf9, 0x1d, 0x00, 0x00, 0x00, 0x60, 0xde, 0xfd, 0x8e, 0xf6, 0x03, 0x00, + 0x00, 0x00, 0xfa, 0xf3, 0xff, 0x39, 0x06, 0x00, 0x00, 0x00, 0xfe, 0xff, + 0xf6, 0x40, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xfc, 0xdb, 0x0f, 0x00, 0x00, + 0x00, 0x70, 0x8f, 0xfb, 0xfb, 0x3f, 0x00, 0x00, 0x00, 0xfc, 0x60, 0xff, + 0xff, 0xe0, 0x00, 0x00, 0x00, 0x8f, 0x7d, 0xff, 0xff, 0x3f, 0x00, 0x00, + 0x00, 0x00, 0xef, 0xfc, 0xec, 0xf7, 0x01, 0x00, 0x00, 0xe0, 0x9e, 0xff, + 0xfd, 0xdd, 0x03, 0x00, 0x00, 0xf8, 0xf1, 0xff, 0x0f, 0xff, 0x3f, 0x00, + 0x00, 0x5c, 0xcc, 0xfe, 0x7f, 0x0c, 0x00, 0x00, 0x80, 0xb3, 0xfb, 0xfb, + 0xbf, 0x73, 0x00, 0x00, 0xc0, 0x60, 0xff, 0xf5, 0xfb, 0x9c, 0x01, 0x00, + 0x00, 0xfc, 0xfc, 0xfb, 0xed, 0xe1, 0x06, 0x00, 0x80, 0x0f, 0x8e, 0xff, + 0xc5, 0x03, 0x1f, 0x00, 0xe0, 0x81, 0x83, 0xf2, 0x69, 0x07, 0x70, 0x00, + 0x18, 0xc0, 0x00, 0xf0, 0x09, 0x1c, 0x80, 0x01, 0x00, 0x00, 0x00, 0xf0, + 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00}; diff --git a/contrib/bitmaps/pine2.icon b/contrib/bitmaps/pine2.icon new file mode 100755 index 00000000..ae5fa74f --- /dev/null +++ b/contrib/bitmaps/pine2.icon @@ -0,0 +1,33 @@ +#define crow_width 50 +#define crow_height 50 +static char crow_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x38, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x80, 0x08, 0x00, + 0x00, 0xfe, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0xff, 0x01, 0x00, 0x80, + 0x08, 0x80, 0x00, 0xfe, 0x00, 0x00, 0x40, 0x10, 0xc0, 0x01, 0xff, 0x01, + 0x00, 0x20, 0x20, 0xe0, 0x83, 0xff, 0x03, 0x00, 0x10, 0x40, 0xf0, 0xc7, + 0xff, 0x07, 0x00, 0x22, 0x20, 0xf8, 0xef, 0xff, 0x0f, 0x00, 0x17, 0x40, + 0xe0, 0xc3, 0xfd, 0x87, 0x00, 0x0f, 0x80, 0xf0, 0xe7, 0xf8, 0xcf, 0x01, + 0x07, 0x00, 0xf9, 0x7f, 0xf0, 0xff, 0x03, 0x0f, 0x80, 0xfc, 0x3f, 0xf0, + 0xff, 0x03, 0x07, 0x00, 0xff, 0x7f, 0xf8, 0xff, 0x03, 0x03, 0x00, 0xff, + 0x7f, 0xf0, 0xff, 0x03, 0x01, 0x00, 0xfe, 0x1f, 0xe0, 0xfd, 0x03, 0x02, + 0x00, 0xfe, 0x3f, 0xc0, 0xf8, 0x03, 0x01, 0x00, 0xff, 0x7f, 0x40, 0xf0, + 0x03, 0x00, 0x80, 0xff, 0xff, 0x80, 0xf8, 0x03, 0x00, 0xc0, 0xff, 0xff, + 0x41, 0xf0, 0x03, 0x00, 0xe0, 0xff, 0xff, 0x23, 0xe0, 0x03, 0x00, 0x80, + 0xff, 0xff, 0x40, 0xf0, 0x03, 0x00, 0xc0, 0xff, 0xff, 0x21, 0xe0, 0x03, + 0x00, 0xe0, 0xff, 0xff, 0x13, 0xc0, 0x03, 0x00, 0xf0, 0xff, 0xff, 0x27, + 0xe0, 0x03, 0x00, 0xf8, 0xff, 0xff, 0x1f, 0xc0, 0x03, 0x10, 0xfc, 0xff, + 0xff, 0x1f, 0x80, 0x03, 0x28, 0xf0, 0xff, 0xff, 0x0f, 0xc0, 0x03, 0x44, + 0xf8, 0xff, 0xff, 0x0f, 0x80, 0x03, 0x82, 0xfc, 0xff, 0xff, 0x1f, 0x00, + 0x03, 0x44, 0xfe, 0xff, 0xff, 0x3f, 0x80, 0x03, 0x82, 0xff, 0xff, 0xff, + 0x7f, 0x00, 0x03, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x02, 0x00, 0xfe, + 0xff, 0xff, 0x3f, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x02, + 0x00, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0xff, + 0x01, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0xfc, 0xff, + 0xff, 0xff, 0x07, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, + 0xf0, 0xff, 0xff, 0xff, 0x03, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, + 0x03, 0x00, 0xf0, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xe0, 0xff, 0xff, + 0xff, 0x1f, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; diff --git a/contrib/bitmaps/unixware/README b/contrib/bitmaps/unixware/README new file mode 100644 index 00000000..685a45bb --- /dev/null +++ b/contrib/bitmaps/unixware/README @@ -0,0 +1,20 @@ + + +Tim Rice tim@trr.metro.net Fri Apr 26 17:16:54 PDT 1996 + +Here are the files needed to have the pine icon show up on the desktop. + +pine.cdb +pine.icon +pine48.icon + +--------< cut here for install script >------- +: +# quick & nasty install script +cp pine.cdb /usr/X/lib/classdb +cp pine.icon /usr/X/lib/pixmaps +cp pine48.icon /usr/X/lib/pixmaps +# tune permissions so "system owners" can modify +chgrp dtadmin /usr/X/lib/classdb/pine.cdb /usr/X/lib/pixmaps/pine.icon /usr/X/lib/pixmaps/pine48.icon +chmod g+w /usr/X/lib/classdb/pine.cdb /usr/X/lib/pixmaps/pine.icon /usr/X/lib/pixmaps/pine48.icon +echo "INCLUDE pine.cdb;" >> /usr/X/lib/classdb/system diff --git a/contrib/bitmaps/unixware/pine.cdb b/contrib/bitmaps/unixware/pine.cdb new file mode 100644 index 00000000..a9e02d07 --- /dev/null +++ b/contrib/bitmaps/unixware/pine.cdb @@ -0,0 +1,11 @@ +CLASS 'Pine' +BEGIN + _CLASSNAME "Pine"; + _FILETYPE "EXEC"; + _PROG_TYPE "UNIX Character"; + _ICONFILE "pine.icon"; + _MINIMIZED_ICONFILE "pine48.icon"; + MENU _Open 'exec xterm -e "%F" &'; + _LPATTERN "pine"; +END + diff --git a/contrib/bitmaps/unixware/pine.icon b/contrib/bitmaps/unixware/pine.icon new file mode 100644 index 00000000..84b0f70c --- /dev/null +++ b/contrib/bitmaps/unixware/pine.icon @@ -0,0 +1,45 @@ +#define pine_format 1 +#define pine_width 32 +#define pine_height 32 +#define pine_ncolors 4 +#define pine_chars_per_pixel 1 +static char * pine_colors[] = { +" " , "#FFFFFFFFFFFF", +"." , "#0000CF3C0000", +"X" , "#30C234D33CF3", +"o" , "#5D7555555144" +} ; +static char * pine_pixels[] = { +" . ", +" . ", +" ... ", +" ... ", +"XXXX XXX . . . ", +" XX XX . . . ", +" XXX . ... . ", +" X .. .. . ", +" . .... . ", +" ... . . . ", +" . .oo. . ", +" . . oo . . XXX XX", +" . .oo. . . XXXX ", +" .... oo ... XX ", +" . . ..oo. .. ", +" .. .oo . . ", +" ... .oo ... ", +" . .. oo. . . ", +" ........oo... . ", +" .. .. ..oo..... ", +" . ...oo . . .. ", +" .... oo. . .. . ", +" .. ...oo... ... ", +" .. ....oo.. . . ", +" ........oo. ... . ", +" ... . ..oo..... . ", +" .... .oo. ... ... ", +" .. .. ..oo.. . ... ", +" ..... ....oo...... .. ", +" ... ......oo.. ...... ", +" oo ", +" oo " +} ; diff --git a/contrib/bitmaps/unixware/pine48.icon b/contrib/bitmaps/unixware/pine48.icon new file mode 100644 index 00000000..ac9d18d3 --- /dev/null +++ b/contrib/bitmaps/unixware/pine48.icon @@ -0,0 +1,59 @@ +#define pine48_format 1 +#define pine48_width 48 +#define pine48_height 48 +#define pine48_ncolors 2 +#define pine48_chars_per_pixel 1 +static char * pine48_colors[] = { +" " , "#FFFFFFFFFFFF", +"." , "#000000000000" +} ; +static char * pine48_pixels[] = { +" . ", +" . ... ", +" . . ..... ", +" . . ....... ", +" . . ......... ", +" . . . ....... ", +" . . ... ......... ", +" . . ..... ........... ", +" . . ....... ............. ", +" . . . ......... ............... ", +"... . . ..... ... ......... .", +".... . ....... ... ......... ..", +"... . ............ ............", +".... . ............ ............", +"... ............... .............", +".. ............... ............", +". ............ .... ......", +" . ............. .. .....", +". ............... . ....", +" ................. . .....", +" ................... . ....", +" ..................... . ...", +" ................. . ....", +" ................... . ...", +" ..................... . ..", +" ....................... . ...", +" .......................... ..", +" . ........................... .", +" . . ........................ ..", +" . . ......................... .", +" . . ........................... ", +" . . ............................. .", +" . ................................ ", +". ................................ ", +" ............................. ", +" ............................... ", +" ............................... ", +" ............................... ", +" ............................... ", +" ................................. ", +" .............................. ", +" .............................. ", +" ...................................", +" ................................ ", +" ................................ ", +" ................................ ", +" ", +" " +} ; diff --git a/contrib/carmel/README b/contrib/carmel/README new file mode 100644 index 00000000..f893ad78 --- /dev/null +++ b/contrib/carmel/README @@ -0,0 +1,34 @@ +Further details on the Carmel driver and the modifications for +the X-BWC-Glyph character set are under the doc directory. + +Building the Carmel driver +-------------------------- +1. Build pine, the c-client and imapd as you would normally so + all machine dependent source directory links get set up correctly + +2. Copy all files from contrib/carmel/c-client into the Pine c-client + directory + +3. run "patch < Makefile.patch" to modify the Makefile. + +4. If you want the X-BWC-GLYPH character set stuff turned on in the + c-client for mail generated before 1994 make sure that + EXTRACFLAGS=-DBWC in the Makefile + +5. If you want the carmel driver to be the default for non-fully + qualified names, change that in the Makefile for your system. + +6. If you want the X-BWC-Glyph display code turned on some patches + have to be applied to Pine. If not, these steps can be skipped. + 6a. Copy all the files from carmel/pine into the pine source + directory. + 6b. Run patch on each of them: "patch < makefile.ult", + "patch < filter.c.pathc". + 6c. You may have to add a -DBWC to the CFLAGS= line in the makefile + for your system if there is no patch file for your system. + +7. Remove the imapd executable imapd/imapd from the Pine source tree + to force it to be rebuilt (Make won't figure it out). + +8. Run build again from the top level directory + 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 */ diff --git a/contrib/carmel/doc/carmel-driver b/contrib/carmel/doc/carmel-driver new file mode 100644 index 00000000..aa157cf8 --- /dev/null +++ b/contrib/carmel/doc/carmel-driver @@ -0,0 +1,157 @@ + + +Carmel driver notes +------------------- +Set folder-collections=#carmel#[] to configure Pine to use the +carmel driver. You may include other colllections like a directory full +of Berkeley mail folder + + + +Be sure you don't have a file called ".mailboxlist" in your home +directory. If you do the list of mailboxes presented to be by +the c-client will be out of that file rather than by querying each +driver. + +If you have files in your home or mail directory, be sure they aren't +called anything that starts with "#carmel#" or %carmel%. They will be +incorrectly included by mail_find for the bezerk and other drivers. + +The carmel driver doesn't grab "inbox". This is to allow other drivers +a shot at it. This may or may not work out in the long run. With the +current version of Pine you must set your inbox-path=#carmel#inbox to +cause the carmel inbox to be used. + + +Patching/Compilation for Pine +----------------------------- + +This set of files and patches was created for Pine 3.85 and the +corresponding version 3.0 of the c-client and 7.63 of imapd. Hopefully +they will continue to work with future versions of all of these. To +apply the patches copy all the files in the subdirectories here (pine, +c-client and imapd) to the corresponding directories in the Pine +source. The run patch on each of the patch files and finally run the +build script in Pine source root directory. This should result in a +pine, pico and imapd binary in the bin directory ready to be +installed. In the c-client directory there will be a bzk2cml binary +for converting a Berkeley format mailbox to a Carmel format mailbox +for the user running the program. + +The modifications to the Pine source are minimal. The two parts are +the calls to link in the carmel driver, and the patches to display +BWC-GLYPH text. Nearly all the patches are for the later. One +additional patch is needed to specify the right driver for creating +new mailboxes. This is currently a bit of a hack in Pine and will +hopefully be fixed future versions of Pine. The patches to imapd are +of exactly the same nature as those to Pine. + +If some of these patches fail, it's probably due to changes made in +Pine (applying them to some other version than 3.85). In that cases +the patches will have to be made manually. + + + +Fixed since the version of Sept '92. +------------------------------------ +* Updated to conform with the changes in UW internal changes from + version 3.5X to 3.84. With these changes all the internal Pine calls + to the c-client are stabilized, possibly except some changes for + mail_create. This makes the carmel driver trivial to integrate with + future versions of Pine. + +* Driver now checkpoints both carmel and carmel2 indexes. This should + save the nightly from deleting mail that it shouldn't. + +* The driver only defaults messages without any content-type to + X-bwc-glyph instead of messages that were tagged text/plain. This will + cause the the glyph to richtext translations not to be applied when + the message already has a MIME header. + +* Some bug fixes to display of BWC-GLYPH text, including bug causing + periodic core dumps. + +* Handles case where data file for a message is missing properly + +* Bug fixes so critical code in driver is properly protected from + signals and interrupts. + +* Bzk2cml utility included to convert Berkeley mail folder into carmel + folders. + +* Fixed bug causing text of currently viewed message to be redisplayed + incorrectly after the arrival of new mail. + +* Carmel text served up by imapd is tagged type "plain" instead of + "X-bwc-glyph", though the text is not yet converted to richtext + and quoted-printable encoded like it should be to work with other + clients. + + +Fixed in the Aug 1 version +-------------------------- +* Only creates a maximum of four Carmel index backup files (COD.FUR files) + +* Folder listing now works with standard IMAP patterns + +* Carmel index is check-pointed on expunges + +* Carmel driver checks for new mail with folder name #carmel#inbox + +* New mail is incorporated on other than inbox + +* MIME decoding works properly now + +* Safer incorporation of new mail from /usr/spool/mail (carmel2 writes flushed) + + +Fixed in Aug 23 version +----------------------- + +* Fetchheader includes the separator so append will work + +* Fixed append to get size right and not insert random NULL's, \n's... + +* Conversion of glyph format to rich text in imapd + + +Fixed in Aug 29 version +----------------------- +* Further fixes to append, including full locking + +* The #carmel#user#folder syntax now works properly + +* Further fixes to imapd to convert to richtext only when appropriate + + +Fixed in Sep 2 version +---------------------- +* Nearly complete conversion from glyph to richtext + +* Fixed bug causing lost new mail on quick in/out Pine sessions + +* A few related Pine bugs fixed: sorting, wrapping + + +Fixed in Sep 25 version +----------------------- + +* Recognizes all messages with subtype beginning with x-bwc-glyph as Glyph text. +* Saves messages of type x-bwc-glyph-wide in files ending with .wid + +* When saving messages, the link is used rather than making a new file + +* Glyph messages after 1994 must have the x-bwc-glyph tag to be recognized + +* Bug fix to properly display '@' in glyph text + +* Incorporation of latest version of c-client (minor bug fixes) + + + +General Features +---------------- +* The driver checks the MAIL environment variable for the place to +look for new incoming mail for the "inbox". + +(There's a lot more than this, but it's not documented yet) diff --git a/contrib/carmel/doc/todo b/contrib/carmel/doc/todo new file mode 100644 index 00000000..acf99f19 --- /dev/null +++ b/contrib/carmel/doc/todo @@ -0,0 +1,43 @@ +Legend: P - Pine, C - Carmel, W - BWC specific hacks, I - Imapd + Capitial letters are bugs, small are feature additions + +Size is in days it will take LL to accomplish. A * means the UW or +other are interested and might actually do this for us if we wait. + +Size Where What +---- ----- ------------- + 3 c Make readonly mode parameters work + 3 C Occaisional problem/error message with Fcc + 6 W Copying/recognizing wide documents as wide (a new mime type?) + 2 b Can't handle _ in folder names + 4 c Testing to make sure all works in disk full conditions + 1 c Quick hacks to work with ECS mail + 5* i Imapd modifications to work with ECS mail properly + 2 b/p A default Bcc to oneself instead of Fcc (can you Fcc:inbox?) + 2* p Better handling of non-recognized attachment + 5 w Printing wide documents + 2 W Problems with glyph text column alignment for torture test +15 Gateway to convert glyph to standard format as leaving BWC + 3 c Initial account set up +10 c Kiss of death to steal write access from another running Pine + 6 C Full IMAP searching in Carmel (not used by Pine yet) + 6 c Carmel driver performance enhancments (for folders > 500 msgs) + 6* ci Auto updating deleted status for folder open by several users + 3 C Doesn't incorporate new mail on start up like other IMAPware + 6 I/C Handling of non-FQN's via IMAP (will solve ECS problems above) + +Things fixed for Aug 31 version +------------------------------- +- fixed bug with 1994 cut off date for forcing Carmel mail to x-bwc-glyph +- fixed bugs in text header searching +- changed to new c-client way of looking up hostname +- works with new c-client for remote creates with FQNs +- now works with new c-client scheme for including new mail file drivers +- integrated x-bwc-glyph stuff with Pine 3.90 ++ (Fixed in by UW) Handles passwords for multiple imap servers/accounts +- Implemented TRYCREATE protocol for IMAP copies and appends + +Previous fixes +-------------- +- Better handling of case of full disk + diff --git a/contrib/carmel/imapd/imapd.c.patch.old b/contrib/carmel/imapd/imapd.c.patch.old new file mode 100644 index 00000000..16ed2981 --- /dev/null +++ b/contrib/carmel/imapd/imapd.c.patch.old @@ -0,0 +1,110 @@ +*** imapd.c Sun Jul 18 21:48:29 1993 +--- imapd.c.new Sun Jul 18 21:47:53 1993 +*************** +*** 109,115 **** +--- 109,119 ---- + extern DRIVER bezerkdriver,tenexdriver,imapdriver,newsdriver,nntpdriver, + dummydriver; + ++ #ifdef BWC ++ extern DRIVER carmeldriver, carmel2driver; ++ #endif + ++ + /* Function prototypes */ + + void main (); +*************** +*** 135,140 **** +--- 139,148 ---- + long cstring (); + long caddr (); + ++ #ifdef BWC ++ char *glyph2richtext(); ++ #endif ++ + extern char *crypt (); + + /* Main program */ +*************** +*** 147,152 **** +--- 155,164 ---- + char *s,*t = "OK",*u,*v; + struct hostent *hst; + void (*f) () = NIL; ++ #ifdef BWC ++ mail_link (&carmeldriver); ++ mail_link (&carmel2driver); ++ #endif + mail_link (&tenexdriver); /* install the Tenex mail driver */ + mail_link (&bezerkdriver); /* install the Berkeley mail driver */ + mail_link (&imapdriver); /* install the IMAP driver */ +*************** +*** 164,170 **** +--- 176,186 ---- + state = SELECT; /* enter select state */ + t = "PREAUTH"; /* pre-authorized */ + } ++ #ifdef BWC ++ printf ("* %s %s IMAP2bis Service %sBWC at %s\015\012",t,host,version,cmdbuf); ++ #else + printf ("* %s %s IMAP2bis Service %s at %s\015\012",t,host,version,cmdbuf); ++ #endif + fflush (stdout); /* dump output buffer */ + signal (SIGALRM,clkint); /* prepare for clock interrupt */ + signal (SIGUSR2,kodint); /* prepare for Kiss Of Death */ +*************** +*** 782,788 **** +--- 798,817 ---- + if (body && (s = mail_fetchbody (stream,i,s,&j))) { + /* and literal string */ + printf ("{%d}\015\012",j); ++ #ifdef BWC_NOT_WORKING_YET ++ { ++ char *ss; ++ s[j] = '\0'; ++ s = glyph2richtext(s); ++ j = strlen(s); ++ ss = rfc822_8bit(s, j, &j); ++ fs_give((void **)&s); ++ while (j -= k) k = fwrite (ss += k,1,j,stdout); ++ fs_give((void **)&ss); ++ } ++ #else + while (j -= k) k = fwrite (s += k,1,j,stdout); ++ #endif + changed_flags (i,f); /* output changed flags */ + } + else fputs ("NIL",stdout); /* can't output anything at all */ +*************** +*** 983,995 **** +--- 1012,1039 ---- + else { /* non-multipart body type */ + pstring ((char *) body_types[body->type]); + putchar (' '); ++ #ifdef BWC ++ if(body->type == TYPETEXT && strcmp(lcase(body->subtype), "x-bwc-glyph") == 0) ++ pstring("plain"); /* Make it plain now, richtext later when fixed */ ++ else ++ pstring (body->subtype); ++ #else + pstring (body->subtype); ++ #endif + if (param = body->parameter) { + fputs (" (",stdout); + do { + pstring (param->attribute); + putchar (' '); ++ #ifdef BWC_NOT_WORKING_YET ++ if(strucmp2(param->attribute, "charset") == 0) { ++ pstring("ISO-8859-1"); ++ } else { ++ pstring (param->value); ++ } ++ #else + pstring (param->value); ++ #endif + if (param = param->next) putchar (' '); + } while (param); + fputs (") ",stdout); diff --git a/contrib/carmel/pine/filter.c.patch b/contrib/carmel/pine/filter.c.patch new file mode 100644 index 00000000..094fc9ae --- /dev/null +++ b/contrib/carmel/pine/filter.c.patch @@ -0,0 +1,409 @@ +*** filter.c Fri Aug 5 18:44:21 1994 +--- filter.c.new Wed Aug 31 18:31:47 1994 +*************** +*** 1937,1939 **** +--- 1937,2340 ---- + + fflush(stdout); + } ++ ++ ++ #ifdef BWC ++ #ifdef NODEF ++ /*-*/ ++ #include <stdio.h> ++ #include <sys/types.h> ++ #include <sys/stat.h> ++ #include <sys/file.h> ++ struct stat buf; ++ char *malloc(); ++ char *glyph2richtext(); ++ int convert2graph(); ++ #endif ++ ++ char *glyph2richtext(); ++ ++ ++ char *gf_glyph2richtext(f, c, flg) ++ FILTER_S *f; ++ int c, flg; ++ { ++ char *line, *p; ++ if(flg == GF_RESET){ ++ f->linep = f->line = (char *)fs_get(1000 * sizeof(char )); ++ ++ } else if(flg == GF_DATA) { ++ if(c == '\n' && *(f->linep - 1) == '\r'){ ++ *(f->linep)++ = '\n'; /* Tie of line */ ++ *(f->linep) = '\0'; /* Tie of line */ ++ line = glyph2richtext(f->line); ++ for(p = line; *p; p++) ++ (*f->next->f)(f->next, *p, GF_DATA); ++ fs_give((void **)&line); ++ f->linep = f->line; ++ } else { ++ *(f->linep)++ = c; ++ } ++ } else if(flg == GF_EOD) { ++ *(f->linep) = '\0'; /* Tie of line */ ++ line = glyph2richtext(f->line); ++ for(p = line; *p; p++) ++ (*f->next->f)(f->next, *p, GF_DATA); ++ fs_give((void **)&line); ++ f->linep = f->line; ++ ++ fs_give((void **)&(f->line)); ++ (*f->next->f)(f->next, 0 , GF_EOD); ++ } ++ } ++ ++ ++ char *glyph2richtext(textin) ++ char *textin; ++ { ++ register char *p, *textout; ++ int stack=10, underline=0, bold=0, graphics=0,i=0,two_underline=0 ; ++ int memory_aloc=0; ++ char stack_str[10][20]; ++ ++ memory_aloc = max(strlen(textin)*8, 80); ++ textout = malloc(memory_aloc+1); ++ *textout = '\0'; ++ dprint(9, (debugfile, "GLYPH2RICH strlen: %d allocated: %d\n", ++ strlen(textin), memory_aloc + 1)); ++ ++ while (stack--) stack_str[stack][0] = '\0'; /*Initialize the array */ ++ stack=0; ++ ++ for(p=textout; *textin;){ /*everything is done in the loop */ ++ ++ if ((p -textout) >(memory_aloc -200)){ /*allocate more memory before */ ++ realloc(textout,(memory_aloc +=1024)); /*we ran out of it */ ++ dprint(9, (debugfile, "GLYPH2RICH left: %d reallocated: %d\n", ++ p - textout, memory_aloc)); ++ } ++ ++ /*======= THE LINE AND PAGE BREAKS =======*/ ++ if(*textin == '\r' && *(textin+1)=='\n'){ /*---- LINE BREAK -----*/ ++ while (stack--){ /*End attributes*/ ++ sprintf(p, "</%s>", stack_str[stack]); ++ p += strlen(p); ++ stack_str[stack][0] = '\0'; ++ } ++ if(two_underline) { /* In case '_' occured near end of line */ ++ strcpy(p, "<\\underline>"); ++ p += strlen(p); ++ } ++ ++ strcpy(p,"\r\n\r\n"); /*Add richtext newline */ ++ p += strlen(p); ++ textin += 2; ++ ++ stack=underline=bold=graphics=two_underline=0; ++ continue; ++ } ++ ++ if( *textin == '{' && /*-------- PAGE BREAK --------*/ ++ *(textin+1) == '~'){ ++ strcpy(p,"<np>"); ++ p += strlen(p); ++ textin +=2; ++ continue; ++ } ++ ++ /*==================== ATTRIBUTES ===============*/ ++ ++ if(*textin == '_'){ /*------next two characters are underlined-----*/ ++ if(underline){ ++ *textin++; /*skip if already underlined*/ ++ } else { ++ two_underline=2; ++ strcpy(p,"<underline>"); ++ p += strlen(p); ++ *textin++; ++ } ++ continue; ++ } ++ ++ if( *textin == '{' && ++ *(textin+1) == '\\'){ /*Some type of attribute found*/ ++ textin += 2; ++ ++ while (stack--){ /*End previous attributes*/ ++ sprintf(p, "</%s>", stack_str[stack]); ++ p += strlen(p); ++ stack_str[stack][0] = '\0'; ++ } ++ ++ stack=underline=bold=0; ++ if ( (*textin - 0x20) & 0x01){ ++ strcpy(stack_str[stack++],"bold"); /*bold*/ ++ bold=1; ++ } ++ if (((*textin - 0x20) >> 1) & 0x01){ /*underline*/ ++ strcpy(stack_str[stack++],"underline"); ++ underline=1; ++ } ++ if (((*textin - 0x20) >> 2) & 0x01){ /*blinking*/ ++ if(bold) strcpy(stack_str[stack++],"superscript"); ++ else if (underline) strcpy(stack_str[stack++],"subscript"); ++ else strcpy(stack_str[stack++],"no-op"); /*can't do blinking alone!*/ ++ } ++ if (((*textin - 0x20) >> 3) & 0x01){ /*reverse video*/ ++ strcat(stack_str[stack++],"italic"); /*Italic on paper*/ ++ } ++ if (((*textin - 0x20) >> 4) & 0x01){ /*graphics*/ ++ graphics=1; ++ } else graphics=0; ++ ++ ++ for(i=0; i<stack; i++){ /*write the staring attributes*/ ++ sprintf(p, "<%s>", stack_str[i]); ++ p += strlen(p); ++ } ++ textin++; ++ continue; ++ } ++ ++ /*========== SPECIAL CHARACTERS ===========*/ ++ if(two_underline){ ++ /* Time to turn off underline? */ ++ if(--two_underline ==0 && underline == 0){ ++ strcpy(p,"</underline>"); ++ p += strlen(p); ++ } ++ } ++ ++ if(*textin == '<'){ ++ /*add richtext smaller sign */ ++ strcpy(p,"<lt>"); ++ p += strlen(p); ++ *textin++; ++ ++ } else if(graphics){ /*convert graphics */ ++ if ( *textin == 'j' || ++ *textin == 'k' || ++ *textin == 'l' || ++ *textin == 'm' || ++ *textin == 'n' || ++ *textin == 't' || ++ *textin == 'u' || ++ *textin == 'v' || ++ *textin == 'w' ) { *p = '+'; *textin++;} ++ else if ( *textin == 'q' ) { *p = '-'; *textin++;} ++ else if ( *textin == 'x' ) { *p = '|'; *textin++;} ++ else *p= *textin++; ++ p++; ++ } else if(*textin == '^'){ /* Accent (similar to \a notation)*/ ++ textin++; ++ if (*textin == 'A' ){*p='\301'; textin++;} ++ else if(*textin == 'E' ){*p='\311'; textin++;} ++ else if(*textin == 'I' ){*p='\315'; textin++;} ++ else if(*textin == 'O' ){*p='\323'; textin++;} ++ else if(*textin == 'U' ){*p='\332'; textin++;} ++ else if(*textin == 'a' ){*p='\341'; textin++;} ++ else if(*textin == 'e' ){*p='\351'; textin++;} ++ else if(*textin == 'i' ){*p='\355'; textin++;} ++ else if(*textin == 'o' ){*p='\363'; textin++;} ++ else if(*textin == 'u' ){*p='\372'; textin++;} ++ else *p= '^'; ++ *++p = '\0'; ++ ++ } else if(*textin == '|') { /* Dot under letter */ ++ textin++; ++ switch(*textin) { ++ case 'd': ++ case 'D': ++ case 's': ++ case 'S': ++ case 'T': ++ case 't': ++ case 'Z': ++ case 'z': ++ *p = *textin++; break; ++ default: ++ *p = '|'; break; ++ } ++ *++p = '\0'; ++ ++ } else if( *textin == '\\'){ /* Decode \ notation for 8 bit chars */ ++ switch(*(textin+1)){ ++ case '@': ++ *p = '@'; textin+=2; /* *textin++;*textin++; */ ++ break; ++ case '\\': /* back slash*/ ++ *p = '\\'; textin+=2; ++ break; ++ case '!': /* reverse exclaimation */ ++ *p = '\241'; textin+=2; ++ break; ++ case '#': /* superscript 1 2 3 */ ++ if (*(textin+2) == '1' ){*p='\271'; textin += 3;} ++ else if(*(textin+2) == '2' ){*p='\262'; textin += 3;} ++ else if(*(textin+2) == '3' ){*p='\263'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case '$': /* cent*/ ++ *p = '\242'; textin+=2; ++ break; ++ case '&': /* ligature*/ ++ *p = '\247'; textin+=2; ++ break; ++ case '*': /* crossed o*/ ++ *p = '\250'; textin+=2; ++ break; ++ case '-': /* minus*/ ++ *p = '-'; textin+=2; ++ break; ++ case '.': /* degree*/ ++ *p = '\260'; textin+=2; ++ break; ++ case '/': /* o slashed*/ ++ if (*(textin+2) == 'O' ){*p='\330'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\370'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case '<': /* double <*/ ++ *p = '\253'; textin+=2; ++ break; ++ case 'n': /* back quote (ayn)*/ ++ *p = '`'; textin+=2; ++ break; ++ case '>': /* double >*/ ++ *p = '\273'; textin+=2; ++ break; ++ case '=': /* ligature*/ ++ if (*(textin+2) == 'A' && ++ *(textin+3) == 'E' ) {*p='\306'; *(textin+=4);} ++ else if(*(textin+2) == 'O' && ++ *(textin+3) == 'E' ) {*p='\327'; *(textin+=4);} ++ else if(*(textin+2) == 'a' && ++ *(textin+3) == 'e' ) {*p='\346'; *(textin+=4);} ++ else if(*(textin+2) == 'o' && ++ *(textin+3) == 'e' ) {*p='\367'; *(textin+=4);} ++ ++ else *p= *textin++; ++ break; ++ case '?': /* reverse question mark*/ ++ *p = '\277'; textin+=2; ++ break; ++ case 'B': /* german SS (B)*/ ++ *p = '\337'; textin+=2; ++ break; ++ case 'C': /* underlined a & o */ ++ if (*(textin+2) == 'a' ){*p='\252'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\272'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'H': /* One half (1/2) */ ++ *p = '\275'; textin+=2; ++ break; ++ case 'P': /* paragrph marker */ ++ *p = '\266'; textin+=2; ++ break; ++ case 'Q': /* One quarter (1/4)*/ ++ *p = '\274'; textin+=2; ++ break; ++ case 'U': /* greek mu */ ++ *p = '\265'; textin+=2; ++ break; ++ case 'Y': /* Yen */ ++ *p = '\245'; textin+=2; ++ break; ++ case 'a': /* Accent*/ ++ if (*(textin+2) == 'A' ){*p='\301'; textin += 3;} ++ else if(*(textin+2) == 'E' ){*p='\311'; textin += 3;} ++ else if(*(textin+2) == 'I' ){*p='\315'; textin += 3;} ++ else if(*(textin+2) == 'O' ){*p='\323'; textin += 3;} ++ else if(*(textin+2) == 'U' ){*p='\332'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\341'; textin += 3;} ++ else if(*(textin+2) == 'e' ){*p='\351'; textin += 3;} ++ else if(*(textin+2) == 'i' ){*p='\355'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\363'; textin += 3;} ++ else if(*(textin+2) == 'u' ){*p='\372'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'c': /* C cedilla*/ ++ if (*(textin+2) == 'C' ){*p='\307'; textin += 3;} ++ else if(*(textin+2) == 'c' ){*p='\347'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'g': /* Grave */ ++ if (*(textin+2) == 'A' ){*p='\300'; textin += 3;} ++ else if(*(textin+2) == 'E' ){*p='\310'; textin += 3;} ++ else if(*(textin+2) == 'I' ){*p='\314'; textin += 3;} ++ else if(*(textin+2) == 'O' ){*p='\322'; textin += 3;} ++ else if(*(textin+2) == 'U' ){*p='\331'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\340'; textin += 3;} ++ else if(*(textin+2) == 'e' ){*p='\350'; textin += 3;} ++ else if(*(textin+2) == 'i' ){*p='\354'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\362'; textin += 3;} ++ else if(*(textin+2) == 'u' ){*p='\371'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'm': /* A angstrom*/ ++ if (*(textin+2) == 'A' ){*p='\305'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\345'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'p': /* pound sterling */ ++ *p = '\243'; textin+=2; ++ break; ++ case 's': /* solidus */ ++ *p = '\267'; textin+=2; ++ break; ++ case 't': /* Tilda */ ++ if (*(textin+2) == 'A' ){*p='\303'; textin += 3;} ++ else if(*(textin+2) == 'N' ){*p='\321'; textin += 3;} ++ else if(*(textin+2) == 'O' ){*p='\325'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\343'; textin += 3;} ++ else if(*(textin+2) == 'n' ){*p='\361'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\365'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'u': /* Umlaut */ ++ if (*(textin+2) == 'A' ){*p='\304'; textin += 3;} ++ else if(*(textin+2) == 'E' ){*p='\313'; textin += 3;} ++ else if(*(textin+2) == 'I' ){*p='\317'; textin += 3;} ++ else if(*(textin+2) == 'O' ){*p='\326'; textin += 3;} ++ else if(*(textin+2) == 'U' ){*p='\334'; textin += 3;} ++ else if(*(textin+2) == 'Y' ){*p='\335'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\344'; textin += 3;} ++ else if(*(textin+2) == 'e' ){*p='\353'; textin += 3;} ++ else if(*(textin+2) == 'i' ){*p='\357'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\366'; textin += 3;} ++ else if(*(textin+2) == 'u' ){*p='\374'; textin += 3;} ++ else if(*(textin+2) == 'y' ){*p='\375'; textin += 3;} ++ else *p= *textin++; ++ break; ++ case 'x': /* Circumflex */ ++ if (*(textin+2) == 'A' ){*p='\302'; textin += 3;} ++ else if(*(textin+2) == 'E' ){*p='\312'; textin += 3;} ++ else if(*(textin+2) == 'I' ){*p='\316'; textin += 3;} ++ else if(*(textin+2) == 'O' ){*p='\324'; textin += 3;} ++ else if(*(textin+2) == 'U' ){*p='\333'; textin += 3;} ++ else if(*(textin+2) == 'a' ){*p='\342'; textin += 3;} ++ else if(*(textin+2) == 'e' ){*p='\352'; textin += 3;} ++ else if(*(textin+2) == 'i' ){*p='\356'; textin += 3;} ++ else if(*(textin+2) == 'o' ){*p='\364'; textin += 3;} ++ else if(*(textin+2) == 'u' ){*p='\373'; textin += 3;} ++ else *p= *textin++; ++ break; ++ default: ++ textin +=2; /* Skip the \x and ignore it */ ++ *p = *textin++; /* Copy third character in group */ ++ break; ++ } /*End switch */ ++ p++; ++ } else{ ++ *p++ = *textin++; /* Finally a plain ordinary ASCII character */ ++ } ++ } /* End for loop over string */ ++ ++ *p=0; /*end of string or file */ ++ ++ return textout; ++ } ++ ++ #endif ++ diff --git a/contrib/carmel/pine/mailview.c.patch b/contrib/carmel/pine/mailview.c.patch new file mode 100644 index 00000000..5dc9c9af --- /dev/null +++ b/contrib/carmel/pine/mailview.c.patch @@ -0,0 +1,39 @@ +*** mailview.c Wed Aug 17 17:19:56 1994 +--- mailview.c.new Wed Aug 31 19:04:19 1994 +*************** +*** 132,139 **** +--- 132,147 ---- + void redraw_scroll_text(); + #endif + ++ #ifdef BWC ++ #ifdef ANSI ++ void gf_glyph2richtext(FILTER_S *, int, int); ++ #else ++ void gf_glyph2richtext(); ++ #endif ++ #endif + + ++ + /*---------------------------------------------------------------------- + Format a buffer with the text of the current message for browser + +*************** +*** 892,897 **** +--- 900,914 ---- + filter_t aux_filter[4]; + int filtcnt = 0, error_found = 0; + char *err; ++ ++ #ifdef BWC ++ if(att->body->subtype != NULL && ++ strucmp(att->body->subtype, "X-bwc-glyph") == 0) { ++ aux_filter[filtcnt++] = gf_glyph2richtext; ++ gf_enriched2plain_opt(0); /* don't strip everything! */ ++ aux_filter[filtcnt++] = gf_enriched2plain; ++ } ++ #endif + + if(att->body->subtype){ + if(!strucmp(att->body->subtype, "richtext")) { diff --git a/contrib/carmel/pine/makefile.sun.patch b/contrib/carmel/pine/makefile.sun.patch new file mode 100644 index 00000000..cab73d4a --- /dev/null +++ b/contrib/carmel/pine/makefile.sun.patch @@ -0,0 +1,19 @@ +*** makefile.sun Mon Jul 25 15:21:59 1994 +--- makefile.sun.new Wed Aug 31 19:08:25 1994 +*************** +*** 59,65 **** + MAKE= make + OPTIMIZE= # -O + PROFILE= # -pg +! DEBUG= -g -DDEBUG + + IMAPDIR= ../c-client + PICODIR= ../pico +--- 59,65 ---- + MAKE= make + OPTIMIZE= # -O + PROFILE= # -pg +! DEBUG= -DBWC -g -DDEBUG + + IMAPDIR= ../c-client + PICODIR= ../pico diff --git a/contrib/carmel/pine/makefile.ult.patch b/contrib/carmel/pine/makefile.ult.patch new file mode 100644 index 00000000..a50e504d --- /dev/null +++ b/contrib/carmel/pine/makefile.ult.patch @@ -0,0 +1,19 @@ +*** makefile.ult Mon Jul 25 15:22:01 1994 +--- makefile.ult.new Wed Aug 31 18:19:59 1994 +*************** +*** 67,73 **** + LIBES= -ltermlib -lauth + LOCLIBES= $(PICODIR)/libpico.a $(IMAPDIR)/c-client.a + +! CFLAGS= -DULT $(OPTIMIZE) $(PROFILE) $(DEBUG) -DSYSTYPE=\"ULT\" + + obj= addrbook.o adrbklib.o args.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ +--- 67,73 ---- + LIBES= -ltermlib -lauth + LOCLIBES= $(PICODIR)/libpico.a $(IMAPDIR)/c-client.a + +! CFLAGS= -DBWC -DULT $(OPTIMIZE) $(PROFILE) $(DEBUG) -DSYSTYPE=\"ULT\" + + obj= addrbook.o adrbklib.o args.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ diff --git a/contrib/keypad.enable/ReadMe b/contrib/keypad.enable/ReadMe new file mode 100644 index 00000000..927519ce --- /dev/null +++ b/contrib/keypad.enable/ReadMe @@ -0,0 +1,7 @@ +These context diffs when applied to pico/tcap.c enable entrance +and exit of "keypad mode". This mode is especially useful (and +necessary!) for HP terminals which require this mode to make the +arrow keys useful. + +These changes are based on a bug report submitted by Jochiam Richter +(zjr@rz.uni-jena.de). diff --git a/contrib/keypad.enable/keypad.enable.diff b/contrib/keypad.enable/keypad.enable.diff new file mode 100644 index 00000000..80643f0d --- /dev/null +++ b/contrib/keypad.enable/keypad.enable.diff @@ -0,0 +1,122 @@ +*** pine/ttyout.c.REAL Thu Dec 21 02:08:20 1995 +--- pine/ttyout.c Sat Feb 10 15:47:12 1996 +*************** +*** 131,137 **** + *_startinsert, *_endinsert, *_insertchar, *_deletechar, + *_deleteline, *_insertline, + *_scrollregion, *_scrollup, *_scrolldown, +! *_termcap_init, *_termcap_end; + char term_name[40]; + #ifndef USE_TERMINFO + static char _terminal[1024]; /* Storage for terminal entry */ +--- 131,138 ---- + *_startinsert, *_endinsert, *_insertchar, *_deletechar, + *_deleteline, *_insertline, + *_scrollregion, *_scrollup, *_scrolldown, +! *_termcap_init, *_termcap_end, +! *_keypad_init, *_keypad_end; + char term_name[40]; + #ifndef USE_TERMINFO + static char _terminal[1024]; /* Storage for terminal entry */ +*************** +*** 228,233 **** +--- 229,236 ---- + _termcap_end = tigetstr("rmcup"); + _lines = tigetnum("lines"); + _columns = tigetnum("cols"); ++ _keypad_init = tigetnum("smkx"); ++ _keypad_end = tigetnum("rmkx"); + _ku = tigetstr("kcuu1"); + _kd = tigetstr("kcud1"); + _kl = tigetstr("kcub1"); +*************** +*** 295,300 **** +--- 298,305 ---- + _termcap_end = tgetstr("te", &ptr); + _lines = tgetnum("li"); + _columns = tgetnum("co"); ++ _keypad_init = tgetstr("ks", &ptr); ++ _keypad_end = tgetstr("ke", &ptr); + _ku = tgetstr("ku", &ptr); + _kd = tgetstr("kd", &ptr); + _kl = tgetstr("kl", &ptr); +*************** +*** 435,440 **** +--- 440,448 ---- + BeginScroll(0, ps_global->ttyo->screen_rows); + fflush(stdout); + } ++ ++ if(_keypad_init) ++ tputs(_keypad_init, 1, outchar); + } + + +*************** +*** 510,515 **** +--- 518,526 ---- + MoveCursor(_lines - 2, 0); + if(_termcap_end != NULL) + tputs(_termcap_end, 1, outchar); ++ ++ if(_keypad_end != NULL) ++ tputs(_keypad_end, 1, outchar); + + if(message){ + StartInverse(); +*** pico/tcap.c.REAL Thu Dec 21 01:54:35 1995 +--- pico/tcap.c Sat Feb 10 16:08:47 1996 +*************** +*** 92,98 **** + *SF, /* scroll text up */ + *SR, /* scroll text down */ + *TI, /* string to start termcap */ +! *TE; /* string to end termcap */ + + + TERM term = { +--- 92,100 ---- + *SF, /* scroll text up */ + *SR, /* scroll text down */ + *TI, /* string to start termcap */ +! *TE, /* string to end termcap */ +! *KS, /* string to enter application keypad mode */ +! *KE; /* string to end application keypad mode */ + + + TERM term = { +*************** +*** 178,183 **** +--- 180,187 ---- + SR = tgetstr("sr", &p); + TI = tgetstr("ti", &p); + TE = tgetstr("te", &p); ++ KS = tgetstr("ks", &p); ++ KE = tgetstr("ke", &p); + + row = tgetnum("li"); + if(row == -1){ +*************** +*** 399,404 **** +--- 403,411 ---- + if (CS) + putpad(tgoto(CS, term.t_nrow, 0)) ; + } ++ ++ if(KS && !Pmaster) /* enter app/keypad mode (cursor) */ ++ putpad(KS); + } + + +*************** +*** 410,415 **** +--- 417,425 ---- + + if(TE) /* any cleanup termcap requires */ + putpad(TE); ++ ++ if(KE) ++ putpad(KE); /* end app/keypad mode */ + } + + kbdestroy(pico_kbesc); /* clean up key board sequence trie */ diff --git a/contrib/ports/aos/README b/contrib/ports/aos/README new file mode 100644 index 00000000..6503853f --- /dev/null +++ b/contrib/ports/aos/README @@ -0,0 +1,20 @@ +From tenser@snafu.eec.psu.edu Tue May 7 10:14:10 1996 +Date: Tue, 16 Apr 1996 14:57:45 -0000 +From: Dan Cross <tenser@snafu.eec.psu.edu> +To: pine@cac.washington.edu +Subject: Pine patches for the IBM RT (Pine 3.93, 4.3BSD) + +Hi there! + I ported pine 3.93 to the IBM RT running AOS using the BSD port. (AOS +is IBM's port of 4.3BSD to the RT architecture.) There were only minor +changes that had to be made to the source to get it running under AOS, so +I just stuck with the BSD port. (It seemed like a waste to change, since +there was so much duplication between the BSD port and an AOS port.) + I wanted to send back the patches to you all to get them re-integrated +into the main distribution so that the RT becomes a supported architecture. +So, without further inane babble by myself, here they are. Thanks! + + - Dan C. + +(ps- if you would like my binaries to put up for FTP, let me know and I'll +pack them up for ya'll!) diff --git a/contrib/ports/aos/aos.diff b/contrib/ports/aos/aos.diff new file mode 100644 index 00000000..a8858529 --- /dev/null +++ b/contrib/ports/aos/aos.diff @@ -0,0 +1,125 @@ +*** os_bsd.c.orig Mon Apr 15 17:46:29 1996 +--- pine3.93/imap/non-ANSI/c-client/os_bsd.c Mon Apr 15 14:02:14 1996 +*************** +*** 67,72 **** +--- 67,74 ---- + #include "memmove.c" + #include "strerror.c" + #include "strstr.c" ++ #ifndef ibm032 + #include "strtol.c" ++ #endif /* !ibm032 */ + #include "strtoul.c" + #include "tz_bsd.c" +*** os_bsd.h.orig Mon Apr 15 17:46:31 1996 +--- pine3.93/imap/non-ANSI/c-client/os_bsd.h Mon Apr 15 13:45:22 1996 +*************** +*** 40,46 **** +--- 40,48 ---- + #include <fcntl.h> + #include <syslog.h> + #include <sys/file.h> ++ #ifndef ibm032 + #include <machine/endian.h> /* needed for htons() prototypes */ ++ #endif /* !ibm032 */ + + + char *getenv (); +*** tz_bsd.c.orig Mon Apr 15 17:46:32 1996 +--- pine3.93/imap/non-ANSI/c-client/tz_bsd.c Mon Apr 15 16:22:38 1996 +*************** +*** 42,47 **** +--- 42,61 ---- + char *s; + void *t; + { ++ #ifndef ibm032 + /* append timezone from tm struct */ + sprintf (s + strlen (s)," (%s)",((struct tm *) t)->tm_zone); ++ #else ++ struct timeval tv; ++ struct timezone tz; ++ ++ (void)memset((char *)&tv, 0, sizeof(tv)); ++ (void)memset((char *)&tz, 0, sizeof(tz)); ++ ++ if (gettimeofday(&tv, &tz) < 0) ++ return; /* Silent error. */ ++ ++ (void)sprintf(s + strlen(s), " (%s)", ++ timezone(tz.tz_minuteswest, tz.tz_dsttime)); ++ #endif /* !ibm032 */ + } +*** other.c.orig Mon Apr 15 17:47:02 1996 +--- pine3.93/pine/other.c Mon Apr 15 15:18:02 1996 +*************** +*** 353,359 **** + pbuf.exittest = sigedit_exit_for_pico; + pbuf.upload = (ps_global->VAR_UPLOAD_CMD + && ps_global->VAR_UPLOAD_CMD[0]) +! ? upload_msg_to_pico : NULL; + pbuf.keybinit = init_keyboard; + pbuf.helper = helper; + pbuf.resize = resize_for_pico; +--- 353,359 ---- + pbuf.exittest = sigedit_exit_for_pico; + pbuf.upload = (ps_global->VAR_UPLOAD_CMD + && ps_global->VAR_UPLOAD_CMD[0]) +! ? (int(*)())upload_msg_to_pico : NULL; + pbuf.keybinit = init_keyboard; + pbuf.helper = helper; + pbuf.resize = resize_for_pico; +*** send.c.orig Mon Apr 15 17:47:12 1996 +--- pine3.93/pine/send.c Mon Apr 15 15:36:46 1996 +*************** +*** 1656,1662 **** + #else + pbuf.upload = (ps_global->VAR_UPLOAD_CMD + && ps_global->VAR_UPLOAD_CMD[0]) +! ? upload_msg_to_pico : NULL; + #endif + + pbuf.raw_io = Raw; +--- 1656,1662 ---- + #else + pbuf.upload = (ps_global->VAR_UPLOAD_CMD + && ps_global->VAR_UPLOAD_CMD[0]) +! ? (int (*)())upload_msg_to_pico : NULL; + #endif + + pbuf.raw_io = Raw; +*** tempfile.orig Mon Apr 15 17:48:22 1996 +--- pine3.93/pine/osdep/tempfile Mon Apr 15 15:39:52 1996 +*************** +*** 2,11 **** +--- 2,31 ---- + Create a temporary file, the name of which we don't care about + and that goes away when it is closed. Just like ANSI C tmpfile. + ----*/ ++ #ifdef ibm032 ++ #define FILENAMESIZE 20 ++ #define FILETMPLATE "/tmp/ptmpXXXXXX" ++ #endif /* ibm032 */ ++ + FILE * + create_tmpfile() + { ++ #ifndef ibm032 + return(tmpfile()); ++ #else ++ char buf[FILENAMESIZE]; ++ int fd; ++ ++ (void)memset(buf, 0, FILENAMESIZE); ++ (void)strcpy(buf, FILETMPLATE); ++ ++ if ((fd = mkstemp(buf)) < 0) ++ return(NULL); ++ ++ (void)unlink(buf); ++ ++ return(fdopen(fd, "w+")); ++ #endif /* !ibm032 */ + } + + diff --git a/contrib/ports/sequent_ptx_4.4.6 b/contrib/ports/sequent_ptx_4.4.6 new file mode 100644 index 00000000..064ed460 --- /dev/null +++ b/contrib/ports/sequent_ptx_4.4.6 @@ -0,0 +1,188 @@ + +Larry Mascarenhas <lmascare@cscinfo.com> found he had to make some changesx +to get Pine 4.21 to compile on Sequent ptx v4.4.6. We didn't have time to +integrate these into the distribution. He was kind enough to send us the +diffs of what he had to do to make it work. + + + +diff -cr pine.dist/imap/src/osdep/unix/Makefile pine4.20/imap/src/osdep/unix/Makefile +*** pine.dist/imap/src/osdep/unix/Makefile Thu Sep 30 01:54:13 1999 +--- pine4.20/imap/src/osdep/unix/Makefile Fri Nov 12 15:26:40 1999 +*************** +*** 418,424 **** + MAILSPOOL=/usr/mail \ + RSHPATH=/usr/bin/resh \ + BASECFLAGS="-Wc,-O3 -Wc,-seq -Dprivate=PRIVATE -DNFSKLUDGE" \ +! BASELDFLAGS="-lseq -lsec -lsocket -linet -lnsl -lgen" \ + RANLIB=true + + pyr: # Pyramid +--- 418,424 ---- + MAILSPOOL=/usr/mail \ + RSHPATH=/usr/bin/resh \ + BASECFLAGS="-Wc,-O3 -Wc,-seq -Dprivate=PRIVATE -DNFSKLUDGE" \ +! BASELDFLAGS="-lseq -lsec -lsocket -lnsl -lgen" \ + RANLIB=true + + pyr: # Pyramid +diff -cr pine.dist/pico/makefile.ptx pine4.20/pico/makefile.ptx +*** pine.dist/pico/makefile.ptx Mon Jun 29 18:23:52 1998 +--- pine4.20/pico/makefile.ptx Fri Nov 12 15:37:28 1999 +*************** +*** 42,48 **** + LIBARGS= ru + RANLIB= true + +! LIBS= $(EXTRALIBES) -ltermlib -linet + + OFILES= attach.o basic.o bind.o browse.o buffer.o \ + composer.o display.o file.o fileio.o line.o pico_os.o \ +--- 42,48 ---- + LIBARGS= ru + RANLIB= true + +! LIBS= $(EXTRALIBES) -ltermlib -lsocket -l nsl + + OFILES= attach.o basic.o bind.o browse.o buffer.o \ + composer.o display.o file.o fileio.o line.o pico_os.o \ +diff -cr pine.dist/pine/cmplhelp.sh pine4.20/pine/cmplhelp.sh +*** pine.dist/pine/cmplhelp.sh Fri Feb 20 19:50:38 1998 +--- pine4.20/pine/cmplhelp.sh Fri Nov 12 15:16:17 1999 +*************** +*** 49,55 **** + s/-sed-backslash-quote-/\\"/g + ' | + +! awk 'BEGIN {in_text = 0; + count = 0; + printf("#include <stdio.h>\n#include \"headers.h\"\n\n\n"); + } +--- 49,55 ---- + s/-sed-backslash-quote-/\\"/g + ' | + +! nawk 'BEGIN {in_text = 0; + count = 0; + printf("#include <stdio.h>\n#include \"headers.h\"\n\n\n"); + } +diff -cr pine.dist/pine/cmplhlp2.sh pine4.20/pine/cmplhlp2.sh +*** pine.dist/pine/cmplhlp2.sh Wed Feb 18 20:46:52 1998 +--- pine4.20/pine/cmplhlp2.sh Fri Nov 12 15:16:28 1999 +*************** +*** 44,50 **** + # + + +! awk ' BEGIN { printf("\n\n\t\t/*\n"); + printf("\t\t * AUTMATICALLY GENERATED FILE!\n"); + printf("\t\t * DO NOT EDIT!!\n\t\t */\n\n\n"); + printf("#define\tHelpType\tchar **\n"); +--- 44,50 ---- + # + + +! nawk ' BEGIN { printf("\n\n\t\t/*\n"); + printf("\t\t * AUTMATICALLY GENERATED FILE!\n"); + printf("\t\t * DO NOT EDIT!!\n\t\t */\n\n\n"); + printf("#define\tHelpType\tchar **\n"); +diff -cr pine.dist/pine/mailtrfc.sh pine4.20/pine/mailtrfc.sh +*** pine.dist/pine/mailtrfc.sh Fri Mar 15 02:14:43 1996 +--- pine4.20/pine/mailtrfc.sh Fri Nov 12 15:18:35 1999 +*************** +*** 47,53 **** + + + +! org=`awk '/^domain/ {print $2}' < /etc/resolv.conf` + domain=`echo $org | sed -e 's/^[^.]*\.//'` + host=`hostname`".$org" + +--- 47,53 ---- + + + +! org=`nawk '/^domain/ {print $2}' < /etc/resolv.conf` + domain=`echo $org | sed -e 's/^[^.]*\.//'` + host=`hostname`".$org" + +*************** +*** 56,62 **** + echo "Hostname: $host" + + sed -n -e '/message-id/s/^.*</</p' | +! awk 'BEGIN {mailers[0] = "Other"; + mailers[1] = "Pine"; + mailers[2] = "MailManager"; + mailers[3] = "sendmail"; +--- 56,62 ---- + echo "Hostname: $host" + + sed -n -e '/message-id/s/^.*</</p' | +! nawk 'BEGIN {mailers[0] = "Other"; + mailers[1] = "Pine"; + mailers[2] = "MailManager"; + mailers[3] = "sendmail"; +*************** +*** 115,121 **** + + + echo $host $org $domain | \ +! awk '{printf(" %.17s %.11s %.11s Off Campus Total\n", $1, $2, $3)}' + egrep -v 'TOTAL|----|^-->' /tmp/syslogx.$$ | sort +0.60rn + egrep 'TOTAL|----' /tmp/syslogx.$$ + grep '^-->' /tmp/syslogx.$$ | sed -e 's/-->//' > other-traffic +--- 115,121 ---- + + + echo $host $org $domain | \ +! nawk '{printf(" %.17s %.11s %.11s Off Campus Total\n", $1, $2, $3)}' + egrep -v 'TOTAL|----|^-->' /tmp/syslogx.$$ | sort +0.60rn + egrep 'TOTAL|----' /tmp/syslogx.$$ + grep '^-->' /tmp/syslogx.$$ | sed -e 's/-->//' > other-traffic +diff -cr pine.dist/pine/send.c pine4.20/pine/send.c +*** pine.dist/pine/send.c Wed Oct 6 16:18:27 1999 +--- pine4.20/pine/send.c Mon Nov 15 12:46:22 1999 +*************** +*** 50,56 **** + #include "../c-client/smtp.h" + #include "../c-client/nntp.h" + +- + #ifndef TCPSTREAM + #define TCPSTREAM void + #endif +--- 50,55 ---- +*************** +*** 3933,3940 **** + if((ps_global->post->pid = fork()) == 0){ + /* + * Put us in new process group... +- */ + setpgrp(0, ps_global->post->pid); + + /* BUG: should fix argv[0] to indicate what we're up to */ + +--- 3932,3940 ---- + if((ps_global->post->pid = fork()) == 0){ + /* + * Put us in new process group... + setpgrp(0, ps_global->post->pid); ++ */ ++ setpgrp(); + + /* BUG: should fix argv[0] to indicate what we're up to */ + +diff -cr pine.dist/pine/signals.c pine4.20/pine/signals.c +*** pine.dist/pine/signals.c Tue Apr 20 20:25:18 1999 +--- pine4.20/pine/signals.c Mon Nov 15 12:46:59 1999 +*************** +*** 53,58 **** +--- 53,59 ---- + ====*/ + + #include "headers.h" ++ #undef sigmask + + /* + * call used by TERM and HUP handlers to quickly close streams diff --git a/contrib/ports/vms/readme.1st b/contrib/ports/vms/readme.1st new file mode 100644 index 00000000..42652494 --- /dev/null +++ b/contrib/ports/vms/readme.1st @@ -0,0 +1,9 @@ + +To build the VMS version of pine, just move the VMSBUILD.COM command +script into the top level pine directory. If the script fails moving +the pine-specific c-client build script (VMSBUILD_CCLIENT.COM) into +place, just copy it by hand into the C-CLIENT.DIR it creates. This +RENAME was added just prior to release by the pine team and was not +part of the contributed port. + +- The Pine Team diff --git a/contrib/ports/vms/readme.vms b/contrib/ports/vms/readme.vms new file mode 100644 index 00000000..da14481f --- /dev/null +++ b/contrib/ports/vms/readme.vms @@ -0,0 +1,102 @@ + VMS readme for PINE/C-CLEINT/PICO + ======================== + +Building +======== + + There are three executables: +Pico/PICO.EXE: This is the stand-alone version of the editor. +C-Client/MTEST: Testing program for debugging purposes. +Pine/PINE: The pine... + +In order to build SET DEF into the top directory (i.e. the one above PINE.DIR, +PICO.DIR, etc.) and then @VMSBUILD. It will rename the C-CLient directory and +then compile Pico, C-Client and Pine. + +Optional parameters to the VMSBUILD command: + +NETLIB - Use the Netlib library. It must be preloaded into [.NETLIB]NETLIB.OLB +MULTINET - Call Multinet's transport directly. + +If more than one option is used - separate them with coma and no spaces. + +There are a few warnnings during the link - ignore them... +On VAX we have to link the objects themselves and can't use libraries since the +linker/librarian lose the global variables; on AXP it is ok... + + +Using +===== + All the user needs is the PINE.EXE; Pine reads the mail from the user's +VMS/MAIL files and send outgoing mail either via mail routines using some +foreign protocol or via direct SMTP to some SMTP server (I preffer this +metod). You use the latter by defining SMTP-SERVER field with some host. +If you do not set it you must define PINE_MAIL_PROTOCOL to the prefix of +the foreign protocol used. For example, if you use SMTP% you have to define +it to SMTP. + The global PINE configuration file (if needed) is located at UTIL$:PINE.CONF; +if you want to recompile it with a different name then modify PINE/OS.H; + + +Why TcpIp communication is needed? +================================== + It is not really needed, but helps much. It is needed in three places: +1. Sending mail: You can either send mail using the xxx% mechanism by defning + PINE_MAIL_PROTOCOL; in this case no TcpIp is needed. + you can use another mechanism: Don't define the above but set some SMTP + server node name in PINE.CONF or .PINERC. In this case you need some + SMTP package. +2. PINE can read NEWS via the NNTP protocol which runs over TcpIp... +3. Remote nodes (usually PC) can access the IMAP daemon and PINE can access + remote IMAP servers using TcpIp. + + +Restrictions +============ +1. In order to not modify the source too much the handling of the special + INBOX folder was not modified. Hence, it always try to open the (empty) + INBOX folder instead of NEWMAIL. + It is possible to define in the system's wide PINE.CONF that + inbox-path=NEWMAIL. In this case NEWMAIL will be opened when PINE is + started. However, the user must not then switch to another folder as long + as NEWMAIL has items. +2. WASTEBASKET folder is not used. +3. .PINERC and .ADDRBOOK are fixed to the user's login directory and cannot + be defined to be elsewhere (the definition is ignored). +4-100. Probably exists and I forgot to mention :-) + + +IMAPD +===== + IMAPD of version 3.89 is available with the old Pine VMS port from +VMS.HUJI.AC.IL; the current version of IMAPD will be ported soon. + + +NETLIB +====== + NETLIB can be obtained from PUBLIC.TGV.COM:/MADISON/NETLIB. NETLIB supports +all the common TcpIp packages like Multinet, UCX, Fusion, Wollongong, etc. + + +Suggested PINE.CONF file. +========================= +here is the PINE.CONF file we use here: + +# Our fully-qualified machine name: +user-domain=vms.huji.ac.il + +# Where to connect to send outputgoing mail. +smtp-server=vms.huji.ac.il + +# Which viewer to see GIF/JPEG/etc. +image-viewer=xv + +# Which folder will be opened automatically when entering PINE. See note above. +inbox-path=NEWMAIL + + +Notes: +===== +1. Due to the readonly definition in the source files we use an external + #define to redefine it to something else. Due to that CTYPE.H fails, so + we use a private copy of it. diff --git a/contrib/ports/vms/vms_link.opt b/contrib/ports/vms/vms_link.opt new file mode 100644 index 00000000..0f454490 --- /dev/null +++ b/contrib/ports/vms/vms_link.opt @@ -0,0 +1 @@ +SYS$SHARE:VAXCRTL/SHARE diff --git a/contrib/ports/vms/vms_multinet_link.opt b/contrib/ports/vms/vms_multinet_link.opt new file mode 100644 index 00000000..5eb99104 --- /dev/null +++ b/contrib/ports/vms/vms_multinet_link.opt @@ -0,0 +1 @@ +MULTINET:MULTINET_SOCKET_LIBRARY/SHARE diff --git a/contrib/ports/vms/vms_netlib_link.opt b/contrib/ports/vms/vms_netlib_link.opt new file mode 100644 index 00000000..4bec72f1 --- /dev/null +++ b/contrib/ports/vms/vms_netlib_link.opt @@ -0,0 +1 @@ +netlib_shr/share diff --git a/contrib/ports/vms/vmsbuild.com b/contrib/ports/vms/vmsbuild.com new file mode 100644 index 00000000..e3f58ebc --- /dev/null +++ b/contrib/ports/vms/vmsbuild.com @@ -0,0 +1,31 @@ +$! VMSBUILD.COM - Calls the subdirectories VMSBUILD. +$! Can be called with options separated with commas and no spaces. The +$! options are: +$! +$! NETLIB - For linking with Netlib. NETLIB.OLB must be pre-loaded into +$! [.NETLIb]NETLIB.OLB +$! MULTINET - Used with Multinet transport (no NETLIB is used). +$! HEBREW - Build the Hebrew version. +$! +$! +$ IF F$EXISTS("[.IMAP.ANSI]C-CLIENT.DIR") THEN RENAME [.IMAP.ANSI]C-CLIENT.DIR []; +$ IF F$EXISTS("[.CONTRIB.VMS]VMSBUILD_CCLIENT.COM") THEN RENAME [.CONTRIB.VMS]VMSBUILD_CCLIENT.COM [.C-CLIENT]VMSBUILD.COM; +$ SET DEF [.PICO] +$@VMSBUILD 'P1' +$ SET DEF [-.C-CLIENT] +$@VMSBUILD 'P1' +$ SET DEF [-.PINE] +$@VMSBUILD 'P1' +$ SET DEF [-] +$! +$ TYPE SYS$INPUT: +Executables can be found in: + +PICO/PICO.EXE - The stand-alone editor (not needed). +C-CLIENT/MTEST.EXE - Interactive testing program (not needed). +C-CLIENT/IMAPD.EXE - the IMAP daemon. Should be copied elsewhere and defined + in the INETD.CONF file or equivalent. +PINE/PINE.EXE - What you waited for... + +$! +$ EXIT diff --git a/contrib/ports/vms/vmsbuild_cclient.com b/contrib/ports/vms/vmsbuild_cclient.com new file mode 100644 index 00000000..7d4c0a20 --- /dev/null +++ b/contrib/ports/vms/vmsbuild_cclient.com @@ -0,0 +1,103 @@ +$! Program: Operating-system dependent routines -- VMS version +$! +$! Author: Yehavi Bourvine, The Hebrew University of Jerusalem. +$! Internet: Yehavi@VMS.huji.ac.il +$! +$! Date: 2 August 1994 +$! Last Edited: 2 August 1994 +$! +$! Copyright 1994 by the University of Washington +$! +$! Permission to use, copy, modify, and distribute this software and its +$! documentation for any purpose and without fee is hereby granted, provided +$! that the above copyright notice appears in all copies and that both the +$! above copyright notice and this permission notice appear in supporting +$! documentation, and that the name of the University of Washington not be +$! used in advertising or publicity pertaining to distribution of the software +$! without specific, written prior permission. This software is made available +$! "as is", and +$! THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, +$! WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED +$! WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN +$! NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL, +$! INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +$! LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT +$! (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION +$! WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +$! +$! VMSBUILD.COM for C-CLIENT. +$ CREATE LINKAGE.H +$ DECK +extern DRIVER imapdriver, nntpdriver, vmsmaildriver; +$ EOD +$ CREATE LINKAGE.C +$ DECK + mail_link((DRIVER *)&imapdriver); + mail_link((DRIVER *)&nntpdriver); + mail_link((DRIVER *)&vmsmaildriver); +$ EOD +$! +$ DEFINE SYS SYS$LIBRARY: ! Normal .H location. +$ DEFINE NETINET SYS$LIBRARY: +$ DEFINE ARPA SYS$LIBRARY: +$! +$ COPY OS_VMS.H OSDEP.H; +$ COPY TCP_VMSN.C TCP_VMS.C; ! Default - no TcpIp support. +$! +$ CC_DEF = ",''P1'" +$ LINK_OPT = "" +$ IF P1 .EQS. "" THEN CC_DEF="" +$ IF F$LOCATE("MULTINET", P1) .LT. F$LENGTH(P1) +$ THEN +$ DEFINE SYS MULTINET_ROOT:[MULTINET.INCLUDE.SYS],sys$library +$ DEFINE NETINET MULTINET_ROOT:[MULTINET.INCLUDE.NETINET] +$ DEFINE ARPA MULTINET_ROOT:[MULTINET.INCLUDE.ARPA] +$ COPY TCP_VMSM.C TCP_VMS.C; ! Multinet support. +$ LINK_OPT = ",[-.CONTRIB.VMS]VMS_MULTINET_LINK/OPTION" +$ ENDIF +$ IF F$LOCATE("NETLIB", P1) .LT. F$LENGTH(P1) +$ THEN +$ LINK_OPT = ",[-.CONTRIB.VMS]VMS_NETLIB_LINK/OPTION" +$ COPY TCP_VMSL.C TCP_VMS.C; ! Netlib support. +$ ENDIF +$! +$ CC_PREF = "" +$ IF F$LOCATE("VAX", F$GETSYI("HW_NAME")) .EQS. F$LENGTH(F$GETSYI("HW_NAME")) +$ THEN +$ CC_PREF = "/PREFIX=(ALL,EXCEPT=(SOCKET,CONNECT,BIND,LISTEN,SOCKET_READ,SOCKET_WRITE,SOCKET_CLOSE,SELECT,ACCEPT,BCMP,BCOPY,BZERO,GETHOSTBYNAME," +$ CC_PREF = CC_PREF + "GETHOSTBYADDR,GETPEERNAME,GETDTABLESIZE,HTONS,HTONL,NTOHS,NTOHL,SEND,SENDTO,RECV,RECVFROM))" +$ CC_PREF = CC_PREF + "/STANDARD=VAXC" +$ ELSE +$ CC_PREF = "/INCLUDE=[]" +$ LINK_OPT = LINK_OPT + ",[-.CONTRIB.VMS]VMS_LINK/OPTION" +$ COPY SYS$LIBRARY:CTYPE.H *.*; +$ EDIT/EDT CTYPE.H +s/readonly// w +exit +$ ENDIF +$ SET VERIFY +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') OS_VMS +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') vms_mail +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') MAIL +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') SMTP +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') RFC822 +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') NNTP +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') nntpcvms +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') MISC +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') IMAP2 +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def', - + L_SET=0) SM_VMS +$! +$ CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') MTEST +$! CC/NOOPTIMIZE'CC_PREF'/define=("readonly"="ReadOnly"'cc_def') IMAPD +$! +$ LIBRARY/OBJECT/CREATE/INSERT C-CLIENT OS_VMS,vms_mail,MAIL,SMTP,RFC822,- + NNTP,nntpcvms,MISC,IMAP2,SM_VMS +$! +$ SET NOVERIFY +$ LINK MTEST,IMAP2,MAIL,MISC,NNTP,nntpcvms,OS_VMS,RFC822,SMTP,- + SM_VMS,VMS_MAIL,SYS$INPUT:/OPTION'LINK_OPT' +PSECT=_CTYPE_,NOWRT +$! LINK IMAPD,imapd_vms,IMAP2,MAIL,MISC,NNTP,nntpcvms,OS_VMS,RFC822,SMTP,- +$! SM_VMS,VMS_MAIL'LINK_OPT' +$ EXIT diff --git a/contrib/smime/README b/contrib/smime/README new file mode 100644 index 00000000..fc9c7895 --- /dev/null +++ b/contrib/smime/README @@ -0,0 +1,65 @@ +S/MIME capabilities in Pine + +Patch Author: Jonathan Paisley +Contact Info: paisleyj@dcs.gla.ac.uk +WWW: http://www.dcs.gla.ac.uk/~paisleyj/ + +This patch adds S/MIME functionality to Pine. It was +last tested thoroughly with Pine 4.33, and it has been updated +to work with the latest version (4.52). + +Check out the unix 'patch' command. An example of using +patch would be: + +$ cd ../.. # Change to top level directory (containing contrib, pine, etc) +$ patch -p1 < contrib/smime/pine-smime-151101.diff + +*** After patching, read pine/README.smime for more details. *** + +The patch will create the following files: + +pine/README.smime +pine/TODO.smime +pine/bss_so.c +pine/bss_so.h +pine/smime.c +pine/smime.h +pine/smkeys.c +pine/smkeys.h + +It will modify the following files: + +imap/src/c-client/mail.c +imap/src/c-client/mail.h +pine/filter.c +pine/init.c +pine/mailcmd.c +pine/mailpart.c +pine/mailview.c +pine/makefile.lnx +pine/makefile.so5 +pine/pine.h +pine/pine.hlp +pine/send.c + +Note that this patch has only been tested on Solaris and Linux (thus only +those makefiles have been patched). The changes that have been made to +the makefiles are as follows: + + Add these SSL variables (these were roughly lifted from imap/src/osdep/unix/Makefile) + + SSLDIR= $(HOME)/local/ssl + SSLCERTS= $(SSLDIR)/certs + SSLINCLUDE= $(SSLDIR)/include + SSLLIB= $(SSLDIR)/lib + + SSLCFLAGS= -I$(SSLINCLUDE) \ + -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" \ + -DSMIME + SSLLDFLAGS= -L$(SSLLIB) -lcrypto + + Add $(SSLLDFLAGS) to the LIBS variable. + Add $(SSLCFLAGS) to the CFLAGS variable. + Add bss_so.o smime.o smkeys.o to the OFILES= variable. + +Similar changes would have to be made to the makefile for other ports. diff --git a/contrib/smime/pine-smime-20030115.diff b/contrib/smime/pine-smime-20030115.diff new file mode 100644 index 00000000..5744ce0d --- /dev/null +++ b/contrib/smime/pine-smime-20030115.diff @@ -0,0 +1,3265 @@ +diff -Ncr pine4.53/imap/src/c-client/mail.c pine4.53-smime/imap/src/c-client/mail.c +*** pine4.53/imap/src/c-client/mail.c Thu Jan 2 17:28:42 2003 +--- pine4.53-smime/imap/src/c-client/mail.c Wed Jan 15 12:48:15 2003 +*************** +*** 5198,5203 **** +--- 5198,5207 ---- + + void mail_free_body_data (BODY *body) + { ++ /* cleanup body if requested by application */ ++ if (body->cleanup) ++ (*body->cleanup)(body); ++ + switch (body->type) { /* free contents */ + case TYPEMULTIPART: /* multiple part */ + mail_free_body_part (&body->nested.part); +diff -Ncr pine4.53/imap/src/c-client/mail.h pine4.53-smime/imap/src/c-client/mail.h +*** pine4.53/imap/src/c-client/mail.h Tue Jan 7 12:33:50 2003 +--- pine4.53-smime/imap/src/c-client/mail.h Wed Jan 15 12:48:15 2003 +*************** +*** 655,660 **** +--- 655,663 ---- + unsigned long bytes; /* size of text in octets */ + } size; + char *md5; /* MD5 checksum */ ++ ++ void *sparep; /* spare pointer reserved for main program */ ++ void (*cleanup)(BODY *); /* cleanup function */ + }; + + +diff -Ncr pine4.53/pine/README.smime pine4.53-smime/pine/README.smime +*** pine4.53/pine/README.smime Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/README.smime Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,129 ---- ++ Quick Start ++ =========== ++ ++ To enable S/MIME support, ensure the SSL related lines in the platform ++ makefile are uncommented (they're all next to one another). ++ ++ CA certificates are expected to be found in the OpenSSL 'cert' dir, in the ++ standard hashed format. ++ ++ User Configuration ++ ================== ++ ++ A directory '~/.pine-smime' must be created. Within this, a further ++ three directories are required: ++ ++ ~/.pine-smime/ ++ ca/ ++ private/ ++ public/ ++ ++ Other people's certificate files should be copied into the 'public' directory. ++ Your certificate file(s) should be copied into the 'private' directory. ++ Certificates for any additional trusted CAs should be put in the 'ca' directory. ++ ++ There are three extra configuration options: ++ ++ sign-default-on ++ encrypt-default-on ++ remember-smime-passphrase ++ ++ Certificates ++ ============ ++ ++ The certificate files specified above should have the following form: ++ ++ public certificates: user@emaildomain.crt ++ private keys: user@emaildomain.key ++ ++ Thus, a typical installation might look like this: ++ ++ ~/.pine-smime/ ++ ca/ ++ [additional trusted CAs here] ++ private/ ++ paisleyj@dcs.gla.ac.uk.crt ++ paisleyj@dcs.gla.ac.uk.key ++ public/ ++ myfriend@dcs.gla.ac.uk.crt ++ myotherfriend@dcs.gla.ac.uk.crt ++ ++ Implementation Details ++ ====================== ++ ++ Link with the OpenSSL crypto library for PKCS7 support. ++ Only tested on linux (slx) and solaris (so5). ++ ++ Added three extra source files (+headers): ++ ++ smime.c Main S/MIME support code ++ smkeys.c Very basic X509 key handling/storage (using the above dirs) ++ bss_so.c OpenSSL BIO using pine STORE_S objects ++ ++ Patches to existing pine sources: ++ ++ init.c ++ Add references to new configuration options. ++ ++ mailcmd.c ++ Add implementation of MC_DECRYPT command which prompts ++ the user for a passphrase if it's required. ++ ++ mailpart.c ++ Comment added to help me remember what I'd done. ++ ++ mailview.c ++ Added description of Decrypt menu option. ++ Make calls out to smime.c functions to handle the decryption. ++ This is done shortly after the BODY of a message is ++ obtained. ++ Added function to describe encrypted messages when they're ++ being displayed. ++ Added code to describe the special case of PKCS7 attachments. ++ ++ makefile.lnx ++ makefile.so5 ++ Added SSL variables etc. ++ ++ pine.h ++ Add enumerations for new configuration options and definition ++ of MC_DECRYPT command ++ Exported the prototype of pine_write_body_header, ++ pine_rfc822_output_body and pine_encode_body since they're ++ needed in smime.c. ++ ++ pine.hlp ++ Added help info for new configuration options. ++ ++ send.c ++ Added 'Encrypt' and 'Sign' menu options when sending email. ++ Make calls to smime.c functions to fiddle message on the ++ way out. ++ Extend pine_encode_body so it makes a few more checks ++ before adding a boundary. ++ ++ Basic method: ++ ++ Incoming ++ ++ Scan BODY of viewed message before it is formatted. If it contains ++ PKCS7 parts, decode them and attempt to decrypt/verify signatures. The ++ original BODY is fiddled in place and turned into a multipart body ++ with one subpart -- the decrypted data. This may consist of a multipart ++ with attachments, for example. ++ ++ This all depends on stashing a pointer to the decrypted data in ++ body->contents.text.data and relying on the fact that the mail_* routines ++ will use this data in preference to fetching it over the network. We ++ also depend on it not being garbage collected while the message is ++ being viewed! ++ ++ Outgoing ++ ++ smime.c pre-builds the message using pine_encode_body, pine_write_body_header ++ and pine_rfc822_output_body, encrypting/signing the resulting data. The ++ body that was going to be sent is then fiddled appropriately after ++ the PKCS7 objects have been built. ++ ++ paisleyj@dcs.gla.ac.uk ++ Mar 7 2001 +diff -Ncr pine4.53/pine/TODO.smime pine4.53-smime/pine/TODO.smime +*** pine4.53/pine/TODO.smime Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/TODO.smime Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,31 ---- ++ ++ Need to be able to view stored certificates to see details ++ (particularly the fingerprint for comparing over the phone, say) ++ --> proper key management system ++ ++ Add client private key and certificate request generation. ++ ++ Send certificate for CA along with certificate of signer. ++ ++ Verify recipient certificate before sending encrypted message. ++ ++ Verify certificates in general. ++ ++ Cache the result of pre-formatting the message during the send/encrypt/sign ++ phase rather than letting call_mailer re-format it all over again. ++ ++ Tidy up the use of global variables considerably. ++ ++ Intelligently pick a certificate for signing purposes based on the ++ From address rather than just picking the first one on the list. ++ ++ Figure out platform dependancies from using readdir() in smkeys.c ++ ++ Handle message/rfc822 sub-parts! ++ ++ Consider what happens with all our cached data. ++ ++ S/MIME info screen help. ++ ++ paisleyj@dcs.gla.ac.uk ++ Mar 7 2001 +diff -Ncr pine4.53/pine/bss_so.c pine4.53-smime/pine/bss_so.c +*** pine4.53/pine/bss_so.c Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/bss_so.c Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,184 ---- ++ #ifdef SMIME ++ ++ /* ++ bss_so.c ++ ++ Basic implementation of an OpenSSL BIO which is ++ backed by a pine STORE_S object ++ */ ++ ++ #include "headers.h" ++ ++ #include <stdio.h> ++ #include <errno.h> ++ #include <openssl/bio.h> ++ #include <openssl/err.h> ++ ++ static int bss_so_write(BIO *h, char *buf, int num); ++ static int bss_so_read(BIO *h, char *buf, int size); ++ static int bss_so_puts(BIO *h, char *str); ++ static int bss_so_gets(BIO *h, char *str, int size); ++ static long bss_so_ctrl(BIO *h, int cmd, long arg1, char *arg2); ++ static int bss_so_new(BIO *h); ++ static int bss_so_free(BIO *data); ++ static BIO_METHOD methods_sop = ++ { ++ BIO_TYPE_MEM, ++ "Storage Object", ++ bss_so_write, ++ bss_so_read, ++ bss_so_puts, ++ bss_so_gets, ++ bss_so_ctrl, ++ bss_so_new, ++ bss_so_free, ++ NULL, ++ }; ++ ++ BIO *BIO_new_so(STORE_S *so) ++ { ++ BIO *ret; ++ ++ if ((ret = BIO_new(&methods_sop)) == NULL) ++ return (NULL); ++ ++ BIO_set_fp(ret, (FILE*) so, 0); ++ return (ret); ++ } ++ ++ ++ static int bss_so_new(BIO *bi) ++ { ++ bi->init = 0; ++ bi->num = 0; ++ bi->ptr = NULL; ++ return (1); ++ } ++ ++ static int bss_so_free(BIO *a) ++ { ++ if (a == NULL) return (0); ++ if (a->shutdown) { ++ if ((a->init) && (a->ptr != NULL)) { ++ a->ptr = NULL; ++ } ++ a->init = 0; ++ } ++ return (1); ++ } ++ ++ static int bss_so_read(BIO *b, char *out, int outl) ++ { ++ int ret = 0; ++ STORE_S *so = (STORE_S*) b->ptr; ++ ++ if (b->init && (out != NULL)) { ++ ++ while (ret < outl) { ++ if (!so->readc((unsigned char *)out, so)) ++ break; ++ out++; ++ ret++; ++ } ++ ++ } ++ return (ret); ++ } ++ ++ static int bss_so_write(BIO *b, char *in, int inl) ++ { ++ int ret = 0; ++ ++ if (b->init && (in != NULL)) { ++ if (so_nputs((STORE_S *)b->ptr, in, inl)) ++ ret = inl; ++ ++ } ++ return (ret); ++ } ++ ++ static long bss_so_ctrl(BIO *b, int cmd, long num, char *ptr) ++ { ++ long ret = 1; ++ STORE_S *so = (STORE_S *)b->ptr; ++ FILE **fpp; ++ char p[4]; ++ ++ switch (cmd) { ++ case BIO_C_FILE_SEEK: ++ case BIO_CTRL_RESET: ++ ret = so_seek(so, num, 0); ++ break; ++ case BIO_CTRL_EOF: ++ ret = 0; ++ break; ++ case BIO_C_FILE_TELL: ++ case BIO_CTRL_INFO: ++ ret = 0; ++ break; ++ case BIO_C_SET_FILE_PTR: ++ bss_so_free(b); ++ b->shutdown = (int)num & BIO_CLOSE; ++ b->ptr = (char *)ptr; ++ b->init = 1; ++ break; ++ case BIO_C_SET_FILENAME: ++ ret = 0; ++ break; ++ case BIO_C_GET_FILE_PTR: ++ if (ptr != NULL) { ++ fpp = (FILE **)ptr; ++ *fpp = (FILE *)NULL; ++ } ++ break; ++ case BIO_CTRL_GET_CLOSE: ++ ret = (long)b->shutdown; ++ break; ++ case BIO_CTRL_SET_CLOSE: ++ b->shutdown = (int)num; ++ break; ++ case BIO_CTRL_FLUSH: ++ break; ++ case BIO_CTRL_DUP: ++ ret = 1; ++ break; ++ ++ case BIO_CTRL_WPENDING: ++ case BIO_CTRL_PENDING: ++ case BIO_CTRL_PUSH: ++ case BIO_CTRL_POP: ++ default: ++ ret = 0; ++ break; ++ } ++ return (ret); ++ } ++ ++ static int bss_so_gets(BIO *bp, char *buf, int size) ++ { ++ int ret = 0; ++ char *b = buf; ++ char *bend = buf + size - 1; ++ STORE_S *so = (STORE_S*) bp->ptr; ++ ++ do { ++ if (!so->readc((unsigned char *)b, so)) ++ break; ++ b++; ++ } while (b < bend && b[ -1] != '\n'); ++ ++ *b = 0; ++ ++ ret = b - buf; ++ return (ret); ++ } ++ ++ static int bss_so_puts(BIO *bp, char *str) ++ { ++ STORE_S *so = (STORE_S*) bp->ptr; ++ ++ return so->puts(so, str); ++ } ++ ++ ++ #endif /* SMIME */ +diff -Ncr pine4.53/pine/bss_so.h pine4.53-smime/pine/bss_so.h +*** pine4.53/pine/bss_so.h Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/bss_so.h Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1 ---- ++ BIO *BIO_new_so(STORE_S *so); +diff -Ncr pine4.53/pine/filter.c pine4.53-smime/pine/filter.c +*** pine4.53/pine/filter.c Wed Jan 8 12:13:50 2003 +--- pine4.53-smime/pine/filter.c Wed Jan 15 12:48:15 2003 +*************** +*** 903,909 **** + + + /* get a character from a file */ +! /* assumes gf_out struct is filled in */ + int + gf_freadc(c) + unsigned char *c; +--- 903,909 ---- + + + /* get a character from a file */ +! /* assumes gf_in struct is filled in */ + int + gf_freadc(c) + unsigned char *c; +*************** +*** 938,944 **** + + + /* get a character from a string, return nonzero if things OK */ +! /* assumes gf_out struct is filled in */ + int + gf_sreadc(c) + unsigned char *c; +--- 938,944 ---- + + + /* get a character from a string, return nonzero if things OK */ +! /* assumes gf_in struct is filled in */ + int + gf_sreadc(c) + unsigned char *c; +diff -Ncr pine4.53/pine/init.c pine4.53-smime/pine/init.c +*** pine4.53/pine/init.c Mon Jan 6 19:39:16 2003 +--- pine4.53-smime/pine/init.c Wed Jan 15 12:48:15 2003 +*************** +*** 2445,2451 **** + F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND}, + {"use-sender-not-x-sender", + F_USE_SENDER_NOT_X, h_config_use_sender_not_x, PREF_SEND}, +! + /* Folder */ + {"combined-subdirectory-display", + F_CMBND_SUBDIR_DISP, h_config_combined_subdir_display, PREF_FLDR}, +--- 2445,2458 ---- + F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND}, + {"use-sender-not-x-sender", + F_USE_SENDER_NOT_X, h_config_use_sender_not_x, PREF_SEND}, +! #ifdef SMIME +! {"sign-default-on", +! F_SIGN_DEFAULT_ON, h_config_sign_default_on, PREF_SEND}, +! {"encrypt-default-on", +! F_ENCRYPT_DEFAULT_ON, h_config_encrypt_default_on, PREF_SEND}, +! {"remember-smime-passphrase", +! F_REMEMBER_SMIME_PASSPHRASE, h_config_remember_smime_passphrase, PREF_SEND}, +! #endif + /* Folder */ + {"combined-subdirectory-display", + F_CMBND_SUBDIR_DISP, h_config_combined_subdir_display, PREF_FLDR}, +diff -Ncr pine4.53/pine/mailcmd.c pine4.53-smime/pine/mailcmd.c +*** pine4.53/pine/mailcmd.c Fri Jan 10 15:29:29 2003 +--- pine4.53-smime/pine/mailcmd.c Wed Jan 15 12:48:15 2003 +*************** +*** 53,58 **** +--- 53,59 ---- + #include "headers.h" + #include "../c-client/imap4r1.h" + ++ #include "smime.h" + + /* + * Internal Prototypes +*************** +*** 1439,1444 **** +--- 1440,1458 ---- + break; + + ++ #ifdef SMIME ++ /*------- Try to decrypt message -----------*/ ++ case MC_DECRYPT: ++ if (g_need_passphrase) ++ get_passphrase(); ++ a_changed = TRUE; ++ break; ++ ++ case MC_SECURITY: ++ state->next_screen = smime_info_screen; ++ break; ++ #endif ++ + /*------- Bounce -----------*/ + case MC_BOUNCE : + cmd_bounce(state, msgmap, 0); +diff -Ncr pine4.53/pine/mailpart.c pine4.53-smime/pine/mailpart.c +*** pine4.53/pine/mailpart.c Wed Nov 27 15:22:54 2002 +--- pine4.53-smime/pine/mailpart.c Wed Jan 15 12:48:15 2003 +*************** +*** 4534,4539 **** +--- 4534,4545 ---- + frd->flags = flags; + frd->size = size; + frd->readc = fetch_readc; ++ ++ /* The call to imap_cache below will return true in the case where ++ we've already stashed fake data in the content of the part. ++ This happens when an S/MIME message is decrypted. ++ */ ++ + if(modern_imap_stream(stream) + && !imap_cache(stream, msgno, section, NULL, NULL) + && size > INIT_FETCH_CHUNK +diff -Ncr pine4.53/pine/mailview.c pine4.53-smime/pine/mailview.c +*** pine4.53/pine/mailview.c Tue Jan 7 16:17:00 2003 +--- pine4.53-smime/pine/mailview.c Wed Jan 15 12:48:15 2003 +*************** +*** 47,52 **** +--- 47,53 ---- + + + #include "headers.h" ++ #include "smime.h" + + + /*---------------------------------------------------------------------- +*************** +*** 207,214 **** +--- 208,220 ---- + + HELP_MENU, + OTHER_MENU, ++ #ifdef SMIME ++ {"^D","Decrypt", {MC_DECRYPT,1,{ctrl('d')},KS_NONE}}, ++ {"^E","Security", {MC_SECURITY,1,{ctrl('e')},KS_NONE}}, ++ #else + NULL_MENU, + NULL_MENU, ++ #endif + RCOMPOSE_MENU, + NULL_MENU, + NULL_MENU, +*************** +*** 227,232 **** +--- 233,240 ---- + #define BOUNCE_KEY 33 + #define FLAG_KEY 34 + #define VIEW_PIPE_KEY 35 ++ #define DECRYPT_KEY (VIEW_PIPE_KEY + 3) ++ #define SECURITY_KEY (DECRYPT_KEY + 1) + + static struct key simple_text_keys[] = + {HELP_MENU, +*************** +*** 432,437 **** +--- 440,447 ---- + else + ps->unseen_in_view = !mc->seen; + ++ flags = 0; ++ + #if defined(DOS) && !defined(WIN32) + /* + * Handle big text for DOS here. +*************** +*** 459,465 **** + ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps) + + SCROLL_LINES_BELOW(ps))); + +! flags = FM_DISPLAY; + if((last_message_viewed != mn_get_cur(ps->msgmap) + || last_was_full_header == 1)) + flags |= FM_NEW_MESS; +--- 469,475 ---- + ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps) + + SCROLL_LINES_BELOW(ps))); + +! flags |= FM_DISPLAY; + if((last_message_viewed != mn_get_cur(ps->msgmap) + || last_was_full_header == 1)) + flags |= FM_NEW_MESS; +*************** +*** 467,472 **** +--- 477,488 ---- + if(offset) /* no pre-paint during resize */ + view_writec_killbuf(); + ++ #ifdef SMIME ++ /* Attempt to handle S/MIME bodies */ ++ if (fiddle_smime_message(body,raw_msgno,(flags&FM_NEW_MESS)!=0)) ++ flags |= FM_NEW_MESS; /* body was changed, force a reload */ ++ #endif ++ + #ifdef _WINDOWS + mswin_noscrollupdate(1); + #endif +*************** +*** 541,546 **** +--- 557,567 ---- + + if(F_OFF(F_ENABLE_FULL_HDR, ps_global)) + clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap); ++ ++ #ifdef SMIME ++ if (!g_need_passphrase) ++ clrbitn(DECRYPT_KEY, scrollargs.keys.bitmap); ++ #endif + + if(!handles){ + /* +*************** +*** 754,760 **** +--- 775,821 ---- + } + + ++ #ifdef SMIME ++ /*---------------------------------------------------------------------- ++ Add descriptive lines to the top of a message being formatted ++ that describe the status of any S/MIME enclosures that ++ have been encountered. ++ ++ Args: body -- top-level body of the message being described ++ pc -- output function for writing to the message display ++ ++ ----*/ ++ static int describe_smime_bodies(BODY *body,gf_io_t pc) ++ { ++ PART *part; ++ int result = 0; ++ ++ if (!body) ++ return result; ++ ++ if (body->type == TYPEMULTIPART) { ++ ++ if (body->subtype && strucmp(body->subtype,"x-pkcs7-enclosure")==0) { ++ ++ if (body->description) { ++ format_editorial(body->description,ps_global->ttyo->screen_cols,pc); ++ gf_puts(NEWLINE,pc); ++ result = 1; ++ } + ++ for (part=body->nested.part; part; part=part->next) { ++ result |= describe_smime_bodies(&(part->body),pc); ++ } ++ ++ } ++ } else if (body->type == TYPEMESSAGE && ++ body->subtype && strucmp(body->subtype, "rfc822")==0) { ++ result |= describe_smime_bodies(body->nested.msg->body,pc); ++ } ++ ++ return result; ++ } ++ #endif + + /*---------------------------------------------------------------------- + Add lines to the attachments structure +*************** +*** 1677,1682 **** +--- 1738,1751 ---- + + show_parts = 0; + ++ #ifdef SMIME ++ if (flgs & FM_DISPLAY) { ++ if (describe_smime_bodies(body,pc)) { ++ gf_puts(NEWLINE, pc); ++ } ++ } ++ #endif ++ + /*======== Now loop through formatting all the parts =======*/ + for(a = ps_global->atmts; a->description != NULL; a++) { + +*************** +*** 6150,6155 **** +--- 6219,6236 ---- + + t = &tmp_20k_buf[strlen(tmp_20k_buf)]; + ++ #ifdef SMIME ++ if (is_pkcs7_body(body) && type!=3) { /* if smime and not attempting print */ ++ ++ sstrcpy(&t,"\015\012"); ++ ++ sstrcpy(&t, ++ "This part is a PKCS7 S/MIME enclosure. " ++ "You may be able to view it by entering the correct passphrase " ++ "with the \"^D\" command. Press \"^E\" for more information."); ++ ++ } else ++ #endif + if(type){ + sstrcpy(&t, "\015\012"); + switch(type) { +diff -Ncr pine4.53/pine/makefile.lnx pine4.53-smime/pine/makefile.lnx +*** pine4.53/pine/makefile.lnx Tue Sep 10 14:34:39 2002 +--- pine4.53-smime/pine/makefile.lnx Wed Jan 15 12:48:15 2003 +*************** +*** 60,79 **** + LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ + mailview.o other.o pine.o strings.o takeaddr.o + + STDLIBS= -lncurses + LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a + LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ + `cat $(CCLIENTDIR)/LDFLAGS` + + STDCFLAGS= -DLNX -DSYSTYPE=\"LNX\" -DMOUSE + CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ + $(STDCFLAGS) + + OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ + mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ + reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ +! os.o + + HFILES= headers.h os.h pine.h context.h helptext.h \ + $(PICODIR)/headers.h $(PICODIR)/estruct.h \ +--- 60,92 ---- + LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ + mailview.o other.o pine.o strings.o takeaddr.o + ++ SSLDIR= $(HOME)/local/ssl ++ SSLCERTS= $(SSLDIR)/certs ++ SSLINCLUDE= $(SSLDIR)/include ++ SSLLIB= $(SSLDIR)/lib ++ ++ SSLCFLAGS= -I$(SSLINCLUDE) \ ++ -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" \ ++ -DSMIME ++ SSLLDFLAGS= -L$(SSLLIB) -lcrypto ++ ++ + STDLIBS= -lncurses + LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a + LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ ++ $(SSLLDFLAGS) \ + `cat $(CCLIENTDIR)/LDFLAGS` + + STDCFLAGS= -DLNX -DSYSTYPE=\"LNX\" -DMOUSE + CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ ++ $(SSLCFLAGS) \ + $(STDCFLAGS) + + OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ + mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ + reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ +! os.o bss_so.o smime.o smkeys.o + + HFILES= headers.h os.h pine.h context.h helptext.h \ + $(PICODIR)/headers.h $(PICODIR)/estruct.h \ +*************** +*** 135,137 **** +--- 148,154 ---- + osdep/sendmail osdep/execview \ + osdep/postreap.wtp osdep/os-lnx.ic + cd osdep; $(MAKE) includer os-lnx.c; cd .. ++ ++ jon.o: jon.c ++ $(CC) $(CFLAGS) -Wall -Wstrict-prototypes -c $< -o $@ ++ +diff -Ncr pine4.53/pine/makefile.so5 pine4.53-smime/pine/makefile.so5 +*** pine4.53/pine/makefile.so5 Tue Oct 23 15:24:51 2001 +--- pine4.53-smime/pine/makefile.so5 Wed Jan 15 12:48:15 2003 +*************** +*** 62,67 **** +--- 62,78 ---- + LDAPOFILES= addrbook.o adrbkcmd.o args.o bldaddr.o init.o \ + mailview.o other.o pine.o strings.o takeaddr.o + ++ SSLDIR= $(HOME)/local/ssl ++ SSLCERTS= $(SSLDIR)/certs ++ SSLINCLUDE= $(SSLDIR)/include ++ SSLLIB= $(SSLDIR)/lib ++ ++ SSLCFLAGS= -I$(SSLINCLUDE) \ ++ -DSSL_CERT_DIRECTORY=\"$(SSLCERTS)\" \ ++ -DSMIME ++ SSLLDFLAGS= -L$(SSLLIB) -lcrypto ++ ++ + # LDCC= /usr/bin/cc + # If you don't have /usr/bin/cc (our Solaris 2.2 doesn't seem to have it, + # it only has /usr/ucb/cc) then change LDCC to the following line and +*************** +*** 72,88 **** + STDLIBS= -ltermlib + LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a + LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ + `cat $(CCLIENTDIR)/LDFLAGS` + + STDCFLAGS= -Dconst= -DSV4 -DSYSTYPE=\"SOL\" -DMOUSE + CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ + $(STDCFLAGS) + + OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ + mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ + reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ +! os.o + + HFILES= headers.h os.h pine.h context.h helptext.h \ + $(PICODIR)/headers.h $(PICODIR)/estruct.h \ +--- 83,101 ---- + STDLIBS= -ltermlib + LOCLIBS= $(PICODIR)/libpico.a $(CCLIENTDIR)/c-client.a + LIBS= $(LOCLIBS) $(LDAPLIBS) $(STDLIBS) \ ++ $(SSLLDFLAGS) \ + `cat $(CCLIENTDIR)/LDFLAGS` + + STDCFLAGS= -Dconst= -DSV4 -DSYSTYPE=\"SOL\" -DMOUSE + CFLAGS= $(OPTIMIZE) $(PROFILE) $(DEBUG) $(EXTRACFLAGS) $(LDAPCFLAGS) \ ++ $(SSLCFLAGS) \ + $(STDCFLAGS) + + OFILES= addrbook.o adrbkcmd.o adrbklib.o args.o bldaddr.o context.o filter.o \ + folder.o help.o helptext.o imap.o init.o mailcap.o mailcmd.o \ + mailindx.o mailpart.o mailview.o newmail.o other.o pine.o \ + reply.o screen.o send.o signals.o status.o strings.o takeaddr.o \ +! os.o bss_so.o smime.o smkeys.o + + HFILES= headers.h os.h pine.h context.h helptext.h \ + $(PICODIR)/headers.h $(PICODIR)/estruct.h \ +diff -Ncr pine4.53/pine/pine.h pine4.53-smime/pine/pine.h +*** pine4.53/pine/pine.h Fri Jan 10 15:25:55 2003 +--- pine4.53-smime/pine/pine.h Wed Jan 15 12:48:15 2003 +*************** +*** 1134,1139 **** +--- 1134,1144 ---- + F_DISABLE_SHARED_NAMESPACES, + F_EXPOSE_HIDDEN_CONFIG, + F_ALT_COMPOSE_MENU, ++ #ifdef SMIME ++ F_SIGN_DEFAULT_ON, ++ F_ENCRYPT_DEFAULT_ON, ++ F_REMEMBER_SMIME_PASSPHRASE, ++ #endif + F_ALWAYS_SPELL_CHECK, + F_QUELL_TIMEZONE, + F_COLOR_LINE_IMPORTANT, +*************** +*** 2031,2036 **** +--- 2036,2043 ---- + } cmdline_val; /* user typed as cmdline arg */ + }; + ++ #define MC_DECRYPT 800 ++ #define MC_SECURITY 801 + + + /* +*************** +*** 4368,4373 **** +--- 4375,4383 ---- + char *pine_send_status PROTO((int, char *, char *, int *)); + void phone_home PROTO((char *)); + void pine_free_body PROTO((BODY **)); ++ int pine_write_body_header PROTO((BODY *, soutr_t, TCPSTREAM *)); ++ long pine_rfc822_output_body PROTO((BODY *,soutr_t,TCPSTREAM *)); ++ void pine_encode_body PROTO((BODY *)); + void simple_header_parse PROTO((char *, char **, char **)); + int valid_subject PROTO((char *, char **, char **,BUILDER_ARG *,int *)); + long new_mail_for_pico PROTO((int, int)); +diff -Ncr pine4.53/pine/pine.hlp pine4.53-smime/pine/pine.hlp +*** pine4.53/pine/pine.hlp Wed Jan 15 11:55:09 2003 +--- pine4.53-smime/pine/pine.hlp Wed Jan 15 12:48:15 2003 +*************** +*** 24338,24340 **** +--- 24338,24347 ---- + ========== h_select_by_smaller_size ========== + Enter a number or ^C to cancel. All messages less than this many characters + in size will be selected. Examples: 2176, 1.53K (1530), or 3M (3000000). ++ ========== h_config_sign_default_on ========== ++ If enabled, the 'Sign' option will default to on when sending messages. ++ ========== h_config_encrypt_default_on ========== ++ If enabled, the 'Encrypt' option will default to on when sending messages. ++ ========== h_config_remember_smime_passphrase ========== ++ If enabled, you will only have to enter your passphrase for your private key ++ once during a pine session. +diff -Ncr pine4.53/pine/send.c pine4.53-smime/pine/send.c +*** pine4.53/pine/send.c Tue Jan 14 13:22:59 2003 +--- pine4.53-smime/pine/send.c Wed Jan 15 12:48:15 2003 +*************** +*** 50,55 **** +--- 50,56 ---- + #include "../c-client/smtp.h" + #include "../c-client/nntp.h" + ++ #include "smime.h" + + #ifndef TCPSTREAM + #define TCPSTREAM void +*************** +*** 5490,5495 **** +--- 5491,5513 ---- + opts[i++].label = ""; + } + ++ #ifdef SMIME ++ { ++ opts[i].ch = 'e'; ++ opts[i].rval = 'e'; ++ opts[i].name = "E"; ++ opts[i++].label = "Encrypt"; ++ ++ opts[i].ch = 'g'; ++ opts[i].rval = 'g'; ++ opts[i].name = "G"; ++ opts[i++].label = "Sign"; ++ ++ g_do_encrypt = F_ON(F_ENCRYPT_DEFAULT_ON,ps_global); ++ g_do_sign = F_ON(F_SIGN_DEFAULT_ON,ps_global); ++ } ++ #endif ++ + opts[i].ch = -1; + no_help = (i >= 12); + +*************** +*** 5574,5579 **** +--- 5592,5627 ---- + sstrcpy(&optp, dsn_string); + } + ++ #ifdef SMIME ++ if (g_do_encrypt) { ++ if(!lparen){ ++ *optp++ = ' '; ++ *optp++ = '('; ++ lparen++; ++ } ++ else{ ++ *optp++ = ','; ++ *optp++ = ' '; ++ } ++ ++ sstrcpy(&optp, "Encrypted"); ++ } ++ ++ if (g_do_sign) { ++ if(!lparen){ ++ *optp++ = ' '; ++ *optp++ = '('; ++ lparen++; ++ } ++ else{ ++ *optp++ = ','; ++ *optp++ = ' '; ++ } ++ ++ sstrcpy(&optp, "Signed"); ++ } ++ #endif ++ + if(lparen) + *optp++ = ')'; + +*************** +*** 5675,5680 **** +--- 5723,5734 ---- + * body on failure. + */ + dsn_requested = (DSN_SHOW | DSN_SUCCESS | DSN_DELAY | DSN_FULL); ++ #ifdef SMIME ++ } else if (rv=='e') { ++ g_do_encrypt = !g_do_encrypt; ++ } else if (rv=='g') { ++ g_do_sign = !g_do_sign; ++ #endif + } + + sprintf(dsn_string, "DSN requested[%s%s%s%s]", +*************** +*** 6418,6423 **** +--- 6472,6478 ---- + char *verbose_file = NULL; + BODY *bp = NULL; + PINEFIELD *pf; ++ BODY *origBody = body; + + #define MAX_ADDR_ERROR 2 /* Only display 2 address errors */ + +*************** +*** 6434,6439 **** +--- 6489,6518 ---- + return(0); + } + ++ ++ #ifdef SMIME ++ if (g_do_encrypt || g_do_sign) { ++ int result; ++ ++ STORE_S *so = lmc.so; ++ lmc.so = NULL; ++ ++ result = 1; ++ ++ if (g_do_encrypt) ++ result = encrypt_outgoing_message(header,&body); ++ ++ /* need to free new body from encrypt if sign fails? */ ++ if (result && g_do_sign) ++ result = sign_outgoing_message(header,&body,g_do_encrypt); ++ ++ lmc.so = so; ++ ++ if (!result) ++ return 0; ++ } ++ #endif ++ + /* set up counts and such to keep track sent percentage */ + send_bytes_sent = 0; + gf_filter_init(); /* zero piped byte count, 'n */ +*************** +*** 6742,6747 **** +--- 6821,6844 ---- + mail_free_envelope(&fake_env); + + done: ++ ++ #ifdef SMIME ++ /* Free replacement encrypted body */ ++ if (body != origBody) { ++ ++ if (body->type==TYPEMULTIPART) { ++ /* Just get rid of first part, it's actually origBody */ ++ void *x = body->nested.part; ++ ++ body->nested.part = body->nested.part->next; ++ ++ fs_give(&x); ++ } ++ ++ pine_free_body(&body); ++ } ++ #endif ++ + if(we_cancel) + cancel_busy_alarm(0); + +*************** +*** 8721,8733 **** + dprint(4, (debugfile, "-- pine_encode_body: %d\n", body ? body->type : 0)); + if (body) switch (body->type) { + case TYPEMULTIPART: /* multi-part */ +! if (!body->parameter) { /* cookie not set up yet? */ + char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ + sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0), + getpid ()); +! body->parameter = mail_newbody_parameter (); +! body->parameter->attribute = cpystr ("BOUNDARY"); +! body->parameter->value = cpystr (tmp); + } + part = body->nested.part; /* encode body parts */ + do pine_encode_body (&part->body); +--- 8818,8834 ---- + dprint(4, (debugfile, "-- pine_encode_body: %d\n", body ? body->type : 0)); + if (body) switch (body->type) { + case TYPEMULTIPART: /* multi-part */ +! if (!body->parameter || strucmp(body->parameter->attribute,"BOUNDARY")!=0) { /* cookie not set up yet? */ + char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/ ++ PARAMETER *param; ++ + sprintf (tmp,"%ld-%ld-%ld=:%ld",gethostid (),random (),time (0), + getpid ()); +! param = mail_newbody_parameter (); +! param->next = body->parameter; +! param->attribute = cpystr ("BOUNDARY"); +! param->value = cpystr (tmp); +! body->parameter = param; + } + part = body->nested.part; /* encode body parts */ + do pine_encode_body (&part->body); +diff -Ncr pine4.53/pine/smime.c pine4.53-smime/pine/smime.c +*** pine4.53/pine/smime.c Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/smime.c Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,1776 ---- ++ /* ++ File: smime.c ++ Author: paisleyj@dcs.gla.ac.uk ++ Date: 01/2001 ++ ++ Description: ++ This file contains all the low-level functions ++ required for dealing with S/MIME objects. ++ ++ References are made to the functions in this file ++ from the following locations: ++ ++ mailview.c:part_desc() -> is_pkcs7_body() ++ send.c:call_mailer() -> encrypt_outgoing_message() ++ send.c:call_mailer() -> sign_outgoing_message() ++ mailcmd.c:process_cmd() -> get_passphrase() ++ mailcmd.c:process_cmd() -> smime_info_screen() ++ */ ++ ++ #ifdef SMIME ++ ++ #include "headers.h" ++ ++ #include <stdio.h> ++ #include <stdlib.h> ++ #include <string.h> ++ #include <time.h> ++ ++ #include <openssl/err.h> ++ #include <openssl/objects.h> ++ #include <openssl/evp.h> ++ ++ #include <openssl/x509.h> ++ #include <openssl/pkcs7.h> ++ #include <openssl/pem.h> ++ #include <openssl/rand.h> ++ ++ #include "bss_so.h" ++ #include "smkeys.h" ++ #include "smime.h" ++ ++ #define PINE_SMIME_DIRNAME ".pine-smime" ++ ++ /* Set true if loading a key failed due to lack of passphrase. ++ Queried in mailcmd.c:process_cmd() before calling get_passphrase() ++ */ ++ int g_need_passphrase = 0; ++ /* User has entered a passphrase */ ++ static int s_entered_passphrase = 0; ++ /* Storage for the entered passphrase */ ++ static char s_passphrase[80]; ++ static char *s_passphrase_emailaddr; ++ ++ /* Set true if encrypting/signing (respectively) ++ Referenced from send.c:call_mailer() and send.c:send_exit_for_pico ++ */ ++ int g_do_encrypt; ++ int g_do_sign; ++ ++ /* Full pathname to ~/.pine-smime */ ++ static char *g_pine_smime_dir; ++ ++ static BIO *bio_err; ++ ++ /* Linked list of PERSONAL_CERT objects */ ++ static PERSONAL_CERT *s_personal_certs; ++ ++ static X509_STORE *s_cert_store; ++ ++ /* State management for randomness functions below */ ++ static int seeded = 0; ++ static int egdsocket = 0; ++ ++ /* Forget any cached private keys */ ++ static void forget_private_keys() ++ { ++ PERSONAL_CERT *pcert; ++ ++ for (pcert=s_personal_certs; pcert; pcert=pcert->next) { ++ ++ if (pcert->key) { ++ EVP_PKEY_free(pcert->key); ++ pcert->key = NULL; ++ } ++ } ++ } ++ ++ /* taken from openssl/apps/app_rand.c */ ++ static int app_RAND_load_file(const char *file, BIO *bio_e, int dont_warn) ++ { ++ int consider_randfile = (file == NULL); ++ char buffer[200]; ++ ++ if (file == NULL) ++ file = RAND_file_name(buffer, sizeof buffer); ++ else if (RAND_egd(file) > 0) ++ { ++ /* we try if the given filename is an EGD socket. ++ if it is, we don't write anything back to the file. */ ++ egdsocket = 1; ++ return 1; ++ } ++ if (file == NULL || !RAND_load_file(file, -1)) ++ { ++ if (RAND_status() == 0 && !dont_warn) ++ { ++ BIO_printf(bio_e,"unable to load 'random state'\n"); ++ BIO_printf(bio_e,"This means that the random number generator has not been seeded\n"); ++ BIO_printf(bio_e,"with much random data.\n"); ++ if (consider_randfile) /* explanation does not apply when a file is explicitly named */ ++ { ++ BIO_printf(bio_e,"Consider setting the RANDFILE environment variable to point at a file that\n"); ++ BIO_printf(bio_e,"'random' data can be kept in (the file will be overwritten).\n"); ++ } ++ } ++ return 0; ++ } ++ seeded = 1; ++ return 1; ++ } ++ ++ /* copied and fiddled from pine/imap/src/osdep/unix/auth_ssl.c */ ++ static void openssl_extra_randomness(void) ++ { ++ #if !defined(WIN32) ++ int fd; ++ unsigned long i; ++ char tmp[MAXPATH]; ++ struct stat sbuf; ++ /* if system doesn't have /dev/urandom */ ++ if (stat ("/dev/urandom",&sbuf)) { ++ if ((fd = open (tmpnam (tmp),O_WRONLY|O_CREAT,0600)) < 0) ++ i = (unsigned long) tmp; ++ else { ++ unlink (tmp); /* don't need the file */ ++ fstat (fd,&sbuf); /* get information about the file */ ++ i = sbuf.st_ino; /* remember its inode */ ++ close (fd); /* or its descriptor */ ++ } ++ /* not great but it'll have to do */ ++ sprintf (tmp + strlen (tmp),"%.80s%lx%lx%lx", ++ tcp_serverhost (),i, ++ (unsigned long) (time (0) ^ gethostid ()), ++ (unsigned long) getpid ()); ++ RAND_seed (tmp,strlen (tmp)); ++ } ++ #endif ++ } ++ ++ /* taken from openssl/apps/app_rand.c */ ++ static int app_RAND_write_file(const char *file, BIO *bio_e) ++ { ++ char buffer[200]; ++ ++ if (egdsocket || !seeded) ++ /* If we did not manage to read the seed file, ++ * we should not write a low-entropy seed file back -- ++ * it would suppress a crucial warning the next time ++ * we want to use it. */ ++ return 0; ++ ++ if (file == NULL) ++ file = RAND_file_name(buffer, sizeof buffer); ++ if (file == NULL || !RAND_write_file(file)) ++ { ++ BIO_printf(bio_e,"unable to write 'random state'\n"); ++ return 0; ++ } ++ return 1; ++ } ++ ++ /* Installed as an atexit() handler to save the random data */ ++ static void openssl_deinit(void) ++ { ++ app_RAND_write_file(NULL, bio_err); ++ } ++ ++ /* Initialise openssl stuff if needed */ ++ static void openssl_init(void) ++ { ++ static int inited = 0; ++ ++ if (!inited) { ++ ++ char *p; ++ char buf[MAXPATH]; ++ ++ /* Find where that .pine-smime thing is */ ++ /* Perhaps we should just use the user's home directory as a start? */ ++ p = last_cmpnt(ps_global->pinerc); ++ buf[0] = '\0'; ++ if(p != NULL) { ++ strncpy(buf, ps_global->pinerc, min(p - ps_global->pinerc, sizeof(buf)-1)); ++ buf[min(p - ps_global->pinerc, sizeof(buf)-1)] = '\0'; ++ } ++ ++ strncat(buf, PINE_SMIME_DIRNAME, sizeof(buf)-1-strlen(buf)); ++ buf[sizeof(buf)-1] = 0; ++ ++ if (can_access(buf, ACCESS_EXISTS)==0) { ++ ++ g_pine_smime_dir = cpystr(buf); ++ s_cert_store = get_ca_store(g_pine_smime_dir); ++ s_personal_certs = get_personal_certs(g_pine_smime_dir); ++ ++ } else g_pine_smime_dir = ""; /* prevent null dereference later */ ++ ++ SSLeay_add_all_algorithms(); ++ ERR_load_crypto_strings(); ++ ++ /* this won't make any sense (since the terminal's in a funny mode) */ ++ if ((bio_err=BIO_new(BIO_s_file())) != NULL) ++ BIO_set_fp(bio_err,stderr,BIO_NOCLOSE|BIO_FP_TEXT); ++ ++ app_RAND_load_file(NULL, bio_err, 1); ++ ++ openssl_extra_randomness(); ++ ++ /* save the rand file when we're done */ ++ atexit(openssl_deinit); ++ ++ inited = 1; ++ } ++ ++ ERR_clear_error(); ++ } ++ ++ /* Get a pointer to a string describing the most recent OpenSSL error. ++ It's statically allocated, so don't change or attempt to free it. ++ */ ++ static const char *openssl_error_string(void) ++ { ++ char *errs; ++ const char *data = NULL; ++ long errn; ++ ++ errn = ERR_peek_error_line_data(NULL, NULL, &data, NULL); ++ errs = (char*)ERR_reason_error_string(ERR_GET_REASON(errn)); ++ ++ if (errs) ++ return errs; ++ else if (data) ++ return data; ++ ++ return "unknown error"; ++ } ++ ++ /* Return true if the body looks like a PKCS7 object */ ++ int is_pkcs7_body(BODY *body) ++ { ++ int result; ++ ++ result = body->type==TYPEAPPLICATION && ++ body->subtype && ++ (strucmp(body->subtype,"pkcs7-mime")==0 || ++ strucmp(body->subtype,"x-pkcs7-mime")==0 || ++ strucmp(body->subtype,"pkcs7-signature")==0 || ++ strucmp(body->subtype,"x-pkcs7-signature")==0); ++ ++ return result; ++ } ++ ++ /* debug utility to dump the contents of a BIO to a file */ ++ static void dump_bio_to_file(BIO *in,char *filename) ++ { ++ char iobuf[4096]; ++ int len; ++ BIO *out; ++ ++ out = BIO_new_file(filename,"w"); ++ ++ if (out) { ++ BIO_reset(in); ++ ++ while ((len = BIO_read(in, iobuf, sizeof(iobuf))) > 0) ++ BIO_write(out, iobuf, len); ++ BIO_free(out); ++ } ++ } ++ ++ /* prompt the user for their passphrase ++ (possibly prompting with the email address in s_passphrase_emailaddr) ++ */ ++ int get_passphrase(void) ++ { ++ int rc; ++ int flags; ++ char prompt[50]; ++ HelpType help = NO_HELP; ++ ++ sprintf(prompt, ++ "Enter passphrase for <%s>: ",s_passphrase_emailaddr ? s_passphrase_emailaddr : "unknown"); ++ ++ do { ++ flags = OE_PASSWD | OE_DISALLOW_HELP; ++ rc = optionally_enter(s_passphrase, -FOOTER_ROWS(ps_global), 0, sizeof(s_passphrase), ++ prompt, NULL, help, &flags); ++ } while (rc!=0 && rc!=1 && rc>0); ++ ++ if (rc==0) ++ s_entered_passphrase = 1; ++ ++ return rc==0; ++ } ++ ++ /* Recursively stash a pointer to the decrypted data in our ++ manufactured body. ++ */ ++ static void create_local_cache(char *base,BODY *b) ++ { ++ if (b->type==TYPEMULTIPART) { ++ PART *p; ++ ++ #if 0 ++ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); ++ #else ++ /* don't really want to copy the real body contents. It shouldn't be ++ used, and in the case of a message with attachments, we'll be ++ duplicating the files multiple times ++ */ ++ cpytxt(&b->contents.text, "BODY UNAVAILABLE", 16); ++ #endif ++ ++ for (p=b->nested.part;p;p=p->next) { ++ create_local_cache(base,(BODY*) p); ++ } ++ } else { ++ cpytxt(&b->contents.text, base + b->contents.offset, b->size.bytes); ++ } ++ } ++ ++ static long rfc822_output_func(void *stream,char *string) ++ { ++ STORE_S *so = (STORE_S*) stream; ++ ++ return so_puts(so,string)!=0; ++ } ++ ++ /* Load a private key from the given file */ ++ static EVP_PKEY *load_key(char *file, char *pass) ++ { ++ BIO *in; ++ EVP_PKEY *key; ++ if(!(in = BIO_new_file(file, "r"))) return NULL; ++ key = PEM_read_bio_PrivateKey(in, NULL,NULL,pass); ++ BIO_free(in); ++ return key; ++ } ++ ++ /* Attempt to load the private key for the given PERSONAL_CERT. ++ This sets the appropriate passphrase globals in order to ++ interact with the user correctly. ++ */ ++ static int load_private_key(PERSONAL_CERT *pcert) ++ { ++ if (!pcert->key) { ++ ++ /* Try empty password by default */ ++ char *password = ""; ++ ++ if (g_need_passphrase) { ++ /* We've already been in here and discovered we need a different password */ ++ ++ if (s_entered_passphrase) ++ password = s_passphrase; /* Use the passphrase if it's been entered */ ++ else return 0; ++ } ++ ++ ERR_clear_error(); ++ ++ if(!(pcert->key = load_key(pcert->file, password))) { ++ long err = ERR_get_error(); ++ ++ /* Couldn't load key... */ ++ ++ if (s_entered_passphrase) { ++ ++ /* The user got the password wrong maybe? */ ++ ++ if ((ERR_GET_LIB(err)==ERR_LIB_EVP && ERR_GET_REASON(err)==EVP_R_BAD_DECRYPT) || ++ (ERR_GET_LIB(err)==ERR_LIB_PEM && ERR_GET_REASON(err)==PEM_R_BAD_DECRYPT)) ++ q_status_message(SM_ORDER | SM_DING,1,1,"Wrong password"); ++ else q_status_message1(SM_ORDER,1,1,"Couldn't read key: %s",(char*)openssl_error_string()); ++ ++ /* This passphrase is no good; forget it */ ++ s_entered_passphrase = 0; ++ } ++ ++ /* Indicate to the UI that we need re-entry (see mailcmd.c:process_cmd())*/ ++ g_need_passphrase = 1; ++ ++ fs_give((void**) &s_passphrase_emailaddr); ++ s_passphrase_emailaddr = get_x509_subject_email(pcert->cert); ++ return 0; ++ } else { ++ /* This key will be cached, so we won't be called again */ ++ s_entered_passphrase = 0; ++ g_need_passphrase = 0; ++ } ++ ++ return 1; ++ } ++ ++ return 0; ++ } ++ ++ static void setup_pkcs7_body_for_signature(BODY *b,char *description,char *type,char *filename) ++ { ++ b->type = TYPEAPPLICATION; ++ b->subtype = cpystr(type); ++ b->encoding = ENCBINARY; ++ ++ b->description = cpystr(description); ++ ++ b->disposition.type = cpystr("attachment"); ++ b->disposition.parameter = mail_newbody_parameter(); ++ b->disposition.parameter->attribute = cpystr("filename"); ++ b->disposition.parameter->value = cpystr(filename); ++ ++ b->parameter = mail_newbody_parameter(); ++ b->parameter->attribute = cpystr("name"); ++ b->parameter->value = cpystr(filename); ++ } ++ ++ /* ++ Look for a personal certificate matching the ++ given address ++ */ ++ PERSONAL_CERT *match_personal_cert_to_email(ADDRESS *a) ++ { ++ PERSONAL_CERT *pcert; ++ char buf[MAXPATH]; ++ char *email; ++ ++ if (!a || !a->mailbox || !a->host) ++ return NULL; ++ ++ snprintf(buf,sizeof(buf),"%s@%s",a->mailbox,a->host); ++ ++ for (pcert=s_personal_certs;pcert;pcert=pcert->next) { ++ ++ if (!pcert->cert) ++ continue; ++ ++ email = get_x509_subject_email(pcert->cert); ++ ++ if (email && strucmp(email,buf)==0) { ++ fs_give((void**) &email); ++ break; ++ } ++ ++ fs_give((void**) &email); ++ } ++ ++ return pcert; ++ } ++ ++ /* ++ Look for a personal certificate matching the from ++ (or reply_to? in the given envelope) ++ */ ++ PERSONAL_CERT *match_personal_cert(ENVELOPE *env) ++ { ++ PERSONAL_CERT *pcert; ++ ++ pcert = match_personal_cert_to_email(env->reply_to); ++ if (!pcert) ++ pcert = match_personal_cert_to_email(env->from); ++ ++ return pcert; ++ } ++ ++ /* ++ Flatten the given body into its MIME representation. ++ Return the result in a CharStar STORE_S. ++ */ ++ static STORE_S *body_to_store(BODY *body) ++ { ++ STORE_S *store; ++ store = so_get(CharStar, NULL, EDIT_ACCESS); ++ if (!store) ++ return NULL; ++ ++ pine_encode_body(body); /* this attaches random boundary strings to multiparts */ ++ pine_write_body_header (body, rfc822_output_func,store); ++ pine_rfc822_output_body(body, rfc822_output_func,store); ++ ++ /* now need to truncate by two characters since the above ++ appends CRLF to the stream ++ */ ++ ++ /** Eek! No way of telling size of a STORE_S. We depend on knowing it's ++ a CharStar */ ++ so_truncate(store,((char*)store->eod-(char*)store->txt)-2); ++ ++ so_seek(store,0,SEEK_SET); ++ ++ return store; ++ } ++ ++ ++ ++ /* ++ Sign a message. Called from call_mailer in send.c. ++ ++ This takes the header for the outgoing message as well as a pointer ++ to the current body (which may be reallocated). ++ */ ++ int sign_outgoing_message(METAENV *header,BODY **bodyP,int dont_detach) ++ { ++ STORE_S *store = NULL; ++ STORE_S *outs = NULL; ++ BODY *body = *bodyP; ++ BODY *newBody = NULL; ++ PART *p1 = NULL; ++ PART *p2 = NULL; ++ PERSONAL_CERT *pcert; ++ BIO *in = NULL; ++ BIO *out = NULL; ++ PKCS7 *p7 = NULL; ++ int result = 0; ++ PARAMETER *param; ++ ++ int flags = dont_detach ? 0 : PKCS7_DETACHED; ++ ++ openssl_init(); ++ ++ store = body_to_store(body); ++ ++ /* Look for a private key matching the sender address... */ ++ ++ pcert = match_personal_cert(header->env); ++ ++ if (!pcert) { ++ q_status_message(SM_ORDER,1,1,"Couldn't find the certificate needed to sign."); ++ goto end; ++ } ++ ++ if (!load_private_key(pcert) && g_need_passphrase) { ++ /* Couldn't load key with blank password, try again */ ++ get_passphrase(); ++ load_private_key(pcert); ++ } ++ ++ if (!pcert->key) ++ goto end; ++ ++ in = BIO_new_so(store); ++ ++ #if 0 ++ dump_bio_to_file(in,"/tmp/signed-data"); ++ #endif ++ ++ BIO_reset(in); ++ ++ p7 = PKCS7_sign(pcert->cert, pcert->key, NULL, in, flags); ++ if (!p7) { ++ q_status_message(SM_ORDER,1,1,"Error creating PKCS7 object."); ++ goto end; ++ } ++ ++ outs = so_get(CharStar,NULL,EDIT_ACCESS); ++ out = BIO_new_so(outs); ++ ++ i2d_PKCS7_bio(out, p7); ++ BIO_flush(out); ++ ++ so_seek(outs,0,SEEK_SET); ++ ++ if ((flags&PKCS7_DETACHED)==0) { ++ ++ /* the simple case: the signed data is in the pkcs7 object */ ++ ++ newBody = mail_newbody(); ++ ++ setup_pkcs7_body_for_signature(newBody,"S/MIME Cryptographically Signed Message","x-pkcs7-mime","smime.p7m"); ++ ++ newBody->contents.text.data = (char*) outs; ++ *bodyP = newBody; ++ ++ result = 1; ++ } else { ++ ++ /* OK. ++ We have to create a new body as follows: ++ ++ multipart/signed; blah blah blah ++ reference to existing body ++ ++ pkcs7 object ++ */ ++ ++ newBody = mail_newbody(); ++ ++ newBody->type = TYPEMULTIPART; ++ newBody->subtype = cpystr("signed"); ++ newBody->encoding = ENC7BIT; ++ ++ newBody->parameter = param = mail_newbody_parameter(); ++ param->attribute = cpystr("protocol"); ++ param->value = cpystr("application/x-pkcs7-signature"); ++ ++ newBody->parameter->next = param = mail_newbody_parameter(); ++ param->attribute = cpystr("micalg"); ++ param->value = cpystr("sha1"); ++ ++ p1 = mail_newbody_part(); ++ p2 = mail_newbody_part(); ++ ++ /* this is nasty. We're just copying the body in here, ++ but since our newBody is freed at the end of call_mailer, ++ we mustn't let this body (the original one) be freed twice. ++ */ ++ p1->body = *body; /* ARRGH. This is special cased at the end of call_mailer */ ++ ++ p1->next = p2; ++ ++ setup_pkcs7_body_for_signature(&p2->body,"S/MIME Cryptographic Signature","x-pkcs7-signature","smime.p7s"); ++ p2->body.contents.text.data = (char*) outs; ++ ++ newBody->nested.part = p1; ++ ++ *bodyP = newBody; ++ ++ result = 1; ++ } ++ ++ end: ++ ++ PKCS7_free(p7); ++ BIO_free(in); ++ BIO_free(out); ++ if (store) ++ so_give(&store); ++ ++ return result; ++ } ++ ++ /* ++ Encrypt a message on the way out. Called from call_mailer in send.c ++ The body may be reallocated. ++ */ ++ int encrypt_outgoing_message(METAENV *header,BODY **bodyP) ++ { ++ PKCS7 *p7 = NULL; ++ BIO *in = NULL; ++ BIO *out = NULL; ++ EVP_CIPHER *cipher = NULL; ++ STACK_OF(X509) *encerts = NULL; ++ STORE_S *store = NULL; ++ STORE_S *outs = NULL; ++ PINEFIELD *pf; ++ ADDRESS *a; ++ BODY *body = *bodyP; ++ BODY *newBody = NULL; ++ int result = 0; ++ ++ openssl_init(); ++ ++ cipher = EVP_des_cbc(); ++ ++ encerts = sk_X509_new_null(); ++ ++ /* Look for a certificate for each of the recipients */ ++ for(pf = header->local; pf && pf->name; pf = pf->next) ++ if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr) { ++ ++ for (a=*pf->addr;a;a=a->next) { ++ X509 *cert; ++ char buf[MAXPATH]; ++ ++ snprintf(buf,sizeof(buf),"%s@%s",a->mailbox,a->host); ++ ++ cert = get_cert_for(g_pine_smime_dir,buf); ++ if (cert) ++ sk_X509_push(encerts,cert); ++ else { ++ q_status_message2(SM_ORDER,1,1, ++ "Unable to find certificate for <%s@%s>",a->mailbox,a->host); ++ goto end; ++ } ++ } ++ } ++ ++ ++ store = body_to_store(body); ++ ++ in = BIO_new_so(store); ++ ++ p7 = PKCS7_encrypt(encerts, in, cipher, 0); ++ ++ outs = so_get(CharStar,NULL,EDIT_ACCESS); ++ out = BIO_new_so(outs); ++ ++ i2d_PKCS7_bio(out, p7); ++ BIO_flush(out); ++ so_seek(outs,0,SEEK_SET); ++ ++ newBody = mail_newbody(); ++ ++ newBody->type = TYPEAPPLICATION; ++ newBody->subtype = cpystr("x-pkcs7-mime"); ++ newBody->encoding = ENCBINARY; ++ ++ newBody->description = cpystr("S/MIME Encrypted Message"); ++ ++ newBody->contents.text.data = (char*) outs; ++ ++ *bodyP = newBody; ++ ++ result = 1; ++ ++ end: ++ ++ BIO_free(in); ++ BIO_free(out); ++ PKCS7_free(p7); ++ sk_X509_pop_free(encerts, X509_free); ++ if (store) ++ so_give(&store); ++ ++ return result; ++ } ++ ++ /* ++ Plonk the contents (mime headers and body) of the given ++ section of a message to a CharStar STORE_S object. ++ */ ++ static STORE_S *get_raw_part(int msgno,const char *section) ++ { ++ long len; ++ STORE_S *store = NULL; ++ char *text; ++ ++ store = so_get(CharStar, NULL, EDIT_ACCESS); ++ if (store) { ++ ++ /* First grab headers of the chap */ ++ text = mail_fetch_mime(ps_global->mail_stream, msgno, (char*) section, &len, 0); ++ ++ if (text) { ++ so_nputs(store,text,len); ++ ++ /** Now grab actual body */ ++ text = mail_fetch_body (ps_global->mail_stream, msgno, (char*) section, &len, 0); ++ if (text) { ++ so_nputs(store,text,len); ++ ++ so_seek(store,0,SEEK_SET); ++ ++ } else so_give(&store); ++ ++ } else so_give(&store); ++ ++ } ++ return store; ++ } ++ ++ /* ++ Get (and decode) the body of the given section of msg ++ */ ++ static STORE_S *get_part_contents(int msgno,const char *section) ++ { ++ long len; ++ gf_io_t pc; ++ STORE_S *store = NULL; ++ char *err; ++ ++ store = so_get(CharStar, NULL, EDIT_ACCESS); ++ if (store) { ++ gf_set_so_writec(&pc,store); ++ ++ err = detach(ps_global->mail_stream, msgno, (char*) section,&len, pc, NULL); ++ ++ gf_clear_so_writec(store); ++ ++ so_seek(store,0,SEEK_SET); ++ ++ if (err) ++ so_give(&store); ++ } ++ return store; ++ } ++ ++ static PKCS7 *get_pkcs7_from_part(int msgno,const char *section) ++ { ++ STORE_S *store = NULL; ++ PKCS7 *p7 = NULL; ++ BIO *in = NULL; ++ ++ store = get_part_contents(msgno,section); ++ ++ if (store) { ++ in = BIO_new_so(store); ++ if (in) { ++ p7=d2i_PKCS7_bio(in,NULL); ++ } ++ } ++ ++ if (store) ++ so_give(&store); ++ ++ BIO_free(in); ++ ++ return p7; ++ } ++ ++ /* ++ Try to verify a signature. ++ ++ p7 - the pkcs7 object to verify ++ in - the plain data to verify (NULL if not detached) ++ out - BIO to which to write the opaque data ++ */ ++ static int do_signature_verify(PKCS7 *p7,BIO *in,BIO *out) ++ { ++ STACK_OF(X509) *otherCerts = NULL; ++ int result; ++ const char *data; ++ long err; ++ ++ #if 0 ++ dump_bio_to_file(in,"/tmp/verified-data"); ++ #endif ++ ++ BIO_reset(in); ++ ++ #if 0 ++ /* testing verification stuff */ ++ { ++ X509 *c; ++ ++ c = get_cert_for(g_pine_smime_dir,"xx"); ++ if (c) { ++ X509_add1_reject_object(c, OBJ_nid2obj(NID_email_protect)); ++ ++ X509_STORE_add_cert(s_cert_store,c); ++ ++ save_cert_for(g_pine_smime_dir,cpystr("yy"),c); ++ } ++ ++ ++ ERR_clear_error(); ++ ++ } ++ #endif ++ ++ result = PKCS7_verify(p7, otherCerts, s_cert_store, ++ in, out, 0); ++ ++ if (result) { ++ q_status_message(SM_ORDER,1,1,"S/MIME signature verified ok"); ++ } else { ++ err = ERR_peek_error_line_data(NULL, NULL, &data, NULL); ++ ++ if (out && err==ERR_PACK(ERR_LIB_PKCS7,PKCS7_F_PKCS7_VERIFY,PKCS7_R_CERTIFICATE_VERIFY_ERROR)) { ++ ++ /* Retry verification so we can get the plain text */ ++ /* Might be better to reimplement PKCS7_verify here? */ ++ ++ PKCS7_verify(p7, otherCerts, s_cert_store, ++ in, out, PKCS7_NOVERIFY); ++ ++ } ++ ++ q_status_message1(SM_ORDER | SM_DING,1,1,"Couldn't verify S/MIME signature: %s",(char*) openssl_error_string()); ++ ++ return result; ++ } ++ ++ /* now try to extract the certificates of any signers */ ++ { ++ STACK_OF(X509) *signers; ++ int i; ++ ++ signers = PKCS7_get0_signers(p7, NULL, 0); ++ ++ if (signers) ++ for (i=0;i<sk_X509_num(signers);i++) { ++ char *email; ++ X509 *x = sk_X509_value(signers,i); ++ X509 *cert; ++ ++ if (!x) ++ continue; ++ ++ email = get_x509_subject_email(x); ++ ++ if (email) { ++ cert = get_cert_for(g_pine_smime_dir,email); ++ if (cert) { ++ X509_free(cert); ++ } else { ++ save_cert_for(g_pine_smime_dir,email,x); ++ } ++ fs_give((void**) &email); ++ } ++ } ++ ++ sk_X509_free(signers); ++ } ++ ++ return result; ++ } ++ ++ /* Hook inside BODY structure for cleaning up S/MIME message bodies */ ++ static void smime_body_cleanup(BODY *b) ++ { ++ #if 0 ++ q_status_message(SM_ORDER,1,1,"smime_body_cleanup called"); ++ #endif ++ ++ if (b->sparep) { ++ PKCS7_free((PKCS7*) b->sparep); ++ b->sparep = NULL; ++ } ++ } ++ ++ /* ++ Given a multipart body of type multipart/signed, attempt to verify ++ it ++ */ ++ static int do_detached_signature_verify(BODY *b,int msgno,char *section) ++ { ++ STORE_S *toVerify = NULL; ++ PKCS7 *p7 = NULL; ++ BIO *in = NULL; ++ PART *p; ++ int result = 0; ++ char seq[100]; ++ char *what_we_did; ++ ++ openssl_init(); ++ ++ snprintf(seq,sizeof(seq),"%s1",section); ++ toVerify = get_raw_part(msgno,seq); ++ ++ if (toVerify) { ++ ++ in = BIO_new_so(toVerify); ++ if (!in) ++ goto end; ++ ++ snprintf(seq,sizeof(seq),"%s2",section); ++ p7 = get_pkcs7_from_part(msgno,seq); ++ ++ if (!p7) ++ goto end; ++ ++ result = do_signature_verify(p7,in,NULL); ++ ++ if (b->subtype) fs_give((void**) &b->subtype); ++ b->subtype = cpystr("x-pkcs7-enclosure"); ++ b->encoding = ENC8BIT; ++ ++ if (b->description) fs_give ((void**) &b->description); ++ ++ what_we_did = result ? "This message was cryptographically signed." : ++ "This message was cryptographically signed but the signature could not be verified."; ++ ++ b->description = cpystr(what_we_did); ++ ++ b->sparep = p7; ++ p7 = NULL; ++ b->cleanup = smime_body_cleanup; ++ ++ p = b->nested.part; ++ ++ /* p is signed plaintext */ ++ if (p && p->next) ++ mail_free_body_part(&p->next); /* hide the pkcs7 from the viewer */ ++ ++ result = 0; ++ } ++ end: ++ BIO_free(in); ++ ++ PKCS7_free(p7); ++ ++ if (toVerify) ++ so_give(&toVerify); ++ ++ return result; ++ } ++ ++ static PERSONAL_CERT *find_certificate_matching_recip_info(PKCS7_RECIP_INFO *ri) ++ { ++ PERSONAL_CERT *x; ++ ++ for (x=s_personal_certs;x;x=x->next) { ++ X509 *mine; ++ ++ mine = x->cert; ++ ++ if (!X509_NAME_cmp(ri->issuer_and_serial->issuer,mine->cert_info->issuer) && ++ !ASN1_INTEGER_cmp(ri->issuer_and_serial->serial,mine->cert_info->serialNumber)) { ++ break; ++ } ++ } ++ ++ return x; ++ } ++ ++ static PERSONAL_CERT *find_certificate_matching_pkcs7(PKCS7 *p7) ++ { ++ int i; ++ STACK_OF(PKCS7_RECIP_INFO) *recips; ++ PERSONAL_CERT *x = NULL; ++ ++ recips = p7->d.enveloped->recipientinfo; ++ ++ for (i=0; i<sk_PKCS7_RECIP_INFO_num(recips); i++) { ++ PKCS7_RECIP_INFO *ri; ++ ++ ri=sk_PKCS7_RECIP_INFO_value(recips,i); ++ ++ if ((x=find_certificate_matching_recip_info(ri))!=0) { ++ break; ++ } ++ } ++ ++ return x; ++ } ++ ++ /* ++ Try to decode (decrypt or verify a signature) a PKCS7 body ++ */ ++ static int do_decoding(BODY *b,int msgno,const char *section) ++ { ++ STORE_S *outs = NULL; ++ int result = 0; ++ ++ BIO *out = NULL; ++ PKCS7 *p7 = NULL; ++ X509 *recip = NULL; ++ EVP_PKEY *key = NULL; ++ PERSONAL_CERT *pcert = NULL; ++ ++ char *what_we_did = ""; ++ ++ openssl_init(); ++ ++ /* ++ Extract binary data from part to an in-memory store ++ */ ++ ++ if (b->sparep) { ++ ++ p7 = (PKCS7*) b->sparep; ++ ++ } else { ++ ++ p7 = get_pkcs7_from_part(msgno,section); ++ if (p7 == NULL) { ++ q_status_message1(SM_ORDER,1,1,"Couldn't load PKCS7 object: %s",(char*)openssl_error_string()); ++ goto end; ++ } ++ ++ /* Save the PKCS7 object for later dealings by the user interface. ++ It will be cleaned up when the body is garbage collected ++ */ ++ b->sparep = p7; ++ b->cleanup = smime_body_cleanup; ++ } ++ ++ if (PKCS7_type_is_signed(p7)) { ++ int sigok; ++ ++ outs = so_get(CharStar, NULL, EDIT_ACCESS); ++ so_puts(outs,"MIME-Version: 1.0\r\n"); /* needed so rfc822_parse_msg_full believes it's MIME */ ++ out = BIO_new_so(outs); ++ ++ sigok = do_signature_verify(p7,NULL,out); ++ ++ /* shouldn't really duplicate these messages */ ++ what_we_did = sigok ? "This message was cryptographically signed." : ++ "This message was cryptographically signed but the signature could not be verified."; ++ ++ } else if (!PKCS7_type_is_enveloped(p7)) { ++ q_status_message(SM_ORDER,1,1,"PKCS7 object not recognised."); ++ goto end; ++ } else { /* It *is* enveloped */ ++ ++ what_we_did = "This message was encrypted."; ++ ++ /* ++ Now need to find a cert that can decrypt this boy ++ */ ++ pcert = find_certificate_matching_pkcs7(p7); ++ ++ if (!pcert) { ++ q_status_message(SM_ORDER,1,1,"Couldn't find the certificate needed to decrypt."); ++ goto end; ++ } ++ ++ recip = pcert->cert; ++ ++ load_private_key(pcert); ++ ++ key = pcert->key; ++ if (!key) ++ goto end; ++ ++ outs = so_get(CharStar, NULL, EDIT_ACCESS); ++ so_puts(outs,"MIME-Version: 1.0\r\n"); ++ ++ out = BIO_new_so(outs); ++ ++ if(!PKCS7_decrypt(p7, key, recip, out, 0)) { ++ q_status_message1(SM_ORDER,1,1,"Error decrypting PKCS7: %s",(char*) openssl_error_string()); ++ goto end; ++ } ++ ++ } ++ ++ /* We've now produced a flattened MIME object in store outs. ++ It needs to be turned back into a BODY ++ */ ++ ++ { ++ BODY *body; ++ ENVELOPE *env; ++ char *h; ++ char *bstart; ++ STRING s; ++ ++ h = so_text(outs); ++ ++ /* look for start of body */ ++ bstart = strstr(h,"\r\n\r\n"); ++ ++ if (!bstart) { ++ q_status_message(SM_ORDER,1,1,"Encrypted data couldn't be parsed."); ++ } else { ++ bstart += 4; /* skip over CRLF*2 */ ++ ++ INIT(&s,mail_string,bstart,strlen(bstart)); ++ rfc822_parse_msg_full(&env,&body,h,bstart-h-2,&s,BADHOST,0,0); ++ mail_free_envelope(&env); /* Don't care about this */ ++ ++ /* ++ Now convert original body (application/pkcs7-mime) ++ to a multipart body with one sub-part (the decrypted body) ++ Note that the sub-part may also be multipart! ++ */ ++ ++ b->type = TYPEMULTIPART; ++ if (b->subtype) fs_give((void**) &b->subtype); ++ ++ /* This subtype is used in mailview.c to annotate the display of ++ encrypted or signed messages. We know for sure then that it's a PKCS7 ++ part because the sparep field is set to the PKCS7 object (see above) ++ */ ++ b->subtype = cpystr("x-pkcs7-enclosure"); ++ b->encoding = ENC8BIT; ++ ++ if (b->description) fs_give ((void**) &b->description); ++ b->description = cpystr(what_we_did); ++ ++ if (b->disposition.type) fs_give ((void **) &b->disposition.type); ++ ++ if (b->contents.text.data) fs_give ((void **) &b->contents.text.data); ++ ++ if (b->parameter) mail_free_body_parameter(&b->parameter); ++ ++ /* Allocate mem for the sub-part, and copy over the contents of our parsed body */ ++ b->nested.part = fs_get(sizeof (PART)); ++ b->nested.part->body = *body; ++ b->nested.part->next = NULL; ++ ++ fs_give((void**) &body); ++ ++ /* IMPORTANT BIT: set the body->contents.text.data elements to contain the decrypted ++ data. Otherwise, it'll try to load it from the original data. Eek. ++ */ ++ create_local_cache(bstart,&b->nested.part->body); ++ ++ result = 1; ++ } ++ } ++ ++ end: ++ ++ BIO_free(out); ++ ++ if (outs) ++ so_give(&outs); ++ ++ return result; ++ } ++ ++ /* ++ Recursively handle PKCS7 bodies in our message. ++ ++ Returns non-zero if some fiddling was done. ++ */ ++ static int do_fiddle_smime_message(BODY *b,int msgno,char *section) ++ { ++ int result = 0; ++ ++ if (is_pkcs7_body(b)) { ++ ++ if (do_decoding(b,msgno,*section ? section : "1")) { ++ /* ++ b should now be a multipart message: fiddle it too in case it's been multiply-encrypted! ++ */ ++ ++ /* fallthru */ ++ result = 1; ++ } ++ } ++ ++ if (b->type==TYPEMULTIPART) { ++ ++ PART *p; ++ int partNum; ++ char newSec[100]; ++ ++ if (b->subtype && strucmp(b->subtype,"signed")==0) { ++ ++ /* Ahah. We have a multipart signed entity. */ ++ ++ /* part 1 (signed thing) ++ part 2 (the pkcs7 object) ++ */ ++ ++ do_detached_signature_verify(b,msgno,section); ++ ++ } else { ++ ++ for (p=b->nested.part,partNum=1;p;p=p->next,partNum++) { ++ ++ /* Append part number to the section string */ ++ ++ snprintf(newSec,sizeof(newSec),"%s%s%d",section,*section ? "." : "",partNum); ++ ++ result |= do_fiddle_smime_message(&p->body,msgno,newSec); ++ } ++ ++ } ++ ++ } ++ ++ return result; ++ } ++ ++ /* ++ Fiddle a message in-place by decrypting/verifying S/MIME entities. ++ Returns non-zero if something was changed. ++ */ ++ ++ int fiddle_smime_message(BODY *b,int msgno,int is_new_message) ++ { ++ if (F_OFF(F_REMEMBER_SMIME_PASSPHRASE,ps_global)) ++ forget_private_keys(); ++ return do_fiddle_smime_message(b,msgno,""); ++ } ++ ++ ++ /********************************************************************************/ ++ ++ static struct key smime_info_keys[] = ++ {HELP_MENU, ++ OTHER_MENU, ++ {"<","Back",{MC_VIEW_TEXT,2,{'<',','}},KS_EXITMODE}, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ PREVPAGE_MENU, ++ NEXTPAGE_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ ++ HELP_MENU, ++ OTHER_MENU, ++ MAIN_MENU, ++ QUIT_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ INDEX_MENU, ++ NULL_MENU, ++ NULL_MENU, ++ }; ++ INST_KEY_MENU(smime_info_keymenu, smime_info_keys); ++ ++ #define SMIME_PARENT_KEY 2 ++ ++ static void get_fingerprint(X509 *cert,const EVP_MD *type,char *buf,int maxLen) ++ { ++ unsigned char md[128]; ++ char *b; ++ int len,i; ++ ++ len = sizeof(md); ++ ++ X509_digest(cert,type,md,&len); ++ ++ b = buf; ++ *b = 0; ++ for (i=0; i<len; i++) ++ { ++ if (b-buf+3>=maxLen) ++ break; ++ ++ if (i != 0) ++ *b++ = ':'; ++ sprintf(b,"%02x",md[i]); ++ b+=2; ++ } ++ } ++ ++ static void output_X509_NAME(X509_NAME *name,gf_io_t pc) ++ { ++ int i,c; ++ int nid; ++ char buf[256]; ++ ++ c = X509_NAME_entry_count(name); ++ ++ for (i=c-1;i>=0;i--) { ++ X509_NAME_ENTRY *e; ++ ++ e = X509_NAME_get_entry(name,i); ++ if (!e) continue; ++ ++ X509_NAME_get_text_by_OBJ(name, e->object,buf,sizeof(buf)); ++ ++ gf_puts(buf,pc); ++ gf_puts(NEWLINE,pc); ++ } ++ ++ } ++ ++ /* ++ Output a string in a distinctive style ++ */ ++ static void gf_puts_uline(const char *txt,gf_io_t pc) ++ { ++ #if 0 ++ pc(TAG_EMBED); pc(TAG_ULINEON); ++ gf_puts(txt,pc); ++ pc(TAG_EMBED); pc(TAG_ULINEOFF); ++ #else ++ pc(TAG_EMBED); pc(TAG_BOLDON); ++ gf_puts(txt,pc); ++ pc(TAG_EMBED); pc(TAG_BOLDOFF); ++ #endif ++ } ++ ++ /* ++ Get a line from the given store (including \n) ++ */ ++ static int so_gets(STORE_S *store,char *buf,int len) ++ { ++ unsigned char c; ++ char *bend = buf + len - 1; ++ char *b = buf; ++ ++ do { ++ if (!store->readc(&c,store)) { ++ *b = 0; ++ return b!=buf; ++ } ++ *b++ = c; ++ } while (c!='\n' && b<bend); ++ ++ *b = 0; ++ ++ return 1; ++ } ++ ++ /* ++ Wrap the text in the given store to the given width. ++ A new store is created for the result. ++ */ ++ static STORE_S *wrap_store(STORE_S *in,int width) ++ { ++ STORE_S *result; ++ void *ws; ++ gf_io_t ipc,opc; ++ ++ if (width<10) ++ width = 10; ++ ++ result = so_get(CharStar,NULL,EDIT_ACCESS); ++ ws = gf_wrap_filter_opt(width,width,0,0); ++ ++ gf_filter_init(); ++ gf_link_filter(gf_wrap,ws); ++ ++ gf_set_so_writec(&opc,result); ++ gf_set_so_readc(&ipc,in); ++ ++ gf_pipe(ipc,opc); ++ ++ gf_clear_so_readc(in); ++ gf_clear_so_writec(result); ++ ++ return result; ++ } ++ ++ /* ++ Output the contents of the given stores (left and right) ++ to the given gf_io_t. ++ The width of the terminal is inspected and two columns ++ are created to fit the stores into. They are then wrapped ++ and merged. ++ */ ++ static void side_by_side(STORE_S *left,STORE_S *right,gf_io_t pc) ++ { ++ STORE_S *left_wrapped; ++ STORE_S *right_wrapped; ++ ++ char buf_l[256]; ++ char buf_r[256]; ++ char *b; ++ int i; ++ int w = ps_global->ttyo->screen_cols/2 - 1; ++ ++ so_seek(left,0,0); ++ so_seek(right,0,0); ++ ++ left_wrapped = wrap_store(left,w); ++ right_wrapped = wrap_store(right,w); ++ ++ so_seek(left_wrapped,0,0); ++ so_seek(right_wrapped,0,0); ++ ++ for (;;) { ++ ++ i = so_gets(left_wrapped,buf_l,sizeof(buf_l)); ++ i += so_gets(right_wrapped,buf_r,sizeof(buf_r)); ++ ++ if (i==0) ++ break; ++ ++ for (i=0, b=buf_l;i<w && *b && *b!='\r' && *b!='\n';i++,b++) { ++ pc(*b); ++ /* reduce accumulated width if an embed tag is discovered */ ++ if (*b==TAG_EMBED) ++ i-=2; ++ } ++ ++ if (buf_r[0]) { ++ ++ while (i<w) { ++ pc(' '); ++ i++; ++ } ++ ++ for (i=0, b=buf_r;i<w && *b && *b!='\r' && *b!='\n';i++,b++) { ++ pc(*b); ++ } ++ } ++ ++ gf_puts(NEWLINE,pc); ++ } ++ ++ so_give(&left_wrapped); ++ so_give(&right_wrapped); ++ } ++ ++ static void print_separator_line(int percent,int ch,gf_io_t pc) ++ { ++ int i; ++ int start,len; ++ ++ len = ps_global->ttyo->screen_cols * percent / 100; ++ start = (ps_global->ttyo->screen_cols - len)/2; ++ ++ for (i=0;i<start;i++) ++ pc(' '); ++ for (i=start;i<start+len;i++) ++ pc(ch); ++ gf_puts(NEWLINE,pc); ++ } ++ ++ static void output_cert_info(X509 *cert,gf_io_t pc) ++ { ++ char buf[256]; ++ STORE_S *left,*right; ++ gf_io_t spc; ++ int i; ++ ++ left = so_get(CharStar,NULL,EDIT_ACCESS); ++ right = so_get(CharStar,NULL,EDIT_ACCESS); ++ ++ gf_set_so_writec(&spc,left); ++ ++ if (!cert->cert_info) { ++ gf_puts("Couldn't find certificate info.",spc); ++ gf_puts(NEWLINE,spc); ++ } else { ++ ++ gf_puts_uline("Subject (whose certificate it is)",spc); ++ gf_puts(NEWLINE, spc); ++ ++ output_X509_NAME(cert->cert_info->subject,spc); ++ gf_puts(NEWLINE,spc); ++ ++ gf_puts_uline("Serial Number",spc); ++ gf_puts(NEWLINE,spc); ++ ++ sprintf(buf,"%d",ASN1_INTEGER_get(cert->cert_info->serialNumber)); ++ gf_puts(buf,spc); ++ gf_puts(NEWLINE,spc); ++ gf_puts(NEWLINE,spc); ++ ++ gf_puts_uline("Validity",spc); ++ gf_puts(NEWLINE,spc); ++ { ++ BIO *mb = BIO_new_so(left); ++ ++ gf_puts("Not Before: ",spc); ++ ASN1_UTCTIME_print(mb,cert->cert_info->validity->notBefore); ++ BIO_flush(mb); ++ gf_puts(NEWLINE,spc); ++ ++ gf_puts("Not After: ",spc); ++ ASN1_UTCTIME_print(mb,cert->cert_info->validity->notAfter); ++ BIO_flush(mb); ++ ++ gf_puts(NEWLINE,spc); ++ gf_puts(NEWLINE,spc); ++ ++ BIO_free(mb); ++ } ++ ++ } ++ ++ gf_clear_so_writec(left); ++ ++ gf_set_so_writec(&spc,right); ++ ++ if (!cert->cert_info) { ++ gf_puts("Couldn't find certificate info.",spc); ++ gf_puts(NEWLINE,spc); ++ } else { ++ gf_puts_uline("Issuer",spc); ++ gf_puts(NEWLINE, spc); ++ ++ output_X509_NAME(cert->cert_info->issuer,spc); ++ gf_puts(NEWLINE,spc); ++ } ++ ++ gf_clear_so_writec(right); ++ ++ side_by_side(left,right,pc); ++ ++ gf_puts_uline("SHA1 Fingerprint",pc); ++ gf_puts(NEWLINE,pc); ++ get_fingerprint(cert,EVP_sha1(),buf,sizeof(buf)); ++ gf_puts(buf,pc); ++ gf_puts(NEWLINE,pc); ++ ++ gf_puts_uline("MD5 Fingerprint",pc); ++ gf_puts(NEWLINE,pc); ++ get_fingerprint(cert,EVP_md5(),buf,sizeof(buf)); ++ gf_puts(buf,pc); ++ gf_puts(NEWLINE,pc); ++ ++ so_give(&left); ++ so_give(&right); ++ } ++ ++ void format_smime_info(int pass,BODY *body,int msgno,gf_io_t pc) ++ { ++ PKCS7 *p7; ++ int i; ++ ++ if (body->type==TYPEMULTIPART) { ++ PART *p; ++ ++ for (p=body->nested.part;p;p=p->next) { ++ format_smime_info(pass,&p->body,msgno,pc); ++ } ++ } ++ ++ p7 = body->sparep; ++ if (p7) { ++ ++ if (PKCS7_type_is_signed(p7)) { ++ STACK_OF(X509) *signers; ++ ++ switch (pass) { ++ case 1: ++ gf_puts("This message was cryptographically signed." NEWLINE,pc); ++ break; ++ case 2: ++ ++ signers = PKCS7_get0_signers(p7, NULL, 0); ++ ++ if (signers) { ++ ++ sprintf(tmp_20k_buf,"Certificate%s used for signing",plural(sk_X509_num(signers))); ++ gf_puts_uline(tmp_20k_buf,pc); ++ gf_puts(NEWLINE,pc); ++ print_separator_line(100,'-',pc); ++ ++ for (i=0;i<sk_X509_num(signers);i++) { ++ X509 *x = sk_X509_value(signers,i); ++ ++ if (x) { ++ output_cert_info(x,pc); ++ gf_puts(NEWLINE,pc); ++ } ++ ++ } ++ } ++ ++ sk_X509_free(signers); ++ break; ++ } ++ ++ } else if (PKCS7_type_is_enveloped(p7)) { ++ ++ switch (pass) { ++ case 1: ++ gf_puts("This message was encrypted." NEWLINE,pc); ++ break; ++ case 2: ++ ++ if (p7->d.enveloped && p7->d.enveloped->enc_data) { ++ X509_ALGOR *alg = p7->d.enveloped->enc_data->algorithm; ++ STACK_OF(PKCS7_RECIP_INFO) *ris = p7->d.enveloped->recipientinfo; ++ int found = 0; ++ ++ gf_puts("The algorithm used to encrypt was ",pc); ++ ++ ++ if (alg) { ++ char *n = OBJ_nid2sn( OBJ_obj2nid(alg->algorithm )); ++ ++ gf_puts(n ? n : "<unknown>",pc); ++ ++ } else gf_puts("<unknown>",pc); ++ ++ gf_puts("." NEWLINE NEWLINE,pc); ++ ++ sprintf(tmp_20k_buf,"Certificate%s for decrypting",plural(sk_PKCS7_RECIP_INFO_num(ris))); ++ gf_puts_uline(tmp_20k_buf,pc); ++ gf_puts(NEWLINE,pc); ++ print_separator_line(100,'-',pc); ++ ++ for (i=0;i<sk_PKCS7_RECIP_INFO_num(ris);i++) { ++ PKCS7_RECIP_INFO *ri; ++ PERSONAL_CERT *pcert; ++ ++ ri = sk_PKCS7_RECIP_INFO_value(ris,i); ++ if (!ri) continue; ++ ++ pcert = find_certificate_matching_recip_info(ri); ++ ++ if (pcert) { ++ ++ if (found) { ++ print_separator_line(25,'*',pc); ++ gf_puts(NEWLINE,pc); ++ } ++ found = 1; ++ ++ output_cert_info(pcert->cert,pc); ++ gf_puts(NEWLINE,pc); ++ ++ } ++ } ++ ++ if (!found) { ++ gf_puts("No certificate capable of decrypting could not be found.",pc); ++ } ++ } ++ break; ++ } ++ ++ } ++ } ++ } ++ ++ void view_writec(); ++ ++ void smime_info_screen(struct pine *ps) ++ { ++ int msgno; ++ OtherMenu what; ++ int cmd; ++ char backtag[64]; ++ BODY *body; ++ ENVELOPE *env; ++ HANDLE_S *handles = NULL; ++ SCROLL_S scrollargs; ++ STORE_S *store = NULL; ++ int offset = 0; ++ ++ ps->prev_screen = smime_info_screen; ++ ps->next_screen = SCREEN_FUN_NULL; ++ ++ if(mn_total_cur(ps->msgmap) > 1L){ ++ q_status_message(SM_ORDER | SM_DING, 0, 3, ++ "Can only view one message's information at a time."); ++ return; ++ } ++ /* else check for existence of smime bits */ ++ ++ msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap)); ++ ++ env = mail_fetch_structure (ps->mail_stream,msgno, ++ &body,0); ++ if (!env || !body) { ++ q_status_message(SM_ORDER, 0, 3, ++ "Can't fetch body of message."); ++ return; ++ } ++ ++ what = FirstMenu; ++ ++ store = so_get(CharStar, NULL, EDIT_ACCESS); ++ ++ while(ps->next_screen == SCREEN_FUN_NULL){ ++ ++ ClearLine(1); ++ ++ so_truncate(store,0); ++ ++ view_writec_init(store, &handles, HEADER_ROWS(ps), ++ HEADER_ROWS(ps) + ++ ps->ttyo->screen_rows - (HEADER_ROWS(ps) ++ + HEADER_ROWS(ps))); ++ ++ gf_puts_uline("Overview",view_writec); ++ gf_puts(NEWLINE,view_writec); ++ ++ format_smime_info(1,body,msgno,view_writec); ++ gf_puts(NEWLINE,view_writec); ++ format_smime_info(2,body,msgno,view_writec); ++ ++ view_writec_destroy(); ++ ++ ++ ps->next_screen = SCREEN_FUN_NULL; ++ ++ memset(&scrollargs, 0, sizeof(SCROLL_S)); ++ scrollargs.text.text = so_text(store); ++ scrollargs.text.src = CharStar; ++ scrollargs.text.desc = "S/MIME information"; ++ scrollargs.body_valid = 1; ++ ++ if(offset){ /* resize? preserve paging! */ ++ scrollargs.start.on = Offset; ++ scrollargs.start.loc.offset = offset; ++ offset = 0L; ++ } ++ ++ scrollargs.bar.title = "S/MIME INFORMATION"; ++ /* scrollargs.end_scroll = view_end_scroll; */ ++ scrollargs.resize_exit = 1; ++ scrollargs.help.text = NULL; ++ scrollargs.help.title = "HELP FOR S/MIME INFORMATION VIEW"; ++ scrollargs.keys.menu = &smime_info_keymenu; ++ scrollargs.keys.what = what; ++ setbitmap(scrollargs.keys.bitmap); ++ ++ if(scrolltool(&scrollargs) == MC_RESIZE) ++ offset = scrollargs.start.loc.offset; ++ } ++ ++ so_give(&store); ++ ++ } ++ ++ ++ #endif /* SMIME */ +diff -Ncr pine4.53/pine/smime.h pine4.53-smime/pine/smime.h +*** pine4.53/pine/smime.h Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/smime.h Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,17 ---- ++ #ifdef SMIME ++ ++ int is_pkcs7_body(BODY *b); ++ ++ int fiddle_smime_message(BODY *b,int msgno,int is_new_message); ++ ++ int encrypt_outgoing_message(METAENV *header,BODY **bodyP); ++ int sign_outgoing_message(METAENV *header,BODY **bodyP,int dont_detach); ++ int get_passphrase(void); ++ void smime_info_screen(struct pine *ps); ++ ++ extern int g_need_passphrase; ++ ++ extern int g_do_encrypt; ++ extern int g_do_sign; ++ ++ #endif /* SMIME */ +diff -Ncr pine4.53/pine/smkeys.c pine4.53-smime/pine/smkeys.c +*** pine4.53/pine/smkeys.c Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/smkeys.c Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,343 ---- ++ #ifdef SMIME ++ ++ #include "headers.h" ++ ++ #include <openssl/err.h> ++ #include <openssl/objects.h> ++ #include <openssl/evp.h> ++ #include <openssl/x509.h> ++ #include <openssl/pkcs7.h> ++ #include <openssl/pem.h> ++ ++ #include "smkeys.h" ++ ++ /*--------------------------------------------------- ++ Remove leading whitespace, trailing whitespace and convert ++ to lowercase. Also remove slash characters ++ ++ Args: s, -- The string to clean ++ ++ Result: the cleaned string ++ ----*/ ++ static char * ++ emailstrclean(string) ++ char *string; ++ { ++ char *s = string, *sc = NULL, *p = NULL; ++ ++ for(; *s; s++){ /* single pass */ ++ if(!isspace((unsigned char)*s)){ ++ p = NULL; /* not start of blanks */ ++ if(!sc) /* first non-blank? */ ++ sc = string; /* start copying */ ++ } ++ else if(!p) /* it's OK if sc == NULL */ ++ p = sc; /* start of blanks? */ ++ ++ if(sc && *s!='/' && *s!='\\') /* if copying, copy */ ++ *sc++ = isupper((unsigned char)(*s)) ++ ? (unsigned char)tolower((unsigned char)(*s)) ++ : (unsigned char)(*s); ++ } ++ ++ if(p) /* if ending blanks */ ++ *p = '\0'; /* tie off beginning */ ++ else if(!sc) /* never saw a non-blank */ ++ *string = '\0'; /* so tie whole thing off */ ++ ++ return(string); ++ } ++ ++ /* ++ Add a lookup for each "*.crt" file in the given directory. ++ */ ++ static void add_certs_in_dir(X509_LOOKUP *lookup,const char *path) ++ { ++ char buf[MAXPATH]; ++ struct direct *d; ++ DIR *dirp; ++ PERSONAL_CERT *result; ++ ++ result = NULL; ++ ++ dirp = opendir(path); ++ if (dirp) { ++ ++ while (d=readdir(dirp)) { ++ BIO *in; ++ X509 *cert; ++ ++ if (srchrstr(d->d_name,".crt")) { ++ ++ build_path(buf,(char*) path,d->d_name,sizeof(buf)); ++ ++ X509_LOOKUP_load_file(lookup,buf,X509_FILETYPE_PEM); ++ } ++ ++ } ++ ++ closedir(dirp); ++ } ++ } ++ ++ /* Get an X509_STORE. This consists of the system ++ certs directory and any certificates in the user's ++ ~/.pine-smime/ca directory. ++ */ ++ X509_STORE *get_ca_store(const char *path) ++ { ++ X509_LOOKUP *lookup; ++ char buf[MAXPATH]; ++ ++ X509_STORE *store; ++ ++ store=X509_STORE_new(); ++ ++ lookup=X509_STORE_add_lookup(store,X509_LOOKUP_file()); ++ ++ build_path(buf,(char*) path,"ca",sizeof(buf)); ++ ++ add_certs_in_dir(lookup,buf); ++ ++ /* X509_LOOKUP_load_file(lookup,NULL,X509_FILETYPE_DEFAULT); */ ++ ++ lookup = X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir()); ++ ++ X509_LOOKUP_add_dir(lookup,SSL_CERT_DIRECTORY,X509_FILETYPE_PEM); ++ ++ /* X509_STORE_set_default_paths(cert_store); ++ X509_STORE_load_locations(cert_store,NULL,"../../certs"); ++ X509_STORE_set_verify_cb_func(cert_store,verify_callback); ++ */ ++ ++ return store; ++ } ++ ++ static EVP_PKEY *load_key(char *file, char *pass) ++ { ++ BIO *in; ++ EVP_PKEY *key; ++ if(!(in = BIO_new_file(file, "r"))) return NULL; ++ key = PEM_read_bio_PrivateKey(in, NULL,NULL,pass); ++ BIO_free(in); ++ return key; ++ } ++ ++ char *get_x509_name_entry(const char *key,X509_NAME *name) ++ { ++ int i,c,n; ++ char buf[256]; ++ char *id; ++ ++ if (!name) ++ return NULL; ++ ++ c = X509_NAME_entry_count(name); ++ ++ for (i=0;i<c;i++) { ++ X509_NAME_ENTRY *e; ++ ++ e = X509_NAME_get_entry(name,i); ++ if (!e) continue; ++ ++ buf[0] = 0; ++ id = buf; ++ ++ n = OBJ_obj2nid(e->object); ++ if ((n == NID_undef) || ((id=(char*) OBJ_nid2sn(n)) == NULL)) { ++ i2t_ASN1_OBJECT(buf,sizeof(buf),e->object); ++ id = buf; ++ } ++ ++ if ((strucmp(id,"email")==0) || (strucmp(id,"emailAddress")==0)) { ++ X509_NAME_get_text_by_OBJ(name, e->object,(char*) buf,sizeof(buf)-1); ++ return cpystr(buf); ++ } ++ } ++ return NULL; ++ } ++ ++ char *get_x509_subject_email(X509 *x) ++ { ++ char* result; ++ result = get_x509_name_entry("email",X509_get_subject_name(x)); ++ if ( !result ) { ++ result = get_x509_name_entry("emailAddress",X509_get_subject_name(x)); ++ } ++ ++ return result; ++ } ++ ++ /* Save the certificate for the given email address in ++ ~/.pine-smime/public. ++ ++ Should consider the security hazards in making a file with ++ the email address that has come from the certificate. ++ ++ The argument email is destroyed. ++ */ ++ ++ void save_cert_for(const char *path,char *email,X509 *cert) ++ { ++ char sf[MAXPATH]; ++ char sf2[MAXPATH]; ++ BIO *tmp; ++ ++ build_path(sf,(char*) path,"public",sizeof(sf)); ++ ++ build_path(sf2,sf,emailstrclean(email),sizeof(sf2)); ++ strncat(sf2,".crt",sizeof(sf2)-1-strlen(sf2)); ++ sf2[sizeof(sf2)-1] = 0; ++ ++ tmp = BIO_new_file(sf2, "w"); ++ if (tmp) { ++ X509_print(tmp,cert); ++ PEM_write_bio_X509_AUX(tmp, cert); ++ BIO_free(tmp); ++ q_status_message1(SM_ORDER,1,1,"Saved certificate for <%s>",(char*)email); ++ } else { ++ q_status_message1(SM_ORDER,1,1,"Couldn't save certificate for <%s>",(char*)email); ++ } ++ } ++ ++ /* ++ Try to retrieve the certificate for the given email address. ++ */ ++ X509 *get_cert_for(const char *path,const char *email) ++ { ++ char buf[MAXPATH]; ++ char buf2[MAXPATH]; ++ char buf3[MAXPATH]; ++ char *p; ++ X509 *cert = NULL; ++ BIO *in; ++ ++ strncpy(buf3,email,sizeof(buf3)-1); ++ buf3[sizeof(buf3)-1] = 0; ++ ++ /* clean it up (lowercase, space removal) */ ++ emailstrclean(buf3); ++ ++ build_path(buf,(char*)path,"public",sizeof(buf)); ++ build_path(buf2,buf,buf3,sizeof(buf2)); ++ strncat(buf2,".crt",sizeof(buf2)-1-strlen(buf2)); ++ buf2[sizeof(buf2)-1] = 0; ++ ++ if((in = BIO_new_file(buf2, "r"))!=0) { ++ ++ cert = PEM_read_bio_X509(in, NULL, NULL,NULL); ++ ++ if (cert) { ++ /* could check email addr in cert matches */ ++ } ++ } ++ BIO_free(in); ++ ++ return cert; ++ } ++ ++ EVP_PKEY *get_key_for(const char *path,const char *email,const char *pass) ++ { ++ char buf[MAXPATH]; ++ char buf2[MAXPATH]; ++ char buf3[MAXPATH]; ++ char *p; ++ EVP_PKEY *key = NULL; ++ BIO *in; ++ ++ strncpy(buf3,email,sizeof(buf3)-1); ++ buf3[sizeof(buf3)-1] = 0; ++ ++ /* clean it up (lowercase, space removal) */ ++ emailstrclean(buf3); ++ ++ build_path(buf,(char*)path,"private",sizeof(buf)); ++ build_path(buf2,buf,buf3,sizeof(buf2)); ++ strncat(buf2,".key",sizeof(buf2)-1-strlen(buf2)); ++ buf2[sizeof(buf2)-1] = 0; ++ ++ key = load_key(buf2,pass); ++ ++ return key; ++ } ++ ++ /* ++ Load the user's personal certificates from ++ ~/.pine-smime/private ++ */ ++ PERSONAL_CERT *get_personal_certs(const char *path) ++ { ++ char buf[MAXPATH]; ++ char buf2[MAXPATH]; ++ struct direct *d; ++ DIR *dirp; ++ PERSONAL_CERT *result; ++ ++ result = NULL; ++ ++ build_path(buf,(char*) path,"private",sizeof(buf)); ++ ++ dirp = opendir(buf); ++ if (dirp) { ++ ++ while (d=readdir(dirp)) { ++ BIO *in; ++ X509 *cert; ++ ++ if (srchrstr(d->d_name,".key")) { ++ ++ /* copy file name to temp buffer */ ++ strcpy(buf2,d->d_name); ++ /* chop off ".key" trailier */ ++ buf2[strlen(buf2)-4] = 0; ++ /* Look for certificate */ ++ cert = get_cert_for(path,buf2); ++ ++ if (cert) { ++ PERSONAL_CERT *pc; ++ ++ /* create a new PERSONAL_CERT, fill it in */ ++ ++ pc = fs_get(sizeof(PERSONAL_CERT)); ++ pc->cert = cert; ++ build_path(buf2,buf,d->d_name,sizeof(buf2)); ++ pc->file = cpystr(buf2); ++ ++ strcpy(pc->file + strlen(pc->file) - 4, ".key"); ++ ++ /* Try to load the key with an empty password */ ++ pc->key = load_key(pc->file,""); ++ ++ pc->next = result; ++ result = pc; ++ } ++ } ++ ++ } ++ ++ closedir(dirp); ++ } ++ ++ return result; ++ } ++ ++ void personal_cert_free(PERSONAL_CERT **pcp) ++ { ++ if (pcp && *pcp) { ++ ++ PERSONAL_CERT *pc = *pcp; ++ ++ fs_give((void**) &pc->file); ++ ++ X509_free(pc->cert); ++ ++ if (pc->key) ++ EVP_PKEY_free(pc->key); ++ ++ personal_cert_free(&pc->next); ++ ++ fs_give((void**) pcp); ++ } ++ } ++ ++ #endif /* SMIME */ +diff -Ncr pine4.53/pine/smkeys.h pine4.53-smime/pine/smkeys.h +*** pine4.53/pine/smkeys.h Wed Dec 31 16:00:00 1969 +--- pine4.53-smime/pine/smkeys.h Wed Jan 15 12:48:15 2003 +*************** +*** 0 **** +--- 1,20 ---- ++ ++ #define PERSONAL_CERT struct personal_cert ++ ++ PERSONAL_CERT { ++ X509 *cert; ++ EVP_PKEY *key; ++ char *file; ++ PERSONAL_CERT *next; ++ }; ++ ++ X509_STORE *get_ca_store(const char *d); ++ ++ PERSONAL_CERT *get_personal_certs(const char *d); ++ ++ X509 *get_cert_for(const char *path,const char *email); ++ void save_cert_for(const char *path,char *email,X509 *cert); ++ ++ void personal_cert_free(PERSONAL_CERT **pc); ++ ++ char *get_x509_subject_email(X509 *x); diff --git a/contrib/utils/ansiprt.c b/contrib/utils/ansiprt.c new file mode 100755 index 00000000..17d6e068 --- /dev/null +++ b/contrib/utils/ansiprt.c @@ -0,0 +1,60 @@ +/* + * ansiprt.c + * + * Simple filter to wrap ANSI media copy escape sequences around + * text on stdin. Writes /dev/tty to get around things that might be + * trapping stdout. This is actually a feature because it was written + * to be used with pine's personal print option set up to take "enscript" + * output and send it displayward to be captured/printed to a postscript + * device. Pine, of course, uses popen() to invoke the personal print + * command, and interprets stdout as diagnostic messages from the command. + * + * Michael Seibel, mikes@cac.washington.edu + * + * 21 Apr 92 + * + */ +#include <stdio.h> +#include <sys/file.h> + +#define BUFSIZ 8192 + +main(argc, argv) +int argc; +char **argv; +{ + char c[BUFSIZ]; + int n, d; + int ctrld = 0; + + if(argc > 1){ + n = 0; + while(argc > ++n){ + if(argv[n][0] == '-'){ + switch(argv[n][1]){ + case 'd': + ctrld++; + break; + default : + fprintf(stderr,"unknown option: %c\n", argv[n][1]); + break; + } + } + } + } + + if((d=open("/dev/tty",O_WRONLY)) < 0){ + perror("/dev/tty"); + exit(1); + } + + write(d,"\033[5i", 4); + while((n=read(0, c, BUFSIZ)) > 0) + write(d, c, n); + + if(ctrld) + write(d, "\004", 1); + + write(d,"\033[4i", 4); + close(d); +} diff --git a/contrib/utils/brk2pine.sh b/contrib/utils/brk2pine.sh new file mode 100755 index 00000000..fab1f885 --- /dev/null +++ b/contrib/utils/brk2pine.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# T H E P I N E M A I L S Y S T E M +# +# Laurence Lundblade and Mike Seibel +# Networks and Distributed Computing +# Computing and Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, Washington, 98195, USA +# Internet: lgl@CAC.Washington.EDU +# mikes@CAC.Washington.EDU +# +# Please address all bugs and comments to "pine-bugs@cac.washington.edu" +# +# Copyright 1989, 1990, 1991, 1992 University of Washington +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose and without fee to the University of +# Washington is hereby granted, provided that the above copyright notice +# appears in all copies and that both the above copyright notice and this +# permission notice appear in supporting documentation, and that the name of +# the University of Washington not be used in advertising or publicity +# pertaining to distribution of the software without specific, written prior +# permission. This software is made available "as is", and +# THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, +# WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN +# NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL, +# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT +# (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# +# Pine is in part based on The Elm Mail System: +# *********************************************************************** +# * The Elm Mail System - $Revision: 2.13 $ $State: Exp $ * +# * * +# * Copyright (c) 1986, 1987 Dave Taylor * +# * Copyright (c) 1988, 1989 USENET Community Trust * +# *********************************************************************** +# +# + + + +# +# A filter to convert personal mail aliases in a .mailrc file into +# pine address book format. +# +# Usage: program [.mailrc] >> .addressbook +# +# Corey Satten, corey@cac.washington.edu, 9/25/91 +# +sed -n ' +# first fold continued lines (ending in \) into a single long line + /\\[ ]*$/ { + : more + s/\\//g + N + s/\n/ / + /\\/b more + } +# next convert all sequences of whitespace into single space + s/[ ][ ]*/ /g +# finally, reformat and print lines containing alias as the first word + /^ *alias / { + s/^ *alias \([!-~][!-~]*\) \(.*\)$/\1 \1 (\2)/ + s/ /,/g + s/(\([^,]*\))/\1/ + p + } +' ${*-$HOME/.mailrc} diff --git a/contrib/utils/mailtrfc.sh b/contrib/utils/mailtrfc.sh new file mode 100755 index 00000000..2ef69cdc --- /dev/null +++ b/contrib/utils/mailtrfc.sh @@ -0,0 +1,130 @@ +#!/bin/sh +# +# T H E P I N E M A I L S Y S T E M +# +# Laurence Lundblade and Mike Seibel +# Networks and Distributed Computing +# Computing and Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, Washington, 98195, USA +# Internet: lgl@CAC.Washington.EDU +# mikes@CAC.Washington.EDU +# +# Please address all bugs and comments to "pine-bugs@cac.washington.edu" +# +# Copyright 1991, 1992 University of Washington +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose and without fee is hereby granted, provided +# that the above copyright notice appears in all copies and that both the +# above copyright notice and this permission notice appear in supporting +# documentation, and that the name of the University of Washington not be +# used in advertising or publicity pertaining to distribution of the software +# without specific, written prior permission. This software is made +# available "as is", and +# THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, +# WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN +# NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL, +# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT +# (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# +# Pine is in part based on The Elm Mail System: +# *********************************************************************** +# * The Elm Mail System - $Revision: 2.13 $ $State: Exp $ * +# * * +# * Copyright (c) 1986, 1987 Dave Taylor * +# * Copyright (c) 1988, 1989 USENET Community Trust * +# *********************************************************************** +# +# + + +# +# mailtrfc.sh -- A shell script to analyze the mail traffic as logged in +# /usr/spool/mqueue/syslog*. This currently as the University of Washington +# domains wired in and needs to be made more general. Also, lots more +# formats of message ID's could be added. +# + + + +org=`awk '/^domain/ {print $2}' < /etc/resolv.conf` +domain=`echo $org | sed -e 's/^[^.]*\.//'` +host=`hostname`".$org" + +echo "Domain: $domain" +echo "Organization: $org" +echo "Hostname: $host" + +sed -n -e '/message-id/s/^.*</</p' | +awk 'BEGIN {mailers[0] = "Other"; + mailers[1] = "Pine"; + mailers[2] = "MailManager"; + mailers[3] = "sendmail"; + mailers[4] = "BITNET"; + mailers[5] = "? news ?"; + mailers[6] = "Sprint"; + mailers[7] = "X.400"; + mailers[8] = "Mac MS"; + mailers[9] = "MMDF"; + mailers[10] = "Andrew"; + mailers[11] = "Columbia MM"; + mailers[12] = "Unknown #1"; + mailers[13] = "EasyMail"; + mailers[14] = "CompuServe"; + mailers[15] = "smail"; + mailers[16] = "MCI Mail"; + mailers[17] = "VAX MAIL(?)"; + mailers[18] = "Gator Mail (?)"; + mailers[19] = "TOTAL"; + max = 19;} + {mailer = 0;} + /^<Pine/ {mailer = 1;} + /^<MailManager/ {mailer = 2;} + /^<[12]?[90]?9[0-9]1?[0-9][1-3]?[0-9]+\.[AaBb][AaBb][0-9]+@/ {mailer = 3;} + /^<[0-9A-Z]+@/ {mailer = 4;} + /^<199[0-9][A-Za-z]..[0-9]*\./ {mailer = 5;} + /@sprint.com/ {mailer = 6;} + /\/[A-Z]*=.*\/[A-Z]*=.*/ {mailer = 7;} + /^<MacMS\.[0-9]+\.[0-9]+\.[a-z]+@/ {mailer = 8;} + /^<MAILQUEUE-[0-9]+\.[0-9]+/ {mailer = 9;} + /^<.[d-l][A-Z0-9a-z=_]+00[A-Za-z0-9_=]+@/ {mailer = 10;} + /^<CMM\.[0-9]+\.[0-9]+\.[0-9]+/ {mailer = 11 ;} + /^<9[0-9][JFMASOND][aepuco][nbrylgptvc][0-9][0-9]?\.[0-9]+[a-z]+\./ {mailer = 12;} + /^<EasyMail\.[0-9]+/ {mailer = 13;} + /@CompuServe.COM/ {mailer = 14;} + /^<m[A-Za-z0-9].....-[0-9A-Za-z].....C@/ {mailer = 15;} + /@mcimail.com/ {mailer = 16;} + /^<9[0-9][01][0-9][0-3][0-9][0-2][0-9][0-5][0-9][0-5][0-9].[0-9a-z]*@/ {mailer = 17;} + /^<0[0-9][0-9]+\.[0-9][0-9][0-9][0-9]+\.[0-9][0-9]+@/ {mailer=18;} + + + + '"/$domain>/"' {campus[mailer]++; campus[max]++} + '"/$org>/"' {u[mailer]++; u[max]++} + '"/$host>/"' {milton[mailer]++; milton[max]++} + {total[mailer]++; total[max]++} + {if(mailer == 0) printf("-->%s\n",$0)} + END { + for(m = 0; m <= max; m++) { + printf("%-10.10s", mailers[m]); + printf(" %11d %11d %11d %11d %11d (%3d%%)\n", milton[m], u[m] - milton[m], campus[m] -u[m], total[m] - campus[m], total[m], (total[m]*100)/total[max]); + } + printf(" ---- (%3d%%) (%3d%%) (%3d%%) (%3d%%)\n", (milton[max]*100)/total[max], ((u[max] - milton[max])*100)/total[max], ((campus[max] - u[max])*100)/total[max], ((total[max] - campus[max])*100)/total[max], (u[max]*100)/total[max]); + + }' > /tmp/syslogx.$$ + + +echo $host $org $domain | \ + awk '{printf(" %.17s %.11s %.11s Off Campus Total\n", $1, $2, $3)}' +egrep -v 'TOTAL|----|^-->' /tmp/syslogx.$$ | sort +0.60rn +egrep 'TOTAL|----' /tmp/syslogx.$$ +grep '^-->' /tmp/syslogx.$$ | sed -e 's/-->//' > other-traffic +rm -f /tmp/syslogx.$$ + + diff --git a/contrib/utils/pwd2pine b/contrib/utils/pwd2pine new file mode 100755 index 00000000..49b3de65 --- /dev/null +++ b/contrib/utils/pwd2pine @@ -0,0 +1,73 @@ +#!/usr/local/bin/perl +# +# passwd2pine - translate passwd to pine addressbook +# +# Author: Paul J Murphy <pjm@dcs.ed.ac.uk> +# +# The fullname field will end up falling into one of the following cases: +# +# Last, First - A person +# }group: fullname - A non-person (determined by group or uid/gid < 1024) +# ~user: fullname - A non-person (determined by username or fullname) +# +# This allows the output to be sorted by fullname, with administrative +# accounts left to last (ie the real people come first). +# +# The comment field will contain the groupname of the user, followed by +# anything from the gecos field which seems to be a comment and not part +# of the name. + +### Start of configurable stuff + +$domain = "dcs.ed.ac.uk"; + +# Regular expression for groups which don't contain people +# Case is significant. +# The expression must match the entire groupname string. +$non_people_groups = "local|misc|aliens|cs_dept|\\d*"; + +# Regular expression for users which are not people +# Case is significant. +# The expression must match the entire username string. +$non_people_users = ".*\\d.*"; + +# Regular expression for words within a fullname which signify a non-person +# Case is not significant. +# The expression must match an entire word within the gecos fullname. +$non_people_names = "account|admin|user|system|crew|test|dummy|\ +unit|department|student|project|.*\\d.*"; + +### End of configurable stuff + +while(<>) { + chop; + ($user, $passwd, $uid, $gid, $gecos, $homedir, $shell) = split(/:/, $_, 7); + unless ($gr_name = getgrgid($gid)) { + $gr_name = $gid; + } + + $nickname = $user; + + $fullname = $gecos; + $fullname =~ s/,.*//g; # Reduce gecos to fullname + $fullname =~ s/[._]/ /g; # Translate alternate name separators to space + if ($uid < 1024 || + $gid < 1024 || + $gr_name =~ /^($non_people_groups)$/o) { + $fullname = "}$gr_name: $fullname"; + } elsif ($user =~ /^($non_people_users)$/o || + $fullname =~ /\b($non_people_names)\b/io) { + $fullname = "~$user: $fullname"; + } elsif (($firstname, $lastname, $gecos_comment) = + ($fullname =~ /^(.*)[._ ]([^._ \d(][^._ \d]+)( *\(.*)?$/)) { + $fullname = "$lastname, $firstname"; + } + + $address = "$user\@$domain"; + +# $fcc = ""; + + $comment = "$gr_name$gecos_comment"; + + print "$nickname\t$fullname\t$address\t$fcc\t$comment\n"; +} diff --git a/contrib/utils/sendit.sh b/contrib/utils/sendit.sh new file mode 100755 index 00000000..a8600f00 --- /dev/null +++ b/contrib/utils/sendit.sh @@ -0,0 +1,49 @@ +#! /bin/sh +# +# $Id:$ +# +# T H E P I N E M A I L S Y S T E M +# +# Mike Seibel +# Networks and Distributed Computing +# Computing and Communications +# University of Washington +# Administration Building, AG-44 +# Seattle, Washington, 98195, USA +# Internet: mikes@CAC.Washington.EDU +# +# Please address all bugs and comments to "pine-bugs@cac.washington.edu" +# +# +# Pine and Pico are registered trademarks of the University of Washington. +# No commercial use of these trademarks may be made without prior written +# permission of the University of Washington. +# +# Pine, Pico, and Pilot software and its included text are Copyright +# 1989-1996 by the University of Washington. +# +# The full text of our legal notices is contained in the file called +# CPYRIGHT, included with this distribution. +# + + +# +# Simple script to expedite mail posting at the expense of timely +# error reporting and 8BITMIME support. +# +# NOTE: If for any reason POSTFILE below is created on an nfs mounted +# file system, the trap statement below must get removed or +# altered, and the last line must get replaced with: +# +# ( ${POSTTOOL} ${POSTARGS} < ${POSTFILE} ; rm -f ${POSTFILE} ) & +# + +POSTFILE=/tmp/send$$ +POSTTOOL=/usr/lib/sendmail +POSTARGS="-oi -oem -t" + +umask 077 +trap "rm -f ${POSTFILE}; exit 0" 0 1 2 13 15 + +cat > ${POSTFILE} +${POSTTOOL} ${POSTARGS} < ${POSTFILE} & diff --git a/contrib/utils/sendtoall b/contrib/utils/sendtoall new file mode 100644 index 00000000..f929f527 --- /dev/null +++ b/contrib/utils/sendtoall @@ -0,0 +1,49 @@ +Date: 13 Sep 1996 22:49:40 GMT +From: William Burrow <aa126@fan1.fan.nb.ca> +To: pine-info@cac.washington.edu +Subject: Re: Composing mail to all entries in addressbook + +Ari Y. Weintraub (aweintra@umabnet.ab.umd.edu) wrote: +: Is there any way in Pine 3.94 to compose mail to all of the entries in my +: addressbook. For example, I recently changed my phone number and would +: like to send one message to everybody with the new number. I can't find a +: way to do this - does anyone out there have any idea? TIA + +This would be a nice suggestion for Pine, however, you can do pretty much +the same in Unix using the following awk script. Copy everything between the +lines into a file named: everyone + +-------------%< clip -------------- +{ + printf("everyone\tEveryone\t("); + + if ( $0 ~ /\(/ ) + continue + else + printf("%s", $1); + + while ( getline > 0 ) { + if ( $0 ~ /\(/ ) + continue + else + printf(",%s", $1); + } + printf(")\n"); +} +-------------%< clip -------------- + +Now, type the following exactly at the Unix prompt: + +awk -f everyone < ~/.addressbook >> ~/.addressbook && echo Done. + +You may wish to first copy your ~/.addressbook file to a backup file, +before executing this command. + +If everything went well, it should say Done. When you start Pine, there +should now be a list in your address book with the name: everyone, +containing all of your address book nicknames (but NOT other lists). + + +-- +-- +William Burrow -- Fredericton Area Network diff --git a/contrib/utils/txtcc.sh b/contrib/utils/txtcc.sh new file mode 100755 index 00000000..0d1bb8c9 --- /dev/null +++ b/contrib/utils/txtcc.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +# On ultrix, this will compile string constants into the text segment +# where they can be shared. Use to build pine by saying: +# +# build CC=txtcc gul +# +# As of Pine4.03, this moves about 675k per process into sharable memory +# +# Corey Satten, corey@cac.washington.edu, 9/11/98 + +TMP1=/tmp/cc$$.s +TMP2=/tmp/cc$$.delayed +trap "rm -f $TMP1 $TMP2" 0 1 2 13 15 + + +for i in "$@"; do + case "$oflag//$i" in + //-c) cflag=1;; + //-o) oflag=1;; + //-g) gflag=1;; + 1//*) ofile="$i"; oflag=;; + //*.c) cfile="$i"; : ${ofile=`basename $i .c`.o};; + esac +done + +case "$cflag" in + 1) sfile=`basename $ofile .o`.s + gcc -S "$@" + + # Postprocess assembly language to move strings to .text segment. + # + # Use sed to collect .ascii statements and their preceding label + # and .align and emit those to $TMP2, the rest to $TMP1. + # + # Start in state0 + # In state0: if .align is seen -> state1; else print, -> state0 + # In state1: if a label is seen -> state2; else -> punt + # In state2: if .ascii is seen -> state3; else -> punt + # In state3: if .ascii is seen -> state4; else write TMP2, -> state0 + # In state4: append to buffer, -> state3 + # In state punt: print unprinted lines, -> state0 + # + sed ' + : state0 + /^[ ][ ]*\.align/ b state1 + b + + : state1 + h + N; s/^[^\n]*\n//; H + /^[!-~]*:/ b state2 + b punt + + : state2 + N; s/^[^\n]*\n//; H + /^[ ][ ]*\.ascii/ b state3 + b punt + + : state3 + N; s/^[^\n]*\n// + /^[ ][ ]*\.ascii/ b state4 + x + w '"$TMP2"' + x + b state0 + + : state4 + H + b state3 + + : punt + x + ' $sfile > $TMP1 + + # now add the deferred .ascii statements to .text segment in $TMP1 + echo ' .text' >> $TMP1 + cat $TMP2 >> $TMP1 + + rm `basename $ofile .o`.s + gcc ${gflag+"-g"} -c -o $ofile $TMP1 + ;; + *) gcc "$@" + ;; +esac |