File fetchmail-add-passwordfile-and-passwordfd-options.patch of Package fetchmail
From: Matthew Ogilvie <mmogilvi+fml@zoho.com>
Date: Sun, 28 May 2017 00:01:02 -0600
Subject: add passwordfile and passwordfd options
Git-repo: https://gitlab.com/fetchmail/fetchmail.git
Git-commit: cdd7182f65734c97723ba5f282040e08d830e650
---
fetchmail.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
fetchmail.h | 2 +
fetchmail.man | 40 +++++++++++++++++++++++++++-
options.c | 16 +++++++++++
rcfile_l.l | 2 +
rcfile_y.y | 6 ++++
6 files changed, 145 insertions(+), 3 deletions(-)
Index: fetchmail-6.5.1/fetchmail.c
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.c
+++ fetchmail-6.5.1/fetchmail.c
@@ -471,7 +471,7 @@ int main(int argc, char **argv)
/* Server won't care what the password is, but there
must be some non-null string here. */
ctl->password = ctl->remotename;
- else
+ else if (!ctl->passwordfile && ctl->passwordfd==-1)
{
const netrc_entry *p;
@@ -649,8 +649,81 @@ int main(int argc, char **argv)
if (ctl->active && !(implicitmode && ctl->server.skip)
&& !NO_PASSWORD(ctl) && !ctl->password)
{
- if (!isatty(0))
+ if (ctl->passwordfd != -1)
{
+ char msg[PASSWORDLEN+1];
+ char *mi;
+
+ /* Read one character at a time to avoid reading too
+ * much if more than one password sent in through this FD
+ * (although that would be a questionable practice).
+ */
+ for (mi = msg; mi<msg+sizeof(msg)-1; ++mi) {
+ int res = read(ctl->passwordfd, mi, 1);
+ if(res == -1) {
+ int saveErrno = errno;
+ fprintf(stderr,
+ GT_("fetchmail: unable to read password from fd=%d: %s\n"),
+ ctl->passwordfd,
+ strerror(saveErrno));
+ memset(msg, 0x55, mi-msg);
+ return PS_AUTHFAIL;
+ }
+ if (res == 0 || *mi == '\n')
+ break;
+ }
+ *mi = '\0';
+ if (mi == msg) {
+ fprintf(stderr,
+ GT_("fetchmail: empty password read from fd=%d\n"),
+ ctl->passwordfd);
+ return PS_AUTHFAIL;
+ }
+
+ ctl->password = xstrdup(msg);
+ memset(msg, 0x55, mi-msg);
+ } else if (ctl->passwordfile) {
+ int fd = open(ctl->passwordfile, O_RDONLY);
+ char msg[PASSWORDLEN+1];
+ char *newline;
+ int res;
+
+ if (fd == -1) {
+ int saveErrno = errno;
+ fprintf(stderr,
+ GT_("fetchmail: unable to open %s: %s\n"),
+ ctl->passwordfile,
+ strerror(saveErrno));
+ return PS_AUTHFAIL;
+ }
+
+ res = read(fd, msg, sizeof(msg)-1);
+ if (res == -1 || close(fd) == -1) {
+ int saveErrno = errno;
+ fprintf(stderr,
+ GT_("fetchmail: error reading %s: %s\n"),
+ ctl->passwordfile,
+ strerror(saveErrno));
+ return PS_AUTHFAIL;
+ }
+ msg[res] = '\0';
+
+ newline = memchr(msg, '\n', res);
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ if (strlen(msg) == 0) {
+ fprintf(stderr,
+ GT_("fetchmail: empty password read from %s\n"),
+ ctl->passwordfile);
+ memset(msg, 0x55, res);
+ return PS_AUTHFAIL;
+ }
+
+ ctl->password = xstrdup(msg);
+ memset(msg, 0x55, res);
+ } else if (!isatty(0)) {
fprintf(stderr,
GT_("fetchmail: can't find a password for %s@%s.\n"),
ctl->remotename, ctl->server.pollname);
@@ -1046,6 +1119,10 @@ static void optmerge(struct query *h2, s
FLAG_MERGE(wildcard);
STRING_MERGE(remotename);
STRING_MERGE(password);
+ FLAG_MERGE(passwordfile);
+ if (force ? h1->passwordfd!=-1 : h2->passwordfd==-1) {
+ h2->passwordfd = h1->passwordfd;
+ }
STRING_MERGE(mda);
STRING_MERGE(bsmtp);
FLAG_MERGE(listener);
@@ -1112,6 +1189,7 @@ static int load_params(int argc, char **
def_opts.smtp_socket = -1;
def_opts.smtpaddress = (char *)0;
def_opts.smtpname = (char *)0;
+ def_opts.passwordfd = -1;
def_opts.server.protocol = P_AUTO;
def_opts.server.timeout = CLIENT_TIMEOUT;
def_opts.server.idle_timeout = CLIENT_IDLE_TIMEOUT;
Index: fetchmail-6.5.1/fetchmail.h
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.h
+++ fetchmail-6.5.1/fetchmail.h
@@ -312,6 +312,8 @@ struct query
int wildcard; /* should unmatched names be passed through */
char *remotename; /* remote login name to use */
char *password; /* remote password to use */
+ char *passwordfile; /* filename; first line contains password */
+ int passwordfd; /* fileno that password will be piped into */
struct idlist *mailboxes; /* list of mailboxes to check */
/* per-forwarding-target data */
Index: fetchmail-6.5.1/fetchmail.man
===================================================================
--- fetchmail-6.5.1.orig/fetchmail.man
+++ fetchmail-6.5.1/fetchmail.man
@@ -1056,6 +1056,37 @@ The default is your login name on the cl
\fBfetchmail\fP.
See USER AUTHENTICATION below for a complete description.
.TP
+.B \-\-passwordfile <filename>
+(Keyword: passwordfile)
+.br
+Specifies a file name from which to read the first line to use as the password.
+Useful if something changes the password/token often without regenerating a
+long fetchmailrc file, such as with typical xoauth2 authentication tokens.
+Protect the file with appropriate permissions to avoid leaking your password.
+Fetchmail might not re-read the file in daemon mode (-d) unless the
+fetchmailrc file also changes, so it might make sense to run it in
+non-daemon mode from some other background process (cron and/or whatever
+updates the password).
+.TP
+.B \-\-passwordfd <integer>
+(Keyword: passwordfd)
+.br
+Specifies a file descriptor number inherited from the calling process,
+from which fetchmail should read one line to use as the password.
+The descriptor will usually be the receiving end of a pipe (equivalent
+to "something | fetchmail \-\-passwordfd 5 5<\&0"),
+although it could also be a redirected input file
+(equivalent to "fetchmail \-\-passwordfd 5 5</path/to/file").
+Useful if something wants to manage password ownership more securely
+than files, or if the password/token changes often,
+such as with typical xoauth2 authentication tokens. Normal interactive
+mode passwords requires that standard input is a terminal and disables
+echo, but passwordfd does not care. Do not do something
+like "echo 'password' | fetchmail ...", since echo's arguments are
+likely to (briefly) be publicly visible in process listings.
+This probably doesn't interact well with deamon mode: when will it
+re-read a new password?
+.TP
.B \-I <specification> | \-\-interface <specification>
(Keyword: interface)
.br
@@ -1148,7 +1179,8 @@ setting also allows the non-standard "xo
the same token) if the server only claims to support "xoauth2".
External tools are necessary to obtain
such tokens. Access tokens often expire fairly quickly (e.g. 1 hour),
-and new ones need to be generated from renewal tokens. See the
+and new ones need to be generated from renewal tokens, so the
+"passwordfile", "passwordfd", or "pwmd_*" options may be useful. See the
oauth2.py script from
.URL https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough "Google's Oauth2 Run Through" ,
and other oauth2 documentation. For services like gmail, an "App Password"
@@ -2100,6 +2132,12 @@ T}
pass[word] \& \& T{
Specify remote account password
T}
+passwordfile \-\-... \& T{
+File name with password in first line.
+T}
+passwordfd \-\-... \& T{
+Inherited file descriptor from which to read one line for the password.
+T}
ssl \& \& T{
Connect to server over the specified base protocol using SSL encryption
T}
Index: fetchmail-6.5.1/options.c
===================================================================
--- fetchmail-6.5.1.orig/options.c
+++ fetchmail-6.5.1/options.c
@@ -29,6 +29,8 @@ enum {
LA_POSTMASTER,
LA_NOBOUNCE,
LA_AUTH,
+ LA_PASSWORDFILE,
+ LA_PASSWORDFD,
LA_FETCHDOMAINS,
LA_BSMTP,
LA_LMTP,
@@ -98,6 +100,8 @@ static const struct option longoptions[]
{"port", required_argument, (int *) 0, 'P' },
{"service", required_argument, (int *) 0, 'P' },
{"auth", required_argument, (int *) 0, LA_AUTH},
+ {"passwordfile", required_argument, (int *) 0, LA_PASSWORDFILE },
+ {"passwordfd", required_argument, (int *) 0, LA_PASSWORDFD },
{"timeout", required_argument, (int *) 0, 't' },
{"envelope", required_argument, (int *) 0, 'E' },
{"qvirtual", required_argument, (int *) 0, 'Q' },
@@ -231,6 +235,7 @@ int parsecmdline (int argc /** argument
memset(ctl, '\0', sizeof(struct query)); /* start clean */
ctl->smtp_socket = -1;
+ ctl->passwordfd = -1;
while (!errflag &&
(c = getopt_long(argc,argv,shortoptions,
@@ -402,6 +407,17 @@ int parsecmdline (int argc /** argument
errflag++;
}
break;
+ case LA_PASSWORDFILE:
+ ctl->passwordfile = optarg;
+ break;
+ case LA_PASSWORDFD:
+ ctl->passwordfd = xatoi(optarg, &errflag);
+ if (ctl->passwordfd < 0) {
+ fprintf(stderr,GT_("Invalid file descriptor %d\n"),
+ ctl->passwordfd);
+ errflag++;
+ }
+ break;
case 't':
ctl->server.timeout = xatoi(optarg, &errflag);
if (ctl->server.timeout == 0)
Index: fetchmail-6.5.1/rcfile_l.l
===================================================================
--- fetchmail-6.5.1.orig/rcfile_l.l
+++ fetchmail-6.5.1/rcfile_l.l
@@ -116,6 +116,8 @@ accept { return ACCEPT; }
reject { return REJECT_; }
user(name)? {SETSTATE(NAME); return USERNAME; }
+passwordfile { return PASSWORDFILE; }
+passwordfd { return PASSWORDFD; }
<INITIAL,NAME>pass(word)? {SETSTATE(NAME); return PASSWORD; }
folder(s)? { return FOLDER; }
smtp(host)? { return SMTPHOST; }
Index: fetchmail-6.5.1/rcfile_y.y
===================================================================
--- fetchmail-6.5.1.orig/rcfile_y.y
+++ fetchmail-6.5.1/rcfile_y.y
@@ -62,6 +62,7 @@ void yyerror (const char *s)
%token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL
%token AUTHENTICATE TIMEOUT IDLETIMEOUT KPOP SDPS ENVELOPE QVIRTUAL
%token USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP
+%token PASSWORDFILE PASSWORDFD
%token SMTPADDRESS SMTPNAME SPAMRESPONSE PRECONNECT POSTCONNECT LIMIT WARNINGS
%token INTERFACE MONITOR PLUGIN PLUGOUT
%token IS HERE THERE TO MAP
@@ -314,6 +315,8 @@ user_option : TO mapping_list HERE
| IS STRING THERE {current.remotename = $2;}
| PASSWORD STRING {current.password = $2;}
+ | PASSWORDFILE STRING {current.passwordfile = $2;}
+ | PASSWORDFD NUMBER {current.passwordfd = NUM_VALUE_IN($2);}
| FOLDER folder_list
| SMTPHOST smtp_list
| FETCHDOMAINS fetch_list
@@ -495,6 +498,7 @@ static void reset_server(const char *nam
trailer = FALSE;
memset(¤t,'\0',sizeof(current));
current.smtp_socket = -1;
+ current.passwordfd = -1;
current.server.pollname = xstrdup(name);
current.server.skip = skip;
}
@@ -515,6 +519,7 @@ static void user_reset(void)
memset(¤t, '\0', sizeof(current));
current.smtp_socket = -1;
+ current.passwordfd = -1;
current.server = save;
}
@@ -535,6 +540,7 @@ struct query *hostalloc(struct query *in
{
memset(node, '\0', sizeof(struct query));
node->smtp_socket = -1;
+ node->passwordfd = -1;
}
/* append to end of list */