Added an improved user interface for sending messages with attachments.
authorJon Steinhart <jon@fourwinds.com>
Mon, 19 Aug 2002 20:50:41 +0000 (20:50 +0000)
committerJon Steinhart <jon@fourwinds.com>
Mon, 19 Aug 2002 20:50:41 +0000 (20:50 +0000)
README-ATTACHMENTS [new file with mode: 0644]
h/prototypes.h
man/anno.man
man/send.man
man/whatnow.man
uip/anno.c
uip/annosbr.c
uip/send.c
uip/sendsbr.c
uip/viamail.c
uip/whatnowsbr.c

diff --git a/README-ATTACHMENTS b/README-ATTACHMENTS
new file mode 100644 (file)
index 0000000..ff9db2a
--- /dev/null
@@ -0,0 +1,281 @@
+Jon Steinhart's (jon@fourwinds.com) Attachment Handling Mods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A bunch of changes have been made 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 changes 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.
+
+The Simple Setup
+~~~~~~~~~~~~~~~~
+
+The gory details are all given below.  Here's how to set things up simply.  This
+only works if you're using the "standard" interface, i.e., whatnow.  Life is more
+complicated if you run mh-e.
+
+Add the following to your .mh_profile:
+
+       send: -attach X-MH-Attachment
+       whatnow: -attach X-MH-Attachment
+
+You may already have send and whatnow lines in your .mh_profile; if you do, just add
+the new stuff to the end of the line.  For example, mine looks like:
+
+       send: -alias aliases -attach X-MH-Attachment
+       whatnow: -attach X-MH-Attachment
+
+That's it.  Next time you want to send an attachment, say "attach filename" at the
+whatnow prompt and you're done.  You can check your attachments by saying "alist".
+
+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.
+
+ o  I added a -append option so that annotations are appended to the headers
+    instead of the default prepend.  Without this, attachments come out in
+    reverse order.
+
+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 -append
+       prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/bar -nodate -append
+       prompt% anno -draft -comp X-MH-Attachment -text /export/home/jon/test/foo -nodate -append
+
+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 -append -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 an -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.
+
+       Jon Steinhart
index d252ead..63ea87e 100644 (file)
@@ -154,11 +154,12 @@ char *OfficialName(char *);
 /*
  * prototypes for some routines in uip
  */
-int annotate (char *, char *, char *, int, int);
+int annotate (char *, char *, char *, int, 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 *);
 
index 7a735bc..b626e2a 100644 (file)
@@ -15,6 +15,12 @@ anno \- annotate messages
 .IR field ]
 .RB [ \-inplace " | " \-noinplace ]
 .RB [ \-date " | " \-nodate ]
+.RB [ \-draft ]
+.RB [ \-append ]
+.RB [ \-list ]
+.RB [ \-delete ]
+.RB [ \-number
+.IR [ num ]]
 .RB [ \-version ]
 .RB [ \-help ]
 .RB [ \-text
@@ -22,8 +28,22 @@ anno \- annotate messages
 .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 ,
@@ -48,6 +68,13 @@ The
 switch inhibits the date annotation, leaving only the
 body annotation.
 .PP
+By default,
+.B anno
+prepends the annotations to the message.
+Annotations are instead appended if the
+.B -append
+option is specified.
+.PP
 If a
 .B \-component
 .I field
@@ -57,8 +84,8 @@ is invoked,
 .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 +94,45 @@ does the annotation inplace in order to preserve
 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
index 5a2f897..85b3164 100644 (file)
@@ -40,6 +40,8 @@ send \- send a message
 \&...] 
 .RB [ \-version ]
 .RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
 .ad
 .SH DESCRIPTION
 .B Send
@@ -63,6 +65,41 @@ profile component.  Most of the features attributed to
 .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
index 5bc5da7..cc2e989 100644 (file)
@@ -22,6 +22,8 @@ whatnow \- prompting front-end for sending messages
 .RI [ file ]
 .RB [ \-version ]
 .RB [ \-help ]
+.RB [ \-attach
+.IR header-field-name ]
 .ad
 .SH DESCRIPTION
 .B Whatnow
