File fetchmail-add-passwordfile-and-passwordfd-options.patch of Package fetchmail.22404
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(-)
diff --git a/fetchmail.c b/fetchmail.c
index ead6d1f2..0292d42a 100644
--- a/fetchmail.c
+++ b/fetchmail.c
@@ -387,7 +387,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)
{
netrc_entry *p;
@@ -554,8 +554,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);
@@ -1000,6 +1073,10 @@ static void optmerge(struct query *h2, struct query *h1, int force)
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);
@@ -1064,6 +1141,7 @@ static int load_params(int argc, char **argv, int optind)
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.esmtp_name = user;
diff --git a/fetchmail.h b/fetchmail.h
index 22b72827..715cc2d0 100644
--- a/fetchmail.h
+++ b/fetchmail.h
@@ -336,6 +336,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 */
diff --git a/fetchmail.man b/fetchmail.man
index aece716e..9c6ed4ad 100644
--- a/fetchmail.man
+++ b/fetchmail.man
@@ -872,6 +872,37 @@ The default is your login name on the client machine that is running
\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
@@ -955,7 +986,8 @@ setting also allows the non-standard "xoauth2" SASL scheme (using
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"
@@ -1844,6 +1876,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}
diff --git a/options.c b/options.c
index a181c6d9..99b8e020 100644
--- a/options.c
+++ b/options.c
@@ -26,6 +26,8 @@ enum {
LA_POSTMASTER,
LA_NOBOUNCE,
LA_AUTH,
+ LA_PASSWORDFILE,
+ LA_PASSWORDFD,
LA_FETCHDOMAINS,
LA_BSMTP,
LA_LMTP,
@@ -103,6 +105,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' },
@@ -236,6 +240,7 @@ int parsecmdline (int argc /** argument count */,
memset(ctl, '\0', sizeof(struct query)); /* start clean */
ctl->smtp_socket = -1;
+ ctl->passwordfd = -1;
while (!errflag &&
(c = getopt_long(argc,argv,shortoptions,
@@ -407,6 +412,17 @@ int parsecmdline (int argc /** argument count */,
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)
diff --git a/rcfile_l.l b/rcfile_l.l
index 824845b3..47a37d0f 100644
--- a/rcfile_l.l
+++ b/rcfile_l.l
@@ -115,6 +115,8 @@ pwmd_socket { return PWMD_SOCKET; }
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; }
diff --git a/rcfile_y.y b/rcfile_y.y
index 815fbd74..3967adb3 100644
--- a/rcfile_y.y
+++ b/rcfile_y.y
@@ -61,6 +61,7 @@ extern char * yytext;
%token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL
%token AUTHENTICATE TIMEOUT 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
@@ -315,6 +316,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
@@ -539,6 +542,7 @@ static void reset_server(const char *name, int skip)
trailer = FALSE;
memset(¤t,'\0',sizeof(current));
current.smtp_socket = -1;
+ current.passwordfd = -1;
current.server.pollname = xstrdup(name);
current.server.skip = skip;
current.server.principal = (char *)NULL;
@@ -560,6 +564,7 @@ static void user_reset(void)
memset(¤t, '\0', sizeof(current));
current.smtp_socket = -1;
+ current.passwordfd = -1;
current.server = save;
}
@@ -580,6 +585,7 @@ struct query *hostalloc(struct query *init /** pointer to block containing
{
memset(node, '\0', sizeof(struct query));
node->smtp_socket = -1;
+ node->passwordfd = -1;
}
/* append to end of list */
--
2.31.1