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