@@ -45,18 +47,18 @@ repetitively prompts the user with \*(lqWhat now?\*(rq
 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 @@ the
 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
index ea60e6d..b92abdc 100644 (file)
@@ -7,6 +7,42 @@
  * This code is Copyright (c) 2002, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
  * complete copyright information.
+ *
+ *     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>
@@ -35,6 +71,16 @@ static struct swit switches[] = {
     { "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 },
+#define        APPENDSW                12
+    { "append", 1 },
     { NULL, 0 }
 };
 
@@ -53,6 +99,12 @@ main (int argc, char **argv)
     char *text = NULL, *folder = NULL, buf[BUFSIZ];
     char **argp, **arguments, **msgs;
     struct msgs *mp;
+    int                append = 0;             /* append annotations instead of default prepend */
+    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, "");
@@ -77,13 +129,13 @@ main (int argc, char **argv)
     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);
@@ -92,33 +144,62 @@ main (int argc, char **argv)
                    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;
+
+               case APPENDSW:          /* append annotations instead of default prepend */
+                   append = 1;
+                   continue;
            }
        }
        if (*cp == '+' || *cp == '@') {
@@ -141,6 +222,28 @@ main (int argc, char **argv)
        }
     }
 
+    /*
+     * 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, append);
+
+       return (done(0));
+    }
+
 #ifdef UCI
     if (strcmp(invo_name, "fanno") == 0)       /* ugh! */
        datesw = 0;
@@ -173,9 +276,14 @@ main (int argc, char **argv)
     make_comp (&comp);
 
     /* 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);
+    for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
+       if (is_selected(mp, msgnum)) {
+           if (list)
+               annolist(m_name(msgnum), comp, text, number);
+           else
+               annotate (m_name (msgnum), comp, text, inplace, datesw, delete, append);
+       }
+    }
 
     context_replace (pfolder, folder); /* update current folder  */
     seq_setcur (mp, mp->lowsel);       /* update current message */
@@ -185,7 +293,6 @@ main (int argc, char **argv)
     return done (0);
 }
 
-
 static void
 make_comp (char **ap)
 {
@@ -214,4 +321,3 @@ make_comp (char **ap)
        if (!isalnum (*cp) && *cp != '-')
            adios (NULL, "invalid component name %s", *ap);
 }
-
index e250c1a..41b0cc2 100644 (file)
@@ -19,41 +19,142 @@ extern int  errno;
 /*
  * static prototypes
  */
-static int annosbr (int, char *, char *, char *, int, int);
+static int annosbr (int, char *, char *, char *, int, 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 append)
 {
     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, append);
     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 append)
 {
     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 ();
 
@@ -65,29 +166,217 @@ annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw)
     }
     chmod (tmpfil, mode);
 
-    if (datesw)
-       fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
-    if ((cp = text)) {
+    /*
+     *  We're going to need to copy some of the message file to the temporary
+     * file while examining the contents.  Convert the message file descriptor
+     * to a file pointer since it's a lot easier and more efficient to use
+     * stdio for this.  Also allocate a buffer to hold the header components
+     * as they're read in.  This buffer is grown as needed later.
+     */
+
+    if (delete >= 0 || append != 0) {
+       if ((fp = fdopen(fd, "r")) == (FILE *)0)
+           adios(NULL, "unable to fdopen file.");
+
+       if ((field = (char *)malloc(field_size = 256)) == (char *)0)
+           adios(NULL, "can't allocate field buffer.");
+    }
+
+    /*
+     * 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) {
+       /*
+        *  Get the length of the field name since we use it often.
+        */
+
+       length = strlen(comp);
+
+       /*
+        *  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 *= 2)) == (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);
+    }
+
+    else {
+       /*
+        *  Find the end of the headers before adding the annotations if we're
+        *  appending instead of the default prepending.
+        */
+
+       if (append) {
+           /*
+            *  Copy lines from the input file to the temporary file until we
+            *  reach the end of the headers.
+            */
+
+           while ((c = getc(fp)) != EOF) {
+               (void)putc(c, tmp);
+
+               if (c == '\n') {
+                   (void)ungetc(c = getc(fp), fp);
+
+                   if (c == '\n' || c == '-')
+                       break;
+               }
+           }
+       }
+
+       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);
+
+    /*
+     * We've been messing with the input file position.  Move the input file
+     *  descriptor to the current place in the file because the stock data
+     * copying routine uses the descriptor, not the pointer.
+     */
+
+    if (append || delete >= 0) {
+       if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1)
+           adios(NULL, "can't seek.");
+    }
+
     cpydata (fd, fileno (tmp), file, tmpfil);
     fclose (tmp);
 
     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);
@@ -111,5 +400,14 @@ annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw)
        }
     }
 
+    /*
+     * 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;
 }
index ec81aaa..1da9440 100644 (file)
@@ -102,6 +102,8 @@ static struct swit switches[] = {
     { "saslmech", SASLminc(-5) },
 #define USERSW                39
     { "user", SASLminc(-4) },
+#define ATTACHSW              40
+    { "attach", 6 },
     { NULL, 0 }
 };
 
@@ -140,6 +142,7 @@ main (int argc, char **argv)
     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 */
@@ -273,6 +276,11 @@ main (int argc, char **argv)
                        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;
@@ -436,7 +444,7 @@ go_to_it:
     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: 
