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