]> diplodocus.org Git - nmh/blob - uip/sendsbr.c
64a1ed683fc9f246d56830b0bb64619f5b0750ce
[nmh] / uip / sendsbr.c
1 /* sendsbr.c -- routines to help WhatNow/Send along
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include <h/fmt_scan.h>
10 #include <h/fmt_compile.h>
11 #include <h/signals.h>
12 #include <setjmp.h>
13 #include <fcntl.h>
14 #include <h/mime.h>
15 #include <h/tws.h>
16 #include <h/utils.h>
17 #include <h/mts.h>
18
19 #ifdef HAVE_SYS_TIME_H
20 # include <sys/time.h>
21 #endif
22 #include <time.h>
23
24 #ifdef OAUTH_SUPPORT
25 #include <h/oauth.h>
26 #endif
27 #include "../sbr/m_mktemp.h"
28
29 #ifdef OAUTH_SUPPORT
30 static int setup_oauth_params(char *[], int *, const char *, const char **);
31 #endif /* OAUTH_SUPPORT */
32
33 int debugsw = 0; /* global */
34 int forwsw = 1;
35 int inplace = 1;
36 int pushsw = 0;
37 int splitsw = -1;
38 int unique = 0;
39 int verbsw = 0;
40
41 char *altmsg = NULL; /* .. */
42 char *annotext = NULL;
43 char *distfile = NULL;
44
45 static jmp_buf env;
46
47 /*
48 * static prototypes
49 */
50 static void alert (char *, int);
51 static int tmp_fd (void);
52 static void anno (int, struct stat *);
53 static void annoaux (int);
54 static int splitmsg (char **, int, char *, char *, struct stat *, int);
55 static int sendaux (char **, int, char *, char *, struct stat *);
56 static void handle_sendfrom(char **, int *, char *, const char *);
57 static int get_from_header_info(const char *, const char **, const char **, const char **);
58 static const char *get_message_header_info(FILE *, char *);
59 static void merge_profile_entry(const char *, const char *, char *[], int *);
60 static void armed_done (int) NORETURN;
61
62 /*
63 * Entry point into (back-end) routines to send message.
64 */
65
66 int
67 sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
68 int rename_drft, const char *auth_svc)
69 {
70 int status, i;
71 pid_t child;
72 char buffer[BUFSIZ], file[BUFSIZ];
73 struct stat sts;
74 char **buildvec, *buildprogram;
75 char *volatile drft = draft;
76 /* nvecs is volatile to prevent warning from gcc about possible clobbering
77 by longjmp. */
78 volatile int nvecs = vecp;
79 int *nvecsp = (int *) &nvecs;
80
81 /*
82 * Run the mimebuildproc (which is by default mhbuild) on the message
83 * with the addition of the "-auto" flag
84 */
85
86 switch (child = fork()) {
87 case NOTOK:
88 adios("fork", "unable to");
89 break;
90
91 case OK:
92 buildvec = argsplit(buildmimeproc, &buildprogram, &i);
93 buildvec[i++] = "-auto";
94 if (distfile)
95 buildvec[i++] = "-dist";
96 buildvec[i++] = (char *) drft;
97 buildvec[i] = NULL;
98 execvp(buildprogram, buildvec);
99 fprintf(stderr, "unable to exec ");
100 perror(buildmimeproc);
101 _exit(-1);
102 break;
103
104 default:
105 if (pidXwait(child, buildmimeproc))
106 return NOTOK;
107 break;
108 }
109
110 done=armed_done;
111 switch (setjmp (env)) {
112 case OK:
113 /*
114 * If given -push and -unique (which is undocumented), then
115 * rename the draft file. I'm not quite sure why.
116 */
117 if (pushsw && unique) {
118 char *cp = m_mktemp2(drft, invo_name, NULL, NULL);
119 if (cp == NULL) {
120 adios(NULL, "unable to create temporary file in %s",
121 get_temp_dir());
122 }
123 if (rename (drft, strncpy(file, cp, sizeof(file))) == NOTOK)
124 adios (file, "unable to rename %s to", drft);
125 drft = file;
126 }
127
128 /*
129 * Add in any necessary profile entries for xoauth
130 */
131
132 if (auth_svc) {
133 #ifdef OAUTH_SUPPORT
134 const char *errmsg;
135 if (setup_oauth_params(vec, nvecsp, auth_svc, &errmsg) != OK) {
136 adios(NULL, errmsg);
137 }
138 #else
139 adios(NULL, "send built without OAUTH_SUPPORT, "
140 "so auth_svc %s is not supported", auth_svc);
141 #endif /* OAUTH_SUPPORT */
142 }
143
144 /*
145 * Rework the vec based on From: header in draft, as specified
146 * by sendfrom-address entries in profile.
147 */
148 if (context_find_prefix("sendfrom-")) {
149 handle_sendfrom(vec, nvecsp, draft, auth_svc);
150 }
151
152 /*
153 * Check if we need to split the message into
154 * multiple messages of type "message/partial".
155 */
156 if (splitsw >= 0 && !distfile && stat ((char *) drft, &sts) != NOTOK
157 && sts.st_size >= CPERMSG) {
158 status = splitmsg (vec, nvecs, program, drft,
159 st, splitsw) ? NOTOK : OK;
160 } else {
161 status = sendaux (vec, nvecs, program, drft, st) ? NOTOK : OK;
162 }
163
164 /* rename the original draft */
165 if (rename_drft && status == OK &&
166 rename (drft, strncpy (buffer, m_backup (drft),
167 sizeof(buffer))) == NOTOK)
168 advise (buffer, "unable to rename %s to", drft);
169 break;
170
171 default:
172 status = DONE;
173 break;
174 }
175
176 done=exit;
177 if (distfile)
178 (void) m_unlink (distfile);
179
180 return status;
181 }
182
183 /*
184 * Split large message into several messages of
185 * type "message/partial" and send them.
186 */
187
188 static int
189 splitmsg (char **vec, int vecp, char *program, char *drft,
190 struct stat *st, int delay)
191 {
192 int compnum, nparts, partno, state, status;
193 long pos, start;
194 time_t clock;
195 char *cp, *dp, buffer[NMH_BUFSIZ], msgid[BUFSIZ];
196 char subject[BUFSIZ];
197 char name[NAMESZ], partnum[BUFSIZ];
198 FILE *in;
199 m_getfld_state_t gstate = 0;
200
201 if ((in = fopen (drft, "r")) == NULL)
202 adios (drft, "unable to open for reading");
203
204 cp = dp = NULL;
205 start = 0L;
206
207 /*
208 * Scan through the message and examine the various header fields,
209 * as well as locate the beginning of the message body.
210 */
211 m_getfld_track_filepos (&gstate, in);
212 for (compnum = 1;;) {
213 int bufsz = sizeof buffer;
214 switch (state = m_getfld (&gstate, name, buffer, &bufsz, in)) {
215 case FLD:
216 case FLDPLUS:
217 compnum++;
218
219 /*
220 * This header field is discarded.
221 */
222 if (!strcasecmp (name, "Message-ID")) {
223 while (state == FLDPLUS) {
224 bufsz = sizeof buffer;
225 state = m_getfld (&gstate, name, buffer, &bufsz, in);
226 }
227 } else if (uprf (name, XXX_FIELD_PRF)
228 || !strcasecmp (name, VRSN_FIELD)
229 || !strcasecmp (name, "Subject")
230 || !strcasecmp (name, "Encrypted")) {
231 /*
232 * These header fields are copied to the enclosed
233 * header of the first message in the collection
234 * of message/partials. For the "Subject" header
235 * field, we also record it, so that a modified
236 * version of it, can be copied to the header
237 * of each message/partial in the collection.
238 */
239 if (!strcasecmp (name, "Subject")) {
240 size_t sublen;
241
242 strncpy (subject, buffer, BUFSIZ);
243 sublen = strlen (subject);
244 if (sublen > 0 && subject[sublen - 1] == '\n')
245 subject[sublen - 1] = '\0';
246 }
247
248 dp = add (concat (name, ":", buffer, NULL), dp);
249 while (state == FLDPLUS) {
250 bufsz = sizeof buffer;
251 state = m_getfld (&gstate, name, buffer, &bufsz, in);
252 dp = add (buffer, dp);
253 }
254 } else {
255 /*
256 * These header fields are copied to the header of
257 * each message/partial in the collection.
258 */
259 cp = add (concat (name, ":", buffer, NULL), cp);
260 while (state == FLDPLUS) {
261 bufsz = sizeof buffer;
262 state = m_getfld (&gstate, name, buffer, &bufsz, in);
263 cp = add (buffer, cp);
264 }
265 }
266
267 start = ftell (in) + 1;
268 continue;
269
270 case BODY:
271 case FILEEOF:
272 break;
273
274 case LENERR:
275 case FMTERR:
276 adios (NULL, "message format error in component #%d", compnum);
277
278 default:
279 adios (NULL, "getfld () returned %d", state);
280 }
281
282 break;
283 }
284 m_getfld_state_destroy (&gstate);
285 if (cp == NULL)
286 adios (NULL, "headers missing from draft");
287
288 nparts = 1;
289 pos = start;
290 while (fgets (buffer, sizeof buffer, in)) {
291 long len;
292
293 if ((pos += (len = strlen (buffer))) > CPERMSG) {
294 nparts++;
295 pos = len;
296 }
297 }
298
299 /* Only one part, nothing to split */
300 if (nparts == 1) {
301 free (cp);
302 mh_xfree(dp);
303
304 fclose (in);
305 return sendaux (vec, vecp, program, drft, st);
306 }
307
308 if (!pushsw) {
309 printf ("Sending as %d Partial Messages\n", nparts);
310 fflush (stdout);
311 }
312 status = OK;
313
314 vec[vecp++] = "-partno";
315 vec[vecp++] = partnum;
316 if (delay == 0)
317 vec[vecp++] = "-queued";
318
319 time (&clock);
320 snprintf (msgid, sizeof(msgid), "%s", message_id (clock, 0));
321
322 fseek (in, start, SEEK_SET);
323 for (partno = 1; partno <= nparts; partno++) {
324 char tmpdrf[BUFSIZ];
325 FILE *out;
326
327 char *cp = m_mktemp2(drft, invo_name, NULL, &out);
328 if (cp == NULL) {
329 adios(NULL, "unable to create temporary file in %s",
330 get_temp_dir());
331 }
332 strncpy(tmpdrf, cp, sizeof(tmpdrf));
333
334 /*
335 * Output the header fields
336 */
337 fputs (cp, out);
338 fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts);
339 fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
340 fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid);
341 fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts);
342 fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts);
343
344 /*
345 * If this is the first in the collection, output the
346 * header fields we are encapsulating at the beginning
347 * of the body of the first message.
348 */
349 if (partno == 1) {
350 if (dp)
351 fputs (dp, out);
352 fprintf (out, "Message-ID: %s\n", msgid);
353 fprintf (out, "\n");
354 }
355
356 pos = 0;
357 for (;;) {
358 long len;
359
360 if (!fgets (buffer, sizeof buffer, in)) {
361 if (partno == nparts)
362 break;
363 adios (NULL, "premature eof");
364 }
365
366 if ((pos += (len = strlen (buffer))) > CPERMSG) {
367 fseek (in, -len, SEEK_CUR);
368 break;
369 }
370
371 fputs (buffer, out);
372 }
373
374 if (fflush (out))
375 adios (tmpdrf, "error writing to");
376
377 fclose (out);
378
379 if (!pushsw && verbsw) {
380 putchar('\n');
381 fflush (stdout);
382 }
383
384 /* Pause here, if a delay is specified */
385 if (delay > 0 && 1 < partno && partno <= nparts) {
386 if (!pushsw) {
387 printf ("pausing %d seconds before sending part %d...\n",
388 delay, partno);
389 fflush (stdout);
390 }
391 sleep ((unsigned int) delay);
392 }
393
394 snprintf (partnum, sizeof(partnum), "%d", partno);
395 status = sendaux (vec, vecp, program, tmpdrf, st);
396 (void) m_unlink (tmpdrf);
397 if (status != OK)
398 break;
399
400 /*
401 * This is so sendaux will only annotate
402 * the altmsg the first time it is called.
403 */
404 annotext = NULL;
405 }
406
407 free (cp);
408 mh_xfree(dp);
409
410 fclose (in); /* close the draft */
411 return status;
412 }
413
414
415 /*
416 * Annotate original message, and
417 * call `postproc' (which is passed down in "program") to send message.
418 */
419
420 static int
421 sendaux (char **vec, int vecp, char *program, char *drft, struct stat *st)
422 {
423 pid_t child_id;
424 int i, status, fd, fd2;
425 char backup[BUFSIZ], buf[BUFSIZ];
426
427 fd = pushsw ? tmp_fd () : NOTOK;
428 fd2 = NOTOK;
429
430 if (annotext) {
431 if ((fd2 = tmp_fd ()) != NOTOK) {
432 vec[vecp++] = "-idanno";
433 snprintf (buf, sizeof(buf), "%d", fd2);
434 vec[vecp++] = buf;
435 } else {
436 inform("unable to create temporary file in %s for "
437 "annotation list, continuing...", get_temp_dir());
438 }
439 }
440 vec[vecp++] = drft;
441 if (distfile && distout (drft, distfile, backup) == NOTOK)
442 done (1);
443 vec[vecp] = NULL;
444
445 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
446 sleep (5);
447
448 switch (child_id) {
449 case -1:
450 /* oops -- fork error */
451 adios ("fork", "unable to");
452 break; /* NOT REACHED */
453
454 case 0:
455 /*
456 * child process -- send it
457 *
458 * If fd is OK, then we are pushing and fd points to temp
459 * file, so capture anything on stdout and stderr there.
460 */
461 if (fd != NOTOK) {
462 dup2 (fd, fileno (stdout));
463 dup2 (fd, fileno (stderr));
464 close (fd);
465 }
466 execvp (program, vec);
467 fprintf (stderr, "unable to exec ");
468 perror (postproc);
469 _exit (-1);
470
471 default:
472 /*
473 * parent process -- wait for it
474 */
475 if ((status = pidwait(child_id, NOTOK)) == OK) {
476 if (annotext && fd2 != NOTOK)
477 anno (fd2, st);
478 } else {
479 /*
480 * If postproc failed, and we have good fd (which means
481 * we pushed), then mail error message (and possibly the
482 * draft) back to the user.
483 */
484 if (fd != NOTOK) {
485 alert (drft, fd);
486 close (fd);
487 } else {
488 inform("message not delivered to anyone");
489 }
490 if (annotext && fd2 != NOTOK)
491 close (fd2);
492 if (distfile) {
493 (void) m_unlink (drft);
494 if (rename (backup, drft) == NOTOK)
495 advise (drft, "unable to rename %s to", backup);
496 }
497 }
498 break;
499 }
500
501 return status;
502 }
503
504
505 /*
506 * Mail error notification (and possibly a copy of the
507 * message) back to the user, using the mailproc
508 */
509
510 static void
511 alert (char *file, int out)
512 {
513 pid_t child_id;
514 int i, in, argp;
515 char buf[BUFSIZ];
516 char *program;
517 char **arglist;
518
519 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
520 sleep (5);
521
522 switch (child_id) {
523 case NOTOK:
524 /* oops -- fork error */
525 advise ("fork", "unable to");
526 /* FALLTHRU */
527
528 case OK:
529 /* child process -- send it */
530 SIGNAL (SIGHUP, SIG_IGN);
531 SIGNAL (SIGINT, SIG_IGN);
532 SIGNAL (SIGQUIT, SIG_IGN);
533 SIGNAL (SIGTERM, SIG_IGN);
534 if (forwsw) {
535 if ((in = open (file, O_RDONLY)) == NOTOK) {
536 admonish (file, "unable to re-open");
537 } else {
538 lseek (out, (off_t) 0, SEEK_END);
539 strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
540 if (write (out, buf, strlen (buf)) < 0) {
541 advise (file, "write");
542 }
543 strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
544 if (write (out, buf, strlen (buf)) < 0) {
545 advise (file, "write");
546 }
547 cpydgst (in, out, file, "temporary file");
548 close (in);
549 strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
550 if (write (out, buf, strlen (buf)) < 0) {
551 advise (file, "write");
552 }
553 if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
554 admonish (buf, "unable to rename %s to", file);
555 }
556 }
557 lseek (out, (off_t) 0, SEEK_SET);
558 dup2 (out, fileno (stdin));
559 close (out);
560 /* create subject for error notification */
561 snprintf (buf, sizeof(buf), "send failed on %s",
562 forwsw ? "enclosed draft" : file);
563
564 arglist = argsplit(mailproc, &program, &argp);
565
566 arglist[argp++] = getusername();
567 arglist[argp++] = "-subject";
568 arglist[argp++] = buf;
569 arglist[argp] = NULL;
570
571 execvp (program, arglist);
572 fprintf (stderr, "unable to exec ");
573 perror (mailproc);
574 _exit (-1);
575
576 default: /* no waiting... */
577 break;
578 }
579 }
580
581
582 static int
583 tmp_fd (void)
584 {
585 int fd;
586 char *tfile;
587
588 if ((tfile = m_mktemp2(NULL, invo_name, &fd, NULL)) == NULL) return NOTOK;
589
590 if (debugsw)
591 inform("temporary file %s selected", tfile);
592 else
593 if (m_unlink (tfile) == NOTOK)
594 advise (tfile, "unable to remove");
595
596 return fd;
597 }
598
599
600 static void
601 anno (int fd, struct stat *st)
602 {
603 pid_t child_id;
604 sigset_t set, oset;
605 static char *cwd = NULL;
606 struct stat st2;
607
608 if (altmsg &&
609 (stat (altmsg, &st2) == NOTOK
610 || st->st_mtime != st2.st_mtime
611 || st->st_dev != st2.st_dev
612 || st->st_ino != st2.st_ino)) {
613 if (debugsw)
614 inform("$mhaltmsg mismatch, continuing...");
615 return;
616 }
617
618 child_id = debugsw ? NOTOK : fork ();
619 switch (child_id) {
620 case NOTOK: /* oops */
621 if (!debugsw)
622 inform("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 inform("$mhfolder not set, continuing...");
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 inform("unable to read folder %s, continuing...", folder);
680 return;
681 }
682
683 /* check for empty folder */
684 if (mp->nummsg == 0) {
685 if (debugsw)
686 inform("no messages in %s, continuing...", folder);
687 goto oops;
688 }
689
690 if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
691 if (debugsw)
692 inform("$mhmessages not set, continuing...");
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 inform("no messages to annotate, continuing...");
712 goto oops;
713 }
714
715 lseek (fd, (off_t) 0, SEEK_SET);
716 if ((fp = fdopen (fd, "r")) == NULL) {
717 if (debugsw)
718 inform("unable to fdopen annotation list, continuing...");
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 inform("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 inform("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 NORETURN
1016 armed_done (int status)
1017 {
1018 longjmp (env, status ? status : NOTOK);
1019 }