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