diff --git a/conf.c b/conf.c
index 3daa4954..7e6428a0 100644
--- a/conf.c
+++ b/conf.c
@@ -284,6 +284,8 @@ void dump_config(struct runctl *runp, struct query *querylist)
 		stringdump("auth", "otp");
 	    else if (ctl->server.authenticate == A_MSN)
 		stringdump("auth", "msn");
+	    else if (ctl->server.authenticate == A_XOAUTH2)
+		stringdump("auth", "xoauth2");
 
 #ifdef HAVE_RES_SEARCH
 	    booldump("dns", ctl->server.dns);
diff --git a/fetchmail.c b/fetchmail.c
index fb1e6015..fc32d3a1 100644
--- a/fetchmail.c
+++ b/fetchmail.c
@@ -452,7 +452,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;
 
@@ -620,8 +620,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);
@@ -1005,6 +1078,10 @@ static void optmerge(struct query *h2, struct query *h1, int force)
     FLAG_MERGE(wildcard);
     STRING_MERGE(remotename);
     STRING_MERGE(password);
+    STRING_MERGE(passwordfile);
+    if (force ? h1->passwordfd!=-1 : h2->passwordfd==-1) {
+	h2->passwordfd = h1->passwordfd;
+    }
     STRING_MERGE(mda);
     STRING_MERGE(bsmtp);
     FLAG_MERGE(listener);
@@ -1069,6 +1159,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.idle_timeout = CLIENT_IDLE_TIMEOUT;
@@ -1748,6 +1826,9 @@ static void dump_params (struct runctl *runp,
 	case A_SSH:
 	    printf(GT_("  End-to-end encryption assumed.\n"));
 	    break;
+	case A_XOAUTH2:
+	    printf(GT_("  XOAUTH2 will be forced; expecting password to really be OAUTH2 authentication token\n"));
+	    break;
 	}
 	if (ctl->server.principal != (char *) NULL)
 	    printf(GT_("  Mail service principal is: %s\n"), ctl->server.principal);
diff --git a/fetchmail.h b/fetchmail.h
index 902aae18..184430e9 100644
--- a/fetchmail.h
+++ b/fetchmail.h
@@ -79,6 +79,7 @@ struct addrinfo;
 #define		A_SSH		8	/* authentication at session level */
 #define		A_MSN		9	/* same as NTLM with keyword MSN */
 #define		A_EXTERNAL	10	/* external authentication (client cert) */
+#define		A_XOAUTH2	11	/* xoauth2 accsss token (not password) */
 
 /* some protocols or authentication types (KERBEROS, GSSAPI, SSH) don't
  * require a password */
@@ -322,6 +323,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 b9a0ea85..69ba52c2 100644
--- a/fetchmail.man
+++ b/fetchmail.man
@@ -919,6 +919,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
@@ -976,7 +1007,7 @@ AUTHENTICATION below for details).  The possible values are \fBany\fP,
 \&\fBpassword\fP, \fBkerberos_v5\fP, \fBkerberos\fP (or, for
 excruciating exactness, \fBkerberos_v4\fP), \fBgssapi\fP,
 \fBcram\-md5\fP, \fBotp\fP, \fBntlm\fP, \fBmsn\fP (only for POP3),
-\fBexternal\fP (only IMAP) and \fBssh\fP.
+\fBexternal\fP (only IMAP), \fBssh\fP and \fBxoauth2\fP (only IMAP).
 When \fBany\fP (the default) is specified, fetchmail tries
 first methods that don't require a password (EXTERNAL, GSSAPI, KERBEROS\ IV,
 KERBEROS\ 5); then it looks for methods that mask your password
@@ -996,6 +1027,20 @@ GSSAPI or K4.  Choosing KPOP protocol automatically selects Kerberos
 authentication.  This option does not work with ETRN.  GSSAPI service names are
 in line with RFC-2743 and IANA registrations, see
 .URL https://www.iana.org/assignments/gssapi-service-names/ "Generic Security Service Application Program Interface (GSSAPI)/Kerberos/Simple Authentication and Security Layer (SASL) Service Names" .
+.sp
+\fBxoauth2\fP expects the supplied password to be an oauth2 authentication
+token instead of a password.  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, so the
+"passwordfile" or "passwordfd" 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"
+is probably preferable if available, because it has roughly the same
+security risks, and is a whole lot simpler to get working.  "App Password"
+and xouath2 both need to protect secrets on the client machine (files) and
+over the network (SSL/TLS).  But "App Password" is
+sometimes completely disabled by business "G-suite" administrators.
 .SS Miscellaneous Options
 .TP
 .B \-f <pathname> | \-\-fetchmailrc <pathname>
