First off, let me say that I didn't mean to suggest stepping on anyone's
toes -- I certainly feel that Dan and Doug have done the world a
service, and I respect that.  I threw out SF as an option because it
would let us beat on an `interim release' or similar without disturbing
the current mhost stuff.  I almost suggested Savanah, but I wanted to
avoid the license question; it sounds like that's a non-issue.  If
Doug's willing, we're probably better off just working from a new module
on mhost.  

Given that mh and nmh are both pretty stable at this point, and that
there are issues with major features and compatibility (such as the
pernicious date parsing discussions), I'd suggest starting work on a new
`major version', so we wouldn't need to feel so bad about the various
incompatibilities.  Aside from the bug fixes, I think we've agreed in
the past that mh should have better support for MIME and IMAP, and that
both will be somewhat complicated; I'm sure that there are other big
projects to consider.  

I suggested 1 Jan as a minimum time to give everyone a chance to
respond; like Ken, I am moved by the quick responses.  Let's see what we
can do.

To directly address Peter's question: yes, I believe that the proposed
changes could be nice for mh-e support, but they don't seem very close
to `deployed' to me right now.  I'm including Jon Stienhart's message
below.

chad


------- Forwarded Message
Message-Id: <[EMAIL PROTECTED]>
To: [EMAIL PROTECTED]
Subject: again - New code that simplifies user interface for MIME attachments to a 
draft
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0"
Content-ID: <[EMAIL PROTECTED]>
Date: Thu, 05 Jul 2001 08:01:34 -0700
From: Jon Steinhart <[EMAIL PROTECTED]>

- ------- =_aaaaaaaaaa0
Content-Type: text/plain; name="sendQ59nO_"; x-unix-mode="0644";
        charset="us-ascii"
Content-ID: <[EMAIL PROTECTED]>
Content-Description:  English text 

Well, that was embarassing.  A last minute fix created a bug in the stuff that
I just sent.  Here it is again with the bug fixed...

I posted some code that improved the user interface for adding MIME attachments
to messages some months ago.  While the code worked fine, I didn't really like
the way that I had implemented it and have subsequently done it over.  Since I
don't have write access to the CVS, please look it over and check it in if it
looks OK to you.  If it doesn't, let me know what you think should be changed.
Here's the contents of the README-ATTACHMENTS file that is included with the
patches:


Jon Steinhart's ([EMAIL PROTECTED]) Attachment Handling Patches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Attached are a set of patches designed to improve the nmh user interface
for handling MIME attachments.

Why Did I Do This?
~~~~~~~~~~~~~~~~~~

Although nmh contains the necessary functionality for MIME message handing,
the interface to this functionality is pretty obtuse.  There's no way that
I'm ever going to convince my partner to write mhbuild composition files!
And even though I know how to write them, I often screw up when sending a
message that contains a shell script because I forget that I have to double
any # at the start of a line, which shell scripts have galore.

These patches simplify the task of managing attachments on draft files.
They allow attachments to be added, listed, and deleted.  MIME messages are
automatically created when drafts with attachments are sent.

Did I Do This Correctly?
~~~~~~~~~~~~~~~~~~~~~~~~

Hard to say.  Despite lots of time looking at the nmh code, I can't say that
I get the philosophy behind its structure.

I am aware of two deviations from what I saw in the nmh code.

 1.  I commented my changes.

 2.  It's been years since I've used a VT-100, so I don't try to make code
     fit into 80 columns anymore.  Seems silly to me.

What Did I Do?
~~~~~~~~~~~~~~

I made changes to the following files:

        h/
                prototypes.h
        man/
                anno.man
                send.man
                whatnow.man
        uip/
                Makefile.in
                anno.c
                annosbr.c
                send.c
                sendsbr.c
                viamail.c       (needed change for new sendsbr argument)
                whatnowsbr.c

Attachments are associated with messages using header fields.  For example, a
draft that looks like this

        To: jon
        Subject: test of attachments
        X-MH-Attachment: /export/home/jon/foo
        X-MH-Attachment: /export/home/jon/bar
        X-MH-Attachment: /export/home/jon/test/foo
        --------

has the files "foo", "bar", and foo as attachments.

Although I use the header field name "X-MH-Attachment" to indicate
attachments, the implementation allows any header field name.

The advantage of using header fields is that the list of attachments
travels with the draft so it remains valid across editing sessions.

Note that the header fields for attachments are removed from the message
before it is sent.

Since I was adding header fields to messages, it seemed sensible to use the
existing anno program to do this for me.  This required several changes to
generalize anno:

 o  I added a -draft option that permits annotations (header fields) to
    be added to the draft instead of a message sequence.

 o  I added a -delete option that allows annotations to be deleted.

 o  I added a -list option that allows annotations to be listed.

 o  I added a -number option that modifies the behavior of -delete and -list.

Using the modified anno, the example above could be created (assuming that the
draft exists) by

        prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/foo -nodate
        prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/bar -nodate
        prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/test/foo 
-nodate

One can quite easily make an "attach" command using shell scripts, aliases or 
functions.
For example, here's a bash function that does the job:

        function attach() { for i in $*; do anno -nodate -draft -comp X-MH-Attachment 
-text "$i"; done; }

The following examples show the different ways in which attachments can be
listed.

        prompt% anno -list -draft -comp X-MH-Attachment
        foo
        bar
        foo

        prompt% anno -list -draft -comp X-MH-Attachment -text /
        /export/home/jon/foo
        /export/home/jon/bar
        /export/home/jon/test/foo

        prompt% anno -list -draft -comp X-MH-Attachment -number
        1       foo
        2       bar
        3       foo

        prompt% anno -list -draft -comp X-MH-Attachment -text / -number
        1       /export/home/jon/foo
        2       /export/home/jon/bar
        3       /export/home/jon/test/foo

        prompt%

Why all these listing options?

I feel that the listing as produced by the first example is what most people
would want most of the time.

The listing as produced by the second example seemed necessary for situations
where there were several attachments with the same file name in different
directories.

I included the numbering option so that attachments could be deleted by number
which might be easier in situations where there were several attachments with
the same file name in different directories, as in the above example.

Attachments are deleted using the -delete option.

        prompt% anno -delete -draft -comp X-MH-Attachment -text foo

deletes the first attachment since the foo matches the basename of the attachment
name.

        prompt% anno -delete -draft -comp X-MH-Attachment -text 
/export/home/jon/test/foo

deletes the third attachment since the text is a full path name and matches.

        prompt% anno -delete -draft -comp X-MH-Attachment -number 2

deletes the second attachment.

The attachment annotations are converted to a MIME message by send.  I'm not
completely sure that this is the right place to do it, as opposed to doing
it in post, but both would work.  It seemed to me to make more sense to do
it in send so that all of the various post options would apply to the MIME
message instead of the original draft file.

I added an -attach option to send that specifies the header field name used
for attachments.  Send performs the following additional steps if this option
is set:

 o  It scans the header of the draft for attachments.  Normal behavior applies
    if none exist.

 o  A temporary mhbuild composition file is created if there are attachments.

 o  All non-attachment headers are copied from the draft file to the
    composition file.

 o  The body of the draft is copied to a temporary body file if it contains at
    least one non-whitespace character.  A mhbuild directive for this file is
    appended to the composition file.  Note that this eliminates the problem
    of lines beginning with the # character in the message body.

 o  A mhbuild directive is appended to the composition file for each attachment
    header.

 o  mhbuild is run on the composition file, converting it to a MIME message.

 o  The converted composition file is substituted for the original draft file
    and run through the rest of send.

 o  The original draft file is renamed instead of the converted composition
    file.  This preserves the original message instead of the composition file
    which is really what a user would want.

 o  The ,file.orig file created by mhbuild is removed as it's a nuisance.

The mhbuild directives appended to the composition file are constructed as
follows:

 o  The content-type a file with a dot-suffix is obtained from the list of
    mhshow-suffix- entries in the profile.

 o  A file with no dot-suffix or no entry in the profile is assigned a
    content-type of application/octet-stream if it contains any non-ASCII
    characters.

 o  A file with no dot-suffix or no entry in the profile is assigned a
    content-type of text/plain if it contains only ASCII characters.

 o  The directive is of the form:

        #content-type; name="basename"; x-unix-mode=mode [ description ] filename

    The content type is derived as discussed above.  The basename is the
    last component of the pathname given in the body of the attachment header
    field.  The mode is file permissions.  The description is obtained by
    running the file command on the attachment file.  The filename is the
    field body from the attachment header field.

I added a -attach option to whatnow that specifies the header field name for
attachments.

I added to the commands available at the whatnow prompt to provide an interface
to the attachment mechanism.

I'm not completely happy with some of these additions because they duplicate
shell functionality.  I'm not sure that there's a good way around it other than
to not use whatnow.

The first three additions (the ones I'm not happy with) are cd, ls, and pwd.
These do the same thing as their system counterparts.  As a matter of fact,
these are implemented by running the commands in a subshell.  I did this because
I wanted proper interpretation of shell-specific things like ~ and wildcard
expansion.

The next command is attach.  This takes a list of files and adds them to the draft
as attachments using the same code as the modified anno.  The list of files is
run through ls using the user's shell, so wildcard expansion and all that works.

The alist command lists the attachments on the current draft using listing function
that I added to anno.  It takes two optional options, -l for a long listing meaning
full path names, and -n for a numbered listing.

The detach command removes attachments from the current draft, again using the
modified anno.  The arguments are interpreted as numbers if the -n option is used,
otherwise they're interpreted as file names.  File names are shoveled through ls
using the user's shell in the directory containing the file for wildcard expansion
and such.  File names are matched against the last pathname component unless they
begin with a / in which case they're matched against the entire name.

What's Left To Do?
~~~~~~~~~~~~~~~~~~

Nothing on this stuff.  When I get time I'd like to improve the interface
for reading messages with attachments.  It's my opinion that, because of the
command line nature of nmh, it makes the most sense to treat attachments as
separate messages.  In other words one should be able to read the next
attachment using next, and the previous one using prev.  One should be able
to show and scan attachments.  This would probably involve a major change
in the message numbering scheme to allow something like 123.4 to indicate
attachment 4 on message 123.

- ------- =_aaaaaaaaaa0
Content-Type: text/plain; name="patches"; x-unix-mode="0644";
        charset="us-ascii"
Content-ID: <[EMAIL PROTECTED]>
Content-Description:  ascii text 

diff -Naur nmh/README-ATTACHMENTS nmh-jon/README-ATTACHMENTS
- --- nmh/README-ATTACHMENTS    Wed Dec 31 16:00:00 1969
+++ nmh-jon/README-ATTACHMENTS  Wed Jul  4 20:04:53 2001
@@ -0,0 +1,254 @@
+Jon Steinhart's ([EMAIL PROTECTED]) Attachment Handling Patches
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Attached are a set of patches designed to improve the nmh user interface
+for handling MIME attachments.
+
+Why Did I Do This?
+~~~~~~~~~~~~~~~~~~
+
+Although nmh contains the necessary functionality for MIME message handing,
+the interface to this functionality is pretty obtuse.  There's no way that
+I'm ever going to convince my partner to write mhbuild composition files!
+And even though I know how to write them, I often screw up when sending a
+message that contains a shell script because I forget that I have to double
+any # at the start of a line, which shell scripts have galore.
+
+These patches simplify the task of managing attachments on draft files.
+They allow attachments to be added, listed, and deleted.  MIME messages are
+automatically created when drafts with attachments are sent.
+
+Did I Do This Correctly?
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Hard to say.  Despite lots of time looking at the nmh code, I can't say that
+I get the philosophy behind its structure.
+
+I am aware of two deviations from what I saw in the nmh code.
+
+ 1.  I commented my changes.
+
+ 2.  It's been years since I've used a VT-100, so I don't try to make code
+     fit into 80 columns anymore.  Seems silly to me.
+
+What Did I Do?
+~~~~~~~~~~~~~~
+
+I made changes to the following files:
+
+       h/
+               prototypes.h
+       man/
+               anno.man
+               send.man
+               whatnow.man
+       uip/
+               Makefile.in
+               anno.c
+               annosbr.c
+               send.c
+               sendsbr.c
+               viamail.c       (needed change for new sendsbr argument)
+               whatnowsbr.c
+
+Attachments are associated with messages using header fields.  For example, a
+draft that looks like this
+
+       To: jon
+       Subject: test of attachments
+       X-MH-Attachment: /export/home/jon/foo
+       X-MH-Attachment: /export/home/jon/bar
+       X-MH-Attachment: /export/home/jon/test/foo
+       --------
+
+has the files "foo", "bar", and foo as attachments.
+
+Although I use the header field name "X-MH-Attachment" to indicate
+attachments, the implementation allows any header field name.
+
+The advantage of using header fields is that the list of attachments
+travels with the draft so it remains valid across editing sessions.
+
+Note that the header fields for attachments are removed from the message
+before it is sent.
+
+Since I was adding header fields to messages, it seemed sensible to use the
+existing anno program to do this for me.  This required several changes to
+generalize anno:
+
+ o  I added a -draft option that permits annotations (header fields) to
+    be added to the draft instead of a message sequence.
+
+ o  I added a -delete option that allows annotations to be deleted.
+
+ o  I added a -list option that allows annotations to be listed.
+
+ o  I added a -number option that modifies the behavior of -delete and -list.
+
+Using the modified anno, the example above could be created (assuming that the
+draft exists) by
+
+       prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/foo -nodate
+       prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/bar -nodate
+       prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/test/foo 
+-nodate
+
+One can quite easily make an "attach" command using shell scripts, aliases or 
+functions.
+For example, here's a bash function that does the job:
+
+       function attach() { for i in $*; do anno -nodate -draft -comp X-MH-Attachment 
+-text "$i"; done; }
+
+The following examples show the different ways in which attachments can be
+listed.
+
+       prompt% anno -list -draft -comp X-MH-Attachment
+       foo
+       bar
+       foo
+
+       prompt% anno -list -draft -comp X-MH-Attachment -text /
+       /export/home/jon/foo
+       /export/home/jon/bar
+       /export/home/jon/test/foo
+
+       prompt% anno -list -draft -comp X-MH-Attachment -number
+       1       foo
+       2       bar
+       3       foo
+
+       prompt% anno -list -draft -comp X-MH-Attachment -text / -number
+       1       /export/home/jon/foo
+       2       /export/home/jon/bar
+       3       /export/home/jon/test/foo
+
+       prompt%
+
+Why all these listing options?
+
+I feel that the listing as produced by the first example is what most people
+would want most of the time.
+
+The listing as produced by the second example seemed necessary for situations
+where there were several attachments with the same file name in different
+directories.
+
+I included the numbering option so that attachments could be deleted by number
+which might be easier in situations where there were several attachments with
+the same file name in different directories, as in the above example.
+
+Attachments are deleted using the -delete option.
+
+       prompt% anno -delete -draft -comp X-MH-Attachment -text foo
+
+deletes the first attachment since the foo matches the basename of the attachment
+name.
+
+       prompt% anno -delete -draft -comp X-MH-Attachment -text 
+/export/home/jon/test/foo
+
+deletes the third attachment since the text is a full path name and matches.
+
+       prompt% anno -delete -draft -comp X-MH-Attachment -number 2
+
+deletes the second attachment.
+
+The attachment annotations are converted to a MIME message by send.  I'm not
+completely sure that this is the right place to do it, as opposed to doing
+it in post, but both would work.  It seemed to me to make more sense to do
+it in send so that all of the various post options would apply to the MIME
+message instead of the original draft file.
+
+I added an -attach option to send that specifies the header field name used
+for attachments.  Send performs the following additional steps if this option
+is set:
+
+ o  It scans the header of the draft for attachments.  Normal behavior applies
+    if none exist.
+
+ o  A temporary mhbuild composition file is created if there are attachments.
+
+ o  All non-attachment headers are copied from the draft file to the
+    composition file.
+
+ o  The body of the draft is copied to a temporary body file if it contains at
+    least one non-whitespace character.  A mhbuild directive for this file is
+    appended to the composition file.  Note that this eliminates the problem
+    of lines beginning with the # character in the message body.
+
+ o  A mhbuild directive is appended to the composition file for each attachment
+    header.
+
+ o  mhbuild is run on the composition file, converting it to a MIME message.
+
+ o  The converted composition file is substituted for the original draft file
+    and run through the rest of send.
+
+ o  The original draft file is renamed instead of the converted composition
+    file.  This preserves the original message instead of the composition file
+    which is really what a user would want.
+
+ o  The ,file.orig file created by mhbuild is removed as it's a nuisance.
+
+The mhbuild directives appended to the composition file are constructed as
+follows:
+
+ o  The content-type a file with a dot-suffix is obtained from the list of
+    mhshow-suffix- entries in the profile.
+
+ o  A file with no dot-suffix or no entry in the profile is assigned a
+    content-type of application/octet-stream if it contains any non-ASCII
+    characters.
+
+ o  A file with no dot-suffix or no entry in the profile is assigned a
+    content-type of text/plain if it contains only ASCII characters.
+
+ o  The directive is of the form:
+
+       #content-type; name="basename"; x-unix-mode=mode [ description ] filename
+
+    The content type is derived as discussed above.  The basename is the
+    last component of the pathname given in the body of the attachment header
+    field.  The mode is file permissions.  The description is obtained by
+    running the file command on the attachment file.  The filename is the
+    field body from the attachment header field.
+
+I added a -attach option to whatnow that specifies the header field name for
+attachments.
+
+I added to the commands available at the whatnow prompt to provide an interface
+to the attachment mechanism.
+
+I'm not completely happy with some of these additions because they duplicate
+shell functionality.  I'm not sure that there's a good way around it other than
+to not use whatnow.
+
+The first three additions (the ones I'm not happy with) are cd, ls, and pwd.
+These do the same thing as their system counterparts.  As a matter of fact,
+these are implemented by running the commands in a subshell.  I did this because
+I wanted proper interpretation of shell-specific things like ~ and wildcard
+expansion.
+
+The next command is attach.  This takes a list of files and adds them to the draft
+as attachments using the same code as the modified anno.  The list of files is
+run through ls using the user's shell, so wildcard expansion and all that works.
+
+The alist command lists the attachments on the current draft using listing function
+that I added to anno.  It takes two optional options, -l for a long listing meaning
+full path names, and -n for a numbered listing.
+
+The detach command removes attachments from the current draft, again using the
+modified anno.  The arguments are interpreted as numbers if the -n option is used,
+otherwise they're interpreted as file names.  File names are shoveled through ls
+using the user's shell in the directory containing the file for wildcard expansion
+and such.  File names are matched against the last pathname component unless they
+begin with a / in which case they're matched against the entire name.
+
+What's Left To Do?
+~~~~~~~~~~~~~~~~~~
+
+Nothing on this stuff.  When I get time I'd like to improve the interface
+for reading messages with attachments.  It's my opinion that, because of the
+command line nature of nmh, it makes the most sense to treat attachments as
+separate messages.  In other words one should be able to read the next
+attachment using next, and the previous one using prev.  One should be able
+to show and scan attachments.  This would probably involve a major change
+in the message numbering scheme to allow something like 123.4 to indicate
+attachment 4 on message 123.
diff -Naur nmh/h/prototypes.h nmh-jon/h/prototypes.h
- --- nmh/h/prototypes.h        Tue May  9 14:44:16 2000
+++ nmh-jon/h/prototypes.h      Wed Jul  4 18:30:34 2001
@@ -154,11 +154,12 @@
 /*
  * prototypes for some routines in uip
  */
- -int annotate (char *, char *, char *, int, int);
+int annotate (char *, char *, char *, int, int, int);
+void annolist(char *, char *, char *, int);
 int distout (char *, char *, char *);
 void replout (FILE *, char *, char *, struct msgs *, int,
        int, char *, char *, char *);
- -int sendsbr (char **, int, char *, struct stat *, int);
+int sendsbr (char **, int, char *, struct stat *, int, char *);
 int what_now (char *, int, int, char *, char *,
        int, struct msgs *, char *, int, char *);
 
diff -Naur nmh/man/anno.man nmh-jon/man/anno.man
- --- nmh/man/anno.man  Thu Jan 25 13:15:52 2001
+++ nmh-jon/man/anno.man        Wed Jul  4 18:29:33 2001
@@ -15,6 +15,11 @@
 .IR field ]
 .RB [ \-inplace " | " \-noinplace ]
 .RB [ \-date " | " \-nodate ]
+.RB [ \-draft ]
+.RB [ \-list ]
+.RB [ \-delete ]
+.RB [ \-number
+.IR [ num ]]
 .RB [ \-version ]
 .RB [ \-help ]
 .RB [ \-text
@@ -22,8 +27,22 @@
 .ad
 .SH DESCRIPTION
 .B Anno
- -annotates the specified messages in the named folder using
- -the field and body.
+manipulates header fields or
+.I annotations
+in messages.
+Header fields consist of a field name and an optional field body
+as defined by RFC-2822.
+The
+.B -component
+option specifies the field name, and the
+.B -text
+option specifies the field body.
+.PP
+The messages are either the
+.I msgs
+in the named folder, or the draft if invoked with the
+.B -draft
+option.
 .PP
 Usually, annotation is performed by the commands
 .BR dist ,
@@ -57,8 +76,8 @@
 .B anno
 will prompt the user for the name of field for the annotation.
 .PP
- -The field specified should be a valid 822-style message field name,
- -which means that it should consist of alphanumerics (or dashes) only.
+The field specified must be a valid 2822-style message field name,
+which means that it may only consist of alphanumerics and dashes,
 The body specified is arbitrary text.
 .PP
 Normally
@@ -67,7 +86,45 @@
 any links to the message.  You may change this by using the
 .B \-noinplace
 switch.
- -
+.PP
+The
+.B -list
+option produces a listing of the field bodies for header fields with
+names matching the specified component, one per line.
+The listing is numbered, starting at 1, if the
+.B -number
+option is also used.
+A tab character separates the number and the field body.
+The field body is treated as if it is a file name, and only the final
+path name component is listed.
+The complete field body is listed if the
+.B -text
+option is used, the contents of the text are ignored.
+.PP
+The
+.B -delete
+option removes header fields from messages.
+The first header field whose name matches the component is deleted if
+no other options are specified.
+If the
+.B -text
+option is used in conjunction with the
+.B -delete
+option, the first header field whose name matches the component and
+whose body matches the text is deleted.
+The text is treated as if it was a file name; if it begins with a
+slash, the entire field body must match the text, otherwise just the
+last path name component of the field body must match.
+If the
+.B -number
+option is used in conjuction with the
+.B -delete
+option, header field
+.I num
+whose name matches the component is deleted.
+The number matches that which is produced by the
+.B -list
+option.
 .SH FILES
 .fc ^ ~
 .nf
diff -Naur nmh/man/send.man nmh-jon/man/send.man
- --- nmh/man/send.man  Thu Jan 25 13:15:59 2001
+++ nmh-jon/man/send.man        Wed Jul  4 18:29:36 2001
@@ -40,6 +40,8 @@
 \&...] 
 .RB [ \-version ]
 .RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
 .ad
 .SH DESCRIPTION
 .B Send
@@ -63,6 +65,41 @@
 .B send
 are actually performed by
 .BR post .
+
+.PP
+If a
+.I header-field-name
+is supplied using the
+.B -attach
+option, the draft is scanned for a header whose field name matches the
+supplied
+.IR header-field-name .
+The draft is converted to a MIME message if one or more matches are found.
+This conversion occurs before all other processing.
+.PP
+The first part of the MIME message is the draft body if that body contains
+any non-blank characters.
+The body of each header field whose name matches the
+.I header-field-name
+is interpreted as a file name, and each file named is included as a separate
+part in the MIME message.
+.PP
+For file names with dot suffixes, the context is scanned for a
+.I mhshow-suffix-
+entry for that suffix.
+The content-type for the part is taken from that context entry if a match is
+found.
+If no match is found or the file does not have a dot suffix, the content-type
+is text/plain if the file contains only ASCII characters or application/octet-stream
+if it contains characters outside of the ASCII range.
+.PP
+Each part contains a name attribute that is the last component of the path name.
+A
+.I x-unix-mode
+attribute containing the file mode accompanies each part.
+Finally, a description attribute is generated by running the
+.I file
+command on the file.
 .PP
 If
 .B \-push
diff -Naur nmh/man/whatnow.man nmh-jon/man/whatnow.man
- --- nmh/man/whatnow.man       Thu Jan 25 13:16:00 2001
+++ nmh-jon/man/whatnow.man     Wed Jul  4 18:29:39 2001
@@ -22,6 +22,8 @@
 .RI [ file ]
 .RB [ \-version ]
 .RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
 .ad
 .SH DESCRIPTION
 .B Whatnow
@@ -45,18 +47,18 @@
 and awaits a response.  The valid responses are:
 .PP
 .RS 5
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B edit
 re\-edit using the same editor that was used on the
 preceding round unless a profile entry
 \*(lq<lasteditor>\-next: <editor>\*(rq names an alternate editor
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B edit <editor>
 invoke <editor> for further editing
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B refile +folder
 refile the draft into the given folder
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B mime
 process the draft as MIME composition file using
 the
@@ -64,38 +66,56 @@
 command
 .RB ( mhbuild
 by default)
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B display
 list the message being distributed/replied\-to
 on the terminal
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B list
 list the draft on the terminal
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B send
 send the message
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B send \-watch
 send the message and monitor the delivery process
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B push
 send the message in the background
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B whom
 list the addresses that the message will go to
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B whom \-check
 list the addresses and verify that they are
 acceptable to the transport service
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B quit
 preserve the draft and exit
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B quit \-delete
 delete the draft and exit
- -.TP \w'refilezzfolderz'u
+.TP \w'refilezzzzfolderz'u
 .B delete
 delete the draft and exit
+.TP \w'refilezzzzfolderz'u
+.B cd directory
+use the directory when interpreting attachment file names
+.TP \w'refilezzzzfolderz'u
+.B pwd
+print the working directory for attachment files
+.TP \w'refilezzzzfolderz'u
+.B ls [ls-options]
+list files in the attachment working directory using the ls command
+.TP \w'refilezzzzfolderz'u
+.B attach files
+add the named files to the draft as MIME attachments
+.TP \w'refilezzzzfolderz'u
+.B alist [-ln]
+list the MIME attachments, either short, long [-l] or numbered [-n]
+.TP \w'refilezzzzfolderz'u
+.B detach [-n] files-or-numbers
+remove MIME attachments, either by file name or by number with -n
 .RE
 .PP
 When entering your response, you need only type enough characters
diff -Naur nmh/uip/anno.c nmh-jon/uip/anno.c
- --- nmh/uip/anno.c    Fri Feb  4 12:28:23 2000
+++ nmh-jon/uip/anno.c  Wed Jul  4 18:29:56 2001
@@ -3,6 +3,42 @@
  * anno.c -- annotate messages
  *
  * $Id: anno.c,v 1.3 2000/02/04 20:28:23 danh Exp $
+ *
+ *     Four new options have been added: delete, list, number, and draft.
+ *     Message header fields are used by the new MIME attachment code in
+ *     the send command.  Adding features to generalize the anno command
+ *     seemed to be a better approach than the creation of a new command
+ *     whose features would overlap with those of the anno command.
+ *
+ *     The -draft option causes anno to operate on the current draft file
+ *     instead of on a message sequence.
+ *
+ *     The -delete option deletes header elements that match the -component
+ *     field name.  If -delete is used without the -text option, the first
+ *     header field whose field name matches the component name is deleted.
+ *     If the -delete is used with the -text option, and the -text argument
+ *     begins with a /, the first header field whose field name matches the
+ *     component name and whose field body matches the text is deleted.  If
+ *     the -text argument does not begin with a /, then the text is assumed
+ *     to be the last component of a path name, and the first header field
+ *     whose field name matches the component name and a field body whose
+ *     last path name component matches the text is deleted.  If the -delete
+ *     option is used with the new -number option described below, the nth
+ *     header field whose field name matches the component name is deleted.
+ *     No header fields are deleted if none of the above conditions are met.
+ *
+ *     The -list option outputs the field bodies from each header field whose
+ *     field name matches the component name, one per line.  If no -text
+ *     option is specified, only the last path name component of each field
+ *     body is output.  The entire field body is output if the -text option
+ *     is used; the contents of the -text argument are ignored.  If the -list
+ *     option is used in conjuction with the new -number option described
+ *     below, each line is numbered starting with 1.  A tab separates the
+ *     number from the field body.
+ *
+ *     The -number option works with both the -delete and -list options as
+ *     described above.  The -number option takes an optional argument.  A
+ *     value of 1 is assumed if this argument is absent.
  */
 
 #include <h/mh.h>
@@ -31,6 +67,14 @@
     { "version", 0 },
 #define        HELPSW  7
     { "help", 0 },
+#define        DRFTSW                   8
+    { "draft", 2 },
+#define        LISTSW                   9
+    { "list", 1 },
+#define        DELETESW                10
+    { "delete", 2 },
+#define        NUMBERSW                11
+    { "number", 2 },
     { NULL, 0 }
 };
 
@@ -49,6 +93,11 @@
     char *text = NULL, *folder = NULL, buf[BUFSIZ];
     char **argp, **arguments, **msgs;
     struct msgs *mp;
+    int                delete = -1;            /* delete header element if set */
+    char       *draft = (char *)0;     /* draft file name */
+    int                isdf = 0;               /* return needed for m_draft() */
+    int                list = 0;               /* list header elements if set */
+    int                number = 0;             /* delete specific number of like 
+elements if set */
 
 #ifdef LOCALE
     setlocale(LC_ALL, "");
@@ -73,13 +122,13 @@
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, switches)) {
- -             case AMBIGSW: 
+               case AMBIGSW:
                    ambigsw (cp, switches);
                    done (1);
- -             case UNKWNSW: 
+               case UNKWNSW:
                    adios (NULL, "-%s unknown", cp);
 
- -             case HELPSW: 
+               case HELPSW:
                    snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
                        invo_name);
                    print_help (buf, switches, 1);
@@ -88,33 +137,59 @@
                    print_version(invo_name);
                    done (1);
 
- -             case COMPSW: 
+               case COMPSW:
                    if (comp)
                        adios (NULL, "only one component at a time!");
                    if (!(comp = *argp++) || *comp == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
                    continue;
 
- -             case DATESW: 
+               case DATESW:
                    datesw++;
                    continue;
- -             case NDATESW: 
+               case NDATESW:
                    datesw = 0;
                    continue;
 
- -             case INPLSW: 
+               case INPLSW:
                    inplace++;
                    continue;
- -             case NINPLSW: 
+               case NINPLSW:
                    inplace = 0;
                    continue;
 
- -             case TEXTSW: 
+               case TEXTSW:
                    if (text)
                        adios (NULL, "only one body at a time!");
                    if (!(text = *argp++) || *text == '-')
                        adios (NULL, "missing argument to %s", argp[-2]);
                    continue;
+
+               case DELETESW:          /* delete annotations */
+                   delete = 0;
+                   continue;
+
+               case DRFTSW:            /* draft message specified */
+                   draft = "";
+                   continue;
+
+               case LISTSW:            /* produce a listing */
+                   list = 1;
+                   continue;
+
+               case NUMBERSW:          /* number listing or delete by number */
+                   if (number != 0)
+                       adios (NULL, "only one number at a time!");
+
+                   if (argp - arguments == argc - 1 || **argp == '-')
+                       number = 1;
+
+                   else if (!(number = atoi(*argp++)))
+                       adios (NULL, "missing argument to %s", argp[-2]);
+
+                   delete = number;
+                   continue;
+
            }
        }
        if (*cp == '+' || *cp == '@') {
@@ -137,6 +212,28 @@
        }
     }
 
+    /*
+     * We're dealing with the draft message instead of message numbers.
+     * Get the name of the draft and deal with it just as we do with
+     * message numbers below.
+     */
+
+    if (draft != (char *)0) {
+       if (nummsgs != 0)
+           adios(NULL, "can only have message numbers or -draft.");
+
+       draft = getcpy(m_draft(folder, (char *)0, 1, &isdf));
+
+       make_comp(&comp);
+
+       if (list)
+           annolist(draft, comp, text, number);
+       else
+           annotate (draft, comp, text, inplace, datesw, delete);
+
+       return (done(0));
+    }
+
 #ifdef UCI
     if (strcmp(invo_name, "fanno") == 0)       /* ugh! */
        datesw = 0;
@@ -171,7 +268,10 @@
     /* annotate all the SELECTED messages */
     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
        if (is_selected(mp, msgnum))
- -         annotate (m_name (msgnum), comp, text, inplace, datesw);
+           if (list)
+               annolist(m_name(msgnum), comp, text, number);
+           else
+               annotate (m_name (msgnum), comp, text, inplace, datesw, delete);
 
     context_replace (pfolder, folder); /* update current folder  */
     seq_setcur (mp, mp->lowsel);       /* update current message */
@@ -181,7 +281,6 @@
     return done (0);
 }
 
- -
 static void
 make_comp (char **ap)
 {
@@ -210,4 +309,3 @@
        if (!isalnum (*cp) && *cp != '-')
            adios (NULL, "invalid component name %s", *ap);
 }
- -
diff -Naur nmh/uip/annosbr.c nmh-jon/uip/annosbr.c
- --- nmh/uip/annosbr.c Mon May 29 00:35:32 2000
+++ nmh-jon/uip/annosbr.c       Wed Jul  4 18:29:59 2001
@@ -15,41 +15,142 @@
 /*
  * static prototypes
  */
- -static int annosbr (int, char *, char *, char *, int, int);
+static int annosbr (int, char *, char *, char *, int, int, int);
 
 
 int
- -annotate (char *file, char *comp, char *text, int inplace, int datesw)
+annotate (char *file, char *comp, char *text, int inplace, int datesw, int delete)
 {
     int i, fd;
 
     /* open and lock the file to be annotated */
     if ((fd = lkopen (file, O_RDWR, 0)) == NOTOK) {
        switch (errno) {
- -         case ENOENT: 
+           case ENOENT:
                break;
 
- -         default: 
+           default:
                admonish (file, "unable to lock and open");
                break;
        }
        return 1;
     }
 
- -    i = annosbr (fd, file, comp, text, inplace, datesw);
+    i = annosbr (fd, file, comp, text, inplace, datesw, delete);
     lkclose (fd, file);
     return i;
 }
 
+/*
+ *  Produce a listing of all header fields (annotations) whose field name matches
+ *  comp.  Number the listing if number is set.  Treate the field bodies as path
+ *  names and just output the last component unless text is non-NULL.  We don't
+ *  care what text is set to.
+ */
+
+void
+annolist(char *file, char *comp, char *text, int number)
+{
+    int                c;              /* current character */
+    int                count;          /* header field (annotation) counter */
+    char       *cp;            /* miscellaneous character pointer */
+    char       *field;         /* buffer for header field */
+    int                field_size;     /* size of field buffer */
+    FILE       *fp;            /* file pointer made from locked file descriptor */
+    int                length;         /* length of field name */
+    int                n;              /* number of bytes written */
+    char       *sp;            /* another miscellaneous character pointer */
+
+    if ((fp = fopen(file, "r")) == (FILE *)0)
+       adios(file, "unable to open");
+
+    /*
+     *  Allocate a buffer to hold the header components as they're read in.
+     *  This buffer might need to be quite large, so we grow it as needed.
+     */
+
+    if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+       adios(NULL, "can't allocate field buffer.");
+
+    /*
+     *  Get the length of the field name since we use it often.
+     */
+
+    length = strlen(comp);
+
+    count = 0;
+
+    do {
+       /*
+        *      Get a line from the input file, growing the field buffer as needed.  
+We do this
+        *      so that we can fit an entire line in the buffer making it easy to do a 
+string
+        *      comparison on both the field name and the field body which might be a 
+long path
+        *      name.
+        */
+
+       for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
+           if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
+               (void)ungetc(c, fp);
+               c = '\n';
+               break;
+           }
+
+           if (++n >= field_size - 1) {
+               if ((field = (char *)realloc((void *)field, field_size += 256)) == 
+(char *)0)
+                   adios(NULL, "can't grow field buffer.");
+               
+               cp = field + n - 1;
+           }
+       }
+
+       /*
+        *      NUL-terminate the field..
+        */
+
+       *cp = '\0';
+
+       if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
+           for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
+               ;
+
+           if (number)
+               (void)printf("%d\t", ++count);
+
+           if (text == (char *)0 && (sp = strrchr(cp, '/')) != (char *)0)
+               cp = sp + 1;
+
+           (void)printf("%s\n", cp);
+       }
+
+    } while (*field != '\0' && *field != '-');
+
+    /*
+     * Clean up.
+     */
+
+    free(field);
+
+    (void)fclose(fp);
+
+    return;
+}
+
 
 static int
- -annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw)
+annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw, int 
+delete)
 {
     int mode, tmpfd;
     char *cp, *sp;
     char buffer[BUFSIZ], tmpfil[BUFSIZ];
     struct stat st;
- -    FILE *tmp;
+    FILE       *tmp;
+    int                c;              /* current character */
+    int                count;          /* header field (annotation) counter */
+    char       *field;         /* buffer for header field */
+    int                field_size;     /* size of field buffer */
+    FILE       *fp;            /* file pointer made from locked file descriptor */
+    int                length;         /* length of field name */
+    int                n;              /* number of bytes written */
 
     mode = fstat (fd, &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot ();
 
@@ -61,21 +162,173 @@
     }
     chmod (tmpfil, mode);
 
- -    if (datesw)
- -     fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
- -    if ((cp = text)) {
+    /*
+     * We're trying to delete a header field (annotation )if the delete flag is
+     * non-negative.  A  value greater than zero means that we're deleting the
+     * nth header field that matches the field (component) name.  A value of
+     * zero means that we're deleting the first field in which both the field
+     * name matches the component name and the field body matches the text.
+     * The text is matched in its entirety if it begins with a slash; otherwise
+     * the text is matched against whatever portion of the field body follows
+     * the last slash.  This allows matching of both absolute and relative path
+     * names.  This is because this functionality was added to support attachments.
+     * It might be worth having a separate flag to indicate path name matching to
+     * make it more general.
+     */
+
+    if (delete >= 0) {
+
+       /*
+        *  Allocate a buffer to hold the header components as they're read in.
+        *  This buffer might need to be quite large, so we grow it as needed.
+        */
+
+       if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+           adios(NULL, "can't allocate field buffer.");
+
+       /*
+        *  Get the length of the field name since we use it often.
+        */
+
+       length = strlen(comp);
+
+       /*
+        *  Convert the file descriptor into a file pointer since it's a
+        *  lot easier and more efficient to use stdio to read the file.
+        */
+
+       if ((fp = fdopen(fd, "r")) == (FILE *)0)
+           adios(NULL, "unable to fdopen file.");
+
+       /*
+        *  Initialize the field counter.  This is only used if we're deleting by
+        *  number.
+        */
+
+       count = 0;
+
+       /*
+        *  Copy lines from the input file to the temporary file until we either find 
+the one
+        *  that we're looking for (which we don't copy) or we reach the end of the 
+headers.
+        *  Both a blank line and a line beginning with a - terminate the headers so 
+that we
+        *  can handle both drafts and RFC-2822 format messages.
+        */
+
        do {
- -         while (*cp == ' ' || *cp == '\t')
- -             cp++;
- -         sp = cp;
- -         while (*cp && *cp++ != '\n')
- -             continue;
- -         if (cp - sp)
- -             fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp);
- -     } while (*cp);
- -     if (cp[-1] != '\n' && cp != text)
- -         putc ('\n', tmp);
+           /*
+            *  Get a line from the input file, growing the field buffer as needed.  
+We do this
+            *  so that we can fit an entire line in the buffer making it easy to do a 
+string
+            *  comparison on both the field name and the field body which might be a 
+long path
+            *  name.
+            */
+
+           for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
+               if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
+                   (void)ungetc(c, fp);
+                   c = '\n';
+                   break;
+               }
+
+               if (++n >= field_size - 1) {
+                   if ((field = (char *)realloc((void *)field, field_size += 256)) == 
+(char *)0)
+                       adios(NULL, "can't grow field buffer.");
+                   
+                   cp = field + n - 1;
+               }
+           }
+
+           /*
+            *  NUL-terminate the field..
+            */
+
+           *cp = '\0';
+
+           /*
+            *  Check for a match on the field name.  We delete the line by not 
+copying it to the
+            *  temporary file if
+            *
+            *   o  The delete flag is 0, meaning that we're going to delete the first 
+matching
+            *      field, and the text is NULL meaning that we don't care about the 
+field body.
+            *
+            *   o  The delete flag is 0, meaning that we're going to delete the first 
+matching
+            *      field, and the text begins with a / meaning that we're looking for 
+a full
+            *      path name, and the text matches the field body.
+            *
+            *   o  The delete flag is 0, meaning that we're going to delete the first 
+matching
+            *      field, the text does not begin with a / meaning that we're looking 
+for the
+            *      last path name component, and the last path name component matches 
+the text.
+            *
+            *   o  The delete flag is non-zero meaning that we're going to delete the 
+nth field
+            *      with a matching field name, and this is the nth matching field 
+name.
+            */
+
+           if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
+               if (delete == 0) {
+                   if (text == (char *)0)
+                       break;
+
+                   for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
+                       ;
+
+                   if (*text == '/') {
+                       if (strcmp(cp, text) == 0)
+                               break;
+                   }
+                   else {
+                       if ((sp = strrchr(cp, '/')) != (char *)0)
+                           cp = sp + 1;
+
+                       if (strcmp(cp, text) == 0)
+                           break;
+                   }
+               }
+
+               else if (++count == delete)
+                   break;
+           }
+
+           /*
+            *  This line wasn't a match so copy it to the temporary file.
+            */
+
+           if ((n = fputs(field, tmp)) == EOF || c == '\n' && fputc('\n', tmp) == EOF)
+               adios(NULL, "unable to write temporary file.");
+
+       } while (*field != '\0' && *field != '-');
+
+       /*
+        *  Get rid of the field buffer because we're done with it.
+        */
+
+       free((void *)field);
+
+       /*
+        *  Move the input file descriptor to the current place in the file because
+        *  the stock data copying routine use the descriptor, not the pointer.
+        */
+
+       if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1)
+           adios(NULL, "can't seek.");
+    }
+
+    else {
+       if (datesw)
+           fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
+       if ((cp = text)) {
+           do {
+               while (*cp == ' ' || *cp == '\t')
+                   cp++;
+               sp = cp;
+               while (*cp && *cp++ != '\n')
+                   continue;
+               if (cp - sp)
+                   fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp);
+           } while (*cp);
+           if (cp[-1] != '\n' && cp != text)
+               putc ('\n', tmp);
+       }
     }
+
     fflush (tmp);
     cpydata (fd, fileno (tmp), file, tmpfil);
     fclose (tmp);
@@ -83,7 +336,17 @@
     if (inplace) {
        if ((tmpfd = open (tmpfil, O_RDONLY)) == NOTOK)
            adios (tmpfil, "unable to open for re-reading");
+
        lseek (fd, (off_t) 0, SEEK_SET);
+
+       /*
+        *  We're making the file shorter if we're deleting a header field
+        *  so the file has to be truncated or it will contain garbage.
+        */
+
+       if (delete >= 0 && ftruncate(fd, 0) == -1)
+           adios(tmpfil, "unable to truncate.");
+
        cpydata (tmpfd, fd, tmpfil, file);
        close (tmpfd);
        unlink (tmpfil);
@@ -106,6 +369,15 @@
            return 1;
        }
     }
+
+    /*
+     * Close the delete file so that we don't run out of file pointers if
+     * we're doing piles of files.  Note that this will make the close() in
+     * lkclose() fail, but that failure is ignored so it's not a problem.
+     */
+
+    if (delete >= 0)
+       (void)fclose(fp);
 
     return 0;
 }
diff -Naur nmh/uip/send.c nmh-jon/uip/send.c
- --- nmh/uip/send.c    Thu Jul  6 20:48:05 2000
+++ nmh-jon/uip/send.c  Wed Jul  4 18:30:08 2001
@@ -98,6 +98,8 @@
     { "saslmech", SASLminc(-5) },
 #define USERSW                39
     { "user", SASLminc(-4) },
+#define ATTACHSW              40
+    { "attach", 6 },
     { NULL, 0 }
 };
 
@@ -136,6 +138,7 @@
     char *msgs[MAXARGS], *vec[MAXARGS];
     struct msgs *mp;
     struct stat st;
+    char       *attach = (char *)0;    /* header field name for attachments */
 #ifdef UCI
     FILE *fp;
 #endif /* UCI */
@@ -269,6 +272,11 @@
                        adios (NULL, "missing argument to %s", argp[-2]);
                    vec[vecp++] = cp;
                    continue;
+               
+               case ATTACHSW:
+                   if (!(attach = *argp++) || *attach == '-')
+                       adios (NULL, "missing argument to %s", argp[-2]);
+                   continue;
            }
        } else {
            msgs[msgp++] = cp;
@@ -432,7 +440,7 @@
     closefds (3);
 
     for (msgnum = 0; msgnum < msgp; msgnum++) {
- -     switch (sendsbr (vec, vecp, msgs[msgnum], &st, 1)) {
+       switch (sendsbr (vec, vecp, msgs[msgnum], &st, 1, attach)) {
            case DONE: 
                done (++status);
            case NOTOK: 
diff -Naur nmh/uip/sendsbr.c nmh-jon/uip/sendsbr.c
- --- nmh/uip/sendsbr.c Sun Sep 12 06:50:12 1999
+++ nmh-jon/uip/sendsbr.c       Thu Jul  5 07:57:58 2001
@@ -38,10 +38,18 @@
 static int armed = 0;
 static jmp_buf env;
 
+static char    body_file_name[MAXPATHLEN + 1];         /* name of temporary file for 
+body content */
+static char    composition_file_name[MAXPATHLEN + 1];  /* name of mhbuild composition 
+temporary file */
+static int     field_size;                             /* size of header field buffer 
+*/
+static char    *field;                                 /* header field buffer */
+static FILE    *draft_file;                            /* draft file pointer */
+static FILE    *body_file;                             /* body file pointer */
+static FILE    *composition_file;                      /* composition file pointer */
+
 /*
  * external prototypes
  */
- -int sendsbr (char **, int, char *, struct stat *, int);
+int sendsbr (char **, int, char *, struct stat *, int, char *);
 int done (int);
 char *getusername (void);
 
@@ -55,17 +63,54 @@
 static int splitmsg (char **, int, char *, struct stat *, int);
 static int sendaux (char **, int, char *, struct stat *);
 
+static int     attach(char *, char *);
+static void    clean_up_temporary_files(void);
+static int     get_line(void);
+static void    make_mime_composition_file_entry(char *);
+
 
 /*
  * Entry point into (back-end) routines to send message.
  */
 
 int
- -sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
+sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft, char 
+*attachment_header_field_name)
 {
     int status;
     char buffer[BUFSIZ], file[BUFSIZ];
     struct stat sts;
+    char       *original_draft;                /* name of original draft file */
+    char       *p;                             /* string pointer for building file 
+name */
+
+    /*
+     * Save the original name of the draft file.  The name of the draft file is 
+changed
+     * to a temporary file containing the built MIME message if there are attachments.
+     * We need the original name so that it can be renamed after the message is sent.
+     */
+
+    original_draft = drft;
+
+    /*
+     * There might be attachments if a header field name for attachments is supplied.
+     * Convert the draft to a MIME message.  Use the mhbuild composition file for the
+     * draft if there was a successful conversion because that now contains the MIME
+     * message.  A nice side effect of this is that it leaves the original draft file
+     * untouched so that it can be retrieved and modified if desired.
+     */
+
+    if (attachment_header_field_name != (char *)0) {
+       switch (attach(attachment_header_field_name, drft)) {
+       case OK:
+           drft = composition_file_name;
+           break;
+
+       case NOTOK:
+           return (NOTOK);
+
+       case DONE:
+           break;
+       }
+    }
 
     armed++;
     switch (setjmp (env)) {
@@ -94,7 +139,7 @@
 
        /* rename the original draft */
        if (rename_drft && status == OK &&
- -             rename (drft, strncpy (buffer, m_backup (drft), sizeof(buffer))) == 
NOTOK)
+               rename (original_draft, strncpy (buffer, m_backup (original_draft), 
+sizeof(buffer))) == NOTOK)
            advise (buffer, "unable to rename %s to", drft);
        break;
 
@@ -107,9 +152,340 @@
     if (distfile)
        unlink (distfile);
 
+    /*
+     * Get rid of any temporary files that we created for attachments.  Also get rid 
+of
+     * the renamed composition file that mhbuild leaves as a turd.  It looks 
+confusing,
+     * but we use the body file name to help build the renamed composition file name.
+     */
+
+    if (drft == composition_file_name) {
+       clean_up_temporary_files();
+
+       if (strlen(composition_file_name) >= sizeof (composition_file_name) - 6)
+           advise((char *)0, "unable to remove original composition file.");
+
+       else {
+           if ((p = strrchr(composition_file_name, '/')) == (char *)0)
+               p = composition_file_name;
+           else
+               p++;
+           
+           (void)strcpy(body_file_name, p);
+           *p++ = ',';
+           (void)strcpy(p, body_file_name);
+           (void)strcat(p, ".orig");
+           
+           (void)unlink(composition_file_name);
+       }
+    }
+
     return status;
 }
 
+static int
+attach(char *attachment_header_field_name, char *draft_file_name)
+{
+    char               buf[MAXPATHLEN + 6];    /* miscellaneous buffer */
+    int                        c;                      /* current character for body 
+copy */
+    int                        has_attachment;         /* draft has at least one 
+attachment */
+    int                        has_body;               /* draft has a message body */
+    int                        length;                 /* length of attachment header 
+field name */
+    char               *p;                     /* miscellaneous string pointer */
+
+    /*
+     * Open up the draft file.
+     */
+
+    if ((draft_file = fopen(draft_file_name, "r")) == (FILE *)0)
+       adios((char *)0, "can't open draft file `%s'.", draft_file_name);
+
+    /*
+     *  Allocate a buffer to hold the header components as they're read in.
+     *  This buffer might need to be quite large, so we grow it as needed.
+     */
+
+    if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+       adios(NULL, "can't allocate field buffer.");
+
+    /*
+     * Scan the draft file for a header field name that matches the -attach
+     * argument.  The existence of one indicates that the draft has attachments.
+     * Bail out if there are no attachments because we're done.  Read to the
+     * end of the headers even if we have no attachments.
+     */
+
+    length = strlen(attachment_header_field_name);
+
+    has_attachment = 0;
+
+    while (get_line() != EOF && *field != '\0' && *field != '-')
+       if (strncasecmp(field, attachment_header_field_name, length) == 0 && 
+field[length] == ':')
+           has_attachment = 1;
+
+    if (has_attachment == 0)
+       return (DONE);
+
+    /*
+     * We have at least one attachment.  Look for at least one non-blank line
+     * in the body of the message which indicates content in the body.
+     */
+
+    has_body = 0;
+
+    while (get_line() != EOF) {
+       for (p = field; *p != '\0'; p++) {
+           if (*p != ' ' && *p != '\t') {
+               has_body = 1;
+               break;
+           }
+       }
+
+       if (has_body)
+           break;
+    }
+
+    /*
+     * Make names for the temporary files.
+     */
+
+    (void)strncpy(body_file_name, m_scratch("", m_maildir(invo_name)), sizeof 
+(body_file_name));
+    (void)strncpy(composition_file_name, m_scratch("", m_maildir(invo_name)), sizeof 
+(composition_file_name));
+
+    if (has_body)
+       body_file = fopen(body_file_name, "w");
+
+    composition_file = fopen(composition_file_name, "w");
+
+    if ((has_body && body_file == (FILE *)0) || composition_file == (FILE *)0) {
+       clean_up_temporary_files();
+       adios((char *)0, "unable to open all of the temporary files.");
+    }
+
+    /*
+     * Start at the beginning of the draft file.  Copy all non-attachment header 
+fields
+     * to the temporary composition file.  Then add the dashed line separator.
+     */
+
+    rewind(draft_file);
+
+    while (get_line() != EOF && *field != '\0' && *field != '-')
+       if (strncasecmp(field, attachment_header_field_name, length) != 0 || 
+field[length] != ':')
+           (void)fprintf(composition_file, "%s\n", field);
+
+    (void)fputs("--------\n", composition_file);
+
+    /*
+     * Copy the message body to a temporary file.
+     */
+
+    if (has_body) {
+       while ((c = getc(draft_file)) != EOF)
+               putc(c, body_file);
+
+       (void)fclose(body_file);
+    }
+
+    /*
+     * Add a mhbuild MIME composition file line for the body if there was one.
+     */
+
+    if (has_body)
+       make_mime_composition_file_entry(body_file_name);
+
+    /*
+     * Now, go back to the beginning of the draft file and look for header fields
+     * that specify attachments.  Add a mhbuild MIME composition file for each.
+     */
+
+    rewind(draft_file);
+
+    while (get_line() != EOF && *field != '\0' && *field != '-') {
+       if (strncasecmp(field, attachment_header_field_name, length) == 0 && 
+field[length] == ':') {
+           for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
+               ;
+
+           make_mime_composition_file_entry(p);
+       }
+    }
+
+    (void)fclose(composition_file);
+
+    /*
+     * We're ready to roll!  Run mhbuild on the composition file.  Note that mhbuild
+     * is in the context as buildmimeproc.
+     */
+
+    (void)sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
+
+    if (system(buf) != 0) {
+       clean_up_temporary_files();
+       return (NOTOK);
+    }
+
+    return (OK);
+}
+
+static void
+clean_up_temporary_files(void)
+{
+    (void)unlink(body_file_name);
+    (void)unlink(composition_file_name);
+
+    return;
+}
+
+static int
+get_line(void)
+{
+    int                c;      /* current character */
+    int                n;      /* number of bytes in buffer */
+    char       *p;     /* buffer pointer */
+
+    /*
+     * Get a line from the input file, growing the field buffer as needed.  We do this
+     * so that we can fit an entire line in the buffer making it easy to do a string
+     * comparison on both the field name and the field body which might be a long path
+     * name.
+     */
+
+    for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
+       if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
+           (void)ungetc(c, draft_file);
+           c = '\n';
+           break;
+       }
+
+       if (++n >= field_size - 1) {
+           if ((field = (char *)realloc((void *)field, field_size += 256)) == (char 
+*)0)
+               adios(NULL, "can't grow field buffer.");
+
+           p = field + n - 1;
+       }
+    }
+
+    /*
+     * NUL-terminate the field..
+     */
+
+    *p = '\0';
+
+    return (c);
+}
+
+static void
+make_mime_composition_file_entry(char *file_name)
+{
+    int                        binary;                 /* binary character found flag 
+*/
+    int                        c;                      /* current character */
+    char               cmd[MAXPATHLEN + 6];    /* file command buffer */
+    char               *content_type;          /* mime content type */
+    FILE               *fp;                    /* content and pipe file pointer */
+    struct     node    *np;                    /* context scan node pointer */
+    char               *p;                     /* miscellaneous string pointer */
+    struct     stat    st;                     /* file status buffer */
+
+    content_type = (char *)0;
+
+    /*
+     * Check the file name for a suffix.  Scan the context for that suffix on a
+     * mhshow-suffix- entry.  We use these entries to be compatible with mhnshow,
+     * and there's no reason to make the user specify each suffix twice.  Context
+     * entries of the form "mhshow-suffix-contenttype" in the name have the suffix
+     * in the field, including the dot.
+     */
+
+    if ((p = strrchr(file_name, '.')) != (char *)0) {
+       for (np = m_defs; np; np = np->n_next) {
+           if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 && strcasecmp(p, 
+np->n_field) == 0) {
+               content_type = np->n_name + 14;
+               break;
+           }
+       }
+    }
+
+    /*
+     * No content type was found, either because there was no matching entry in the
+     * context or because the file name has no suffix.  Open the file and check for
+     * non-ASCII characters.  Choose the content type based on this check.
+     */
+
+    if (content_type == (char *)0) {
+       if ((fp = fopen(file_name, "r")) == (FILE *)0) {
+           clean_up_temporary_files();
+           adios((char *)0, "unable to access file \"%s\"", file_name);
+       }
+
+       binary = 0;
+
+       while ((c = getc(fp)) != EOF) {
+           if (c > 127 || c < 0) {
+               binary = 1;
+               break;
+           }
+       }
+
+       (void)fclose(fp);
+
+       content_type = binary ? "application/octet-stream" : "text/plain";
+    }
+
+    /*
+     * Make sure that the attachment file exists and is readable.  Append a mhbuild
+     * directive to the draft file.  This starts with the content type.  Append a
+     * file name attribute and a private x-unix-mode attribute.  Also append a
+     * description obtained (if possible) by running the "file" command on the file.
+     */
+
+    if (stat(file_name, &st) == -1 || access(file_name, R_OK) != 0) {
+       clean_up_temporary_files();
+       adios((char *)0, "unable to access file \"%s\"", file_name);
+    }
+
+    (void)fprintf(composition_file, "#%s; name=\"%s\"; x-unix-mode=0%.3ho",
+     content_type, ((p = strrchr(file_name, '/')) == (char *)0) ? file_name : p + 1, 
+(unsigned short)(st.st_mode & 0777));
+
+    if (strlen(file_name) > MAXPATHLEN) {
+       clean_up_temporary_files();
+       adios((char *)0, "attachment file name `%s' too long.", file_name);
+    }
+
+    (void)sprintf(cmd, "file %s", file_name);
+
+    if ((fp = popen(cmd, "r")) != (FILE *)0 && fgets(cmd, sizeof (cmd), fp) != (char 
+*)0) {
+       *strchr(cmd, '\n') = '\0';
+
+       /*
+        *  The output of the "file" command is of the form
+        *
+        *      file:   description
+        *
+        *  Strip off the "file:" and subsequent white space.
+        */
+
+       for (p = cmd; *p != '\0'; p++) {
+           if (*p == ':') {
+               for (p++; *p != '\0'; p++) {
+                   if (*p != '\t')
+                       break;
+               }
+               break;
+           }
+       }
+
+       if (*p != '\0')
+           (void)fprintf(composition_file, " [ %s ]", p);
+
+       (void)pclose(fp);
+    }
+
+    /*
+     * Finish up with the file name.
+     */
+
+    (void)fprintf(composition_file, " %s\n", file_name);
+
+    return;
+}
 
 /*
  * Split large message into several messages of
@@ -640,7 +1016,7 @@
        if (is_selected(mp, msgnum)) {
            if (debugsw)
                advise (NULL, "annotate message %d", msgnum);
- -         annotate (m_name (msgnum), annotext, cp, inplace, 1);
+           annotate (m_name (msgnum), annotext, cp, inplace, 1, 0);
        }
     }
 
diff -Naur nmh/uip/viamail.c nmh-jon/uip/viamail.c
- --- nmh/uip/viamail.c Wed Dec 13 17:30:49 2000
+++ nmh-jon/uip/viamail.c       Wed Jul  4 19:05:51 2001
@@ -233,7 +233,7 @@
     if (verbsw)
        vec[vecp++] = "-verbose";
 
- -    switch (sendsbr (vec, vecp, tmpfil, &st, 0)) {
+    switch (sendsbr (vec, vecp, tmpfil, &st, 0, (char *)0)) {
        case DONE:
        case NOTOK:
            status++;
diff -Naur nmh/uip/whatnowsbr.c nmh-jon/uip/whatnowsbr.c
- --- nmh/uip/whatnowsbr.c      Thu Jul  6 20:48:05 2000
+++ nmh-jon/uip/whatnowsbr.c    Thu Jul  5 07:29:47 2001
@@ -3,6 +3,37 @@
  * whatnowsbr.c -- the WhatNow shell
  *
  * $Id: whatnowsbr.c,v 1.4 2000/07/07 03:48:05 kim Exp $
+ *
+ *  Several options have been added to ease the inclusion of attachments
+ *  using the header field name mechanism added to anno and send.  The
+ *  -attach option is used to specify the header field name for attachments.
+ *
+ *  Several commands have been added at the whatnow prompt:
+ *
+ *     cd [ directory ]        This option works just like the shell's
+ *                             cd command and lets the user change the
+ *                             directory from which attachments are
+ *                             taken so that long path names are not
+ *                             needed with every file.
+ *
+ *     ls [ ls-options ]       This option works just like the normal
+ *                             ls command and exists to allow the user
+ *                             to verify file names in the directory.
+ *
+ *     pwd                     This option works just like the normal
+ *                             pwd command and exists to allow the user
+ *                             to verify the directory.
+ *
+ *     attach files            This option attaches the named files to
+ *                             the draft.
+ *
+ *     alist [-ln]             This option lists the attachments on the
+ *                             draft.  -l gets long listings, -n gets
+ *                             numbered listings.
+ *
+ *     detach files            This option removes attachments from the
+ *     detach -n numbers       draft.  This can be done by file name or
+ *                             by attachment number.
  */
 
 #include <h/mh.h>
