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