From 080eabe2e8a184ff40b7380aaaa9418714acace9 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 11 Jan 2015 12:35:44 -0500
Subject: [PATCH] Fix libpq's behavior when /etc/passwd isn't readable.

Some users run their applications in chroot environments that lack an
/etc/passwd file.  This means that the current UID's user name and home
directory are not obtainable.  libpq used to be all right with that,
so long as the database role name to use was specified explicitly.
But commit a4c8f14364c27508233f8a31ac4b10a4c90235a9 broke such cases by
causing any failure of pg_fe_getauthname() to be treated as a hard error.
In any case it did little to advance its nominal goal of causing errors
in pg_fe_getauthname() to be reported better.  So revert that and instead
put some real error-reporting code in place.  This requires changes to the
APIs of pg_fe_getauthname() and pqGetpwuid(), since the latter had
departed from the POSIX-specified API of getpwuid_r() in a way that made
it impossible to distinguish actual lookup errors from "no such user".

To allow such failures to be reported, while not failing if the caller
supplies a role name, add a second call of pg_fe_getauthname() in
connectOptions2().  This is a tad ugly, and could perhaps be avoided with
some refactoring of PQsetdbLogin(), but I'll leave that idea for later.
(Note that the complained-of misbehavior only occurs in PQsetdbLogin,
not when using the PQconnect functions, because in the latter we will
never bother to call pg_fe_getauthname() if the user gives a role name.)

In passing also clean up the Windows-side usage of GetUserName(): the
recommended buffer size is 257 bytes, the passed buffer length should
be the buffer size not buffer size less 1, and any error is reported
by GetLastError() not errno.

