]> diplodocus.org Git - nmh/blob - uip/sendsbr.c
Alter mh-chart(7)'s NAME to be lowercase.
[nmh] / uip / sendsbr.c
1
2 /*
3 * sendsbr.c -- routines to help WhatNow/Send along
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
10 #include <h/mh.h>
11 #include <h/fmt_scan.h>
12 #include <h/fmt_compile.h>
13 #include <h/signals.h>
14 #include <setjmp.h>
15 #include <fcntl.h>
16 #include <h/mime.h>
17 #include <h/tws.h>
18 #include <h/utils.h>
19 #include <h/mts.h>
20
21 #ifdef HAVE_SYS_TIME_H
22 # include <sys/time.h>
23 #endif
24 #include <time.h>
25
26 #ifdef OAUTH_SUPPORT
27 #include <h/oauth.h>
28
29 static int setup_oauth_params(char *[], int *, const char *, const char **);
30 #endif /* OAUTH_SUPPORT */
31
32 int debugsw = 0; /* global */
33 int forwsw = 1;
34 int inplace = 1;
35 int pushsw = 0;
36 int splitsw = -1;
37 int unique = 0;
38 int verbsw = 0;
39
40 char *altmsg = NULL; /* .. */
41 char *annotext = NULL;
42 char *distfile = NULL;
43
44 static jmp_buf env;
45
46 /*
47 * static prototypes
48 */
49 static void alert (char *, int);
50 static int tmp_fd (void);
51 static void anno (int, struct stat *);
52 static void annoaux (int);
53 static int splitmsg (char **, int, char *, char *, struct stat *, int);
54 static int sendaux (char **, int, char *, char *, struct stat *);
55 static void handle_sendfrom(char **, int *, char *, const char *);
56 static int get_from_header_info(const char *, const char **, const char **, const char **);
57 static const char *get_message_header_info(FILE *, char *);
58 static void merge_profile_entry(const char *, const char *, char *[], int *);
59 static void armed_done (int) NORETURN;
60
61 /*
62 * Entry point into (back-end) routines to send message.
63 */
64
65 int
66 sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
67 int rename_drft, const char *auth_svc)
68 {
69 int status, i;
70 pid_t child;
71 char buffer[BUFSIZ], file[BUFSIZ];
72 struct stat sts;
73 char **buildvec, *buildprogram;
74 char *volatile drft = draft;
75 /* nvecs is volatile to prevent warning from gcc about possible clobbering
76 by longjmp. */
77 volatile int nvecs = vecp;
78 int *nvecsp = (int *) &nvecs;
79
80 /*
81 * Run the mimebuildproc (which is by default mhbuild) on the message
82 * with the addition of the "-auto" flag
83 */
84
85 switch (child = fork()) {
86 case NOTOK:
87 adios("fork", "unable to");
88 break;
89
90 case OK:
91 buildvec = argsplit(buildmimeproc, &buildprogram, &i);
92 buildvec[i++] = "-auto";
93 if (distfile)
94 buildvec[i++] = "-dist";
95 buildvec[i++] = (char *) drft;
96 buildvec[i] = NULL;
97 execvp(buildprogram, buildvec);
98 fprintf(stderr, "unable to exec ");
99 perror(buildmimeproc);
100 _exit(-1);
101 break;
102
103 default:
104 if (pidXwait(child, buildmimeproc))
105 return NOTOK;
106 break;
107 }
108
109 done=armed_done;
110 switch (setjmp (env)) {
111 case OK:
112 /*
113 * If given -push and -unique (which is undocumented), then
114 * rename the draft file. I'm not quite sure why.
115 */
116 if (pushsw && unique) {
117 char *cp = m_mktemp2(drft, invo_name, NULL, NULL);
118 if (cp == NULL) {
119 adios(NULL, "unable to create temporary file in %s",
120 get_temp_dir());
121 }
122 if (rename (drft, strncpy(file, cp, sizeof(file))) == NOTOK)
123 adios (file, "unable to rename %s to", drft);
124 drft = file;
125 }
126
127 /*
128 * Add in any necessary profile entries for xoauth
129 */
130
131 if (auth_svc) {
132 #ifdef OAUTH_SUPPORT
133 const char *errmsg;
134 if (setup_oauth_params(vec, nvecsp, auth_svc, &errmsg) != OK) {
135 adios(NULL, errmsg);
136 }
137 #else
138 adios(NULL, "send built without OAUTH_SUPPORT, "
139 "so auth_svc %s is not supported", auth_svc);
140 #endif /* OAUTH_SUPPORT */
141 }
142
143 /*
144 * Rework the vec based on From: header in draft, as specified
145 * by sendfrom-address entries in profile.
146 */
147 if (context_find_prefix("sendfrom-")) {
148 handle_sendfrom(vec, nvecsp, draft, auth_svc);
149 }
150
151 /*
152 * Check if we need to split the message into
153 * multiple messages of type "message/partial".
154 */
155 if (splitsw >= 0 && !distfile && stat ((char *) drft, &sts) != NOTOK
156 && sts.st_size >= CPERMSG) {
157 status = splitmsg (vec, nvecs, program, drft,
158 st, splitsw) ? NOTOK : OK;
159 } else {
160 status = sendaux (vec, nvecs, program, drft, st) ? NOTOK : OK;
161 }
162
163 /* rename the original draft */
164 if (rename_drft && status == OK &&
165 rename (drft, strncpy (buffer, m_backup (drft),
166 sizeof(buffer))) == NOTOK)
167 advise (buffer, "unable to rename %s to", drft);
168 break;
169
170 default:
171 status = DONE;
172 break;
173 }
174
175 done=exit;
176 if (distfile)
177 (void) m_unlink (distfile);
178
179 return status;
180 }
181
182 /*
183 * Split large message into several messages of
184 * type "message/partial" and send them.
185 */
186
187 static int
188 splitmsg (char **vec, int vecp, char *program, char *drft,
189 struct stat *st, int delay)
190 {
191 int compnum, nparts, partno, state, status;
192 long pos, start;
193 time_t clock;
194 char *cp, *dp, buffer[BUFSIZ], msgid[BUFSIZ];
195 char subject[BUFSIZ];
196 char name[NAMESZ], partnum[BUFSIZ];
197 FILE *in;
198 m_getfld_state_t gstate = 0;
199
200 if ((in = fopen (drft, "r")) == NULL)
201 adios (drft, "unable to open for reading");
202
203 cp = dp = NULL;
204 start = 0L;
205
206 /*
207 * Scan through the message and examine the various header fields,
208 * as well as locate the beginning of the message body.
209 */
210 m_getfld_track_filepos (&gstate, in);
211 for (compnum = 1;;) {
212 int bufsz = sizeof buffer;
213 switch (state = m_getfld (&gstate, name, buffer, &bufsz, in)) {
214 case FLD:
215 case FLDPLUS:
216 compnum++;
217
218 /*
219 * This header field is discarded.
220 */
221 if (!strcasecmp (name, "Message-ID")) {
222 while (state == FLDPLUS) {
223 bufsz = sizeof buffer;
224 state = m_getfld (&gstate, name, buffer, &bufsz, in);
225 }
226 } else if (uprf (name, XXX_FIELD_PRF)
227 || !strcasecmp (name, VRSN_FIELD)
228 || !strcasecmp (name, "Subject")
229 || !strcasecmp (name, "Encrypted")) {
230 /*
231 * These header fields are copied to the enclosed
232 * header of the first message in the collection
233 * of message/partials. For the "Subject" header
234 * field, we also record it, so that a modified
235 * version of it, can be copied to the header
236 * of each message/partial in the collection.
237 */
238 if (!strcasecmp (name, "Subject")) {
239 size_t sublen;
240
241 strncpy (subject, buffer, BUFSIZ);
242 sublen = strlen (subject);
243 if (sublen > 0 && subject[sublen - 1] == '\n')
244 subject[sublen - 1] = '\0';
245 }
246
247 dp = add (concat (name, ":", buffer, NULL), dp);
248 while (state == FLDPLUS) {
249 bufsz = sizeof buffer;
250 state = m_getfld (&gstate, name, buffer, &bufsz, in);
251 dp = add (buffer, dp);
252 }
253 } else {
254 /*
255 * These header fields are copied to the header of
256 * each message/partial in the collection.
257 */
258 cp = add (concat (name, ":", buffer, NULL), cp);
259 while (state == FLDPLUS) {
260 bufsz = sizeof buffer;
261 state = m_getfld (&gstate, name, buffer, &bufsz, in);
262 cp = add (buffer, cp);
263 }
264 }
265
266 start = ftell (in) + 1;
267 continue;
268
269 case BODY:
270 case FILEEOF:
271 break;
272
273 case LENERR:
274 case FMTERR:
275 adios (NULL, "message format error in component #%d", compnum);
276
277 default:
278 adios (NULL, "getfld () returned %d", state);
279 }
280
281 break;
282 }
283 m_getfld_state_destroy (&gstate);
284 if (cp == NULL)
285 adios (NULL, "headers missing from draft");
286
287 nparts = 1;
288 pos = start;
289 while (fgets (buffer, sizeof buffer, in)) {
290 long len;
291
292 if ((pos += (len = strlen (buffer))) > CPERMSG) {
293 nparts++;
294 pos = len;
295 }
296 }
297
298 /* Only one part, nothing to split */
299 if (nparts == 1) {
300 free (cp);
301 mh_xfree(dp);
302
303 fclose (in);
304 return sendaux (vec, vecp, program, drft, st);
305 }
306
307 if (!pushsw) {
308 printf ("Sending as %d Partial Messages\n", nparts);
309 fflush (stdout);
310 }
311 status = OK;
312
313 vec[vecp++] = "-partno";
314 vec[vecp++] = partnum;
315 if (delay == 0)
316 vec[vecp++] = "-queued";
317
318 time (&clock);
319 snprintf (msgid, sizeof(msgid), "%s", message_id (clock, 0));
320
321 fseek (in, start, SEEK_SET);
322 for (partno = 1; partno <= nparts; partno++) {
323 char tmpdrf[BUFSIZ];
324 FILE *out;
325
326 char *cp = m_mktemp2(drft, invo_name, NULL, &out);
327 if (cp == NULL) {
328 adios(NULL, "unable to create temporary file in %s",
329 get_temp_dir());
330 }
331 strncpy(tmpdrf, cp, sizeof(tmpdrf));
332
333 /*
334 * Output the header fields
335 */
336 fputs (cp, out);
337 fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts);
338 fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
339 fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid);
340 fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts);
341 fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts);
342
343 /*
344 * If this is the first in the collection, output the
345 * header fields we are encapsulating at the beginning
346 * of the body of the first message.
347 */
348 if (partno == 1) {
349 if (dp)
350 fputs (dp, out);
351 fprintf (out, "Message-ID: %s\n", msgid);
352 fprintf (out, "\n");
353 }
354
355 pos = 0;
356 for (;;) {
357 long len;
358
359 if (!fgets (buffer, sizeof buffer, in)) {
360 if (partno == nparts)
361 break;
362 adios (NULL, "premature eof");
363 }
364
365 if ((pos += (len = strlen (buffer))) > CPERMSG) {
366 fseek (in, -len, SEEK_CUR);
367 break;
368 }
369
370 fputs (buffer, out);
371 }
372
373 if (fflush (out))
374 adios (tmpdrf, "error writing to");
375
376 fclose (out);
377
378 if (!pushsw && verbsw) {
379 putchar('\n');
380 fflush (stdout);
381 }
382
383 /* Pause here, if a delay is specified */
384 if (delay > 0 && 1 < partno && partno <= nparts) {
385 if (!pushsw) {
386 printf ("pausing %d seconds before sending part %d...\n",
387 delay, partno);
388 fflush (stdout);
389 }
390 sleep ((unsigned int) delay);
391 }
392
393 snprintf (partnum, sizeof(partnum), "%d", partno);
394 status = sendaux (vec, vecp, program, tmpdrf, st);
395 (void) m_unlink (tmpdrf);
396 if (status != OK)
397 break;
398
399 /*
400 * This is so sendaux will only annotate
401 * the altmsg the first time it is called.
402 */
403 annotext = NULL;
404 }
405
406 free (cp);
407 mh_xfree(dp);
408
409 fclose (in); /* close the draft */
410 return status;
411 }
412
413
414 /*
415 * Annotate original message, and
416 * call `postproc' (which is passed down in "program") to send message.
417 */
418
419 static int
420 sendaux (char **vec, int vecp, char *program, char *drft, struct stat *st)
421 {
422 pid_t child_id;
423 int i, status, fd, fd2;
424 char backup[BUFSIZ], buf[BUFSIZ];
425
426 fd = pushsw ? tmp_fd () : NOTOK;
427 fd2 = NOTOK;
428
429 if (annotext) {
430 if ((fd2 = tmp_fd ()) != NOTOK) {
431 vec[vecp++] = "-idanno";
432 snprintf (buf, sizeof(buf), "%d", fd2);
433 vec[vecp++] = buf;
434 } else {
435 admonish (NULL, "unable to create temporary file in %s "
436 "for annotation list", get_temp_dir());
437 }
438 }
439 vec[vecp++] = drft;
440 if (distfile && distout (drft, distfile, backup) == NOTOK)
441 done (1);
442 vec[vecp] = NULL;
443
444 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
445 sleep (5);
446
447 switch (child_id) {
448 case -1:
449 /* oops -- fork error */
450 adios ("fork", "unable to");
451 break; /* NOT REACHED */
452
453 case 0:
454 /*
455 * child process -- send it
456 *
457 * If fd is ok, then we are pushing and fd points to temp
458 * file, so capture anything on stdout and stderr there.
459 */
460 if (fd != NOTOK) {
461 dup2 (fd, fileno (stdout));
462 dup2 (fd, fileno (stderr));
463 close (fd);
464 }
465 execvp (program, vec);
466 fprintf (stderr, "unable to exec ");
467 perror (postproc);
468 _exit (-1);
469
470 default:
471 /*
472 * parent process -- wait for it
473 */
474 if ((status = pidwait(child_id, NOTOK)) == OK) {
475 if (annotext && fd2 != NOTOK)
476 anno (fd2, st);
477 } else {
478 /*
479 * If postproc failed, and we have good fd (which means
480 * we pushed), then mail error message (and possibly the
481 * draft) back to the user.
482 */
483 if (fd != NOTOK) {
484 alert (drft, fd);
485 close (fd);
486 } else {
487 advise (NULL, "message not delivered to anyone");
488 }
489 if (annotext && fd2 != NOTOK)
490 close (fd2);
491 if (distfile) {
492 (void) m_unlink (drft);
493 if (rename (backup, drft) == NOTOK)
494 advise (drft, "unable to rename %s to", backup);
495 }
496 }
497 break;
498 }
499
500 return status;
501 }
502
503
504 /*
505 * Mail error notification (and possibly a copy of the
506 * message) back to the user, using the mailproc
507 */
508
509 static void
510 alert (char *file, int out)
511 {
512 pid_t child_id;
513 int i, in, argp;
514 char buf[BUFSIZ];
515 char *program;
516 char **arglist;
517
518 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
519 sleep (5);
520
521 switch (child_id) {
522 case NOTOK:
523 /* oops -- fork error */
524 advise ("fork", "unable to");
525 /* FALLTHRU */
526
527 case OK:
528 /* child process -- send it */
529 SIGNAL (SIGHUP, SIG_IGN);
530 SIGNAL (SIGINT, SIG_IGN);
531 SIGNAL (SIGQUIT, SIG_IGN);
532 SIGNAL (SIGTERM, SIG_IGN);
533 if (forwsw) {
534 if ((in = open (file, O_RDONLY)) == NOTOK) {
535 admonish (file, "unable to re-open");
536 } else {
537 lseek (out, (off_t) 0, SEEK_END);
538 strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
539 if (write (out, buf, strlen (buf)) < 0) {
540 advise (file, "write");
541 }
542 strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
543 if (write (out, buf, strlen (buf)) < 0) {
544 advise (file, "write");
545 }
546 cpydgst (in, out, file, "temporary file");
547 close (in);
548 strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
549 if (write (out, buf, strlen (buf)) < 0) {
550 advise (file, "write");
551 }
552 if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
553 admonish (buf, "unable to rename %s to", file);
554 }
555 }
556 lseek (out, (off_t) 0, SEEK_SET);
557 dup2 (out, fileno (stdin));
558 close (out);
559 /* create subject for error notification */
560 snprintf (buf, sizeof(buf), "send failed on %s",
561 forwsw ? "enclosed draft" : file);
562
563 arglist = argsplit(mailproc, &program, &argp);
564
565 arglist[argp++] = getusername();
566 arglist[argp++] = "-subject";
567 arglist[argp++] = buf;
568 arglist[argp] = NULL;
569
570 execvp (program, arglist);
571 fprintf (stderr, "unable to exec ");
572 perror (mailproc);
573 _exit (-1);
574
575 default: /* no waiting... */
576 break;
577 }
578 }
579
580
581 static int
582 tmp_fd (void)
583 {
584 int fd;
585 char *tfile;
586
587 if ((tfile = m_mktemp2(NULL, invo_name, &fd, NULL)) == NULL) return NOTOK;
588
589 if (debugsw)
590 advise (NULL, "temporary file %s selected", tfile);
591 else
592 if (m_unlink (tfile) == NOTOK)
593 advise (tfile, "unable to remove");
594
595 return fd;
596 }
597
598
599 static void
600 anno (int fd, struct stat *st)
601 {
602 pid_t child_id;
603 sigset_t set, oset;
604 static char *cwd = NULL;
605 struct stat st2;
606
607 if (altmsg &&
608 (stat (altmsg, &st2) == NOTOK
609 || st->st_mtime != st2.st_mtime
610 || st->st_dev != st2.st_dev
611 || st->st_ino != st2.st_ino)) {
612 if (debugsw)
613 admonish (NULL, "$mhaltmsg mismatch");
614 return;
615 }
616
617 child_id = debugsw ? NOTOK : fork ();
618 switch (child_id) {
619 case NOTOK: /* oops */
620 if (!debugsw)
621 advise (NULL,
622 "unable to fork, so doing annotations by hand...");
623 if (cwd == NULL)
624 cwd = mh_xstrdup(pwd ());
625 /* FALLTHRU */
626
627 case OK:
628 /* block a few signals */
629 sigemptyset (&set);
630 sigaddset (&set, SIGHUP);
631 sigaddset (&set, SIGINT);
632 sigaddset (&set, SIGQUIT);
633 sigaddset (&set, SIGTERM);
634 sigprocmask (SIG_BLOCK, &set, &oset);
635
636 unregister_for_removal(0);
637
638 annoaux (fd);
639 if (child_id == OK)
640 _exit (0);
641
642 /* reset the signal mask */
643 sigprocmask (SIG_SETMASK, &oset, &set);
644
645 if (chdir (cwd) < 0) {
646 advise (cwd, "chdir");
647 }
648 break;
649
650 default: /* no waiting... */
651 close (fd);
652 break;
653 }
654 }
655
656
657 static void
658 annoaux (int fd)
659 {
660 int fd2, fd3, msgnum;
661 char *cp, *folder, *maildir;
662 char buffer[BUFSIZ], **ap;
663 FILE *fp;
664 struct msgs *mp;
665
666 if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) {
667 if (debugsw)
668 admonish (NULL, "$mhfolder not set");
669 return;
670 }
671 maildir = m_maildir (folder);
672 if (chdir (maildir) == NOTOK) {
673 if (debugsw)
674 admonish (maildir, "unable to change directory to");
675 return;
676 }
677 if (!(mp = folder_read (folder, 0))) {
678 if (debugsw)
679 admonish (NULL, "unable to read folder %s", folder);
680 return;
681 }
682
683 /* check for empty folder */
684 if (mp->nummsg == 0) {
685 if (debugsw)
686 admonish (NULL, "no messages in %s", folder);
687 goto oops;
688 }
689
690 if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
691 if (debugsw)
692 admonish (NULL, "$mhmessages not set");
693 goto oops;
694 }
695 if (!debugsw /* MOBY HACK... */
696 && pushsw
697 && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK
698 && (fd2 = dup (fileno (stderr))) != NOTOK) {
699 dup2 (fd3, fileno (stderr));
700 close (fd3);
701 }
702 else
703 fd2 = NOTOK;
704 for (ap = brkstring (cp = mh_xstrdup(cp), " ", NULL); *ap; ap++)
705 m_convert (mp, *ap);
706 free (cp);
707 if (fd2 != NOTOK)
708 dup2 (fd2, fileno (stderr));
709 if (mp->numsel == 0) {
710 if (debugsw)
711 admonish (NULL, "no messages to annotate");
712 goto oops;
713 }
714
715 lseek (fd, (off_t) 0, SEEK_SET);
716 if ((fp = fdopen (fd, "r")) == NULL) {
717 if (debugsw)
718 admonish (NULL, "unable to fdopen annotation list");
719 goto oops;
720 }
721 cp = NULL;
722 while (fgets (buffer, sizeof(buffer), fp) != NULL)
723 cp = add (buffer, cp);
724 fclose (fp);
725
726 if (debugsw)
727 advise (NULL, "annotate%s with %s: \"%s\"",
728 inplace ? " inplace" : "", annotext, cp);
729 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
730 if (is_selected(mp, msgnum)) {
731 if (debugsw)
732 advise (NULL, "annotate message %d", msgnum);
733 annotate (m_name (msgnum), annotext, cp, inplace, 1, -2, 0);
734 }
735 }
736
737 free (cp);
738
739 oops:
740 folder_free (mp); /* free folder/message structure */
741 }
742
743
744 static
745 void
746 handle_sendfrom(char **vec, int *vecp, char *draft, const char *auth_svc) {
747 const char *addr, *host;
748 const char *message;
749
750 /* Extract address and host from From: header line in draft. */
751 if (get_from_header_info(draft, &addr, &host, &message) != OK) {
752 adios(draft, message);
753 }
754
755 /* Merge in any address or host specific switches to post(1) from profile. */
756 merge_profile_entry(addr, host, vec, vecp);
757 free((void *) host);
758 free((void *) addr);
759
760 vec[*vecp] = NULL;
761
762 {
763 char **vp;
764
765 for (vp = vec; *vp; ++vp) {
766 if (strcmp(*vp, "xoauth2") == 0) {
767 #ifdef OAUTH_SUPPORT
768 if (setup_oauth_params(vec, vecp, auth_svc, &message) != OK) {
769 adios(NULL, message);
770 }
771 break;
772 #else
773 NMH_UNUSED(auth_svc);
774 adios(NULL, "send built without OAUTH_SUPPORT, "
775 "so -saslmech xoauth2 is not supported");
776 #endif /* OAUTH_SUPPORT */
777 }
778 }
779 }
780 }
781
782
783 #ifdef OAUTH_SUPPORT
784 /*
785 * For XOAUTH2, append profile entries so post can do the heavy lifting
786 */
787 static int
788 setup_oauth_params(char *vec[], int *vecp, const char *auth_svc,
789 const char **message) {
790 const char *saslmech = NULL, *user = NULL;
791 mh_oauth_service_info svc;
792 char errbuf[256];
793 int i;
794
795 /* Make sure we have all the information we need. */
796 for (i = 1; i < *vecp; ++i) {
797 /* Don't support abbreviated switches, to avoid collisions in the
798 future if new ones are added. */
799 if (! strcmp(vec[i-1], "-saslmech")) {
800 saslmech = vec[i];
801 } else if (! strcmp(vec[i-1], "-user")) {
802 user = vec[i];
803 } else if (! strcmp(vec[i-1], "-authservice")) {
804 auth_svc = vec[i];
805 }
806 }
807
808 if (auth_svc == NULL) {
809 if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
810 *message = "must specify -authservice with -saslmech xoauth2";
811 return NOTOK;
812 }
813 } else {
814 if (user == NULL) {
815 *message = "must specify -user with -saslmech xoauth2";
816 return NOTOK;
817 }
818
819 if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
820 if (! mh_oauth_get_service_info(auth_svc, &svc, errbuf,
821 sizeof(errbuf)))
822 adios(NULL, "Unable to retrieve oauth profile entries: %s",
823 errbuf);
824
825 vec[(*vecp)++] = mh_xstrdup("-authservice");
826 vec[(*vecp)++] = mh_xstrdup(auth_svc);
827 vec[(*vecp)++] = mh_xstrdup("-oauthcredfile");
828 vec[(*vecp)++] = mh_xstrdup(mh_oauth_cred_fn(auth_svc));
829 vec[(*vecp)++] = mh_xstrdup("-oauthclientid");
830 vec[(*vecp)++] = getcpy(svc.client_id);
831 vec[(*vecp)++] = mh_xstrdup("-oauthclientsecret");
832 vec[(*vecp)++] = getcpy(svc.client_secret);
833 vec[(*vecp)++] = mh_xstrdup("-oauthauthendpoint");
834 vec[(*vecp)++] = getcpy(svc.auth_endpoint);
835 vec[(*vecp)++] = mh_xstrdup("-oauthredirect");
836 vec[(*vecp)++] = getcpy(svc.redirect_uri);
837 vec[(*vecp)++] = mh_xstrdup("-oauthtokenendpoint");
838 vec[(*vecp)++] = getcpy(svc.token_endpoint);
839 vec[(*vecp)++] = mh_xstrdup("-oauthscope");
840 vec[(*vecp)++] = getcpy(svc.scope);
841 }
842 }
843
844 return 0;
845 }
846 #endif /* OAUTH_SUPPORT */
847
848
849 /*
850 * Extract user and domain from From: header line in draft.
851 */
852 static
853 int
854 get_from_header_info(const char *filename, const char **addr, const char **host, const char **message) {
855 struct stat st;
856 FILE *in;
857
858 if (stat (filename, &st) == NOTOK) {
859 *message = "unable to stat draft file";
860 return NOTOK;
861 }
862
863 if ((in = fopen (filename, "r")) != NULL) {
864 /* There must be a non-blank Envelope-From or {Resent-}Sender or
865 {Resent-}From header. */
866 char *addrformat = "%(addr{Envelope-From})";
867 char *hostformat = "%(host{Envelope-From})";
868
869 if ((*addr = get_message_header_info (in, addrformat)) == NULL ||
870 !**addr) {
871 addrformat = distfile == NULL ? "%(addr{Sender})" : "%(addr{Resent-Sender})";
872 hostformat = distfile == NULL ? "%(host{Sender})" : "%(host{Resent-Sender})";
873
874 if ((*addr = get_message_header_info (in, addrformat)) == NULL) {
875 addrformat = distfile == NULL ? "%(addr{From})" : "%(addr{Resent-From})";
876 hostformat = distfile == NULL ? "%(host{From})" : "%(host{Resent-From})";
877
878 if ((*addr = get_message_header_info (in, addrformat)) == NULL) {
879 *message = "unable to find sender address in";
880 fclose(in);
881 return NOTOK;
882 }
883 }
884 }
885
886 /* Use the hostformat that corresponds to the successful addrformat. */
887 if ((*host = get_message_header_info(in, hostformat)) == NULL) {
888 fclose(in);
889 *message = "unable to find sender host in";
890 fclose(in);
891 return NOTOK;
892 }
893 fclose(in);
894
895 return OK;
896 }
897
898 *message = "unable to open";
899 return NOTOK;
900 }
901
902
903 /*
904 * Get formatted information from header of a message.
905 * Adapted from process_single_file() in uip/fmttest.c.
906 */
907 static
908 const char *
909 get_message_header_info(FILE *in, char *format) {
910 int dat[5];
911 struct format *fmt;
912 struct stat st;
913 int parsing_header;
914 m_getfld_state_t gstate = 0;
915 charstring_t buffer = charstring_create(0);
916 char *retval;
917
918 dat[0] = dat[1] = dat[4] = 0;
919 dat[2] = fstat(fileno(in), &st) == 0 ? st.st_size : 0;
920 dat[3] = INT_MAX;
921
922 (void) fmt_compile(new_fs(NULL, NULL, format), &fmt, 1);
923 free_fs();
924
925 /*
926 * Read in the message and process the header.
927 */
928 rewind (in);
929 parsing_header = 1;
930 do {
931 char name[NAMESZ], rbuf[NMH_BUFSIZ];
932 int bufsz = sizeof rbuf;
933 int state = m_getfld(&gstate, name, rbuf, &bufsz, in);
934
935 switch (state) {
936 case FLD:
937 case FLDPLUS: {
938 int bucket = fmt_addcomptext(name, rbuf);
939
940 if (bucket != -1) {
941 while (state == FLDPLUS) {
942 bufsz = sizeof rbuf;
943 state = m_getfld(&gstate, name, rbuf, &bufsz, in);
944 fmt_appendcomp(bucket, name, rbuf);
945 }
946 }
947
948 while (state == FLDPLUS) {
949 bufsz = sizeof rbuf;
950 state = m_getfld(&gstate, name, rbuf, &bufsz, in);
951 }
952 break;
953 }
954 default:
955 parsing_header = 0;
956 }
957 } while (parsing_header);
958 m_getfld_state_destroy(&gstate);
959
960 fmt_scan(fmt, buffer, INT_MAX, dat, NULL);
961 fmt_free(fmt, 1);
962
963 /* Trim trailing newline, if any. */
964 retval = rtrim(charstring_buffer_copy((buffer)));
965 charstring_free(buffer);
966 if (*retval)
967 return retval;
968
969 free(retval);
970 return NULL;
971 }
972
973
974 /*
975 * Look in profile for entry corresponding to addr or host, and add its contents to vec.
976 *
977 * Could do some of this automatically, by looking for:
978 * 1) access-$(mbox{from}) in oauth-svc file using mh_oauth_cred_load(), which isn't
979 * static and doesn't have side effects; free the result with mh_oauth_cred_free())
980 * 2) machine $(mbox{from}) in creds
981 * If no -server passed in from profile or commandline, could use smtp.<svc>.com for gmail,
982 * but that might not generalize for other svcs.
983 */
984 static
985 void
986 merge_profile_entry(const char *addr, const char *host, char *vec[], int *vecp) {
987 char *addr_entry = concat("sendfrom-", addr, NULL);
988 char *profile_entry = context_find(addr_entry);
989
990 free(addr_entry);
991 if (profile_entry == NULL) {
992 /* No entry for the user. Look for one for the host. */
993 char *host_entry = concat("sendfrom-", host, NULL);
994
995 profile_entry = context_find(host_entry);
996 free(host_entry);
997 }
998
999 /* Use argsplit() to do the real work of splitting the args in the profile entry. */
1000 if (profile_entry && *profile_entry) {
1001 int profile_vecp;
1002 char *file;
1003 char **profile_vec = argsplit(profile_entry, &file, &profile_vecp);
1004 int i;
1005
1006 for (i = 0; i < profile_vecp; ++i) {
1007 vec[(*vecp)++] = getcpy(profile_vec[i]);
1008 }
1009
1010 arglist_free(file, profile_vec);
1011 }
1012 }
1013
1014
1015 static void
1016 armed_done (int status)
1017 {
1018 longjmp (env, status ? status : NOTOK);
1019 }