]> diplodocus.org Git - nmh/commitdiff
Moved sendfrom code from contrib into send(1).
authorDavid Levine <levinedl@acm.org>
Tue, 5 Jul 2016 16:02:59 +0000 (12:02 -0400)
committerDavid Levine <levinedl@acm.org>
Tue, 5 Jul 2016 16:02:59 +0000 (12:02 -0400)
Makefile.am
docs/contrib/sendfrom.c [deleted file]
docs/pending-release-notes
man/send.man
test/send/README [new file with mode: 0644]
test/send/test-sendfrom [new file with mode: 0755]
uip/sendsbr.c

index fa681971c51a96cce618b8c748df42a2a48cbbe6..f5c644483d854a62dcce74371a9362e175ec2c87 100644 (file)
@@ -97,6 +97,7 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/scan/test-scan test/scan/test-scan-multibyte \
        test/sequences/test-flist test/sequences/test-mark \
        test/sequences/test-out-of-range \
        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 \
        test/show/test-show \
        test/slocal/test-slocal \
        test/whatnow/test-attach-detach test/whatnow/test-cd \
@@ -233,8 +234,7 @@ dist_doc_DATA = COPYRIGHT INSTALL NEWS README VERSION \
 docs_contribdir = $(docdir)/contrib
 dist_docs_contrib_SCRIPTS = docs/contrib/replyfilter docs/contrib/build_nmh \
                            docs/contrib/ml docs/contrib/vpick
 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
 
 ##
 ## Our man pages
@@ -293,7 +293,8 @@ EXTRA_DIST = autogen.sh config/version.sh sbr/sigmsg.awk sbr/icalparse.h \
             test/mhbuild/somebinary \
             test/mhbuild/nulls \
             test/mhbuild/textplain \
             test/mhbuild/somebinary \
             test/mhbuild/nulls \
             test/mhbuild/textplain \
-            test/post/test-post-common.sh test/valgrind.supp uip/mhmail \
+            test/post/test-post-common.sh test/send/README \
+            test/valgrind.supp uip/mhmail \
             SPECS/nmh.spec SPECS/build-nmh-cygwin $(man_SRCS)
 
 ##
             SPECS/nmh.spec SPECS/build-nmh-cygwin $(man_SRCS)
 
 ##
@@ -413,7 +414,7 @@ uip_scan_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
 uip_send_SOURCES = uip/send.c uip/sendsbr.c uip/annosbr.c \
                   uip/distsbr.c
 
 uip_send_SOURCES = uip/send.c uip/sendsbr.c uip/annosbr.c \
                   uip/distsbr.c
-uip_send_LDADD = $(LDADD) $(POSTLINK)
+uip_send_LDADD = $(LDADD) $(TERMLIB) $(POSTLINK)
 
 uip_show_SOURCES = uip/show.c uip/mhlsbr.c
 uip_show_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
 
 uip_show_SOURCES = uip/show.c uip/mhlsbr.c
 uip_show_LDADD = $(LDADD) $(TERMLIB) $(ICONVLIB) $(POSTLINK)
@@ -470,7 +471,7 @@ uip_slocal_LDADD = $(LDADD) $(NDBM_LIBS) $(POSTLINK)
 
 uip_viamail_SOURCES = uip/viamail.c uip/mhmisc.c uip/sendsbr.c \
                      uip/annosbr.c uip/distsbr.c
 
 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) $(POSTLINK)
 
 test_getfullname_SOURCES = test/getfullname.c
 test_getfullname_LDADD = $(LDADD) $(POSTLINK)
 
 test_getfullname_SOURCES = test/getfullname.c
 test_getfullname_LDADD = $(LDADD) $(POSTLINK)
@@ -500,9 +501,6 @@ etc_gen_ctype_checked_LDADD = $(POSTLINK)
 
 ## Hard-code the config/version.c target, instead of using $@, so this
 ## rule works for the distcheck target with Solaris (System V) make.
 
 ## Hard-code the config/version.c target, instead of using $@, so this
 ## rule works for the distcheck target with Solaris (System V) make.
-docs_contrib_sendfrom_SOURCES = docs/contrib/sendfrom.c
-docs_contrib_sendfrom_LDADD = $(LDADD) $(TERMLIB) $(CURLLIB) $(POSTLINK)
-
 ## 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
 ## 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
