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