@@ -1907,6 +1952,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}
@@ -2297,7 +2348,8 @@ Legal protocol identifiers for use with the 'protocol' keyword are:
 .PP
 Legal authentication types are 'any', 'password', 'kerberos',
 \&'kerberos_v4', 'kerberos_v5' and 'gssapi', 'cram\-md5', 'otp', 'msn'
-(only for POP3), 'ntlm', 'ssh', 'external' (only IMAP).
+(only for POP3), 'ntlm', 'ssh', 'external' (only IMAP), 'xoauth2' (only
+IMAP).
 The 'password' type specifies
 authentication by normal transmission of a password (the password may be
 plain text or subject to protocol-specific encryption as in CRAM-MD5);
diff --git a/imap.c b/imap.c
index 90c3f92c..77c44a8a 100644
--- a/imap.c
+++ b/imap.c
@@ -26,6 +26,10 @@
 #define IMAP4		0	/* IMAP4 rev 0, RFC1730 */
 #define IMAP4rev1	1	/* IMAP4 rev 1, RFC2060 */
 
+/* imap_plus_cont_context values */
+#define IPLUS_NONE	0
+#define IPLUS_XOAUTH2	1	/* xoauth2 (for more error info) */
+
 /* global variables: please reinitialize them explicitly for proper
  * working in daemon mode */
 
@@ -38,6 +42,8 @@ static int imap_version = IMAP4;
 static flag do_idle = FALSE, has_idle = FALSE;
 static int expunge_period = 1;
 
+static int plus_cont_context = IPLUS_NONE;
+
 /* mailbox variables initialized in imap_getrange() */
 static int count = 0, oldcount = 0, recentcount = 0, unseen = 0, deletions = 0;
 static unsigned int startcount = 1;
@@ -202,6 +208,21 @@ static int imap_response(int sock, char *argbuf, struct RecvSplit *rs)
 	if (ok != PS_SUCCESS)
 	    return(ok);
 
+	if (buf[0] == '+' && buf[1] == ' ') {
+	    if (plus_cont_context == IPLUS_XOAUTH2) {
+		/* future: Consider decoding the base64-encoded JSON
+		 * error response info and logging it.  But for now,
+		 * ignore continuation data, send the expected blank
+		 * line, and assume that the next response will be
+		 * a tagged "NO" as documented.
+		 */
+		SockWrite(sock, "\r\n", 2);
+		if (outlevel >= O_MONITOR)
+		    report(stdout, "IMAP> \n");
+		continue;
+	    }
+	}
+
 	/* all tokens in responses are caseblind */
 	for (cp = buf; *cp; cp++)
 	    if (islower((unsigned char)*cp))
@@ -316,6 +337,56 @@ static int do_imap_ntlm(int sock, struct query *ctl)
 }
 #endif /* NTLM */
 
