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