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