index b055bb0..7576889 100644 (file)
@@ -42,10 +42,18 @@ char *distfile = NULL;
 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);
 
@@ -59,17 +67,54 @@ static void annoaux (int);
 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)) {
@@ -98,7 +143,7 @@ sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
 
        /* 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;
 
@@ -111,9 +156,340 @@ sendsbr (char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
     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
@@ -644,7 +1020,7 @@ annoaux (int fd)
        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, 0);
        }
     }
 
index de0e950..b4cd5e2 100644 (file)
@@ -237,7 +237,7 @@ via_mail (char *mailsw, char *subjsw, char *parmsw, char *descsw,
     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++;
index ab98074..53320d3 100644 (file)
@@ -7,6 +7,37 @@
  * This code is Copyright (c) 2002, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
  * complete copyright information.
+ *
+ *  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>
@@ -31,6 +62,8 @@ static struct swit whatnowswitches[] = {
     { "version", 0 },
 #define        HELPSW                  7
     { "help", 0 },
+#define        ATTACHSW                8
+    { "attach header-field-name", 0 },
     { NULL, 0 }
 };
 
@@ -58,6 +91,18 @@ static struct swit aleqs[] = {
     { "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 }
 };
 
@@ -89,6 +134,13 @@ WhatNow (int argc, char **argv)
     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], '/');
 
@@ -98,16 +150,24 @@ WhatNow (int argc, char **argv)
     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);
@@ -115,7 +175,7 @@ WhatNow (int argc, char **argv)
                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 == '-')
@@ -123,23 +183,23 @@ WhatNow (int argc, char **argv)
                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;
 
@@ -147,6 +207,13 @@ WhatNow (int argc, char **argv)
                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)
@@ -200,7 +267,7 @@ WhatNow (int argc, char **argv)
                done (1);
            break;
 
-       case LISTSW: 
+       case LISTSW:
            /* display the draft file */
            showfile (++argp, drft);
            break;
@@ -232,18 +299,258 @@ WhatNow (int argc, char **argv)
                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, 1);
+                   else {
+                       (void)sprintf(file, "%s/%s", cwd, shell);
+                       (void)annotate(drft, attach, file, 1, 0, -1, 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, 1);
+
+                       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, 1);
+                       }
+
+                       pclose(f);
+                   }
+                   else {
+                       advise("popen", "could not get file from shell");
+                   }
+               }
+           }
+
+           break;
+
+       default:
            /* Unknown command */
            advise (NULL, "say what?");
            break;
@@ -330,12 +637,12 @@ editfile (char **ed, char **arg, char *file, int use, struct msgs *mp,
     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) {
@@ -357,7 +664,7 @@ editfile (char **ed, char **arg, char *file, int use, struct msgs *mp,
            perror (*ed);
            _exit (-1);
 
-       default: 
+       default:
            if ((status = pidwait (pid, NOTOK))) {
 #ifdef ATTVIBUG
                if ((cp = r1bindex (*ed, '/'))
@@ -492,9 +799,9 @@ sendfile (char **arg, char *file, int pushsw)
     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)
@@ -510,7 +817,7 @@ sendfile (char **arg, char *file, int pushsw)
            perror (sendproc);
            _exit (-1);
 
-       default: 
+       default:
            if (pidwait(child_id, OK) == 0)
                done (0);
            return 1;
@@ -696,6 +1003,8 @@ static struct swit  sendswitches[] = {
     { "saslmech", SASLminc(-5) },
 #define USERSW           38
     { "user", SASLminc(-4) },
+#define SNDATTACHSW       39
+    { "attach file", 6 },
     { NULL, 0 }
 };
 
@@ -720,6 +1029,7 @@ sendit (char *sp, char **arg, char *file, int pushed)
     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;
@@ -777,14 +1087,14 @@ sendit (char *sp, char **arg, char *file, int pushed)
     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;
@@ -792,34 +1102,34 @@ sendit (char *sp, char **arg, char *file, int pushed)
                    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;
@@ -828,33 +1138,33 @@ sendit (char *sp, char **arg, char *file, int pushed)
                    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;
@@ -865,13 +1175,20 @@ sendit (char *sp, char **arg, char *file, int pushed)
                    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;
            }
        }
@@ -938,7 +1255,7 @@ sendit (char *sp, char **arg, char *file, int pushed)
     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);
 }
 
@@ -957,11 +1274,11 @@ whomfile (char **arg, char *file)
     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;
@@ -975,7 +1292,7 @@ whomfile (char **arg, char *file)
            perror (whomproc);
            _exit (-1);         /* NOTREACHED */
 
-       default: 
+       default:
            return (pidwait (pid, NOTOK) & 0377 ? 1 : 0);
     }
 }