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