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