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