]> diplodocus.org Git - nmh/blob - uip/whatnowsbr.c
Finished replacing mh_strcasecmp() with strcasecmp(). Removed
[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 && *++argp) {
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 if (pushsw)
856 vec[vecp++] = "-push";
857 if (arg)
858 while (*arg)
859 vec[vecp++] = *arg++;
860 vec[vecp++] = file;
861 vec[vecp] = NULL;
862
863 execvp (program, vec);
864 fprintf (stderr, "unable to exec ");
865 perror (sendproc);
866 _exit (-1);
867
868 default:
869 if (pidwait(child_id, OK) == 0)
870 done (0);
871 return 1;
872 }
873 }
874
875
876 /*
877 * Translate MIME composition file (call buildmimeproc)
878 */
879
880 static int
881 buildfile (char **argp, char *file)
882 {
883 int i;
884 char **args, *ed;
885
886 ed = buildmimeproc;
887
888 /* allocate space for arguments */
889 i = 0;
890 if (argp) {
891 while (argp[i])
892 i++;
893 }
894 args = (char **) mh_xmalloc((i + 2) * sizeof(char *));
895
896 /*
897 * For backward compatibility, we need to add -build
898 * if we are using mhn as buildmimeproc
899 */
900 i = 0;
901 if (strcmp (r1bindex (ed, '/'), "mhn") == 0)
902 args[i++] = "-build";
903
904 /* copy any other arguments */
905 while (argp && *argp)
906 args[i++] = *argp++;
907 args[i] = NULL;
908
909 i = editfile (&ed, args, file, NOUSE, NULL, NULL, NULL, 0, 0);
910 free (args);
911
912 return (i ? NOTOK : OK);
913 }
914
915
916 /*
917 * Check if draft is a mhbuild composition file
918 */
919
920 static int
921 check_draft (char *msgnam)
922 {
923 int state;
924 char buf[BUFSIZ], name[NAMESZ];
925 FILE *fp;
926 m_getfld_state_t gstate = 0;
927
928 if ((fp = fopen (msgnam, "r")) == NULL)
929 return 0;
930 for (;;) {
931 int bufsz = sizeof buf;
932 switch (state = m_getfld (&gstate, name, buf, &bufsz, fp)) {
933 case FLD:
934 case FLDPLUS:
935 /*
936 * If draft already contains any of the
937 * Content-XXX fields, then assume it already
938 * been converted.
939 */
940 if (uprf (name, XXX_FIELD_PRF)) {
941 fclose (fp);
942 m_getfld_state_destroy (&gstate);
943 return 0;
944 }
945 while (state == FLDPLUS) {
946 bufsz = sizeof buf;
947 state = m_getfld (&gstate, name, buf, &bufsz, fp);
948 }
949 break;
950
951 case BODY:
952 do {
953 char *bp;
954
955 for (bp = buf; *bp; bp++)
956 if (*bp != ' ' && *bp != '\t' && *bp != '\n') {
957 fclose (fp);
958 m_getfld_state_destroy (&gstate);
959 return 1;
960 }
961
962 bufsz = sizeof buf;
963 state = m_getfld (&gstate, name, buf, &bufsz, fp);
964 } while (state == BODY);
965 /* and fall... */
966
967 default:
968 fclose (fp);
969 m_getfld_state_destroy (&gstate);
970 return 0;
971 }
972 }
973 }
974
975
976 #ifndef CYRUS_SASL
977 # define SASLminc(a) (a)
978 #else /* CYRUS_SASL */
979 # define SASLminc(a) 0
980 #endif /* CYRUS_SASL */
981
982 #ifndef TLS_SUPPORT
983 # define TLSminc(a) (a)
984 #else /* TLS_SUPPORT */
985 # define TLSminc(a) 0
986 #endif /* TLS_SUPPORT */
987
988 #define SEND_SWITCHES \
989 X("alias aliasfile", 0, ALIASW) \
990 X("debug", -5, DEBUGSW) \
991 X("filter filterfile", 0, FILTSW) \
992 X("nofilter", 0, NFILTSW) \
993 X("format", 0, FRMTSW) \
994 X("noformat", 0, NFRMTSW) \
995 X("forward", 0, FORWSW) \
996 X("noforward", 0, NFORWSW) \
997 X("mime", 0, MIMESW) \
998 X("nomime", 0, NMIMESW) \
999 X("msgid", 0, MSGDSW) \
1000 X("nomsgid", 0, NMSGDSW) \
1001 X("push", 0, SPSHSW) \
1002 X("nopush", 0, NSPSHSW) \
1003 X("split seconds", 0, SPLITSW) \
1004 X("unique", -6, UNIQSW) \
1005 X("nounique", -8, NUNIQSW) \
1006 X("verbose", 0, VERBSW) \
1007 X("noverbose", 0, NVERBSW) \
1008 X("watch", 0, WATCSW) \
1009 X("nowatch", 0, NWATCSW) \
1010 X("width columns", 0, WIDTHSW) \
1011 X("version", 0, SVERSIONSW) \
1012 X("help", 0, SHELPSW) \
1013 X("dashstuffing", -12, BITSTUFFSW) \
1014 X("nodashstuffing", -14, NBITSTUFFSW) \
1015 X("client host", -6, CLIESW) \
1016 X("server host", 6, SERVSW) \
1017 X("snoop", -5, SNOOPSW) \
1018 X("draftfolder +folder", -6, SDRFSW) \
1019 X("draftmessage msg", -6, SDRMSW) \
1020 X("nodraftfolder", -3, SNDRFSW) \
1021 X("sasl", SASLminc(-4), SASLSW) \
1022 X("nosasl", SASLminc(-6), NOSASLSW) \
1023 X("saslmaxssf", SASLminc(-10), SASLMXSSFSW) \
1024 X("saslmech", SASLminc(-5), SASLMECHSW) \
1025 X("user", SASLminc(-4), USERSW) \
1026 X("attach file", 6, SNDATTACHSW) \
1027 X("noattach", 0, SNDNOATTACHSW) \
1028 X("attachformat", 7, SNDATTACHFORMAT) \
1029 X("port server-port-name/number", 4, PORTSW) \
1030 X("tls", TLSminc(-3), TLSSW) \
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 while ((cp = *argp++)) {
1134 if (*cp == '-') {
1135 switch (smatch (++cp, sendswitches)) {
1136 case AMBIGSW:
1137 ambigsw (cp, sendswitches);
1138 return;
1139 case UNKWNSW:
1140 advise (NULL, "-%s unknown\n", cp);
1141 return;
1142
1143 case SHELPSW:
1144 snprintf (buf, sizeof(buf), "%s [switches]", sp);
1145 print_help (buf, sendswitches, 1);
1146 return;
1147 case SVERSIONSW:
1148 print_version (invo_name);
1149 return;
1150
1151 case SPSHSW:
1152 pushed++;
1153 continue;
1154 case NSPSHSW:
1155 pushed = 0;
1156 continue;
1157
1158 case SPLITSW:
1159 if (!(cp = *argp++) || sscanf (cp, "%d", &splitsw) != 1) {
1160 advise (NULL, "missing argument to %s", argp[-2]);
1161 return;
1162 }
1163 continue;
1164
1165 case UNIQSW:
1166 unique++;
1167 continue;
1168 case NUNIQSW:
1169 unique = 0;
1170 continue;
1171 case FORWSW:
1172 forwsw++;
1173 continue;
1174 case NFORWSW:
1175 forwsw = 0;
1176 continue;
1177
1178 case VERBSW:
1179 verbsw++;
1180 vec[vecp++] = --cp;
1181 continue;
1182 case NVERBSW:
1183 verbsw = 0;
1184 vec[vecp++] = --cp;
1185 continue;
1186
1187 case DEBUGSW:
1188 debugsw++; /* fall */
1189 case NFILTSW:
1190 case FRMTSW:
1191 case NFRMTSW:
1192 case BITSTUFFSW:
1193 case NBITSTUFFSW:
1194 case MIMESW:
1195 case NMIMESW:
1196 case MSGDSW:
1197 case NMSGDSW:
1198 case WATCSW:
1199 case NWATCSW:
1200 case SNOOPSW:
1201 case SASLSW:
1202 case NOSASLSW:
1203 case TLSSW:
1204 case NTLSSW:
1205 vec[vecp++] = --cp;
1206 continue;
1207
1208 case ALIASW:
1209 case FILTSW:
1210 case WIDTHSW:
1211 case CLIESW:
1212 case SERVSW:
1213 case SASLMXSSFSW:
1214 case SASLMECHSW:
1215 case USERSW:
1216 case PORTSW:
1217 case MTSSW:
1218 case MESSAGEIDSW:
1219 vec[vecp++] = --cp;
1220 if (!(cp = *argp++) || *cp == '-') {
1221 advise (NULL, "missing argument to %s", argp[-2]);
1222 return;
1223 }
1224 vec[vecp++] = cp;
1225 continue;
1226
1227 case SDRFSW:
1228 case SDRMSW:
1229 if (!(cp = *argp++) || *cp == '-') {
1230 advise (NULL, "missing argument to %s", argp[-2]);
1231 return;
1232 }
1233 case SNDRFSW:
1234 continue;
1235
1236 case SNDATTACHSW:
1237 if (!(attach = *argp++) || *attach == '-') {
1238 advise (NULL, "missing argument to %s", argp[-2]);
1239 return;
1240 }
1241 continue;
1242 case SNDNOATTACHSW:
1243 attach = NULL;
1244 continue;
1245
1246 case SNDATTACHFORMAT:
1247 if (! *argp || **argp == '-')
1248 adios (NULL, "missing argument to %s", argp[-1]);
1249 else {
1250 attachformat = atoi (*argp);
1251 if (attachformat < 0 ||
1252 attachformat > ATTACHFORMATS - 1) {
1253 advise (NULL, "unsupported attachformat %d",
1254 attachformat);
1255 continue;
1256 }
1257 }
1258 ++argp;
1259 continue;
1260 }
1261 }
1262 advise (NULL, "usage: %s [switches]", sp);
1263 return;
1264 }
1265
1266 /* allow Aliasfile: profile entry */
1267 if ((cp = context_find ("Aliasfile"))) {
1268 char **ap, *dp;
1269
1270 dp = getcpy (cp);
1271 for (ap = brkstring (dp, " ", "\n"); ap && *ap; ap++) {
1272 vec[vecp++] = "-alias";
1273 vec[vecp++] = *ap;
1274 }
1275 }
1276
1277 if ((cp = getenv ("SIGNATURE")) == NULL || *cp == 0)
1278 if ((cp = context_find ("signature")) && *cp)
1279 m_putenv ("SIGNATURE", cp);
1280
1281 if ((annotext = getenv ("mhannotate")) == NULL || *annotext == 0)
1282 annotext = NULL;
1283 if ((altmsg = getenv ("mhaltmsg")) == NULL || *altmsg == 0)
1284 altmsg = NULL;
1285 if (annotext && ((cp = getenv ("mhinplace")) != NULL && *cp != 0))
1286 inplace = atoi (cp);
1287
1288 if ((cp = getenv ("mhdist"))
1289 && *cp
1290 #ifndef lint
1291 && (distsw = atoi (cp))
1292 #endif /* not lint */
1293 && altmsg) {
1294 vec[vecp++] = "-dist";
1295 distfile = getcpy (m_mktemp2(altmsg, invo_name, NULL, NULL));
1296 unlink(distfile);
1297 if (link (altmsg, distfile) == NOTOK)
1298 adios (distfile, "unable to link %s to", altmsg);
1299 } else {
1300 distfile = NULL;
1301 }
1302
1303 if (altmsg == NULL || stat (altmsg, &st) == NOTOK) {
1304 st.st_mtime = 0;
1305 st.st_dev = 0;
1306 st.st_ino = 0;
1307 }
1308 if ((pushsw = pushed))
1309 push ();
1310
1311 closefds (3);
1312
1313 if (sendsbr (vec, vecp, program, file, &st, 1, attach, attachformat) == OK)
1314 done (0);
1315 }
1316
1317 /*
1318 * WHOM
1319 */
1320
1321 static int
1322 whomfile (char **arg, char *file)
1323 {
1324 pid_t pid;
1325 int vecp;
1326 char **vec, *program;
1327
1328 context_save (); /* save the context file */
1329 fflush (stdout);
1330
1331 switch (pid = fork()) {
1332 case NOTOK:
1333 advise ("fork", "unable to");
1334 return 1;
1335
1336 case OK:
1337 vec = argsplit(whomproc, &program, &vecp);
1338 vec[vecp++] = file;
1339 if (arg)
1340 while (*arg)
1341 vec[vecp++] = *arg++;
1342 vec[vecp] = NULL;
1343
1344 execvp (program, vec);
1345 fprintf (stderr, "unable to exec ");
1346 perror (whomproc);
1347 _exit (-1); /* NOTREACHED */
1348
1349 default:
1350 return (pidwait (pid, NOTOK) & 0377 ? 1 : 0);
1351 }
1352 }
1353
1354
1355 /*
1356 * Remove the draft file
1357 */
1358
1359 static int
1360 removefile (char *drft)
1361 {
1362 if (unlink (drft) == NOTOK)
1363 adios (drft, "unable to unlink");
1364
1365 return OK;
1366 }