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