diff --git a/docs/contrib/sendfrom.c b/docs/contrib/sendfrom.c
deleted file mode 100644 (file)
index 2ae8bd5..0000000
+++ /dev/null
@@ -1,416 +0,0 @@
-/*
- * 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;
-}
index 5e70513090769d65f23628e1d562dd574b0bf94d..d56ae28d8913207b6700637cb94348b128910088 100644 (file)
@@ -43,6 +43,8 @@ NEW FEATURES
   numbers as, for example, "10K", or "2.3Mi"
 - Support for the -sendmail flag to send/post to change the sendmail
   binary when using the sendmail/pipe MTS.
   numbers as, for example, "10K", or "2.3Mi"
 - Support for the -sendmail flag to send/post to change the sendmail
   binary when using the sendmail/pipe MTS.
+- Added support to send(1) to specify switches to post(1) based on address or
+  domain name in From: header line in message draft.
 
 -----------------
 OBSOLETE FEATURES
 
 -----------------
 OBSOLETE FEATURES
index 1673c5ced138e86b9cd75db76a78789de4b6c385..b14d9589e0f1204aa090d277d440a6d26665c165 100644 (file)
@@ -1,7 +1,7 @@
 .\"
 .\" %nmhwarning%
 .\"
 .\"
 .\" %nmhwarning%
 .\"
-.TH SEND %manext1% "July 8, 2014" "%nmhversion%"
+.TH SEND %manext1% "July 5, 2016" "%nmhversion%"
 .SH NAME
 send \- send a message
 .SH SYNOPSIS
 .SH NAME
 send \- send a message
 .SH SYNOPSIS
@@ -463,6 +463,96 @@ can be named).
 See
 .IR mh\-alias (5)
 for more information.
 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
 .SH FILES
 .fc ^ ~
 .nf
@@ -480,6 +570,8 @@ for more information.
 ^Signature:~^To determine the user's mail signature
 ^mailproc:~^Program to post failure notices
 ^postproc:~^Program to post the message
 ^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),
 .fi
 .SH "SEE ALSO"
 .IR comp (1),
diff --git a/test/send/README b/test/send/README
new file mode 100644 (file)
index 0000000..2fb7d64
--- /dev/null
@@ -0,0 +1,2 @@
+This directory is sparse, but that doesn't mean that send is lightly
+tested.  The tests in ../post/ rely on send.
diff --git a/test/send/test-sendfrom b/test/send/test-sendfrom
new file mode 100755 (executable)
index 0000000..68ddb27
--- /dev/null
@@ -0,0 +1,77 @@
+#!/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}
index 6bfc548e7270be61318fe94c176b493a18a265e7..a3270e097eb3d80d9744aca880849c0455a0ad72 100644 (file)
@@ -8,6 +8,8 @@
  */
 
 #include <h/mh.h>
  */
 
 #include <h/mh.h>
+#include <h/fmt_scan.h>
+#include <h/fmt_compile.h>
 #include <h/signals.h>
 #include <setjmp.h>
 #include <fcntl.h>
 #include <h/signals.h>
 #include <setjmp.h>
 #include <fcntl.h>
 #endif
 #include <time.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;
 int debugsw = 0;               /* global */
 int forwsw  = 1;
 int inplace = 1;
@@ -38,14 +46,17 @@ static jmp_buf env;
 /*
  * static prototypes
  */
 /*
  * 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 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.
 
 /*
  * Entry point into (back-end) routines to send message.
@@ -61,6 +72,10 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
     struct stat sts;
     char **buildvec, *buildprogram;
     char *volatile drft = draft;
     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
 
     /*
      * Run the mimebuildproc (which is by default mhbuild) on the message
@@ -93,7 +108,7 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
 
     done=armed_done;
     switch (setjmp (env)) {
 
     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.
        /*
         * If given -push and -unique (which is undocumented), then
         * rename the draft file.  I'm not quite sure why.
@@ -109,16 +124,24 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
            drft = file;
        }
 
            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) {
        /*
         * 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 {
                               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 */
        }
 
        /* rename the original draft */
@@ -128,7 +151,7 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st,
            advise (buffer, "unable to rename %s to", drft);
        break;
 
            advise (buffer, "unable to rename %s to", drft);
        break;
 
-    default: 
+    default:
        status = DONE;
        break;
     }
        status = DONE;
        break;
     }
@@ -323,7 +346,7 @@ splitmsg (char **vec, int vecp, char *program, char *drft,
                    break;
                adios (NULL, "premature eof");
            }
                    break;
                adios (NULL, "premature eof");
            }
-           
+
            if ((pos += (len = strlen (buffer))) > CPERMSG) {
                fseek (in, -len, SEEK_CUR);
                break;
            if ((pos += (len = strlen (buffer))) > CPERMSG) {
                fseek (in, -len, SEEK_CUR);
                break;
@@ -585,7 +608,7 @@ anno (int fd, struct stat *st)
            if (cwd == NULL)
                cwd = getcpy (pwd ());
 
            if (cwd == NULL)
                cwd = getcpy (pwd ());
 
-       case OK: 
+       case OK:
            /* block a few signals */
            sigemptyset (&set);
            sigaddset (&set, SIGHUP);
            /* block a few signals */
            sigemptyset (&set);
            sigaddset (&set, SIGHUP);
@@ -702,6 +725,247 @@ oops:
 }
 
 
 }
 
 
+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)
 {
 static void
 armed_done (int status)
 {