+static int do_imap_xoauth2(int sock, struct query *ctl)
+{
+    /* Implements https://developers.google.com/gmail/imap/xoauth2-protocol
+     *
+     * This assumes something external manages obtaining an up-to-date
+     * authentication/bearer token and arranging for it to be in
+     * ctl->password.  This may involve renewing it ahead of time if
+     * necessary using a renewal token that fetchmail knows nothing about.
+     * See:
+     * https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough
+     */
+    char *oauth2str;
+    int oauth2len;
+    int saved_suppress_tags = suppress_tags;
+
+    char *oauth2b64;
+
+    int ok;
+
+    oauth2len = strlen(ctl->remotename) + strlen(ctl->password) + 32;
+    oauth2str = (char *)xmalloc(oauth2len);
+    snprintf(oauth2str, oauth2len,
+             "user=%s\1auth=Bearer %s\1\1",
+             ctl->remotename,
+             ctl->password);
+
+    oauth2b64 = (char *)xmalloc(2*strlen(oauth2str)+8);
+    to64frombits(oauth2b64, oauth2str, strlen(oauth2str),
+                 2*strlen(oauth2str)+8);
+
+    memset(oauth2str, 0x55, strlen(oauth2str));
+    free(oauth2str);
+
+    /* Protect the access token like a password in logs, despite the
+     * usually-short expiration time and base64 encoding:
+     */
+    strlcpy(shroud, oauth2b64, sizeof(shroud));
+
+    plus_cont_context = IPLUS_XOAUTH2;
+    ok = gen_transact(sock, "AUTHENTICATE XOAUTH2 %s", oauth2b64);
+    plus_cont_context = IPLUS_NONE;
+
+    memset(shroud, 0x55, sizeof(shroud));
+    shroud[0] = '\0';
+    memset(oauth2b64, 0x55, strlen(oauth2b64));
+    free(oauth2b64);
+
+    return ok;
+}
+
 static void imap_canonicalize(char *result, char *raw, size_t maxlen)
 /* encode an IMAP password as per RFC1730's quoting conventions */
 {
@@ -510,6 +581,18 @@ static int imap_getauth(int sock, struct query *ctl, char *greeting)
      */
     ok = PS_AUTHFAIL;
 
+    if (ctl->server.authenticate == A_XOAUTH2)
+    {
+	/* Fetchmail's xoauth2 support expects the "password"
+	 * to actually be an xoauth2 authentication token, so only
+	 * use xoauth2 if specifically enabled.
+	 * (Generating a token using the complex https-based oauth2
+	 * protocol is left as an exercise for the user.)
+	 */
+	ok = do_imap_xoauth2(sock, ctl);
+	return ok;
+    }
+
     /* Yahoo hack - we'll just try ID if it was offered by the server,
      * and IGNORE errors. */
     {
diff --git a/options.c b/options.c
index 90eeaaff..4a852f9c 100644
--- a/options.c
+++ b/options.c
@@ -31,6 +31,8 @@ enum {
     LA_POSTMASTER,
     LA_NOBOUNCE,
     LA_AUTH,
+    LA_PASSWORDFILE,
+    LA_PASSWORDFD,
     LA_FETCHDOMAINS,
     LA_BSMTP,
     LA_LMTP,
@@ -94,6 +96,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' },
@@ -261,6 +265,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,
@@ -421,11 +426,24 @@ int parsecmdline (int argc /** argument count */,
 		ctl->server.authenticate = A_ANY;
 	    else if (strcmp(optarg, "msn") == 0)
 		ctl->server.authenticate = A_MSN;
+	    else if (strcmp(optarg, "xoauth2") == 0)
+		ctl->server.authenticate = A_XOAUTH2;
 	    else {
 		fprintf(stderr,GT_("Invalid authentication `%s' specified.\n"), optarg);
 		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 d1786881..e03b90db 100644
--- a/rcfile_l.l
+++ b/rcfile_l.l
@@ -106,6 +106,8 @@ cram(-md5)?	{ SETSTATE(0); yylval.proto = A_CRAM_MD5; return AUTHTYPE;}
 msn		{ SETSTATE(0); yylval.proto = A_MSN; return AUTHTYPE;}
 ntlm		{ SETSTATE(0); yylval.proto = A_NTLM; return AUTHTYPE;}
 <AUTH>password	{ SETSTATE(0); yylval.proto = A_PASSWORD; return AUTHTYPE;}
+xoauth2 	{ SETSTATE(0); yylval.proto = A_XOAUTH2; return AUTHTYPE;}
+oauthbearer 	{ SETSTATE(0); yylval.proto = A_XOAUTH2; return AUTHTYPE;}
 timeout		{ return TIMEOUT;}
 idletimeout	{ return IDLETIMEOUT;}
 envelope	{ return ENVELOPE; }
@@ -118,6 +120,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; }
diff --git a/rcfile_y.y b/rcfile_y.y
index 705bbbf1..2aa01a38 100644
--- a/rcfile_y.y
+++ b/rcfile_y.y
@@ -63,6 +63,7 @@ extern char * yytext;
 %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
@@ -307,6 +308,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
@@ -507,6 +510,7 @@ static void reset_server(const char *name, int skip)
     trailer = FALSE;
     memset(&current,'\0',sizeof(current));
     current.smtp_socket = -1;
+    current.passwordfd = -1;
     current.server.pollname = xstrdup(name);
     current.server.skip = skip;
     current.server.principal = (char *)NULL;
@@ -528,6 +532,7 @@ static void user_reset(void)
 
     memset(&current, '\0', sizeof(current));
     current.smtp_socket = -1;
+    current.passwordfd = -1;
 
     current.server = save;
 }
@@ -548,6 +553,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 */
