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