@@ -27,6 +58,8 @@
     { "version", 0 },
 #define        HELPSW                  7
     { "help", 0 },
+#define        ATTACHSW                8
+    { "attach header-field-name", 0 },
     { NULL, 0 }
 };
 
@@ -54,6 +87,18 @@
     { "quit [-delete]", 0 },
 #define DELETESW                       9
     { "delete", 0 },
+#define        CDCMDSW                       10
+    { "cd [directory]", 0 },
+#define        PWDCMDSW                      11
+    { "pwd", 0 },
+#define        LSCMDSW                       12
+    { "ls", 0 },
+#define        ATTACHCMDSW                   13
+    { "attach", 0 },
+#define        DETACHCMDSW                   14
+    { "detach [-n]", 2 },
+#define        ALISTCMDSW                    15
+    { "alist [-ln] ", 2 },
     { NULL, 0 }
 };
 
@@ -85,6 +130,13 @@
     char buf[BUFSIZ], prompt[BUFSIZ];
     char **argp, **arguments;
     struct stat st;
+    char       *attach = (char *)0;    /* attachment header field name */
+    char       cwd[MAXPATHLEN + 1];    /* current working directory */
+    char       file[MAXPATHLEN + 1];   /* file name buffer */
+    char       shell[MAXPATHLEN + 1];  /* shell response buffer */
+    FILE       *f;                     /* read pointer for bgnd proc */
+    char       *l;                     /* set on -l to alist  command */
+    int                n;                      /* set on -n to alist command */
 
     invo_name = r1bindex (argv[0], '/');
 
