test/mhstore/test-mhstore test/mkstemp/test-mkstemp \
test/new/test-basic test/pick/test-pick test/pick/test-stderr \
test/oauth/test-mhlogin test/oauth/test-mhparam test/oauth/test-send \
- test/oauth/test-inc test/oauth/test-share \
+ test/oauth/test-sendfrom test/oauth/test-inc test/oauth/test-share \
test/post/test-post-aliases test/post/test-post-basic \
test/post/test-post-multiple test/post/test-post-bcc \
test/post/test-post-dcc test/post/test-post-fcc \
test/scan/test-scan test/scan/test-scan-multibyte \
test/sequences/test-flist test/sequences/test-mark \
test/sequences/test-out-of-range \
+ test/send/test-sendfrom \
test/show/test-show \
test/slocal/test-slocal \
test/whatnow/test-attach-detach test/whatnow/test-cd \
docs_contribdir = $(docdir)/contrib
dist_docs_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \
docs/contrib/ml docs/contrib/vpick
-dist_docs_contrib_DATA = docs/contrib/replaliases docs/contrib/sendfrom.c
-docs_contrib_PROGRAMS = docs/contrib/sendfrom
+dist_docs_contrib_DATA = docs/contrib/replaliases
##
## Our man pages
test/mhbuild/somebinary \
test/mhbuild/nulls \
test/mhbuild/textplain \
- test/post/test-post-common.sh test/valgrind.supp \
+ test/post/test-post-common.sh test/send/README \
test/oauth/common.sh \
- uip/mhmail \
+ test/valgrind.supp uip/mhmail \
SPECS/nmh.spec SPECS/build-nmh-cygwin $(man_SRCS)
##
uip_send_SOURCES = uip/send.c uip/sendsbr.c uip/annosbr.c \
uip/distsbr.c
-uip_send_LDADD = $(LDADD) $(CURLLIB) $(POSTLINK)
+uip_send_LDADD = $(LDADD) $(TERMLIB) $(CURLLIB) $(POSTLINK)
uip_show_SOURCES = uip/show.c uip/mhlsbr.c
uip_show_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/sendsbr.c \
uip/annosbr.c uip/distsbr.c
-uip_viamail_LDADD = $(LDADD) $(POSTLINK)
+uip_viamail_LDADD = $(LDADD) $(TERMLIB) $(CURLLIB) $(POSTLINK)
##
## Other program definitions
test_getfullname_SOURCES = test/getfullname.c
test_getfullname_LDADD = $(LDADD) $(POSTLINK)
-##
-## Other program definitions
-##
-
test_getcanon_SOURCES = test/getcanon.c
test_getcanon_LDADD = $(POSTLINK)
etc_gen_ctype_checked_SOURCES = etc/gen-ctype-checked.c
etc_gen_ctype_checked_LDADD = $(POSTLINK)
-docs_contrib_sendfrom_SOURCES = docs/contrib/sendfrom.c
-docs_contrib_sendfrom_LDADD = $(LDADD) $(TERMLIB) $(CURLLIB) $(POSTLINK)
-
##
## Our rebuild rules for files that aren't built via the normal mechanisms
##
## Hard-code the config/version.c target, instead of using $@, so this
## rule works for the distcheck target with Solaris (System V) make.
-
## distcheck uses VPATH, causes that make to prepend the VPATH to $@.
config/version.c: Makefile $(srcdir)/config/version.sh
env srcdir="$(srcdir)" sh $(srcdir)/config/version.sh $(VERSION) > ./config/version.c
+++ /dev/null
-/*
- * sendfrom.c -- postproc that selects user, and then mail server, from draft
- *
- * Author: David Levine <levinedl@acm.org>
- *
- * This program fits between send(1) and post(1), as a postproc. It makes
- * up for the facts that send doesn't parse the message draft and post
- * doesn't load the profile.
- *
- * To use:
- *
- * 1) Add profile entries of the form:
- *
- * sendfrom-<email address or domain name>: <post(1) switches>
- *
- * The email address is extracted from the From: header line of the message draft. Multiple
- * profile entries, with different email addresses or domain names, are supported. This
- * allows different switches to post(1), such as -user, to be associated with different email
- * addresses. If a domain name is used, it matches all users in that domain.
- *
- * Example profile entry using OAuth for account hosted by gmail:
- *
- * sendfrom-gmail_address@example.com: -saslmech xoauth2 -authservice gmail -tls
- * -server smtp.gmail.com -user gmail_login@example.com
- *
- * (Indentation indicates a continued line, as supported in MH profiles.) The username
- * need not be the same as the sender address, which was extracted from the From: header line.
- *
- * Example profile entries that use an nmh credentials file:
- *
- * credentials: file:nmhcreds
- * sendfrom-sendgrid_address@example.com: -sasl -tls -server smtp.sendgrid.net
- * sendfrom-outbound.att.net: -sasl -initialtls -server outbound.att.net -port 465
- * sendfrom-fastmail.com: -initialtls -sasl -saslmech LOGIN
- * -server smtps-proxy.messagingengine.com -port 80
- *
- * where nmhcreds is in the user's nmh directory (from the Path profile component) and contains:
- *
- * machine smtp.sendgrid.net
- * login sendgrid_login@example.com
- * password ********
- * machine outbound.att.net
- * login att_login@example.com
- * password ********
- * machine smtps-proxy.messagingengine.com
- * login fastmail_login@example.com
- * password ********
- *
- * 2) To enable, add a line like this to your profile:
- *
- * postproc: <docdir>/contrib/sendfrom
- *
- * with <docdir> expanded. This source file is in docdir. Also, "mhparam docdir" will show it.
- *
- * For more information on authentication to mail servers, see mhlogin(1) for OAuth
- * services, and mh-profile(5) for login credentials.
- *
- * This program follows these steps:
- * 1) Loads profile.
- * 2) Saves command line arguments in vec.
- * 3) Extracts address and domain name from From: header line in draft.
- * 4) Extracts any address or host specific switches to post(1) from profile. Appends
- to vec, along with implied switches, such as OAUTH access token.
- * 5) Calls post, or with -dryrun, echos how it would be called.
- */
-
-#include <h/mh.h>
-#include <h/fmt_scan.h>
-#include <h/fmt_compile.h>
-#include <h/utils.h>
-
-#ifdef OAUTH_SUPPORT
-#include <h/oauth.h>
-
-static int setup_oauth_params(char *vec[], int *, int, const char **);
-#endif /* OAUTH_SUPPORT */
-
-extern char *mhlibexecdir; /* from config.c */
-
-static int get_from_header_info(const char *, const char **, const char **, const char **);
-static const char *get_message_header_info(FILE *, char *);
-static void merge_profile_entry(const char *, const char *, char *vec[], int *vecp);
-static int run_program(char *, char **);
-
-int
-main(int argc, char **argv) {
- /* Make sure that nmh's post gets called by specifying its full path. */
- char *realpost = concat(mhlibexecdir, "/", "post", NULL);
- int whom = 0, dryrun = 0, snoop = 0;
- char **arguments, **argp;
- char *program, **vec;
- int vecp = 0;
- char *msg = NULL;
-
- /* Load profile. */
- if (nmh_init(argv[0], 1)) { return NOTOK; }
-
- /* Save command line arguments in vec, except for the last argument, if it's the draft file path. */
- vec = argsplit(realpost, &program, &vecp);
- for (argp = arguments = getarguments(invo_name, argc, argv, 1); *argp; ++argp) {
- /* Don't pass -dryrun to post by putting it in vec. */
- if (strcmp(*argp, "-dryrun") == 0) {
- dryrun = 1;
- } else {
- vec[vecp++] = getcpy(*argp);
-
- if (strcmp(*argp, "-snoop") == 0) {
- snoop = 1;
- } else if (strcmp(*argp, "-whom") == 0) {
- whom = 1;
- } else {
- /* Need to keep filename, from last arg (though it's not used below with -whom). */
- msg = *argp;
- }
- }
- }
-
- free(arguments);
-
- if (! whom && msg == NULL) {
- adios(NULL, "requires exactly 1 filename argument");
- } else {
- char **vp;
-
- /* With post, but without -whom, need to keep filename as last arg. */
- if (! whom) {
- /* At this point, vecp points to the next argument to be added. Back it up to
- remove the last one, which is the filename. Add it back at the end of vec below. */
- free(vec[--vecp]);
- }
- /* Null terminate vec because it's used below. */
- vec[vecp] = NULL;
-
- if (! whom) {
- /* Some users might need the switch from the profile entry with -whom, but that would
- require figuring out which command line argument has the filename. With -whom, it's
- not necessarily the last one. */
-
- const char *addr, *host;
- const char *message;
-
- /* Extract address and host from From: header line in draft. */
- if (get_from_header_info(msg, &addr, &host, &message) != OK) {
- adios(msg, message);
- }
-
- /* Merge in any address or host specific switches to post(1) from profile. */
- merge_profile_entry(addr, host, vec, &vecp);
- free((void *) host);
- free((void *) addr);
- }
-
- vec[vecp] = NULL;
- for (vp = vec; *vp; ++vp) {
- if (strcmp(*vp, "xoauth2") == 0) {
-#ifdef OAUTH_SUPPORT
- const char *message;
-
- if (setup_oauth_params(vec, &vecp, snoop, &message) != OK) {
- adios(NULL, message);
- }
- break;
-#else
- NMH_UNUSED(snoop);
- NMH_UNUSED(vp);
- adios(NULL, "sendfrom built without OAUTH_SUPPORT, so -saslmech xoauth2 is not supported");
-#endif /* OAUTH_SUPPORT */
- }
- }
-
- if (! whom) {
- /* Append filename as last non-null vec entry. */
- vec[vecp++] = getcpy(msg);
- vec[vecp] = NULL;
- }
-
- /* Call post, or with -dryrun, echo how it would be called. */
- if (dryrun) {
- /* Show the program name, because run_program() won't. */
- printf("%s ", realpost);
- fflush(stdout);
- }
- if (run_program(dryrun ? "echo" : realpost, vec) != OK) {
- adios(realpost, "failure in ");
- }
-
- arglist_free(program, vec);
- free(realpost);
- }
-
- return 0;
-}
-
-
-
-#ifdef OAUTH_SUPPORT
-/*
- * For XOAUTH2, append access token, from mh_oauth_do_xoauth(), for the user to vec.
- */
-static
-int
-setup_oauth_params(char *vec[], int *vecp, int snoop, const char **message) {
- const char *saslmech = NULL, *user = NULL, *auth_svc = NULL;
- int i;
-
- /* Make sure we have all the information we need. */
- for (i = 1; i < *vecp; ++i) {
- /* Don't support abbreviated switches, to avoid collisions in the future if new ones
- are added. */
- if (! strcmp(vec[i-1], "-saslmech")) {
- saslmech = vec[i];
- } else if (! strcmp(vec[i-1], "-user")) {
- user = vec[i];
- } else if (! strcmp(vec[i-1], "-authservice")) {
- auth_svc = vec[i];
- }
- }
-
- if (auth_svc == NULL) {
- if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
- *message = "must specify -authservice with -saslmech xoauth2";
- return NOTOK;
- }
- } else {
- if (user == NULL) {
- *message = "must specify -user with -saslmech xoauth2";
- return NOTOK;
- }
-
- vec[(*vecp)++] = getcpy("-authservice");
- if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
- vec[(*vecp)++] = mh_oauth_do_xoauth(user, auth_svc, snoop ? stderr : NULL);
- } else {
- vec[(*vecp)++] = getcpy(auth_svc);
- }
- }
-
- return 0;
-}
-#endif /* OAUTH_SUPPORT */
-
-
-/*
- * Extract user and domain from From: header line in draft.
- */
-static
-int
-get_from_header_info(const char *filename, const char **addr, const char **host, const char **message) {
- struct stat st;
- FILE *in;
-
- if (stat (filename, &st) == NOTOK) {
- *message = "unable to stat draft file";
- return NOTOK;
- }
-
- if ((in = fopen(filename, "r")) != NULL) {
- char *addrformat = "%(addr{from})", *hostformat = "%(host{from})";
-
- if ((*addr = get_message_header_info(in, addrformat)) == NULL) {
- *message = "unable to find From: address in";
- return NOTOK;
- }
- rewind(in);
-
- if ((*host = get_message_header_info(in, hostformat)) == NULL) {
- fclose(in);
- *message = "unable to find From: host in";
- return NOTOK;
- }
- fclose(in);
-
- return OK;
- } else {
- *message = "unable to open";
- return NOTOK;
- }
-}
-
-
-/*
- * Get formatted information from header of a message.
- * Adapted from process_single_file() in uip/fmttest.c.
- */
-static
-const char *
-get_message_header_info(FILE *in, char *format) {
- int dat[5];
- struct format *fmt;
- struct stat st;
- int parsing_header;
- m_getfld_state_t gstate = 0;
- charstring_t buffer = charstring_create(0);
- char *retval;
-
- dat[0] = dat[1] = dat[4] = 0;
- dat[2] = fstat(fileno(in), &st) == 0 ? st.st_size : 0;
- dat[3] = INT_MAX;
-
- (void) fmt_compile(new_fs(NULL, NULL, format), &fmt, 1);
- free_fs();
-
- /*
- * Read in the message and process the header.
- */
- parsing_header = 1;
- do {
- char name[NAMESZ], rbuf[NMH_BUFSIZ];
- int bufsz = sizeof rbuf;
- int state = m_getfld(&gstate, name, rbuf, &bufsz, in);
-
- switch (state) {
- case FLD:
- case FLDPLUS: {
- int bucket = fmt_addcomptext(name, rbuf);
-
- if (bucket != -1) {
- while (state == FLDPLUS) {
- bufsz = sizeof rbuf;
- state = m_getfld(&gstate, name, rbuf, &bufsz, in);
- fmt_appendcomp(bucket, name, rbuf);
- }
- }
-
- while (state == FLDPLUS) {
- bufsz = sizeof rbuf;
- state = m_getfld(&gstate, name, rbuf, &bufsz, in);
- }
- break;
- }
- default:
- parsing_header = 0;
- }
- } while (parsing_header);
- m_getfld_state_destroy(&gstate);
-
- fmt_scan(fmt, buffer, INT_MAX, dat, NULL);
- fmt_free(fmt, 1);
-
- /* Trim trailing newline, if any. */
- retval = rtrim(charstring_buffer_copy((buffer)));
- charstring_free(buffer);
-
- return retval;
-}
-
-
-/*
- * Look in profile for entry corresponding to addr or host, and add its contents to vec.
- *
- * Could do some of this automatically, by looking for:
- * 1) access-$(mbox{from}) in oauth-svc file using mh_oauth_cred_load(), which isn't
- * static and doesn't have side effects; free the result with mh_oauth_cred_free())
- * 2) machine $(mbox{from}) in creds
- * If no -server passed in from profile or commandline, could use smtp.<svc>.com for gmail,
- * but that might not generalize for other svcs.
- */
-static
-void
-merge_profile_entry(const char *addr, const char *host, char *vec[], int *vecp) {
- char *addr_entry = concat("sendfrom-", addr, NULL);
- char *profile_entry = context_find(addr_entry);
-
- free(addr_entry);
- if (profile_entry == NULL) {
- /* No entry for the user. Look for one for the host. */
- char *host_entry = concat("sendfrom-", host, NULL);
-
- profile_entry = context_find(host_entry);
- free(host_entry);
- }
-
- /* Use argsplit() to do the real work of splitting the args in the profile entry. */
- if (profile_entry && strlen(profile_entry) > 0) {
- int profile_vecp;
- char *file;
- char **profile_vec = argsplit(profile_entry, &file, &profile_vecp);
- int i;
-
- for (i = 0; i < profile_vecp; ++i) {
- vec[(*vecp)++] = getcpy(profile_vec[i]);
- }
-
- arglist_free(file, profile_vec);
- }
-}
-
-
-/*
- * Fork and exec.
- */
-static
-int
-run_program(char *realpost, char **vec) {
- pid_t child_id;
- int i;
-
- for (i = 0; (child_id = fork()) == NOTOK && i < 5; ++i) { sleep(5); }
-
- switch (child_id) {
- case -1:
- /* oops -- fork error */
- return NOTOK;
- case 0:
- (void) execvp(realpost, vec);
- fprintf(stderr, "unable to exec ");
- perror(realpost);
- _exit(-1);
- default:
- if (pidXwait(child_id, realpost)) {
- return NOTOK;
- }
- }
-
- return OK;
-}
.\"
.\" %nmhwarning%
.\"
-.TH SEND %manext1% "March 23, 2016" "%nmhversion%"
+.TH SEND %manext1% "July 5, 2016" "%nmhversion%"
.SH NAME
send \- send a message
.SH SYNOPSIS
See
.IR mh\-alias (5)
for more information.
+.SS Selection based on From: address: sendfrom
+One or more
+.I sendfrom
+profile components can be used to select a mail server address, mail server
+port, or any other switch that can be supplied to
+.BR post .
+It works by first looking at the address and domain name in the From: header
+line in the message draft.
+It then looks for a corresponding profile entry, which contains the
+.B post
+switches.
+To enable, add profile entries of the form:
+.PP
+.RS 5
+.RI sendfrom- "address/domain name" : " post switches"
+.RE
+.PP
+The email address is extracted from the From: header line of the message draft.
+Multiple profile entries, with different email addresses or domain names, are
+supported.
+This allows different switches to
+.BR post ,
+such as -user, to be associated with different email addresses.
+If a domain name is used, it matches all users in that domain.
+.PP
+Here is an example profile entry using OAuth for an account hosted by gmail:
+.PP
+.nf
+.RS 5
+sendfrom-gmail_address@example.com: -saslmech xoauth2
+.RS 5
+-authservice gmail -tls -server smtp.gmail.com
+-user gmail_login@example.com
+.RE
+.RE
+.fi
+.PP
+(Indentation indicates a continued line, as supported in MH profiles.)
+The username need not be the same as the sender address, which was extracted
+from the From: header line.
+.PP
+Here are example profile entries that use an nmh credentials file:
+.PP
+.nf
+.RS 5
+credentials: file:nmhcreds
+sendfrom-sendgrid_address@example.com: -sasl -tls
+.RS 5
+-server smtp.sendgrid.net
+.RE
+sendfrom-outbound.att.net: -sasl -initialtls
+.RS 5
+-server outbound.att.net -port 465
+.RE
+sendfrom-fastmail.com: -initialtls -sasl -saslmech LOGIN
+.RS 5
+-server smtps-proxy.messagingengine.com -port 80
+.RE
+.RE
+.fi
+.PP
+where nmhcreds is in the user's nmh directory (from the Path profile component)
+and contains:
+.PP
+.nf
+.RS 5
+machine smtp.sendgrid.net
+.RS 5
+login sendgrid_login@example.com
+password ********
+.RE
+machine outbound.att.net
+.RS 5
+login att_login@example.com
+password ********
+.RE
+machine smtps-proxy.messagingengine.com
+.RS 5
+login fastmail_login@example.com
+password ********
+.RE
+.RE
+.fi
+.PP
+For more information on authentication to mail servers, see the
+.IR mhlogin (1)
+man page for OAuth services, and
+.IR mh-profile (5)
+man page for login credentials.
+.PP
.SH FILES
.fc ^ ~
.nf
^Signature:~^To determine the user's mail signature
^mailproc:~^Program to post failure notices
^postproc:~^Program to post the message
+^sendfrom-address:~^Switches to post for From: address
+^sendfrom-domain:~^Switches to post for From: domain name
.fi
.SH "SEE ALSO"
.IR comp (1),
#!/bin/sh
#
-# Test the XOAUTH2 support in sen
+# Test the XOAUTH2 support in send
#
if test -z "${MH_OBJ_DIR}"; then
--- /dev/null
+#!/bin/sh
+#
+# Test the XOAUTH2 support with sendfrom
+#
+
+if test -z "${MH_OBJ_DIR}"; then
+ srcdir=`dirname "$0"`/../..
+ MH_OBJ_DIR=`cd "${srcdir}" && pwd`; export MH_OBJ_DIR
+fi
+
+. "${srcdir}/test/oauth/common.sh"
+
+# fakesmtp uses the XOAUTH environment variable
+export XOAUTH
+XOAUTH='dXNlcj11c2VyQGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyIHRlc3QtYWNjZXNzAQE='
+
+# TEST
+start_test 'with sendfrom'
+
+setup_draft
+
+fake_creds <<EOF
+access-user@example.com: test-access
+refresh-user@example.com: test-refresh
+expire-user@example.com: 2000000000
+EOF
+
+#### Set up profile to use sendfrom by adding a sendfrom-addr. This one adds
+#### the oauth switches, which are necessary for this test.
+sendfrom="$MH_INST_DIR${datarootdir}/doc/nmh/contrib/sendfrom"
+cat >> "$MH" <<EOF
+sendfrom-nobody@example.com: -server 127.0.0.1 -port ${smtp_port}
+ -client localhost -saslmech xoauth2 -authservice test -user user@example.com
+EOF
+
+cat > "${testname}.expected" <<EOF
+EHLO localhost
+AUTH XOAUTH2 dXNlcj11c2VyQGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyIHRlc3QtYWNjZXNzAQE=
+MAIL FROM:<nobody@example.com>
+RCPT TO:<somebody@example.com>
+DATA
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Date:
+
+This is a test
+.
+QUIT
+EOF
+
+start_fakesmtp
+run_test "send -draft"
+sed 's/^Date:.*/Date:/' "${testname}.smtp-req" > "${testname}.smtp-req".clean
+check "${testname}.smtp-req".clean "${testname}.expected"
+
+
+clean_fakesmtp
+clean_fakehttp
+finish_test
+
+exit ${failed:-0}
--- /dev/null
+This directory is sparse, but that doesn't mean that send is lightly
+tested. The tests in ../post/ rely on send.
--- /dev/null
+#!/bin/sh
+######################################################
+#
+# Test sendfrom
+#
+######################################################
+
+set -e
+
+if test -z "${MH_OBJ_DIR}"; then
+ srcdir=`dirname "$0"`/../..
+ MH_OBJ_DIR=`cd "$srcdir" && pwd`; export MH_OBJ_DIR
+fi
+
+. "${srcdir}/test/post/test-post-common.sh"
+
+
+# TEST
+start_test 'sendfrom, using -snoop'
+
+#### Set up profile to use sendfrom by adding a sendfrom-addr. This one adds
+#### -snoop to the send switches. The output from snoop is constant, and so
+#### is easily checked below.
+cat >> "$MH" <<EOF
+sendfrom-nobody@example.com: -snoop
+EOF
+
+cat > "${MH_TEST_DIR}/Mail/draft" <<EOF
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+
+This is a test.
+EOF
+
+cat > "${testname}.expected" <<EOF
+EHLO nosuchhost.example.com
+MAIL FROM:<nobody@example.com>
+RCPT TO:<somebody@example.com>
+DATA
+From: Mr Nobody <nobody@example.com>
+To: Somebody Else <somebody@example.com>
+Subject: Test
+MIME-Version: 1.0
+Content-Type: text/plain; charset="us-ascii"
+Date:
+
+This is a test.
+.
+QUIT
+EOF
+
+cat > "${testname}.post-expected" <<EOF
+<= 220 Not really an ESMTP server
+=> EHLO nosuchhost.example.com
+<= 250 I'll buy that for a dollar!
+=> MAIL FROM:<nobody@example.com>
+<= 250 I'll buy that for a dollar!
+=> RCPT TO:<somebody@example.com>
+<= 250 I'll buy that for a dollar!
+=> DATA
+<= 354 Go ahead
+=> .
+<= 250 Thanks for the info!
+=> QUIT
+<= 221 Later alligator!
+EOF
+
+test_post "${testname}.actual" "${testname}.expected" \
+ > ${testname}.post 2>/dev/null
+
+check "${testname}.post-expected" "${testname}.post"
+
+
+finish_test
+
+exit ${failed:-0}
*/
#include <h/mh.h>
+#include <h/fmt_scan.h>
+#include <h/fmt_compile.h>
#include <h/signals.h>
#include <setjmp.h>
#include <fcntl.h>
#endif
#include <time.h>
+#ifdef OAUTH_SUPPORT
+#include <h/oauth.h>
+
+static int setup_oauth_params(char *[], int *, int, const char **);
+#endif /* OAUTH_SUPPORT */
+
int debugsw = 0; /* global */
int forwsw = 1;
int inplace = 1;
/*
* static prototypes
*/
-static void armed_done (int) NORETURN;
static void alert (char *, int);
static int tmp_fd (void);
static void anno (int, struct stat *);
static void annoaux (int);
static int splitmsg (char **, int, char *, char *, struct stat *, int);
static int sendaux (char **, int, char *, char *, struct stat *);
-
+static void handle_sendfrom(char **, int *, char *);
+static int get_from_header_info(const char *, const char **, const char **, const char **);
+static const char *get_message_header_info(FILE *, char *);
+static void merge_profile_entry(const char *, const char *, char *[], int *);
+static void armed_done (int) NORETURN;
/*
* Entry point into (back-end) routines to send message.
struct stat sts;
char **buildvec, *buildprogram;
char *volatile drft = draft;
+ /* nvecs is volatile to prevent warning from gcc about possible clobbering
+ by longjmp. */
+ volatile int nvecs = vecp;
+ int *nvecsp = (int *) &nvecs;
/*
* Run the mimebuildproc (which is by default mhbuild) on the message
done=armed_done;
switch (setjmp (env)) {
- case OK:
+ case OK:
/*
* If given -push and -unique (which is undocumented), then
* rename the draft file. I'm not quite sure why.
drft = file;
}
+ /*
+ * Rework the vec based on From: header in draft, as specified
+ * by sendfrom-address entries in profile.
+ */
+ if (context_find_prefix("sendfrom-")) {
+ handle_sendfrom(vec, nvecsp, draft);
+ }
+
/*
* Check if we need to split the message into
* multiple messages of type "message/partial".
*/
if (splitsw >= 0 && !distfile && stat ((char *) drft, &sts) != NOTOK
&& sts.st_size >= CPERMSG) {
- status = splitmsg (vec, vecp, program, drft,
+ status = splitmsg (vec, nvecs, program, drft,
st, splitsw) ? NOTOK : OK;
} else {
- status = sendaux (vec, vecp, program, drft, st) ? NOTOK : OK;
+ status = sendaux (vec, nvecs, program, drft, st) ? NOTOK : OK;
}
/* rename the original draft */
advise (buffer, "unable to rename %s to", drft);
break;
- default:
+ default:
status = DONE;
break;
}
break;
adios (NULL, "premature eof");
}
-
+
if ((pos += (len = strlen (buffer))) > CPERMSG) {
fseek (in, -len, SEEK_CUR);
break;
if (cwd == NULL)
cwd = getcpy (pwd ());
- case OK:
+ case OK:
/* block a few signals */
sigemptyset (&set);
sigaddset (&set, SIGHUP);
}
+static
+void
+handle_sendfrom(char **vec, int *vecp, char *draft) {
+ const char *addr, *host;
+ const char *message;
+
+ /* Extract address and host from From: header line in draft. */
+ if (get_from_header_info(draft, &addr, &host, &message) != OK) {
+ adios(draft, message);
+ }
+
+ /* Merge in any address or host specific switches to post(1) from profile. */
+ merge_profile_entry(addr, host, vec, vecp);
+ free((void *) host);
+ free((void *) addr);
+
+ vec[*vecp] = NULL;
+
+ {
+ char **vp;
+
+ for (vp = vec; *vp; ++vp) {
+ if (strcmp(*vp, "xoauth2") == 0) {
+#ifdef OAUTH_SUPPORT
+ int snoop = 0;
+
+ /* -snoop will be in vec if it was enabled. */
+ for (vp = vec; vp && *vp; ++vp) {
+ if (strcmp(*vp, "-snoop") == 0) {
+ snoop = 1;
+ break;
+ }
+ }
+
+ if (setup_oauth_params(vec, vecp, snoop, &message) != OK) {
+ adios(NULL, message);
+ }
+ break;
+#else
+ adios(NULL, "sendfrom built without OAUTH_SUPPORT, "
+ "so -saslmech xoauth2 is not supported");
+#endif /* OAUTH_SUPPORT */
+ }
+ }
+ }
+}
+
+
+#ifdef OAUTH_SUPPORT
+/*
+ * For XOAUTH2, append access token, from mh_oauth_do_xoauth(), for the user to vec.
+ */
+static
+int
+setup_oauth_params(char *vec[], int *vecp, int snoop, const char **message) {
+ const char *saslmech = NULL, *user = NULL, *auth_svc = NULL;
+ int i;
+
+ /* Make sure we have all the information we need. */
+ for (i = 1; i < *vecp; ++i) {
+ /* Don't support abbreviated switches, to avoid collisions in the
+ future if new ones are added. */
+ if (! strcmp(vec[i-1], "-saslmech")) {
+ saslmech = vec[i];
+ } else if (! strcmp(vec[i-1], "-user")) {
+ user = vec[i];
+ } else if (! strcmp(vec[i-1], "-authservice")) {
+ auth_svc = vec[i];
+ }
+ }
+
+ if (auth_svc == NULL) {
+ if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
+ *message = "must specify -authservice with -saslmech xoauth2";
+ return NOTOK;
+ }
+ } else {
+ if (user == NULL) {
+ *message = "must specify -user with -saslmech xoauth2";
+ return NOTOK;
+ }
+
+ vec[(*vecp)++] = getcpy("-authservice");
+ if (saslmech && ! strcasecmp(saslmech, "xoauth2")) {
+ vec[(*vecp)++] = mh_oauth_do_xoauth(user, auth_svc, snoop ? stderr : NULL);
+ } else {
+ vec[(*vecp)++] = getcpy(auth_svc);
+ }
+ }
+
+ return 0;
+}
+#endif /* OAUTH_SUPPORT */
+
+
+/*
+ * Extract user and domain from From: header line in draft.
+ */
+static
+int
+get_from_header_info(const char *filename, const char **addr, const char **host, const char **message) {
+ struct stat st;
+ FILE *in;
+
+ if (stat (filename, &st) == NOTOK) {
+ *message = "unable to stat draft file";
+ return NOTOK;
+ }
+
+ if ((in = fopen(filename, "r")) != NULL) {
+ char *addrformat = "%(addr{from})", *hostformat = "%(host{from})";
+
+ if ((*addr = get_message_header_info(in, addrformat)) == NULL) {
+ *message = "unable to find From: address in";
+ return NOTOK;
+ }
+ rewind(in);
+
+ if ((*host = get_message_header_info(in, hostformat)) == NULL) {
+ fclose(in);
+ *message = "unable to find From: host in";
+ return NOTOK;
+ }
+ fclose(in);
+
+ return OK;
+ } else {
+ *message = "unable to open";
+ return NOTOK;
+ }
+}
+
+
+/*
+ * Get formatted information from header of a message.
+ * Adapted from process_single_file() in uip/fmttest.c.
+ */
+static
+const char *
+get_message_header_info(FILE *in, char *format) {
+ int dat[5];
+ struct format *fmt;
+ struct stat st;
+ int parsing_header;
+ m_getfld_state_t gstate = 0;
+ charstring_t buffer = charstring_create(0);
+ char *retval;
+
+ dat[0] = dat[1] = dat[4] = 0;
+ dat[2] = fstat(fileno(in), &st) == 0 ? st.st_size : 0;
+ dat[3] = INT_MAX;
+
+ (void) fmt_compile(new_fs(NULL, NULL, format), &fmt, 1);
+ free_fs();
+
+ /*
+ * Read in the message and process the header.
+ */
+ parsing_header = 1;
+ do {
+ char name[NAMESZ], rbuf[NMH_BUFSIZ];
+ int bufsz = sizeof rbuf;
+ int state = m_getfld(&gstate, name, rbuf, &bufsz, in);
+
+ switch (state) {
+ case FLD:
+ case FLDPLUS: {
+ int bucket = fmt_addcomptext(name, rbuf);
+
+ if (bucket != -1) {
+ while (state == FLDPLUS) {
+ bufsz = sizeof rbuf;
+ state = m_getfld(&gstate, name, rbuf, &bufsz, in);
+ fmt_appendcomp(bucket, name, rbuf);
+ }
+ }
+
+ while (state == FLDPLUS) {
+ bufsz = sizeof rbuf;
+ state = m_getfld(&gstate, name, rbuf, &bufsz, in);
+ }
+ break;
+ }
+ default:
+ parsing_header = 0;
+ }
+ } while (parsing_header);
+ m_getfld_state_destroy(&gstate);
+
+ fmt_scan(fmt, buffer, INT_MAX, dat, NULL);
+ fmt_free(fmt, 1);
+
+ /* Trim trailing newline, if any. */
+ retval = rtrim(charstring_buffer_copy((buffer)));
+ charstring_free(buffer);
+
+ return retval;
+}
+
+
+/*
+ * Look in profile for entry corresponding to addr or host, and add its contents to vec.
+ *
+ * Could do some of this automatically, by looking for:
+ * 1) access-$(mbox{from}) in oauth-svc file using mh_oauth_cred_load(), which isn't
+ * static and doesn't have side effects; free the result with mh_oauth_cred_free())
+ * 2) machine $(mbox{from}) in creds
+ * If no -server passed in from profile or commandline, could use smtp.<svc>.com for gmail,
+ * but that might not generalize for other svcs.
+ */
+static
+void
+merge_profile_entry(const char *addr, const char *host, char *vec[], int *vecp) {
+ char *addr_entry = concat("sendfrom-", addr, NULL);
+ char *profile_entry = context_find(addr_entry);
+
+ free(addr_entry);
+ if (profile_entry == NULL) {
+ /* No entry for the user. Look for one for the host. */
+ char *host_entry = concat("sendfrom-", host, NULL);
+
+ profile_entry = context_find(host_entry);
+ free(host_entry);
+ }
+
+ /* Use argsplit() to do the real work of splitting the args in the profile entry. */
+ if (profile_entry && strlen(profile_entry) > 0) {
+ int profile_vecp;
+ char *file;
+ char **profile_vec = argsplit(profile_entry, &file, &profile_vecp);
+ int i;
+
+ for (i = 0; i < profile_vecp; ++i) {
+ vec[(*vecp)++] = getcpy(profile_vec[i]);
+ }
+
+ arglist_free(file, profile_vec);
+ }
+}
+
+
static void
armed_done (int status)
{