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