From: David Levine Date: Tue, 5 Jul 2016 16:02:59 +0000 (-0400) Subject: Moved sendfrom code from contrib into send(1). X-Git-Url: https://diplodocus.org/git/nmh/commitdiff_plain/7c545bcaf9158021853d3dc097d2e41b7417ad10?ds=sidebyside;hp=--cc Moved sendfrom code from contrib into send(1). --- 7c545bcaf9158021853d3dc097d2e41b7417ad10 diff --git a/Makefile.am b/Makefile.am index fa681971..f5c64448 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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/send/test-sendfrom \ 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 -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 @@ -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/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) ## @@ -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_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) @@ -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_LDADD = $(LDADD) $(POSTLINK) +uip_viamail_LDADD = $(LDADD) $(TERMLIB) $(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. -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 diff --git a/docs/contrib/sendfrom.c b/docs/contrib/sendfrom.c deleted file mode 100644 index 2ae8bd53..00000000 --- a/docs/contrib/sendfrom.c +++ /dev/null @@ -1,416 +0,0 @@ -/* - * sendfrom.c -- postproc that selects user, and then mail server, from draft - * - * Author: David Levine - * - * 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-: - * - * 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: /contrib/sendfrom - * - * with 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 -#include -#include -#include - -#ifdef OAUTH_SUPPORT -#include - -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..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; -} diff --git a/docs/pending-release-notes b/docs/pending-release-notes index 5e705130..d56ae28d 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -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. +- 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 diff --git a/man/send.man b/man/send.man index 1673c5ce..b14d9589 100644 --- a/man/send.man +++ b/man/send.man @@ -1,7 +1,7 @@ .\" .\" %nmhwarning% .\" -.TH SEND %manext1% "July 8, 2014" "%nmhversion%" +.TH SEND %manext1% "July 5, 2016" "%nmhversion%" .SH NAME send \- send a message .SH SYNOPSIS @@ -463,6 +463,96 @@ can be named). 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 @@ -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 +^sendfrom-address:~^Switches to post for From: address +^sendfrom-domain:~^Switches to post for From: domain name .fi .SH "SEE ALSO" .IR comp (1), diff --git a/test/send/README b/test/send/README new file mode 100644 index 00000000..2fb7d641 --- /dev/null +++ b/test/send/README @@ -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 index 00000000..68ddb276 --- /dev/null +++ b/test/send/test-sendfrom @@ -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" < "${MH_TEST_DIR}/Mail/draft" < +To: Somebody Else +Subject: Test + +This is a test. +EOF + +cat > "${testname}.expected" < +RCPT TO: +DATA +From: Mr Nobody +To: Somebody Else +Subject: Test +MIME-Version: 1.0 +Content-Type: text/plain; charset="us-ascii" +Date: + +This is a test. +. +QUIT +EOF + +cat > "${testname}.post-expected" < EHLO nosuchhost.example.com +<= 250 I'll buy that for a dollar! +=> MAIL FROM: +<= 250 I'll buy that for a dollar! +=> RCPT TO: +<= 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} diff --git a/uip/sendsbr.c b/uip/sendsbr.c index 6bfc548e..a3270e09 100644 --- a/uip/sendsbr.c +++ b/uip/sendsbr.c @@ -8,6 +8,8 @@ */ #include +#include +#include #include #include #include @@ -21,6 +23,12 @@ #endif #include +#ifdef OAUTH_SUPPORT +#include + +static int setup_oauth_params(char *[], int *, int, const char **); +#endif /* OAUTH_SUPPORT */ + int debugsw = 0; /* global */ int forwsw = 1; int inplace = 1; @@ -38,14 +46,17 @@ static jmp_buf env; /* * 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. @@ -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; + /* 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 @@ -93,7 +108,7 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st, 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. @@ -109,16 +124,24 @@ sendsbr (char **vec, int vecp, char *program, char *draft, struct stat *st, 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 */ @@ -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; - default: + default: status = DONE; break; } @@ -323,7 +346,7 @@ splitmsg (char **vec, int vecp, char *program, char *drft, break; adios (NULL, "premature eof"); } - + 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 ()); - case OK: + case OK: /* 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..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) {