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