@@ -94,16 +146,24 @@
     arguments = getarguments (invo_name, argc, argv, 1);
     argp = arguments;
 
+    /*
+     * Get the initial current working directory.
+     */
+
+    if (getcwd(cwd, sizeof (cwd)) == (char *)0) {
+       adios("getcwd", "could not get working directory");
+    }
+
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, whatnowswitches)) {
- -         case AMBIGSW: 
+           case AMBIGSW:
                ambigsw (cp, whatnowswitches);
                done (1);
- -         case UNKWNSW: 
+           case UNKWNSW:
                adios (NULL, "-%s unknown", cp);
 
- -         case HELPSW: 
+           case HELPSW:
                snprintf (buf, sizeof(buf), "%s [switches] [file]", invo_name);
                print_help (buf, whatnowswitches, 1);
                done (1);
@@ -111,7 +171,7 @@
                print_version(invo_name);
                done (1);
 
- -         case DFOLDSW: 
+           case DFOLDSW:
                if (dfolder)
                    adios (NULL, "only one draft folder at a time!");
                if (!(cp = *argp++) || *cp == '-')
@@ -119,23 +179,23 @@
                dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
                                *cp != '@' ? TFOLDER : TSUBCWF);
                continue;
- -         case DMSGSW: 
+           case DMSGSW:
                if (dmsg)
                    adios (NULL, "only one draft message at a time!");
                if (!(dmsg = *argp++) || *dmsg == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                continue;
- -         case NDFLDSW: 
+           case NDFLDSW:
                dfolder = NULL;
                isdf = NOTOK;
                continue;
 
- -         case EDITRSW: 
+           case EDITRSW:
                if (!(ed = *argp++) || *ed == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                nedit = 0;
                continue;
- -         case NEDITSW: 
+           case NEDITSW:
                nedit++;
                continue;
 
@@ -143,6 +203,13 @@
                if (!(myprompt = *argp++) || *myprompt == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
                continue;
+
+           case ATTACHSW:
+               if (attach != (char *)0)
+                   adios(NULL, "only one attachment header field name at a time!");
+               if (!(attach = *argp++) || *attach == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               continue;
            }
        }
        if (drft)
@@ -196,7 +263,7 @@
                done (1);
            break;
 
- -     case LISTSW: 
+       case LISTSW:
            /* display the draft file */
            showfile (++argp, drft);
            break;
@@ -228,18 +295,258 @@
                done (1);
            break;
 
- -     case SENDSW: 
+       case SENDSW:
            /* Send draft */
            sendfile (++argp, drft, 0);
            break;
 
- -     case REFILEOPT: 
+       case REFILEOPT:
            /* Refile the draft */
            if (refile (++argp, drft) == 0)
                done (0);
            break;
 
- -     default: 
+       case CDCMDSW:
+           /* Change the working directory for attachments
+            *
+            *  Run the directory through the user's shell so that
+            *  we can take advantage of any syntax that the user
+            *  is accustomed to.  Read back the absolute path.
+            */
+
+           if (*++argp == (char *)0) {
+               (void)sprintf(buf, "$SHELL -c \"cd;pwd\"");
+           }
+           else if (strlen(*argp) >= BUFSIZ) {
+               adios((char *)0, "arguments too long");
+           }
+           else {
+               (void)sprintf(buf, "$SHELL -c \"cd %s;cd %s;pwd\"", cwd, *argp);
+           }
+           if ((f = popen(buf, "r")) != (FILE *)0) {
+               fgets(cwd, sizeof (cwd), f);
+
+               if (strchr(cwd, '\n') != (char *)0)
+                       *strchr(cwd, '\n') = '\0';
+
+               pclose(f);
+           }
+           else {
+               advise("popen", "could not get directory");
+           }
+
+           break;
+
+       case PWDCMDSW:
+           /* Print the working directory for attachments */
+           printf("%s\n", cwd);
+           break;
+
+       case LSCMDSW:
+           /* List files in the current attachment working directory
+            *
+            *  Use the user's shell so that we can take advantage of any
+            *  syntax that the user is accustomed to.
+            */
+
+           cp = buf + sprintf(buf, "$SHELL -c \" cd %s;ls", cwd);
+
+           while (*++argp != (char *)0) {
+               if (cp + strlen(*argp) + 2 >= buf + BUFSIZ)
+                   adios((char *)0, "arguments too long");
+
+               cp += sprintf(cp, " %s", *argp);
+           }
+
+           (void)strcat(cp, "\"");
+           (void)system(buf);
+           break;
+
+       case ALISTCMDSW:
+           /*
+            *  List attachments on current draft.  Options are:
+            *
+            *   -l     long listing (full path names)
+            *   -n     numbers listing
+            */
+
+           if (attach == (char *)0) {
+               advise((char *)0, "can't list because no header field name was 
+given.");
+               break;
+           }
+
+           l = (char *)0;
+           n = 0;
+
+           while (*++argp != (char *)0) {
+               if (strcmp(*argp, "-l") == 0)
+                   l = "/";
+
+               else if (strcmp(*argp, "-n") == 0)
+                   n = 1;
+
+               else if (strcmp(*argp, "-ln") == 0 || strcmp(*argp, "-nl") == 0) {
+                   l = "/";
+                   n = 1;
+               }
+
+               else {
+                   n = -1;
+                   break;
+               }
+           }
+
+           if (n == -1)
+               advise((char *)0, "usage is alist [-ln].");
+
+           else
+               annolist(drft, attach, l, n);
+
+           break;
+
+       case ATTACHCMDSW:
+           /*
+            *  Attach files to current draft.
+            */
+
+           if (attach == (char *)0) {
+               advise((char *)0, "can't attach because no header field name was 
+given.");
+               break;
+           }
+
+           /*
+            *  Build a command line that causes the user's shell to list the file name
+            *  arguments.  This handles and wildcard expansion, tilde expansion, etc.
+            */
+
+           cp = buf + sprintf(buf, "$SHELL -c \" cd %s;ls", cwd);
+
+           while (*++argp != (char *)0) {
+               if (cp + strlen(*argp) + 3 >= buf + BUFSIZ)
+                   adios((char *)0, "arguments too long");
+
+               cp += sprintf(cp, " %s", *argp);
+           }
+
+           (void)strcat(cp, "\"");
+
+           /*
+            *  Read back the response from the shell, which contains a number of lines
+            *  with one file name per line.  Remove off the newline.  Determine 
+whether
+            *  we have an absolute or relative path name.  Prepend the current working
+            *  directory to relative path names.  Add the attachment annotation to the
+            *  draft.
+            */
+
+           if ((f = popen(buf, "r")) != (FILE *)0) {
+               while (fgets(shell, sizeof (shell), f) != (char *)0) {
+                   *(strchr(shell, '\n')) = '\0';
+
+                   if (*shell == '/')
+                       (void)annotate(drft, attach, shell, 1, 0, -1);
+                   else {
+                       (void)sprintf(file, "%s/%s", cwd, shell);
+                       (void)annotate(drft, attach, file, 1, 0, -1);
+                   }
+               }
+
+               pclose(f);
+           }
+           else {
+               advise("popen", "could not get file from shell");
+           }
+
+           break;
+
+       case DETACHCMDSW:
+           /*
+            *  Detach files from current draft.
+            */
+
+           if (attach == (char *)0) {
+               advise((char *)0, "can't detach because no header field name was 
+given.");
+               break;
+           }
+
+           /*
+            *  Scan the arguments for a -n.  Mixed file names and numbers aren't 
+allowed,
+            *  so this catches a -n anywhere in the argument list.
+            */
+
+           for (n = 0, arguments = argp + 1; *arguments != (char *)0; arguments++) {
+               if (strcmp(*arguments, "-n") == 0) {
+                       n = 1;
+                       break;
+               }
+           }
+
+           /*
+            *  A -n was found so interpret the arguments as attachment numbers.
+            *  Decrement any remaining argument number that is greater than the one
+            *  just processed after processing each one so that the numbering stays
+            *  correct.
+            */
+
+           if (n == 1) {
+               for (arguments = argp + 1; *arguments != (char *)0; arguments++) {
+                   if (strcmp(*arguments, "-n") == 0)
+                       continue;
+
+                   if (**arguments != '\0') {
+                       n = atoi(*arguments);
+                       (void)annotate(drft, attach, (char *)0, 1, 0, n);
+
+                       for (argp = arguments + 1; *argp != (char *)0; argp++) {
+                           if (atoi(*argp) > n) {
+                               if (atoi(*argp) == 1)
+                                   *argp = "";
+                               else
+                                   (void)sprintf(*argp, "%d", atoi(*argp) - 1);
+                           }
+                       }
+                   }
+               }
+           }
+
+           /*
+            *  The arguments are interpreted as file names.  Run them through the
+            *  user's shell for wildcard expansion and other goodies.  Do this from
+            *  the current working directory if the argument is not an absolute path
+            *  name (does not begin with a /).
+            */
+
+           else {
+               for (arguments = argp + 1; *arguments != (char *)0; arguments++) {
+                   if (**arguments == '/') {
+                       if (strlen(*arguments) + sizeof ("$SHELL -c \"ls \"") >= 
+sizeof (buf))
+                           adios((char *)0, "arguments too long");
+
+                       (void)sprintf(buf, "$SHELL -c \"ls %s\"", *arguments);
+                   }
+                   else {
+                       if (strlen(cwd) + strlen(*arguments) + sizeof ("$SHELL -c \" 
+cd ; ls \"") >= sizeof (buf))
+                           adios((char *)0, "arguments too long");
+
+                       (void)sprintf(buf, "$SHELL -c \" cd %s;ls %s\"", cwd, 
+*arguments);
+                   }
+
+                   if ((f = popen(buf, "r")) != (FILE *)0) {
+                       while (fgets(shell, sizeof (cwd), f) != (char *)0) {
+                           *(strchr(shell, '\n')) = '\0';
+                           (void)annotate(drft, attach, shell, 1, 0, 0);
+                       }
+
+                       pclose(f);
+                   }
+                   else {
+                       advise("popen", "could not get file from shell");
+                   }
+               }
+           }
+
+           break;
+
+       default:
            /* Unknown command */
            advise (NULL, "say what?");
            break;
@@ -326,12 +633,12 @@
     fflush (stdout);
 
     switch (pid = vfork ()) {
- -     case NOTOK: 
+       case NOTOK:
            advise ("fork", "unable to");
            status = NOTOK;
            break;
 
- -     case OK: 
+       case OK:
            if (cwd)
                chdir (cwd);
            if (altmsg) {
@@ -353,7 +660,7 @@
            perror (*ed);
            _exit (-1);
 
- -     default: 
+       default:
            if ((status = pidwait (pid, NOTOK))) {
 #ifdef ATTVIBUG
                if ((cp = r1bindex (*ed, '/'))
@@ -488,9 +795,9 @@
     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
        sleep (5);
     switch (child_id) {
- -     case NOTOK: 
+       case NOTOK:
            advise (NULL, "unable to fork, so sending directly...");
- -     case OK: 
+       case OK:
            vecp = 0;
            vec[vecp++] = invo_name;
            if (pushsw)
@@ -506,7 +813,7 @@
            perror (sendproc);
            _exit (-1);
 
- -     default: 
+       default:
            if (pidwait(child_id, OK) == 0)
                done (0);
            return 1;
@@ -692,6 +999,8 @@
     { "saslmech", SASLminc(-5) },
 #define USERSW           38
     { "user", SASLminc(-4) },
+#define SNDATTACHSW       39
+    { "attach file", 6 },
     { NULL, 0 }
 };
 
@@ -716,6 +1025,7 @@
     char *cp, buf[BUFSIZ], **argp;
     char **arguments, *vec[MAXARGS];
     struct stat st;
+    char       *attach = (char *)0;    /* attachment header field name */
 
 #ifndef        lint
     int        distsw = 0;
@@ -773,14 +1083,14 @@
     while ((cp = *argp++)) {
        if (*cp == '-') {
            switch (smatch (++cp, sendswitches)) {
- -             case AMBIGSW: 
+               case AMBIGSW:
                    ambigsw (cp, sendswitches);
                    return;
- -             case UNKWNSW: 
+               case UNKWNSW:
                    advise (NULL, "-%s unknown\n", cp);
                    return;
 
- -             case SHELPSW: 
+               case SHELPSW:
                    snprintf (buf, sizeof(buf), "%s [switches]", sp);
                    print_help (buf, sendswitches, 1);
                    return;
@@ -788,34 +1098,34 @@
                    print_version (invo_name);
                    return;
 
- -             case SPSHSW: 
+               case SPSHSW:
                    pushed++;
                    continue;
- -             case NSPSHSW: 
+               case NSPSHSW:
                    pushed = 0;
                    continue;
 
- -             case SPLITSW: 
+               case SPLITSW:
                    if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) {
                        advise (NULL, "missing argument to %s", argp[-2]);
                        return;
                    }
                    continue;
 
- -             case UNIQSW: 
+               case UNIQSW:
                    unique++;
                    continue;
- -             case NUNIQSW: 
+               case NUNIQSW:
                    unique = 0;
                    continue;
- -             case FORWSW: 
+               case FORWSW:
                    forwsw++;
                    continue;
- -             case NFORWSW: 
+               case NFORWSW:
                    forwsw = 0;
                    continue;
 
- -             case VERBSW: 
+               case VERBSW:
                    verbsw++;
                    vec[vecp++] = --cp;
                    continue;
@@ -824,33 +1134,33 @@
                    vec[vecp++] = --cp;
                    continue;
 
- -             case DEBUGSW: 
+               case DEBUGSW:
                    debugsw++;  /* fall */
- -             case NFILTSW: 
- -             case FRMTSW: 
- -             case NFRMTSW: 
+               case NFILTSW:
+               case FRMTSW:
+               case NFRMTSW:
                case BITSTUFFSW:
                case NBITSTUFFSW:
- -             case MIMESW: 
- -             case NMIMESW: 
- -             case MSGDSW: 
- -             case NMSGDSW: 
- -             case WATCSW: 
- -             case NWATCSW: 
- -             case MAILSW: 
- -             case SAMLSW: 
- -             case SSNDSW: 
- -             case SOMLSW: 
- -             case SNOOPSW: 
+               case MIMESW:
+               case NMIMESW:
+               case MSGDSW:
+               case NMSGDSW:
+               case WATCSW:
+               case NWATCSW:
+               case MAILSW:
+               case SAMLSW:
+               case SSNDSW:
+               case SOMLSW:
+               case SNOOPSW:
                case SASLSW:
                    vec[vecp++] = --cp;
                    continue;
 
- -             case ALIASW: 
- -             case FILTSW: 
- -             case WIDTHSW: 
- -             case CLIESW: 
- -             case SERVSW: 
+               case ALIASW:
+               case FILTSW:
+               case WIDTHSW:
+               case CLIESW:
+               case SERVSW:
                case SASLMECHSW:
                case USERSW:
                    vec[vecp++] = --cp;
@@ -861,13 +1171,20 @@
                    vec[vecp++] = cp;
                    continue;
 
- -             case SDRFSW: 
- -             case SDRMSW: 
+               case SDRFSW:
+               case SDRMSW:
                    if (!(cp = *argp++) || *cp == '-') {
                        advise (NULL, "missing argument to %s", argp[-2]);
                        return;
                    }
- -             case SNDRFSW: 
+               case SNDRFSW:
+                   continue;
+
+               case SNDATTACHSW:
+                   if (!(attach = *argp++) || *attach == '-') {
+                       advise (NULL, "missing argument to %s", argp[-2]);
+                       return;
+                   }
                    continue;
            }
        }
@@ -934,7 +1251,7 @@
     vec[0] = r1bindex (postproc, '/');
     closefds (3);
 
- -    if (sendsbr (vec, vecp, file, &st, 1) == OK)
+    if (sendsbr (vec, vecp, file, &st, 1, attach) == OK)
        done (0);
 }
 
@@ -953,11 +1270,11 @@
     fflush (stdout);
 
     switch (pid = vfork ()) {
- -     case NOTOK: 
+       case NOTOK:
            advise ("fork", "unable to");
            return 1;
 
- -     case OK: 
+       case OK:
            vecp = 0;
            vec[vecp++] = r1bindex (whomproc, '/');
            vec[vecp++] = file;
@@ -971,7 +1288,7 @@
            perror (whomproc);
            _exit (-1);         /* NOTREACHED */
 
- -     default: 
+       default:
            return (pidwait (pid, NOTOK) & 0377 ? 1 : 0);
     }
 }

- ------- =_aaaaaaaaaa0--


------- End of Forwarded Message

Reply via email to