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