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