Rearranged whitespace (and comments) in all the code!
[mmh] / uip / whatnowsbr.c
1 /*
2  * whatnowsbr.c -- the WhatNow shell
3  *
4  * This code is Copyright (c) 2002, by the authors of nmh.  See the
5  * COPYRIGHT file in the root directory of the nmh distribution for
6  * complete copyright information.
7  *
8  *  Several options have been added to ease the inclusion of attachments
9  *  using the header field name mechanism added to anno and send.  The
10  *  -attach option is used to specify the header field name for attachments.
11  *
12  *  Several commands have been added at the whatnow prompt:
13  *
14  *        cd [ directory ]        This option works just like the shell's
15  *                                cd command and lets the user change the
16  *                                directory from which attachments are
17  *                                taken so that long path names are not
18  *                                needed with every file.
19  *
20  *        ls [ ls-options ]       This option works just like the normal
21  *                                ls command and exists to allow the user
22  *                                to verify file names in the directory.
23  *
24  *        pwd                     This option works just like the normal
25  *                                pwd command and exists to allow the user
26  *                                to verify the directory.
27  *
28  *        attach files            This option attaches the named files to
29  *                                the draft.
30  *
31  *        alist [-ln]             This option lists the attachments on the
32  *                                draft.  -l gets long listings, -n gets
33  *                                numbered listings.
34  *
35  *        detach files            This option removes attachments from the
36  *        detach -n numbers       draft.  This can be done by file name or
37  *                                by attachment number.
38  */
39
40 #include <h/mh.h>
41 #include <fcntl.h>
42 #include <signal.h>
43 #include <h/mime.h>
44 #include <h/utils.h>
45
46 static struct swit whatnowswitches[] = {
47 #define DFOLDSW  0
48         { "draftfolder +folder", 0 },
49 #define DMSGSW  1
50         { "draftmessage msg", 0 },
51 #define NDFLDSW  2
52         { "nodraftfolder", 0 },
53 #define EDITRSW  3
54         { "editor editor", 0 },
55 #define NEDITSW  4
56         { "noedit", 0 },
57 #define PRMPTSW  5
58         { "prompt string", 4 },
59 #define VERSIONSW  6
60         { "version", 0 },
61 #define HELPSW  7
62         { "help", 0 },
63 #define ATTACHSW  8
64         { "attach header-field-name", 0 },
65         { NULL, 0 }
66 };
67
68 /*
69  * Options at the "whatnow" prompt
70  */
71 static struct swit aleqs[] = {
72 #define EDITSW  0
73         { "edit [<editor> <switches>]", 0 },
74 #define REFILEOPT  1
75         { "refile [<switches>] +folder", 0 },
76 #define BUILDMIMESW  2
77         { "mime [<switches>]", 0 },
78 #define DISPSW  3
79         { "display [<switches>]", 0 },
80 #define LISTSW  4
81         { "list [<switches>]", 0 },
82 #define SENDSW  5
83         { "send [<switches>]", 0 },
84 #define PUSHSW  6
85         { "push [<switches>]", 0 },
86 #define WHOMSW  7
87         { "whom [<switches>]", 0 },
88 #define QUITSW  8
89         { "quit [-delete]", 0 },
90 #define DELETESW  9
91         { "delete", 0 },
92 #define CDCMDSW  10
93         { "cd [directory]", 0 },
94 #define PWDCMDSW  11
95         { "pwd", 0 },
96 #define LSCMDSW  12
97         { "ls", 0 },
98 #define ATTACHCMDSW  13
99         { "attach", 0 },
100 #define DETACHCMDSW  14
101         { "detach [-n]", 2 },
102 #define ALISTCMDSW  15
103         { "alist [-ln] ", 2 },
104         { NULL, 0 }
105 };
106
107 static char *myprompt = "\nWhat now? ";
108
109 /*
110  * static prototypes
111  */
112 static int editfile (char **, char **, char *, int, struct msgs *,
113         char *, char *, int);
114 static int sendfile (char **, char *, int);
115 static void sendit (char *, char **, char *, int);
116 static int buildfile (char **, char *);
117 static int check_draft (char *);
118 static int whomfile (char **, char *);
119 static int removefile (char *);
120 static void writelscmd(char *, int, char **);
121 static void writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp);
122 static FILE* popen_in_dir(const char *dir, const char *cmd, const char *type);
123 static int system_in_dir(const char *dir, const char *cmd);
124
125
126 #ifdef HAVE_LSTAT
127 static int copyf (char *, char *);
128 #endif
129
130
131 int
132 WhatNow (int argc, char **argv)
133 {
134         int isdf = 0, nedit = 0, use = 0;
135         char *cp, *dfolder = NULL, *dmsg = NULL;
136         char *ed = NULL, *drft = NULL, *msgnam = NULL;
137         char buf[BUFSIZ], prompt[BUFSIZ];
138         char **argp, **arguments;
139         struct stat st;
140         char *attach = (char *)0;  /* attachment header field name */
141         char cwd[MAXPATHLEN + 1];  /* current working directory */
142         char file[MAXPATHLEN + 1];  /* file name buffer */
143         char shell[MAXPATHLEN + 1];  /* shell response buffer */
144         FILE *f;  /* read pointer for bgnd proc */
145         char *l;  /* set on -l to alist  command */
146         int n;  /* set on -n to alist command */
147
148         invo_name = r1bindex (argv[0], '/');
149
150         /* read user profile/context */
151         context_read();
152
153         arguments = getarguments (invo_name, argc, argv, 1);
154         argp = arguments;
155
156         /*
157          * Get the initial current working directory.
158          */
159
160         if (getcwd(cwd, sizeof (cwd)) == (char *)0) {
161                 adios("getcwd", "could not get working directory");
162         }
163
164         while ((cp = *argp++)) {
165                 if (*cp == '-') {
166                         switch (smatch (++cp, whatnowswitches)) {
167                         case AMBIGSW:
168                                 ambigsw (cp, whatnowswitches);
169                                 done (1);
170                         case UNKWNSW:
171                                 adios (NULL, "-%s unknown", cp);
172
173                         case HELPSW:
174                                 snprintf (buf, sizeof(buf), "%s [switches] [file]", invo_name);
175                                 print_help (buf, whatnowswitches, 1);
176                                 done (1);
177                         case VERSIONSW:
178                                 print_version(invo_name);
179                                 done (1);
180
181                         case DFOLDSW:
182                                 if (dfolder)
183                                         adios (NULL, "only one draft folder at a time!");
184                                 if (!(cp = *argp++) || *cp == '-')
185                                         adios (NULL, "missing argument to %s", argp[-2]);
186                                 dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
187                                         *cp != '@' ? TFOLDER : TSUBCWF);
188                                 continue;
189                         case DMSGSW:
190                                 if (dmsg)
191                                         adios (NULL, "only one draft message at a time!");
192                                 if (!(dmsg = *argp++) || *dmsg == '-')
193                                         adios (NULL, "missing argument to %s", argp[-2]);
194                                 continue;
195                         case NDFLDSW:
196                                 dfolder = NULL;
197                                 isdf = NOTOK;
198                                 continue;
199
200                         case EDITRSW:
201                                 if (!(ed = *argp++) || *ed == '-')
202                                         adios (NULL, "missing argument to %s", argp[-2]);
203                                 nedit = 0;
204                                 continue;
205                         case NEDITSW:
206                                 nedit++;
207                                 continue;
208
209                         case PRMPTSW:
210                                 if (!(myprompt = *argp++) || *myprompt == '-')
211                                         adios (NULL, "missing argument to %s", argp[-2]);
212                                 continue;
213
214                         case ATTACHSW:
215                                 if (attach != (char *)0)
216                                         adios(NULL, "only one attachment header field name at a time!");
217                                 if (!(attach = *argp++) || *attach == '-')
218                                         adios (NULL, "missing argument to %s", argp[-2]);
219                                 continue;
220                         }
221                 }
222                 if (drft)
223                         adios (NULL, "only one draft at a time!");
224                 else
225                         drft = cp;
226         }
227
228         if ((drft == NULL && (drft = getenv ("mhdraft")) == NULL) || *drft == 0)
229                 drft = getcpy (m_draft (dfolder, dmsg, 1, &isdf));
230
231         msgnam = (cp = getenv ("mhaltmsg")) && *cp ? getcpy (cp) : NULL;
232
233         if ((cp = getenv ("mhuse")) && *cp)
234                 use = atoi (cp);
235
236         if (ed == NULL && ((ed = getenv ("mheditor")) == NULL || *ed == 0)) {
237                 ed = NULL;
238                 nedit++;
239         }
240
241         /* start editing the draft, unless -noedit was given */
242         if (!nedit && editfile (&ed, NULL, drft, use, NULL, msgnam, NULL, 1) < 0)
243                 done (1);
244
245         snprintf (prompt, sizeof(prompt), myprompt, invo_name);
246         for (;;) {
247                 if (!(argp = getans (prompt, aleqs))) {
248                         unlink (altmsglink);
249                         done (1);
250                 }
251                 switch (smatch (*argp, aleqs)) {
252                 case DISPSW:
253                         /* display the message being replied to, or distributed */
254                         if (msgnam)
255                                 showfile (++argp, msgnam);
256                         else
257                                 advise (NULL, "no alternate message to display");
258                         break;
259
260                 case BUILDMIMESW:
261                         /* Translate MIME composition file */
262                         buildfile (++argp, drft);
263                         break;
264
265                 case EDITSW:
266                         /* Call an editor on the draft file */
267                         if (*++argp)
268                                 ed = *argp++;
269                         if (editfile (&ed, argp, drft, NOUSE, NULL, msgnam, NULL, 1) == NOTOK)
270                                 done (1);
271                         break;
272
273                 case LISTSW:
274                         /* display the draft file */
275                         showfile (++argp, drft);
276                         break;
277
278                 case WHOMSW:
279                         /* Check to whom the draft would be sent */
280                         whomfile (++argp, drft);
281                         break;
282
283                 case QUITSW:
284                         /* Quit, and possibly delete the draft */
285                         if (*++argp && (*argp[0] == 'd' ||
286                                 ((*argp)[0] == '-' && (*argp)[1] == 'd'))) {
287                                 removefile (drft);
288                         } else {
289                                 if (stat (drft, &st) != NOTOK)
290                                         advise (NULL, "draft left on %s", drft);
291                         }
292                         done (1);
293
294                 case DELETESW:
295                         /* Delete draft and exit */
296                         removefile (drft);
297                         done (1);
298
299                 case PUSHSW:
300                         /* Send draft in background */
301                         if (sendfile (++argp, drft, 1))
302                                 done (1);
303                         break;
304
305                 case SENDSW:
306                         /* Send draft */
307                         sendfile (++argp, drft, 0);
308                         break;
309
310                 case REFILEOPT:
311                         /* Refile the draft */
312                         if (refile (++argp, drft) == 0)
313                                 done (0);
314                         break;
315
316                 case CDCMDSW:
317                         /*
318                          * Change the working directory for attachments
319                          *
320                          * Run the directory through the user's shell
321                          * so that we can take advantage of any syntax
322                          * that the user is accustomed to.  Read back
323                          * the absolute path.
324                          */
325
326                         if (*(argp+1) == (char *)0) {
327                                 (void)sprintf(buf, "$SHELL -c \"cd;pwd\"");
328                         } else {
329                                 writesomecmd(buf, BUFSIZ, "cd", "pwd", argp);
330                         }
331                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
332                                 fgets(cwd, sizeof (cwd), f);
333
334                                 if (strchr(cwd, '\n') != (char *)0)
335                                                 *strchr(cwd, '\n') = '\0';
336
337                                 pclose(f);
338                         } else {
339                                 advise("popen", "could not get directory");
340                         }
341
342                         break;
343
344                 case PWDCMDSW:
345                         /* Print the working directory for attachments */
346                         printf("%s\n", cwd);
347                         break;
348
349                 case LSCMDSW:
350                         /*
351                          * List files in the current attachment working
352                          * directory
353                          *
354                          * Use the user's shell so that we can take
355                          * advantage of any syntax that the user is
356                          * accustomed to.
357                          */
358                         writelscmd(buf, sizeof(buf), argp);
359                         (void)system_in_dir(cwd, buf);
360                         break;
361
362                 case ALISTCMDSW:
363                         /*
364                          * List attachments on current draft.  Options are:
365                          *
366                          * -l    long listing (full path names)
367                          * -n    numbers listing
368                          */
369
370                         if (attach == (char *)0) {
371                                 advise((char *)0, "can't list because no header field name was given.");
372                                 break;
373                         }
374
375                         l = (char *)0;
376                         n = 0;
377
378                         while (*++argp != (char *)0) {
379                                 if (strcmp(*argp, "-l") == 0)
380                                         l = "/";
381
382                                 else if (strcmp(*argp, "-n") == 0)
383                                         n = 1;
384
385                                 else if (strcmp(*argp, "-ln") == 0 || strcmp(*argp, "-nl") == 0) {
386                                         l = "/";
387                                         n = 1;
388                                 } else {
389                                         n = -1;
390                                         break;
391                                 }
392                         }
393
394                         if (n == -1)
395                                 advise((char *)0, "usage is alist [-ln].");
396
397                         else
398                                 annolist(drft, attach, l, n);
399
400                         break;
401
402                 case ATTACHCMDSW:
403                         /*
404                          * Attach files to current draft.
405                          */
406
407                         if (attach == (char *)0) {
408                                 advise((char *)0, "can't attach because no header field name was given.");
409                                 break;
410                         }
411
412                         if (*(argp+1) == (char *)0) {
413                                 advise((char *)0, "attach command requires file argument(s).");
414                                 break;
415                         }
416
417                         /*
418                          * Build a command line that causes the user's
419                          * shell to list the file name arguments.
420                          * This handles and wildcard expansion, tilde
421                          * expansion, etc.
422                          */
423                         writelscmd(buf, sizeof(buf), argp);
424
425                         /*
426                          * Read back the response from the shell,
427                          * which contains a number of lines with one
428                          * file name per line.  Remove off the newline.
429                          * Determine whether we have an absolute or
430                          * relative path name.  Prepend the current
431                          * working directory to relative path names.
432                          * Add the attachment annotation to the draft.
433                          */
434
435                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
436                                 while (fgets(shell, sizeof (shell), f) != (char *)0) {
437                                         *(strchr(shell, '\n')) = '\0';
438
439                                         if (*shell == '/')
440                                                 (void)annotate(drft, attach, shell, 1, 0, -2, 1);
441                                         else {
442                                                 (void)sprintf(file, "%s/%s", cwd, shell);
443                                                 (void)annotate(drft, attach, file, 1, 0, -2, 1);
444                                         }
445                                 }
446
447                                 pclose(f);
448                         } else {
449                                 advise("popen", "could not get file from shell");
450                         }
451
452                         break;
453
454                 case DETACHCMDSW:
455                         /*
456                          * Detach files from current draft.
457                          */
458
459                         if (attach == (char *)0) {
460                                 advise((char *)0, "can't detach because no header field name was given.");
461                                 break;
462                         }
463
464                         /*
465                          * Scan the arguments for a -n.  Mixed file
466                          * names and numbers aren't allowed, so this
467                          * catches a -n anywhere in the argument list.
468                          */
469
470                         for (n = 0, arguments = argp + 1; *arguments != (char *)0; arguments++) {
471                                 if (strcmp(*arguments, "-n") == 0) {
472                                                 n = 1;
473                                                 break;
474                                 }
475                         }
476
477                         /*
478                          * A -n was found so interpret the arguments as
479                          * attachment numbers.  Decrement any remaining
480                          * argument number that is greater than the one
481                          * just processed after processing each one so
482                          * that the numbering stays correct.
483                          */
484
485                         if (n == 1) {
486                                 for (arguments = argp + 1; *arguments != (char *)0; arguments++) {
487                                         if (strcmp(*arguments, "-n") == 0)
488                                                 continue;
489
490                                         if (**arguments != '\0') {
491                                                 n = atoi(*arguments);
492                                                 (void)annotate(drft, attach, (char *)0, 1, 0, n, 1);
493
494                                                 for (argp = arguments + 1; *argp != (char *)0; argp++) {
495                                                         if (atoi(*argp) > n) {
496                                                                 if (atoi(*argp) == 1)
497                                                                         *argp = "";
498                                                                 else
499                                                                         (void)sprintf(*argp, "%d", atoi(*argp) - 1);
500                                                         }
501                                                 }
502                                         }
503                                 }
504                         }
505
506                         /*
507                          * The arguments are interpreted as file names.
508                          * Run them through the user's shell for wildcard
509                          * expansion and other goodies.  Do this from
510                          * the current working directory if the argument
511                          * is not an absolute path name (does not begin
512                          * with a /).
513                          *
514                          * We feed all the file names to the shell at
515                          * once, otherwise you can't provide a file name
516                          * with a space in it.
517                          */
518                         writelscmd(buf, sizeof(buf), argp);
519                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
520                                 while (fgets(shell, sizeof (shell), f) != (char *)0) {
521                                         *(strchr(shell, '\n')) = '\0';
522                                         (void)annotate(drft, attach, shell, 1, 0, 0, 1);
523                                 }
524                                 pclose(f);
525                         } else {
526                                 advise("popen", "could not get file from shell");
527                         }
528
529                         break;
530
531                 default:
532                         /* Unknown command */
533                         advise (NULL, "say what?");
534                         break;
535                 }
536         }
537         /*NOTREACHED*/
538 }
539
540
541
542 /*
543  * Build a command line of the form $SHELL -c "cd 'cwd'; cmd argp ... ;
544  * trailcmd".
545  */
546 static void
547 writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
548 {
549         char *cp;
550         /* Note that we do not quote -- the argp from the user
551          * is assumed to be quoted as they desire. (We can't treat
552          * it as pure literal as that would prevent them using ~,
553          * wildcards, etc.) The buffer produced by this function
554          * should be given to popen_in_dir() or system_in_dir() so
555          * that the current working directory is set correctly.
556          */
557         int ln = snprintf(buf, bufsz, "$SHELL -c \"%s", cmd);
558         /* NB that some snprintf() return -1 on overflow rather than the
559          * new C99 mandated 'number of chars that would have been written'
560          */
561         /* length checks here and inside the loop allow for the
562          * trailing ';', trailcmd, '"' and NUL
563          */
564         int trailln = strlen(trailcmd) + 3;
565         if (ln < 0 || ln + trailln > bufsz)
566                 adios((char *)0, "arguments too long");
567
568         cp = buf + ln;
569
570         while (*++argp != (char *)0) {
571                 ln = strlen(*argp);
572                 /* +1 for leading space */
573                 if (ln + trailln + 1 > bufsz - (cp-buf))
574                         adios((char *)0, "arguments too long");
575                 *cp++ = ' ';
576                 memcpy(cp, *argp, ln+1);
577                 cp += ln;
578         }
579         if (*trailcmd) {
580                 *cp++ = ';';
581                 strcpy(cp, trailcmd);
582                 cp += trailln - 3;
583         }
584         *cp++ = '"';
585         *cp = 0;
586 }
587
588 /*
589  * Build a command line that causes the user's shell to list the file name
590  * arguments.  This handles and wildcard expansion, tilde expansion, etc.
591  */
592 static void
593 writelscmd(char *buf, int bufsz, char **argp)
594 {
595         writesomecmd(buf, bufsz, "ls", "", argp);
596 }
597
598 /* Like system(), but run the command in directory dir.
599  * This assumes the program is single-threaded!
600  */
601 static int
602 system_in_dir(const char *dir, const char *cmd)
603 {
604         char olddir[BUFSIZ];
605         int r;
606         if (getcwd(olddir, sizeof(olddir)) == 0)
607                 adios("getcwd", "could not get working directory");
608         if (chdir(dir) != 0)
609                 adios("chdir", "could not change working directory");
610         r = system(cmd);
611         if (chdir(olddir) != 0)
612                 adios("chdir", "could not change working directory");
613         return r;
614 }
615
616 /* ditto for popen() */
617 static FILE*
618 popen_in_dir(const char *dir, const char *cmd, const char *type)
619 {
620         char olddir[BUFSIZ];
621         FILE *f;
622         if (getcwd(olddir, sizeof(olddir)) == 0)
623                 adios("getcwd", "could not get working directory");
624         if (chdir(dir) != 0)
625                 adios("chdir", "could not change working directory");
626         f = popen(cmd, type);
627         if (chdir(olddir) != 0)
628                 adios("chdir", "could not change working directory");
629         return f;
630 }
631
632
633 /*
634  * EDIT
635  */
636
637 static int  reedit = 0;  /* have we been here before? */
638 static char *edsave = NULL;  /* the editor we used previously */
639
640
641 static int
642 editfile (char **ed, char **arg, char *file, int use, struct msgs *mp,
643         char *altmsg, char *cwd, int save_editor)
644 {
645         int pid, status, vecp;
646         char altpath[BUFSIZ], linkpath[BUFSIZ];
647         char *cp, *vec[MAXARGS];
648         struct stat st;
649
650 #ifdef HAVE_LSTAT
651         int slinked = 0;
652 #if 0
653         int oumask;  /* PJS: for setting permissions on symlinks. */
654 #endif
655 #endif /* HAVE_LSTAT */
656
657         /* Was there a previous edit session? */
658         if (reedit) {
659                 if (!*ed) {  /* no explicit editor */
660                         *ed = edsave;  /* so use the previous one */
661                         if ((cp = r1bindex (*ed, '/')) == NULL)
662                                 cp = *ed;
663
664                         /* unless we've specified it via "editor-next" */
665                         cp = concat (cp, "-next", NULL);
666                         if ((cp = context_find (cp)) != NULL)
667                                 *ed = cp;
668                 }
669         } else {
670                 /* set initial editor */
671                 if (*ed == NULL && (*ed = context_find ("editor")) == NULL)
672                         *ed = defaulteditor;
673         }
674
675         if (altmsg) {
676                 if (mp == NULL || *altmsg == '/' || cwd == NULL)
677                         strncpy (altpath, altmsg, sizeof(altpath));
678                 else
679                         snprintf (altpath, sizeof(altpath), "%s/%s", mp->foldpath, altmsg);
680                 if (cwd == NULL)
681                         strncpy (linkpath, altmsglink, sizeof(linkpath));
682                 else
683                         snprintf (linkpath, sizeof(linkpath), "%s/%s", cwd, altmsglink);
684         }
685
686         if (altmsg) {
687                 unlink (linkpath);
688 #ifdef HAVE_LSTAT
689                 if (link (altpath, linkpath) == NOTOK) {
690 #if 0
691                         /* I don't think permission on symlinks matters /JLR */
692                         oumask = umask(0044);  /* PJS: else symlinks are world 'r' */
693 #endif
694                         symlink (altpath, linkpath);
695 #if 0
696                         umask(oumask);  /* PJS: else symlinks are world 'r' */
697 #endif
698                         slinked = 1;
699                 } else {
700                         slinked = 0;
701                 }
702 #else /* not HAVE_LSTAT */
703                 link (altpath, linkpath);
704 #endif /* not HAVE_LSTAT */
705         }
706
707         context_save ();  /* save the context file */
708         fflush (stdout);
709
710         switch (pid = vfork ()) {
711                 case NOTOK:
712                         advise ("fork", "unable to");
713                         status = NOTOK;
714                         break;
715
716                 case OK:
717                         if (cwd)
718                                 chdir (cwd);
719                         if (altmsg) {
720                                 if (mp)
721                                         m_putenv ("mhfolder", mp->foldpath);
722                                 m_putenv ("editalt", altpath);
723                         }
724
725                         vecp = 0;
726                         vec[vecp++] = r1bindex (*ed, '/');
727                         if (arg)
728                                 while (*arg)
729                                         vec[vecp++] = *arg++;
730                         vec[vecp++] = file;
731                         vec[vecp] = NULL;
732
733                         execvp (*ed, vec);
734                         fprintf (stderr, "unable to exec ");
735                         perror (*ed);
736                         _exit (-1);
737
738                 default:
739                         if ((status = pidwait (pid, NOTOK))) {
740 #ifdef ATTVIBUG
741                                 if ((cp = r1bindex (*ed, '/'))
742                                                 && strcmp (cp, "vi") == 0
743                                                 && (status & 0x00ff) == 0)
744                                         status = 0;
745                                 else {
746 #endif
747                                 if (((status & 0xff00) != 0xff00)
748                                         && (!reedit || (status & 0x00ff))) {
749                                         if (!use && (status & 0xff00) &&
750                                                         (rename (file, cp = m_backup (file)) != NOTOK)) {
751                                                 advise (NULL, "problems with edit--draft left in %s", cp);
752                                         } else {
753                                                 advise (NULL, "problems with edit--%s preserved", file);
754                                         }
755                                 }
756                                 status = -2;  /* maybe "reedit ? -2 : -1"? */
757                                 break;
758 #ifdef ATTVIBUG
759                                 }
760 #endif
761                         }
762
763                         reedit++;
764 #ifdef HAVE_LSTAT
765                         if (altmsg
766                                         && mp
767                                         && !is_readonly(mp)
768                                         && (slinked
769                                                    ? lstat (linkpath, &st) != NOTOK
770                                                                 && S_ISREG(st.st_mode)
771                                                                 && copyf (linkpath, altpath) == NOTOK
772                                                    : stat (linkpath, &st) != NOTOK
773                                                                 && st.st_nlink == 1
774                                                                 && (unlink (altpath) == NOTOK
775                                                                                 || link (linkpath, altpath) == NOTOK)))
776                                 advise (linkpath, "unable to update %s from", altmsg);
777 #else /* HAVE_LSTAT */
778                         if (altmsg
779                                         && mp
780                                         && !is_readonly(mp)
781                                         && stat (linkpath, &st) != NOTOK
782                                         && st.st_nlink == 1
783                                         && (unlink (altpath) == NOTOK
784                                                 || link (linkpath, altpath) == NOTOK))
785                                 advise (linkpath, "unable to update %s from", altmsg);
786 #endif /* HAVE_LSTAT */
787         }
788
789         /* normally, we remember which editor we used */
790         if (save_editor)
791                 edsave = getcpy (*ed);
792
793         *ed = NULL;
794         if (altmsg)
795                 unlink (linkpath);
796
797         return status;
798 }
799
800
801 #ifdef HAVE_LSTAT
802 static int
803 copyf (char *ifile, char *ofile)
804 {
805         int i, in, out;
806         char buffer[BUFSIZ];
807
808         if ((in = open (ifile, O_RDONLY)) == NOTOK)
809                 return NOTOK;
810         if ((out = open (ofile, O_WRONLY | O_TRUNC)) == NOTOK) {
811                 admonish (ofile, "unable to open and truncate");
812                 close (in);
813                 return NOTOK;
814         }
815
816         while ((i = read (in, buffer, sizeof(buffer))) > OK)
817                 if (write (out, buffer, i) != i) {
818                         advise (ofile, "may have damaged");
819                         i = NOTOK;
820                         break;
821                 }
822
823         close (in);
824         close (out);
825         return i;
826 }
827 #endif /* HAVE_LSTAT */
828
829
830 /*
831  * SEND
832  */
833
834 static int
835 sendfile (char **arg, char *file, int pushsw)
836 {
837         pid_t child_id;
838         int i, vecp;
839         char *cp, *sp, *vec[MAXARGS];
840
841         /* Translate MIME composition file, if necessary */
842         if ((cp = context_find ("automimeproc"))
843                         && (!strcmp (cp, "1"))
844                         && !getenv ("NOMHNPROC")
845                         && check_draft (file)
846                         && (buildfile (NULL, file) == NOTOK))
847                 return 0;
848
849         /* For backwards compatibility */
850         if ((cp = context_find ("automhnproc"))
851                         && !getenv ("NOMHNPROC")
852                         && check_draft (file)
853                         && (i = editfile (&cp, NULL, file, NOUSE, NULL, NULL, NULL, 0)))
854                 return 0;
855
856         /*
857          * If the sendproc is the nmh command `send', then we call
858          * those routines directly rather than exec'ing the command.
859          */
860         if (strcmp (sp = r1bindex (sendproc, '/'), "send") == 0) {
861                 cp = invo_name;
862                 sendit (invo_name = sp, arg, file, pushsw);
863                 invo_name = cp;
864                 return 1;
865         }
866
867         context_save ();  /* save the context file */
868         fflush (stdout);
869
870         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
871                 sleep (5);
872         switch (child_id) {
873                 case NOTOK:
874                         advise (NULL, "unable to fork, so sending directly...");
875                 case OK:
876                         vecp = 0;
877                         vec[vecp++] = invo_name;
878                         if (pushsw)
879                                 vec[vecp++] = "-push";
880                         if (arg)
881                                 while (*arg)
882                                         vec[vecp++] = *arg++;
883                         vec[vecp++] = file;
884                         vec[vecp] = NULL;
885
886                         execvp (sendproc, vec);
887                         fprintf (stderr, "unable to exec ");
888                         perror (sendproc);
889                         _exit (-1);
890
891                 default:
892                         if (pidwait(child_id, OK) == 0)
893                                 done (0);
894                         return 1;
895         }
896 }
897
898
899 /*
900  * Translate MIME composition file (call buildmimeproc)
901  */
902
903 static int
904 buildfile (char **argp, char *file)
905 {
906         int i;
907         char **args, *ed;
908
909         ed = buildmimeproc;
910
911         /* allocate space for arguments */
912         i = 0;
913         if (argp) {
914                 while (argp[i])
915                         i++;
916         }
917         args = (char **) mh_xmalloc((i + 2) * sizeof(char *));
918
919         /*
920          * For backward compatibility, we need to add -build
921          * if we are using mhn as buildmimeproc
922          */
923         i = 0;
924         if (strcmp (r1bindex (ed, '/'), "mhn") == 0)
925                 args[i++] = "-build";
926
927         /* copy any other arguments */
928         while (argp && *argp)
929                 args[i++] = *argp++;
930         args[i] = NULL;
931
932         i = editfile (&ed, args, file, NOUSE, NULL, NULL, NULL, 0);
933         free (args);
934
935         return (i ? NOTOK : OK);
936 }
937
938
939 /*
940  *  Check if draft is a mhbuild composition file
941  */
942
943 static int
944 check_draft (char *msgnam)
945 {
946         int state;
947         char buf[BUFSIZ], name[NAMESZ];
948         FILE *fp;
949
950         if ((fp = fopen (msgnam, "r")) == NULL)
951                 return 0;
952         for (state = FLD;;)
953                 switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) {
954                         case FLD:
955                         case FLDPLUS:
956                         case FLDEOF:
957                                 /*
958                                  * If draft already contains any of the
959                                  * Content-XXX fields, then assume it already
960                                  * been converted.
961                                  */
962                                 if (uprf (name, XXX_FIELD_PRF)) {
963                                         fclose (fp);
964                                         return 0;
965                                 }
966                                 while (state == FLDPLUS)
967                                         state = m_getfld (state, name, buf, sizeof(buf), fp);
968                                 break;
969
970                         case BODY:
971                                 do {
972                                         char *bp;
973
974                                         for (bp = buf; *bp; bp++)
975                                                 if (*bp != ' ' && *bp != '\t' && *bp != '\n') {
976                                                         fclose (fp);
977                                                         return 1;
978                                                 }
979
980                                         state = m_getfld (state, name, buf, sizeof(buf), fp);
981                                 } while (state == BODY);
982                                 /* and fall... */
983
984                         default:
985                                 fclose (fp);
986                                 return 0;
987                 }
988 }
989
990
991 static struct swit  sendswitches[] = {
992 #define ALIASW  0
993         { "alias aliasfile", 0 },
994 #define DEBUGSW  1
995         { "debug", -5 },
996 #define FILTSW  2
997         { "filter filterfile", 0 },
998 #define NFILTSW  3
999         { "nofilter", 0 },
1000 #define FRMTSW  4
1001         { "format", 0 },
1002 #define NFRMTSW  5
1003         { "noformat", 0 },
1004 #define FORWSW  6
1005         { "forward", 0 },
1006 #define NFORWSW  7
1007         { "noforward", 0 },
1008 #define MIMESW  8
1009         { "mime", 0 },
1010 #define NMIMESW  9
1011         { "nomime", 0 },
1012 #define MSGDSW  10
1013         { "msgid", 0 },
1014 #define NMSGDSW  11
1015         { "nomsgid", 0 },
1016 #define SPSHSW  12
1017         { "push", 0 },
1018 #define NSPSHSW  13
1019         { "nopush", 0 },
1020 #define SPLITSW  14
1021         { "split seconds", 0 },
1022 #define UNIQSW  15
1023         { "unique", -6 },
1024 #define NUNIQSW  16
1025         { "nounique", -8 },
1026 #define VERBSW  17
1027         { "verbose", 0 },
1028 #define NVERBSW  18
1029         { "noverbose", 0 },
1030 #define WATCSW  19
1031         { "watch", 0 },
1032 #define NWATCSW  20
1033         { "nowatch", 0 },
1034 #define WIDTHSW  21
1035         { "width columns", 0 },
1036 #define SVERSIONSW  22
1037         { "version", 0 },
1038 #define SHELPSW  23
1039         { "help", 0 },
1040 #define BITSTUFFSW  24
1041         { "dashstuffing", -12 },
1042 #define NBITSTUFFSW  25
1043         { "nodashstuffing", -14 },
1044 #define MAILSW  26
1045         { "mail", -4 },
1046 #define SAMLSW  27
1047         { "saml", -4 },
1048 #define SSNDSW  28
1049         { "send", -4 },
1050 #define SOMLSW  29
1051         { "soml", -4 },
1052 #define CLIESW  30
1053         { "client host", -6 },
1054 #define SERVSW  31
1055         { "server host", 6 },
1056 #define SNOOPSW  32
1057         { "snoop", -5 },
1058 #define SDRFSW  33
1059         { "draftfolder +folder", -6 },
1060 #define SDRMSW  34
1061         { "draftmessage msg", -6 },
1062 #define SNDRFSW  35
1063         { "nodraftfolder", -3 },
1064 #define SNDATTACHSW  39
1065         { "attach file", 6 },
1066 #define SNDATTACHFORMAT   40
1067         { "attachformat", 7 },
1068 #define PORTSW  41
1069         { "port server-port-name/number", 4 },
1070         { NULL, 0 }
1071 };
1072
1073
1074 extern int debugsw;  /* from sendsbr.c */
1075 extern int forwsw;
1076 extern int inplace;
1077 extern int pushsw;
1078 extern int splitsw;
1079 extern int unique;
1080 extern int verbsw;
1081
1082 extern char *altmsg;  /*  .. */
1083 extern char *annotext;
1084 extern char *distfile;
1085
1086
1087 static void
1088 sendit (char *sp, char **arg, char *file, int pushed)
1089 {
1090         int vecp, n = 1;
1091         char *cp, buf[BUFSIZ], **argp;
1092         char **arguments, *vec[MAXARGS];
1093         struct stat st;
1094         char *attach = (char *)0;  /* attachment header field name */
1095         int attachformat = 0;  /* mhbuild format specifier for
1096                                                                                    attachments */
1097
1098 #ifndef lint
1099         int distsw = 0;
1100 #endif
1101 #ifdef UCI
1102         FILE *fp;
1103 #endif
1104
1105         /*
1106          * Make sure these are defined.  In particular, we need
1107          * vec[1] to be NULL, in case "arg" is NULL below.  It
1108          * doesn't matter what is the value of vec[0], but we
1109          * set it to NULL, to help catch "off-by-one" errors.
1110          */
1111         vec[0] = NULL;
1112         vec[1] = NULL;
1113
1114         /*
1115          * Temporarily copy arg to vec, since the brkstring() call in
1116          * getarguments() will wipe it out before it is merged in.
1117          * Also, we skip the first element of vec, since getarguments()
1118          * skips it.  Then we count the number of arguments
1119          * copied.  The value of "n" will be one greater than
1120          * this in order to simulate the standard argc/argv.
1121          */
1122         if (arg) {
1123                 char **bp;
1124
1125                 copyip (arg, vec+1, MAXARGS-1);
1126                 bp = vec+1;
1127                 while (*bp++)
1128                         n++;
1129         }
1130
1131         /*
1132          * Merge any arguments from command line (now in vec)
1133          * and arguments from profile.
1134          */
1135         arguments = getarguments (sp, n, vec, 1);
1136         argp = arguments;
1137
1138         debugsw = 0;
1139         forwsw = 1;
1140         inplace = 1;
1141         unique = 0;
1142
1143         altmsg = NULL;
1144         annotext = NULL;
1145         distfile = NULL;
1146
1147         vecp = 1;  /* we'll get the zero'th element later */
1148         vec[vecp++] = "-library";
1149         vec[vecp++] = getcpy (m_maildir (""));
1150
1151         while ((cp = *argp++)) {
1152                 if (*cp == '-') {
1153                         switch (smatch (++cp, sendswitches)) {
1154                                 case AMBIGSW:
1155                                         ambigsw (cp, sendswitches);
1156                                         return;
1157                                 case UNKWNSW:
1158                                         advise (NULL, "-%s unknown\n", cp);
1159                                         return;
1160
1161                                 case SHELPSW:
1162                                         snprintf (buf, sizeof(buf), "%s [switches]", sp);
1163                                         print_help (buf, sendswitches, 1);
1164                                         return;
1165                                 case SVERSIONSW:
1166                                         print_version (invo_name);
1167                                         return;
1168
1169                                 case SPSHSW:
1170                                         pushed++;
1171                                         continue;
1172                                 case NSPSHSW:
1173                                         pushed = 0;
1174                                         continue;
1175
1176                                 case SPLITSW:
1177                                         if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) {
1178                                                 advise (NULL, "missing argument to %s", argp[-2]);
1179                                                 return;
1180                                         }
1181                                         continue;
1182
1183                                 case UNIQSW:
1184                                         unique++;
1185                                         continue;
1186                                 case NUNIQSW:
1187                                         unique = 0;
1188                                         continue;
1189                                 case FORWSW:
1190                                         forwsw++;
1191                                         continue;
1192                                 case NFORWSW:
1193                                         forwsw = 0;
1194                                         continue;
1195
1196                                 case VERBSW:
1197                                         verbsw++;
1198                                         vec[vecp++] = --cp;
1199                                         continue;
1200                                 case NVERBSW:
1201                                         verbsw = 0;
1202                                         vec[vecp++] = --cp;
1203                                         continue;
1204
1205                                 case DEBUGSW:
1206                                         debugsw++;  /* fall */
1207                                 case NFILTSW:
1208                                 case FRMTSW:
1209                                 case NFRMTSW:
1210                                 case BITSTUFFSW:
1211                                 case NBITSTUFFSW:
1212                                 case MIMESW:
1213                                 case NMIMESW:
1214                                 case MSGDSW:
1215                                 case NMSGDSW:
1216                                 case WATCSW:
1217                                 case NWATCSW:
1218                                 case MAILSW:
1219                                 case SAMLSW:
1220                                 case SSNDSW:
1221                                 case SOMLSW:
1222                                 case SNOOPSW:
1223                                         vec[vecp++] = --cp;
1224                                         continue;
1225
1226                                 case ALIASW:
1227                                 case FILTSW:
1228                                 case WIDTHSW:
1229                                 case CLIESW:
1230                                 case SERVSW:
1231                                 case PORTSW:
1232                                         vec[vecp++] = --cp;
1233                                         if (!(cp = *argp++) || *cp == '-') {
1234                                                 advise (NULL, "missing argument to %s", argp[-2]);
1235                                                 return;
1236                                         }
1237                                         vec[vecp++] = cp;
1238                                         continue;
1239
1240                                 case SDRFSW:
1241                                 case SDRMSW:
1242                                         if (!(cp = *argp++) || *cp == '-') {
1243                                                 advise (NULL, "missing argument to %s", argp[-2]);
1244                                                 return;
1245                                         }
1246                                 case SNDRFSW:
1247                                         continue;
1248
1249                                 case SNDATTACHSW:
1250                                         if (!(attach = *argp++) || *attach == '-') {
1251                                                 advise (NULL, "missing argument to %s", argp[-2]);
1252                                                 return;
1253                                         }
1254                                         continue;
1255
1256                                 case SNDATTACHFORMAT:
1257                                         if (! *argp || **argp == '-')
1258                                                 adios (NULL, "missing argument to %s", argp[-1]);
1259                                         else {
1260                                                 attachformat = atoi (*argp);
1261                                                 if (attachformat < 0 ||
1262                                                         attachformat > ATTACHFORMATS - 1) {
1263                                                         advise (NULL, "unsupported attachformat %d",
1264                                                                         attachformat);
1265                                                         continue;
1266                                                 }
1267                                         }
1268                                         ++argp;
1269                                         continue;
1270                         }
1271                 }
1272                 advise (NULL, "usage: %s [switches]", sp);
1273                 return;
1274         }
1275
1276         /* allow Aliasfile: profile entry */
1277         if ((cp = context_find ("Aliasfile"))) {
1278                 char **ap, *dp;
1279
1280                 dp = getcpy (cp);
1281                 for (ap = brkstring (dp, " ", "\n"); ap && *ap; ap++) {
1282                         vec[vecp++] = "-alias";
1283                         vec[vecp++] = *ap;
1284                 }
1285         }
1286
1287         if ((cp = getenv ("SIGNATURE")) == NULL || *cp == 0)
1288                 if ((cp = context_find ("signature")) && *cp)
1289                         m_putenv ("SIGNATURE", cp);
1290 #ifdef UCI
1291                 else {
1292                         snprintf (buf, sizeof(buf), "%s/.signature", mypath);
1293                         if ((fp = fopen (buf, "r")) != NULL
1294                                 && fgets (buf, sizeof(buf), fp) != NULL) {
1295                                         fclose (fp);
1296                                         if (cp = strchr (buf, '\n'))
1297                                                 *cp = 0;
1298                                         m_putenv ("SIGNATURE", buf);
1299                         }
1300                 }
1301 #endif /* UCI */
1302
1303         if ((annotext = getenv ("mhannotate")) == NULL || *annotext == 0)
1304                 annotext = NULL;
1305         if ((altmsg = getenv ("mhaltmsg")) == NULL || *altmsg == 0)
1306                 altmsg = NULL;
1307         if (annotext && ((cp = getenv ("mhinplace")) != NULL && *cp != 0))
1308                 inplace = atoi (cp);
1309
1310         if ((cp = getenv ("mhdist"))
1311                         && *cp
1312 #ifndef lint
1313                         && (distsw = atoi (cp))
1314 #endif /* not lint */
1315                         && altmsg) {
1316                 vec[vecp++] = "-dist";
1317                 distfile = getcpy (m_mktemp2(altmsg, invo_name, NULL, NULL));
1318                 if (link (altmsg, distfile) == NOTOK)
1319                         adios (distfile, "unable to link %s to", altmsg);
1320         } else {
1321                 distfile = NULL;
1322         }
1323
1324         if (altmsg == NULL || stat (altmsg, &st) == NOTOK) {
1325                 st.st_mtime = 0;
1326                 st.st_dev = 0;
1327                 st.st_ino = 0;
1328         }
1329         if ((pushsw = pushed))
1330                 push ();
1331
1332         vec[0] = r1bindex (postproc, '/');
1333         closefds (3);
1334
1335         if (sendsbr (vec, vecp, file, &st, 1, attach, attachformat) == OK)
1336                 done (0);
1337 }
1338
1339 /*
1340  * WHOM
1341  */
1342
1343 static int
1344 whomfile (char **arg, char *file)
1345 {
1346         pid_t pid;
1347         int vecp;
1348         char *vec[MAXARGS];
1349
1350         context_save ();  /* save the context file */
1351         fflush (stdout);
1352
1353         switch (pid = vfork ()) {
1354                 case NOTOK:
1355                         advise ("fork", "unable to");
1356                         return 1;
1357
1358                 case OK:
1359                         vecp = 0;
1360                         vec[vecp++] = r1bindex (whomproc, '/');
1361                         vec[vecp++] = file;
1362                         if (arg)
1363                                 while (*arg)
1364                                         vec[vecp++] = *arg++;
1365                         vec[vecp] = NULL;
1366
1367                         execvp (whomproc, vec);
1368                         fprintf (stderr, "unable to exec ");
1369                         perror (whomproc);
1370                         _exit (-1);  /* NOTREACHED */
1371
1372                 default:
1373                         return (pidwait (pid, NOTOK) & 0377 ? 1 : 0);
1374         }
1375 }
1376
1377
1378 /*
1379  * Remove the draft file
1380  */
1381
1382 static int
1383 removefile (char *drft)
1384 {
1385         if (unlink (drft) == NOTOK)
1386                 adios (drft, "unable to unlink");
1387
1388         return OK;
1389 }