Per report from Christoph Berg.  Back-patch to 9.4 where the chroot
failure case was introduced.  The generally poor reporting of errors
here is of very long standing, of course, but given the lack of field
complaints about it we won't risk changing these APIs further back
(even though they're theoretically internal to libpq).
---
 src/common/username.c             | 12 ++++---
 src/include/port.h                |  2 +-
 src/interfaces/libpq/fe-auth.c    | 50 ++++++++++++++++++++--------
 src/interfaces/libpq/fe-auth.h    |  2 +-
 src/interfaces/libpq/fe-connect.c | 54 +++++++++++++++++++++----------
 src/port/path.c                   |  3 +-
 src/port/thread.c                 | 17 +++++++---
 7 files changed, 97 insertions(+), 43 deletions(-)

diff --git a/src/common/username.c b/src/common/username.c
index ee5ef1c0727..686a6a43c51 100644
--- a/src/common/username.c
+++ b/src/common/username.c
@@ -26,8 +26,8 @@
 #include "common/username.h"
 
 /*
- * Returns the current user name in a static buffer, or NULL on error and
- * sets errstr
+ * Returns the current user name in a static buffer
+ * On error, returns NULL and sets *errstr to point to a palloc'd message
  */
 const char *
 get_user_name(char **errstr)
@@ -50,15 +50,17 @@ get_user_name(char **errstr)
 
 	return pw->pw_name;
 #else
-	/* UNLEN = 256, 'static' variable remains after function exit */
+	/* Microsoft recommends buffer size of UNLEN+1, where UNLEN = 256 */
+	/* "static" variable remains after function exit */
 	static char username[256 + 1];
-	DWORD		len = sizeof(username) - 1;
+	DWORD		len = sizeof(username);
 
 	*errstr = NULL;
 
 	if (!GetUserName(username, &len))
 	{
-		*errstr = psprintf(_("user name lookup failure: %s"), strerror(errno));
+		*errstr = psprintf(_("user name lookup failure: error code %lu"),
+						   GetLastError());
 		return NULL;
 	}
 
diff --git a/src/include/port.h b/src/include/port.h
index 1d53e4ec0b8..26d7fcd6721 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -433,7 +433,7 @@ extern void srandom(unsigned int seed);
 /* thread.h */
 extern char *pqStrerror(int errnum, char *strerrbuf, size_t buflen);
 
-#if !defined(WIN32) || defined(__CYGWIN__)
+#ifndef WIN32
 extern int pqGetpwuid(uid_t uid, struct passwd * resultbuf, char *buffer,
 		   size_t buflen, struct passwd ** result);
 #endif
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 179793e2a0e..8927df4f064 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -714,22 +714,26 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 /*
  * pg_fe_getauthname
  *
- * Returns a pointer to dynamic space containing whatever name the user
- * has authenticated to the system.  If there is an error, return NULL.
+ * Returns a pointer to malloc'd space containing whatever name the user
+ * has authenticated to the system.  If there is an error, return NULL,
+ * and put a suitable error message in *errorMessage if that's not NULL.
  */
 char *
-pg_fe_getauthname(void)
+pg_fe_getauthname(PQExpBuffer errorMessage)
 {
+	char	   *result = NULL;
 	const char *name = NULL;
-	char	   *authn;
 
 #ifdef WIN32
-	char		username[128];
-	DWORD		namesize = sizeof(username) - 1;
+	/* Microsoft recommends buffer size of UNLEN+1, where UNLEN = 256 */
+	char		username[256 + 1];
+	DWORD		namesize = sizeof(username);
 #else
+	uid_t		user_id = geteuid();
 	char		pwdbuf[BUFSIZ];
 	struct passwd pwdstr;
 	struct passwd *pw = NULL;
+	int			pwerr;
 #endif
 
 	/*
@@ -741,24 +745,42 @@ pg_fe_getauthname(void)
 	 */
 	pglock_thread();
 
-	/*
-	 * We document PQconndefaults() to return NULL for a memory allocation
-	 * failure.  We don't have an API to return a user name lookup failure, so
-	 * we just assume it always succeeds.
-	 */
 #ifdef WIN32
 	if (GetUserName(username, &namesize))
 		name = username;
+	else if (errorMessage)
+		printfPQExpBuffer(errorMessage,
+				 libpq_gettext("user name lookup failure: error code %lu\n"),
+						  GetLastError());
 #else
-	if (pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pw) == 0)
+	pwerr = pqGetpwuid(user_id, &pwdstr, pwdbuf, sizeof(pwdbuf), &pw);
+	if (pw != NULL)
 		name = pw->pw_name;
+	else if (errorMessage)
+	{
+		if (pwerr != 0)
+			printfPQExpBuffer(errorMessage,
+				   libpq_gettext("could not look up local user ID %d: %s\n"),
+							  (int) user_id,
+							  pqStrerror(pwerr, pwdbuf, sizeof(pwdbuf)));
+		else
+			printfPQExpBuffer(errorMessage,
+					 libpq_gettext("local user with ID %d does not exist\n"),
+							  (int) user_id);
+	}
 #endif
 
-	authn = name ? strdup(name) : NULL;
+	if (name)
+	{
+		result = strdup(name);
+		if (result == NULL && errorMessage)
+			printfPQExpBuffer(errorMessage,
+							  libpq_gettext("out of memory\n"));
+	}
 
 	pgunlock_thread();
 
-	return authn;
+	return result;
 }
 
 
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 59b6c16a6d4..8d35767f7a0 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -19,6 +19,6 @@
 
 
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
-extern char *pg_fe_getauthname(void);
+extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index b2f556c1c30..25961b1f10d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -764,11 +764,27 @@ connectOptions1(PGconn *conn, const char *conninfo)
 static bool
 connectOptions2(PGconn *conn)
 {
+	/*
+	 * If user name was not given, fetch it.  (Most likely, the fetch will
+	 * fail, since the only way we get here is if pg_fe_getauthname() failed
+	 * during conninfo_add_defaults().  But now we want an error message.)
+	 */
+	if (conn->pguser == NULL || conn->pguser[0] == '\0')
+	{
+		if (conn->pguser)
+			free(conn->pguser);
+		conn->pguser = pg_fe_getauthname(&conn->errorMessage);
+		if (!conn->pguser)
+		{
+			conn->status = CONNECTION_BAD;
+			return false;
+		}
+	}
+
 	/*
 	 * If database name was not given, default it to equal user name
 	 */
-	if ((conn->dbName == NULL || conn->dbName[0] == '\0')
-		&& conn->pguser != NULL)
+	if (conn->dbName == NULL || conn->dbName[0] == '\0')
 	{
 		if (conn->dbName)
 			free(conn->dbName);
@@ -1967,6 +1983,7 @@ keep_going:						/* We will come back to here until there is
 					char		pwdbuf[BUFSIZ];
 					struct passwd pass_buf;
 					struct passwd *pass;
+					int			passerr;
 					uid_t		uid;
 					gid_t		gid;
 
@@ -1987,13 +2004,18 @@ keep_going:						/* We will come back to here until there is
 						goto error_return;
 					}
 
-					pqGetpwuid(uid, &pass_buf, pwdbuf, sizeof(pwdbuf), &pass);
-
+					passerr = pqGetpwuid(uid, &pass_buf, pwdbuf, sizeof(pwdbuf), &pass);
 					if (pass == NULL)
 					{
-						appendPQExpBuffer(&conn->errorMessage,
-										  libpq_gettext("local user with ID %d does not exist\n"),
-										  (int) uid);
+						if (passerr != 0)
+							appendPQExpBuffer(&conn->errorMessage,
+											  libpq_gettext("could not look up local user ID %d: %s\n"),
+											  (int) uid,
+											  pqStrerror(passerr, sebuf, sizeof(sebuf)));
+						else
+							appendPQExpBuffer(&conn->errorMessage,
+											  libpq_gettext("local user with ID %d does not exist\n"),
+											  (int) uid);
 						goto error_return;
 					}
 
@@ -4605,18 +4627,15 @@ conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
 		}
 
 		/*
-		 * Special handling for "user" option
+		 * Special handling for "user" option.  Note that if pg_fe_getauthname
+		 * fails, we just leave the value as NULL; there's no need for this to
+		 * be an error condition if the caller provides a user name.  The only
+		 * reason we do this now at all is so that callers of PQconndefaults
+		 * will see a correct default (barring error, of course).
 		 */
 		if (strcmp(option->keyword, "user") == 0)
 		{
-			option->val = pg_fe_getauthname();
-			if (!option->val)
-			{
-				if (errorMessage)
-					printfPQExpBuffer(errorMessage,
-									  libpq_gettext("out of memory\n"));
-				return false;
-			}
+			option->val = pg_fe_getauthname(NULL);
 			continue;
 		}
 	}
@@ -5843,7 +5862,8 @@ pqGetHomeDirectory(char *buf, int bufsize)
 	struct passwd pwdstr;
 	struct passwd *pwd = NULL;
 
-	if (pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd) != 0)
+	(void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
+	if (pwd == NULL)
 		return false;
 	strlcpy(buf, pwd->pw_dir, bufsize);
 	return true;
diff --git a/src/port/path.c b/src/port/path.c
index e8faac3a260..d0f72df29c2 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -777,7 +777,8 @@ get_home_path(char *ret_path)
 	struct passwd pwdstr;
 	struct passwd *pwd = NULL;
 
-	if (pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd) != 0)
+	(void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
+	if (pwd == NULL)
 		return false;
 	strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
 	return true;
diff --git a/src/port/thread.c b/src/port/thread.c
index 1568803d626..aab74516ac9 100644
--- a/src/port/thread.c
+++ b/src/port/thread.c
@@ -83,6 +83,12 @@ pqStrerror(int errnum, char *strerrbuf, size_t buflen)
 /*
  * Wrapper around getpwuid() or getpwuid_r() to mimic POSIX getpwuid_r()
  * behaviour, if it is not available or required.
+ *
+ * Per POSIX, the possible cases are:
+ * success: returns zero, *result is non-NULL
+ * uid not found: returns zero, *result is NULL
+ * error during lookup: returns an errno code, *result is NULL
+ * (caller should *not* assume that the errno variable is set)
  */
 #ifndef WIN32
 int
@@ -93,22 +99,25 @@ pqGetpwuid(uid_t uid, struct passwd * resultbuf, char *buffer,
 
 #ifdef GETPWUID_R_5ARG
 	/* POSIX version */
-	getpwuid_r(uid, resultbuf, buffer, buflen, result);
+	return getpwuid_r(uid, resultbuf, buffer, buflen, result);
 #else
 
 	/*
 	 * Early POSIX draft of getpwuid_r() returns 'struct passwd *'.
 	 * getpwuid_r(uid, resultbuf, buffer, buflen)
 	 */
+	errno = 0;
 	*result = getpwuid_r(uid, resultbuf, buffer, buflen);
+	/* paranoia: ensure we return zero on success */
+	return (*result == NULL) ? errno : 0;
 #endif
 #else
-
 	/* no getpwuid_r() available, just use getpwuid() */
+	errno = 0;
 	*result = getpwuid(uid);
+	/* paranoia: ensure we return zero on success */
+	return (*result == NULL) ? errno : 0;
 #endif
-
-	return (*result == NULL) ? -1 : 0;
 }
 #endif
 
-- 
GitLab