Thread: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

[PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Anders Kaseorg
Date:
According to getpwnam(3):

  An application that wants to determine its user's home directory
  should inspect the value of HOME (rather than the value
  getpwuid(getuid())->pw_dir) since this allows the user to modify
  their notion of "the home directory" during a login session.

This is important for systems where many users share the same UID, and for test systems that change HOME to avoid
interferencewith the user’s real home directory.  It matches what most applications do, as well as what glibc does for
glob("~",GLOB_TILDE, …) and wordexp("~", …). 

There was some previous discussion of this in 2016, where although there were some questions about the use case, there
seemedto be general support for the concept: 

https://www.postgresql.org/message-id/flat/CAEH6cQqbdbXoUHJBbX9ixwfjFFsUC-a8hFntKcci%3DdiWgBb3fQ%40mail.gmail.com

Regardless of whether one thinks modifying HOME is a good idea, if we happen to find ourselves in that case, we should
respectthe modified HOME, so that when the user creates (say) a ~/.pgpass file, we’ll look for it at the same place the
user’seditor created it.  getenv() also skips the overhead of reading /etc/passwd as an added bonus. 

The way I ran into this issue myself was in a test suite that runs on GitHub Actions, which automatically sets
HOME=/github/home.

Anders

Attachment

Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Alvaro Herrera
Date:
On 2021-Oct-14, Anders Kaseorg wrote:

> This is important for systems where many users share the same UID, and
> for test systems that change HOME to avoid interference with the
> user’s real home directory.  It matches what most applications do, as
> well as what glibc does for glob("~", GLOB_TILDE, …) and wordexp("~",
> …).
> 
> There was some previous discussion of this in 2016, where although
> there were some questions about the use case, there seemed to be
> general support for the concept:
> 
> https://www.postgresql.org/message-id/flat/CAEH6cQqbdbXoUHJBbX9ixwfjFFsUC-a8hFntKcci%3DdiWgBb3fQ%40mail.gmail.com

I think modifying $HOME is a strange way to customize things, but given
how widespread it is [claimed to be] today, it seems reasonable to do
things that way.

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Michael Paquier
Date:
On Mon, Oct 18, 2021 at 07:23:50PM -0300, Alvaro Herrera wrote:
> I think modifying $HOME is a strange way to customize things, but given
> how widespread it is [claimed to be] today, it seems reasonable to do
> things that way.

I am not sure about this claim, but it seems to me that we could get
rid of the duplications in src/port/path.c, libpq/fe-connect.c and
psql/command.c (this one is different for WIN32 but consistency would
be a good thing) as the proposed patch outlines.  So I would suggest
to begin with that rather than changing three places to do the same
thing.
--
Michael

Attachment

Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Kyotaro Horiguchi
Date:
At Mon, 18 Oct 2021 19:23:50 -0300, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote in 
> On 2021-Oct-14, Anders Kaseorg wrote:
> 
> > This is important for systems where many users share the same UID, and
> > for test systems that change HOME to avoid interference with the
> > user’s real home directory.  It matches what most applications do, as
> > well as what glibc does for glob("~", GLOB_TILDE, …) and wordexp("~",
> > …).
> > 
> > There was some previous discussion of this in 2016, where although
> > there were some questions about the use case, there seemed to be
> > general support for the concept:
> > 
> > https://www.postgresql.org/message-id/flat/CAEH6cQqbdbXoUHJBbX9ixwfjFFsUC-a8hFntKcci%3DdiWgBb3fQ%40mail.gmail.com
> 
> I think modifying $HOME is a strange way to customize things, but given
> how widespread it is [claimed to be] today, it seems reasonable to do
> things that way.

I tend to agree to this, but seeing ssh ignoring $HOME, I'm not sure
it's safe that we follow the variable at least when accessing
confidentiality(?) files.  Since I don't understand the exact
reasoning for the ssh's behavior so it's just my humbole opinion.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Anders Kaseorg
Date:
On 10/19/21 01:34, Kyotaro Horiguchi wrote:
> I tend to agree to this, but seeing ssh ignoring $HOME, I'm not sure
> it's safe that we follow the variable at least when accessing
> confidentiality(?) files.  Since I don't understand the exact
> reasoning for the ssh's behavior so it's just my humbole opinion.

According to https://bugzilla.mindrot.org/show_bug.cgi?id=3048#c1, it 
used to be supported to install the ssh binary as setuid.  A 
setuid/setgid binary needs to treat all environment variables with 
suspicion: if it can be convinced to write a file to $HOME with root 
privileges, then a user who modifies $HOME before invoking the binary 
could cause it to write to a file that the user normally couldn’t.

There’s no such concern for a binary that isn’t setuid/setgid.  Anyone 
with the ability to modify $HOME can be assumed to already have full 
control of the user account.

Anders



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Kyotaro Horiguchi
Date:
At Tue, 19 Oct 2021 02:44:03 -0700, Anders Kaseorg <andersk@mit.edu> wrote in 
> On 10/19/21 01:34, Kyotaro Horiguchi wrote:
> > I tend to agree to this, but seeing ssh ignoring $HOME, I'm not sure
> > it's safe that we follow the variable at least when accessing
> > confidentiality(?) files.  Since I don't understand the exact
> > reasoning for the ssh's behavior so it's just my humbole opinion.
> 
> According to https://bugzilla.mindrot.org/show_bug.cgi?id=3048#c1, it
> used to be supported to install the ssh binary as setuid.  A
> setuid/setgid binary needs to treat all environment variables with
> suspicion: if it can be convinced to write a file to $HOME with root
> privileges, then a user who modifies $HOME before invoking the binary
> could cause it to write to a file that the user normally couldn’t.
> 
> There’s no such concern for a binary that isn’t setuid/setgid.  Anyone
> with the ability to modify $HOME can be assumed to already have full
> control of the user account.

Thansk for the link.  Still I'm not sure it's the fact but it sounds
reasonable enough.  If that's the case, I vote +1 for psql or other
commands honoring $HOME.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center

Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Daniel Gustafsson
Date:
> On 20 Oct 2021, at 07:40, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:
>
> At Tue, 19 Oct 2021 02:44:03 -0700, Anders Kaseorg <andersk@mit.edu> wrote in
>> On 10/19/21 01:34, Kyotaro Horiguchi wrote:
>>> I tend to agree to this, but seeing ssh ignoring $HOME, I'm not sure
>>> it's safe that we follow the variable at least when accessing
>>> confidentiality(?) files.  Since I don't understand the exact
>>> reasoning for the ssh's behavior so it's just my humbole opinion.
>>
>> According to https://bugzilla.mindrot.org/show_bug.cgi?id=3048#c1, it
>> used to be supported to install the ssh binary as setuid.  A
>> setuid/setgid binary needs to treat all environment variables with
>> suspicion: if it can be convinced to write a file to $HOME with root
>> privileges, then a user who modifies $HOME before invoking the binary
>> could cause it to write to a file that the user normally couldn’t.
>>
>> There’s no such concern for a binary that isn’t setuid/setgid.  Anyone
>> with the ability to modify $HOME can be assumed to already have full
>> control of the user account.
>
> Thansk for the link.  Still I'm not sure it's the fact but it sounds
> reasonable enough.  If that's the case, I vote +1 for psql or other
> commands honoring $HOME.

Is the proposed change portable across all linux/unix systems we support?
Reading aobut indicates that it's likely to be, but neither NetBSD nor FreeBSD
have the upthread referenced wording in their manpages.

--
Daniel Gustafsson        https://vmware.com/




Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Anders Kaseorg
Date:
On 10/20/21 04:55, Daniel Gustafsson wrote:
> Is the proposed change portable across all linux/unix systems we support?
> Reading aobut indicates that it's likely to be, but neither NetBSD nor FreeBSD
> have the upthread referenced wording in their manpages.

Since the proposed change falls back to the old behavior if HOME is 
unset or empty, I assume this is a question about convention and not 
literally about whether it will work on these systems. I don’t find it 
surprising that this convention isn’t explicitly called out in every 
system’s manpage for the wrong function, but it still applies to these 
systems.

POSIX specifies that the shell uses the HOME environment variable for 
‘cd’ with no arguments and for the expansion of ~. This implies by 
reference that this behavior is required of wordexp() as well.

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html
https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_01
https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html

libc’s glob() and wordexp() respect HOME in glibc, musl, NetBSD, and 
FreeBSD.

https://sourceware.org/git/?p=glibc.git;a=blob;f=posix/glob.c;hb=glibc-2.34#l622
https://sourceware.org/git/?p=glibc.git;a=blob;f=posix/wordexp.c;hb=glibc-2.34#l293

https://git.musl-libc.org/cgit/musl/tree/src/regex/glob.c?h=v1.2.2#n203
https://git.musl-libc.org/cgit/musl/tree/src/misc/wordexp.c?h=v1.2.2#n111

https://github.com/NetBSD/src/blob/netbsd-9/lib/libc/gen/glob.c#L424
https://github.com/NetBSD/src/blob/netbsd-9/lib/libc/gen/wordexp.c#L129-L150
https://github.com/NetBSD/src/blob/netbsd-9/bin/sh/expand.c#L434-L441

https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/gen/glob.c#L457
https://github.com/freebsd/freebsd-src/blob/release/13.0.0/lib/libc/gen/wordexp.c#L171-L190
https://github.com/freebsd/freebsd-src/blob/release/13.0.0/bin/sh/expand.c#L396

(Today I learned that musl and BSD libc literally spawn a shell process 
to handle wordexp(). Wow.)

Anders



Re: Is my home $HOME or is it getpwent()->pw_dir ?

From
Chapman Flack
Date:
On 12/20/21 09:15, Peter Eisentraut wrote:
> On 18.12.21 21:57, Chapman Flack wrote:
>> When I start psql, strace shows $HOME being honored when looking
>> for .terminfo and .inputrc, and getpwent()->pw_dir being used
>> to look for .pgpass, .psqlrc, and .psql_history, which of course
>> aren't there.
>>
>> I'm sure the .terminfo and .inputrc lookups are being done by library code.
>> In my experience, it seems traditionally unixy to let $HOME take precedence.
> 
> See this patch: https://commitfest.postgresql.org/36/3362/

Wow, just a couple months ago. Yes, I should have tagged on to that
rather than starting a new thread.

I was proposing an option or variable on the assumption that just changing
the default behavior would be off the table. But I am +1 on just changing
the default behavior, if that's not off the table.

Regards,
-Chap

*seeing that RFC 5322 3.6.4 permits more than one msg-id for in-reply-to,
crosses fingers to see what PGLister will make of it*



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Tom Lane
Date:
Anders Kaseorg <andersk@mit.edu> writes:
> On 10/20/21 04:55, Daniel Gustafsson wrote:
>> Is the proposed change portable across all linux/unix systems we support?
>> Reading aobut indicates that it's likely to be, but neither NetBSD nor FreeBSD
>> have the upthread referenced wording in their manpages.

> Since the proposed change falls back to the old behavior if HOME is
> unset or empty, I assume this is a question about convention and not
> literally about whether it will work on these systems. I don’t find it
> surprising that this convention isn’t explicitly called out in every
> system’s manpage for the wrong function, but it still applies to these
> systems.

Given the POSIX requirements, it's basically impossible to believe
that there are interesting cases where $HOME isn't set.  Thus, it
seems to me that keeping the getpwuid calls will just mean carrying
untestable dead code, so we should simplify matters by ripping
those out and *only* consulting $HOME.

The v1 patch also neglects the matter of documentation.  I think
the simplest and most transparent thing to do is just to explicitly
mention $HOME everyplace we talk about files that are sought there,
in place of our current convention to write "~".  (I'm too lazy
to go digging in the git history, but I have a feeling that this is
undoing somebody's intentional change from a long time back.)

BTW, not directly impacted by this patch but adjacent to it,
I noted that on Windows psql's \cd defaults to changing to "/".
That seems a bit surprising, and we definitely fail to document it.
I settled for noting it in the documentation, but should we make
it do something else?

PFA v2 patch.

            regards, tom lane

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 14f35d37f6..faf36f051f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1214,7 +1214,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <para>
        Specifies the name of the file used to store passwords
        (see <xref linkend="libpq-pgpass"/>).
-       Defaults to <filename>~/.pgpass</filename>, or
+       Defaults to <filename>$HOME/.pgpass</filename>, or
        <filename>%APPDATA%\postgresql\pgpass.conf</filename> on Microsoft Windows.
        (No error is reported if this file does not exist.)
       </para>
@@ -1670,7 +1670,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
        <para>
         This parameter specifies the file name of the client SSL
         certificate, replacing the default
-        <filename>~/.postgresql/postgresql.crt</filename>.
+        <filename>$HOME/.postgresql/postgresql.crt</filename>.
         This parameter is ignored if an SSL connection is not made.
        </para>
       </listitem>
@@ -1683,7 +1683,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         This parameter specifies the location for the secret key used for
         the client certificate. It can either specify a file name that will
         be used instead of the default
-        <filename>~/.postgresql/postgresql.key</filename>, or it can specify a key
+        <filename>$HOME/.postgresql/postgresql.key</filename>, or it can specify a key
         obtained from an external <quote>engine</quote> (engines are
         <productname>OpenSSL</productname> loadable modules).  An external engine
         specification should consist of a colon-separated engine name and
@@ -1733,7 +1733,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         certificate authority (<acronym>CA</acronym>) certificate(s).
         If the file exists, the server's certificate will be verified
         to be signed by one of these authorities.  The default is
-        <filename>~/.postgresql/root.crt</filename>.
+        <filename>$HOME/.postgresql/root.crt</filename>.
        </para>
       </listitem>
      </varlistentry>
@@ -1749,7 +1749,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         <xref linkend='libpq-connect-sslcrl'/> nor
         <xref linkend='libpq-connect-sslcrldir'/> is set, this setting is
         taken as
-        <filename>~/.postgresql/root.crl</filename>.
+        <filename>$HOME/.postgresql/root.crl</filename>.
        </para>
       </listitem>
      </varlistentry>
@@ -7776,7 +7776,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
       </indexterm>
       <envar>PGSERVICEFILE</envar> specifies the name of the per-user
       connection service file.  If not set, it defaults
-      to <filename>~/.pg_service.conf</filename>
+      to <filename>$HOME/.pg_service.conf</filename>
       (see <xref linkend="libpq-pgservice"/>).
      </para>
     </listitem>
@@ -8151,7 +8151,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
    system-wide file.  If the same service name exists in both the user
    and the system file, the user file takes precedence.
    By default, the per-user service file is located
-   at <filename>~/.pg_service.conf</filename>; this can be overridden by
+   at <filename>$HOME/.pg_service.conf</filename>; this can be overridden by
    setting the environment variable <envar>PGSERVICEFILE</envar>.
    The system-wide file is named <filename>pg_service.conf</filename>.
    By default it is sought in the <filename>etc</filename> directory
@@ -8354,8 +8354,8 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)

   <para>
    To allow server certificate verification, one or more root certificates
-   must be placed in the file <filename>~/.postgresql/root.crt</filename>
-   in the user's home directory.  (On Microsoft Windows the file is named
+   must be placed in the file <filename>$HOME/.postgresql/root.crt</filename>.
+   (On Microsoft Windows the file is named
    <filename>%APPDATA%\postgresql\root.crt</filename>.)  Intermediate
    certificates should also be added to the file if they are needed to link
    the certificate chain sent by the server to the root certificates
@@ -8364,7 +8364,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)

   <para>
    Certificate Revocation List (CRL) entries are also checked
-   if the file <filename>~/.postgresql/root.crl</filename> exists
+   if the file <filename>$HOME/.postgresql/root.crl</filename> exists
    (<filename>%APPDATA%\postgresql\root.crl</filename> on Microsoft
    Windows).
   </para>
@@ -8398,10 +8398,10 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
    If the server attempts to verify the identity of the
    client by requesting the client's leaf certificate,
    <application>libpq</application> will send the certificates stored in
-   file <filename>~/.postgresql/postgresql.crt</filename> in the user's home
-   directory.  The certificates must chain to the root certificate trusted
+   the file <filename>$HOME/.postgresql/postgresql.crt</filename>.
+   The certificates must chain to the root certificate trusted
    by the server.  A matching
-   private key file <filename>~/.postgresql/postgresql.key</filename> must also
+   private key file <filename>$HOME/.postgresql/postgresql.key</filename> must also
    be present. The private
    key file must not allow any access to world or group; achieve this by the
    command <command>chmod 0600 ~/.postgresql/postgresql.key</command>.
@@ -8643,27 +8643,27 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
     <tbody>

      <row>
-      <entry><filename>~/.postgresql/postgresql.crt</filename></entry>
+      <entry><filename>$HOME/.postgresql/postgresql.crt</filename></entry>
       <entry>client certificate</entry>
       <entry>sent to server</entry>
      </row>

      <row>
-      <entry><filename>~/.postgresql/postgresql.key</filename></entry>
+      <entry><filename>$HOME/.postgresql/postgresql.key</filename></entry>
       <entry>client private key</entry>
       <entry>proves client certificate sent by owner; does not indicate
       certificate owner is trustworthy</entry>
      </row>

      <row>
-      <entry><filename>~/.postgresql/root.crt</filename></entry>
+      <entry><filename>$HOME/.postgresql/root.crt</filename></entry>
       <entry>trusted certificate authorities</entry>
       <entry>checks that server certificate is signed by a trusted certificate
       authority</entry>
      </row>

      <row>
-      <entry><filename>~/.postgresql/root.crl</filename></entry>
+      <entry><filename>$HOME/.postgresql/root.crl</filename></entry>
       <entry>certificates revoked by certificate authorities</entry>
       <entry>server certificate must not be on this list</entry>
      </row>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1ab200a4ad..6b6f5b1864 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -553,7 +553,7 @@ EOF
       <para>
       Do not read the start-up file (neither the system-wide
       <filename>psqlrc</filename> file nor the user's
-      <filename>~/.psqlrc</filename> file).
+      <filename>$HOME/.psqlrc</filename> file).
       </para>
       </listitem>
     </varlistentry>
@@ -677,7 +677,7 @@ EOF
     <envar>PGPORT</envar> and/or <envar>PGUSER</envar> to appropriate
     values. (For additional environment variables, see <xref
     linkend="libpq-envars"/>.) It is also convenient to have a
-    <filename>~/.pgpass</filename> file to avoid regularly having to type in
+    <filename>$HOME/.pgpass</filename> file to avoid regularly having to type in
     passwords. See <xref linkend="libpq-pgpass"/> for more information.
     </para>

@@ -984,7 +984,8 @@ testdb=>
         <para>
          Changes the current working directory to
          <replaceable>directory</replaceable>. Without argument, changes
-         to the current user's home directory.
+         to the current user's home directory (<literal>$HOME</literal>), or
+         the root directory on Windows.
         </para>

         <tip>
@@ -3791,7 +3792,7 @@ bar
          behavior, but autocommit-off is closer to the SQL spec.  If you
          prefer autocommit-off, you might wish to set it in the system-wide
          <filename>psqlrc</filename> file or your
-         <filename>~/.psqlrc</filename> file.
+         <filename>$HOME/.psqlrc</filename> file.
         </para>
         </note>
         </listitem>
@@ -3960,13 +3961,13 @@ bar
         The file name that will be used to store the history list.  If unset,
         the file name is taken from the <envar>PSQL_HISTORY</envar>
         environment variable.  If that is not set either, the default
-        is <filename>~/.psql_history</filename>,
+        is <filename>$HOME/.psql_history</filename>,
         or <filename>%APPDATA%\postgresql\psql_history</filename> on Windows.
         For example, putting:
 <programlisting>
 \set HISTFILE ~/.psql_history-:DBNAME
 </programlisting>
-        in <filename>~/.psqlrc</filename> will cause
+        in <filename>$HOME/.psqlrc</filename> will cause
         <application>psql</application> to maintain a separate history for
         each database.
         </para>
@@ -4774,13 +4775,13 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '

  <variablelist>
   <varlistentry>
-   <term><filename>psqlrc</filename> and <filename>~/.psqlrc</filename></term>
+   <term><filename>psqlrc</filename> and <filename>$HOME/.psqlrc</filename></term>
    <listitem>
     <para>
      Unless it is passed an <option>-X</option> option,
      <application>psql</application> attempts to read and execute commands
      from the system-wide startup file (<filename>psqlrc</filename>) and then
-     the user's personal startup file (<filename>~/.psqlrc</filename>), after
+     the user's personal startup file (<filename>$HOME/.psqlrc</filename>), after
      connecting to the database but before accepting normal commands.
      These files can be used to set up the client and/or the server to taste,
      typically with <command>\set</command> and <command>SET</command>
@@ -4809,8 +4810,8 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
      can be made <application>psql</application>-version-specific
      by appending a dash and the <productname>PostgreSQL</productname>
      major or minor release number to the file name,
-     for example <filename>~/.psqlrc-9.2</filename> or
-     <filename>~/.psqlrc-9.2.5</filename>.  The most specific
+     for example <filename>$HOME/.psqlrc-9.2</filename> or
+     <filename>$HOME/.psqlrc-9.2.5</filename>.  The most specific
      version-matching file will be read in preference to a
      non-version-specific file.
     </para>
@@ -4822,7 +4823,7 @@ PSQL_EDITOR_LINENUMBER_ARG='--line '
    <listitem>
     <para>
      The command-line history is stored in the file
-     <filename>~/.psql_history</filename>, or
+     <filename>$HOME/.psql_history</filename>, or
      <filename>%APPDATA%\postgresql\psql_history</filename> on Windows.
     </para>
     <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 053332e7af..e2ea0d11aa 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -558,19 +558,13 @@ exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
         else
         {
 #ifndef WIN32
-            struct passwd *pw;
-            uid_t        user_id = geteuid();
-
-            errno = 0;            /* clear errno before call */
-            pw = getpwuid(user_id);
-            if (!pw)
+            /* On Unix-like systems, consult $HOME */
+            dir = getenv("HOME");
+            if (dir == NULL || dir[0] == '\0')
             {
-                pg_log_error("could not get home directory for user ID %ld: %s",
-                             (long) user_id,
-                             errno ? strerror(errno) : _("user does not exist"));
-                exit(EXIT_FAILURE);
+                pg_log_error("HOME environment variable is not set");
+                success = false;
             }
-            dir = pw->pw_dir;
 #else                            /* WIN32 */

             /*
@@ -581,7 +575,8 @@ exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
 #endif                            /* WIN32 */
         }

-        if (chdir(dir) == -1)
+        if (success &&
+            chdir(dir) < 0)
         {
             pg_log_error("\\%s: could not change directory to \"%s\": %m",
                          cmd, dir);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 72914116ee..f018bf6a9c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -7267,14 +7267,11 @@ bool
 pqGetHomeDirectory(char *buf, int bufsize)
 {
 #ifndef WIN32
-    char        pwdbuf[BUFSIZ];
-    struct passwd pwdstr;
-    struct passwd *pwd = NULL;
+    const char *home = getenv("HOME");

-    (void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
-    if (pwd == NULL)
+    if (home == NULL || home[0] == '\0')
         return false;
-    strlcpy(buf, pwd->pw_dir, bufsize);
+    strlcpy(buf, home, bufsize);
     return true;
 #else
     char        tmppath[MAX_PATH];
diff --git a/src/port/path.c b/src/port/path.c
index ee4227ec98..a5a2e9238c 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -807,14 +807,11 @@ bool
 get_home_path(char *ret_path)
 {
 #ifndef WIN32
-    char        pwdbuf[BUFSIZ];
-    struct passwd pwdstr;
-    struct passwd *pwd = NULL;
+    const char *home = getenv("HOME");

-    (void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
-    if (pwd == NULL)
+    if (home == NULL || home[0] == '\0')
         return false;
-    strlcpy(ret_path, pwd->pw_dir, MAXPGPATH);
+    strlcpy(ret_path, home, MAXPGPATH);
     return true;
 #else
     char       *tmppath;

Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Anders Kaseorg
Date:
On 1/9/22 10:59, Tom Lane wrote:
> Given the POSIX requirements, it's basically impossible to believe
> that there are interesting cases where $HOME isn't set.  Thus, it
> seems to me that keeping the getpwuid calls will just mean carrying
> untestable dead code, so we should simplify matters by ripping
> those out and *only* consulting $HOME.

While POSIX requires that the login program put you in a conforming 
environment, nothing stops the user from building a non-conforming 
environment, such as with ‘env -i’.  One could argue that such a user 
deserves whatever broken behavior they might get.  But to me it seems 
prudent to continue working there if it worked before.

> The v1 patch also neglects the matter of documentation.  I think
> the simplest and most transparent thing to do is just to explicitly
> mention $HOME everyplace we talk about files that are sought there,
> in place of our current convention to write "~".  (I'm too lazy
> to go digging in the git history, but I have a feeling that this is
> undoing somebody's intentional change from a long time back.)

The reason I didn’t change the documentation is that this is already 
what “~” is supposed to mean according to POSIX and common 
implementations.  See previous discussion:

https://www.postgresql.org/message-id/1634252654444.90107%40mit.edu
https://www.postgresql.org/message-id/d452fd57-8c34-0a94-79c1-4498eb4ffbdc%40mit.edu

I consider my patch a bug fix that implements the behavior one would 
already expect from the existing documentation.

Therefore, I still prefer my v1 patch on both counts.  I am willing to 
be overruled if you still disagree, but I wanted to explain my reasoning.

Anders



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Tom Lane
Date:
Anders Kaseorg <andersk@mit.edu> writes:
> On 1/9/22 10:59, Tom Lane wrote:
>> Given the POSIX requirements, it's basically impossible to believe
>> that there are interesting cases where $HOME isn't set.  Thus, it
>> seems to me that keeping the getpwuid calls will just mean carrying
>> untestable dead code, so we should simplify matters by ripping
>> those out and *only* consulting $HOME.

> While POSIX requires that the login program put you in a conforming 
> environment, nothing stops the user from building a non-conforming 
> environment, such as with ‘env -i’.  One could argue that such a user 
> deserves whatever broken behavior they might get.  But to me it seems 
> prudent to continue working there if it worked before.

The only case that the v1 patch helps such a user for is if they
unset HOME or set it precisely to ''.  If they set it to anything
else, it's still broken from their perspective.  So I do not find
that that argument holds water.

Moreover, ISTM that the only plausible use-case for unsetting HOME
is to prevent programs from finding stuff in your home directory.
What would be the point otherwise?  So it's pretty hard to envision
a case where somebody is actually using, and happy with, the
behavior you argue we ought to keep.

>> The v1 patch also neglects the matter of documentation.

> The reason I didn’t change the documentation is that this is already 
> what “~” is supposed to mean according to POSIX and common 
> implementations.

The point here is precisely that we're changing what *we* think ~
means.  I don't think we can just leave the docs unchanged.

            regards, tom lane



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Anders Kaseorg
Date:
On 1/9/22 13:04, Tom Lane wrote:
> The only case that the v1 patch helps such a user for is if they
> unset HOME or set it precisely to ''.  If they set it to anything
> else, it's still broken from their perspective.  So I do not find
> that that argument holds water.
> 
> Moreover, ISTM that the only plausible use-case for unsetting HOME
> is to prevent programs from finding stuff in your home directory.
> What would be the point otherwise?  So it's pretty hard to envision
> a case where somebody is actually using, and happy with, the
> behavior you argue we ought to keep.

Obviously a user who intentionally breaks their environment should 
expect problems.  But what I’m saying is that a user could have written 
a script that unsets HOME by *accident* while intending to clear *other* 
things out of the environment.  They might have developed it by starting 
with an empty environment and adding back the minimal set of variables 
they needed to get something to work.  Since most programs (including 
most libcs and shells) do in fact fall back to getpwuid when HOME is 
unset, they may not have noticed an unset HOME as a problem.  Unsetting 
HOME does not, in practice, prevent most programs from finding stuff in 
your home directory.

Anders



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Justin Pryzby
Date:
On Sun, Jan 09, 2022 at 01:59:02PM -0500, Tom Lane wrote:
> Given the POSIX requirements, it's basically impossible to believe
> that there are interesting cases where $HOME isn't set.

I've run into this before - children of init may not have HOME set.

It's easy enough to add it if it's needed, but should probably be called out in
the release notes.

-- 
Justin



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Christoph Moench-Tegeder
Date:
## Tom Lane (tgl@sss.pgh.pa.us):

> Given the POSIX requirements, it's basically impossible to believe
> that there are interesting cases where $HOME isn't set.

When I look at a random Debian with the usual PGDG packages, the
postmaster process (and every backend) has a rather minimal environment
without HOME. When I remember the code correctly, walreceiver uses
the functions from fe-connect.c and may need to find the service file,
a password file or certificates. If I'm correct with that, requiring
HOME to be set would be a significant change for existing "normal"
installations.
What about containers and similar "reduced" environments?

Regards,
Christoph

-- 
Spare Space



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Tom Lane
Date:
Christoph Moench-Tegeder <cmt@burggraben.net> writes:
> ## Tom Lane (tgl@sss.pgh.pa.us):
>> Given the POSIX requirements, it's basically impossible to believe
>> that there are interesting cases where $HOME isn't set.

> When I look at a random Debian with the usual PGDG packages, the
> postmaster process (and every backend) has a rather minimal environment
> without HOME. When I remember the code correctly, walreceiver uses
> the functions from fe-connect.c and may need to find the service file,
> a password file or certificates. If I'm correct with that, requiring
> HOME to be set would be a significant change for existing "normal"
> installations.
> What about containers and similar "reduced" environments?

Isn't that a flat out violation of POSIX 8.3 Other Environment Variables?

    HOME
        The system shall initialize this variable at the time of login to
        be a pathname of the user's home directory. See <pwd.h>.

To claim it's not, you have to claim these programs aren't logged in,
in which case where did they get any privileges from?

            regards, tom lane



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Christoph Moench-Tegeder
Date:
## Tom Lane (tgl@sss.pgh.pa.us):

> Isn't that a flat out violation of POSIX 8.3 Other Environment Variables?
> 
>     HOME
>         The system shall initialize this variable at the time of login to
>         be a pathname of the user's home directory. See <pwd.h>.
> 
> To claim it's not, you have to claim these programs aren't logged in,
> in which case where did they get any privileges from?

After poking around across some Linuxes, it looks like people silently
agreed that "services" are not logged-in users: among the daemons,
having HOME set (as observed in /proc/*/environ) is an exception,
not the norm. I'm not sure if that's a "new" thing with systemd,
I don't have a linux with pure SysV-init available (but I guess those
are rare animals anyways).

Regards,
Christoph

-- 
Spare Space



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Tom Lane
Date:
Christoph Moench-Tegeder <cmt@burggraben.net> writes:
> ## Tom Lane (tgl@sss.pgh.pa.us):
>> Isn't that a flat out violation of POSIX 8.3 Other Environment Variables?

> After poking around across some Linuxes, it looks like people silently
> agreed that "services" are not logged-in users: among the daemons,
> having HOME set (as observed in /proc/*/environ) is an exception,
> not the norm.

Meh.  I guess there's not much point in arguing with facts on the
ground.  Anders' proposed behavior seems like the way to go then,
at least so far as libpq is concerned.  (I remain skeptical that
psql would be run in such an environment, but there's no value
in making it act different from libpq.)

            regards, tom lane



Re: [PATCH] Prefer getenv("HOME") to find the UNIX home directory

From
Tom Lane
Date:
I wrote:
> Meh.  I guess there's not much point in arguing with facts on the
> ground.  Anders' proposed behavior seems like the way to go then,
> at least so far as libpq is concerned.

So I pushed that, but while working on it I grew quite annoyed
at the messy API exhibited by src/port/thread.c, particularly
at how declaring its functions in port.h requires #including
<netdb.h> and <pwd.h> there.  That means those headers are
read by every compile in our tree, though only a tiny number
of modules actually need either.  So here are a couple of
follow-on patches to improve that situation.

For pqGethostbyname, there is no consumer other than
src/port/getaddrinfo.c, which makes it even sillier because that
file isn't even compiled on a large majority of platforms, making
pqGethostbyname dead code that we nonetheless build everywhere.
So 0001 attached just moves that function into getaddrinfo.c.

For pqGetpwuid, the best solution seemed to be to add a
less platform-dependent API layer, which I did in 0002
attached.  Perhaps someone would object to the API details
I chose, but by and large this seems like an improvement
that reduces the amount of code duplication in the callers.

Thoughts?

            regards, tom lane

diff --git a/src/include/port.h b/src/include/port.h
index 22ea292a6d..df81fa97c6 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -14,7 +14,6 @@
 #define PG_PORT_H

 #include <ctype.h>
-#include <netdb.h>
 #include <pwd.h>

 /*
@@ -485,12 +484,6 @@ extern int    pqGetpwuid(uid_t uid, struct passwd *resultbuf, char *buffer,
                        size_t buflen, struct passwd **result);
 #endif

-extern int    pqGethostbyname(const char *name,
-                            struct hostent *resultbuf,
-                            char *buffer, size_t buflen,
-                            struct hostent **result,
-                            int *herrno);
-
 extern void pg_qsort(void *base, size_t nel, size_t elsize,
                      int (*cmp) (const void *, const void *));
 extern int    pg_qsort_strcmp(const void *a, const void *b);
diff --git a/src/port/Makefile b/src/port/Makefile
index b3754d8940..bfe1feb0d4 100644
--- a/src/port/Makefile
+++ b/src/port/Makefile
@@ -84,6 +84,10 @@ libpgport.a: $(OBJS)
     rm -f $@
     $(AR) $(AROPT) $@ $^

+# getaddrinfo.o and getaddrinfo_shlib.o need PTHREAD_CFLAGS (but getaddrinfo_srv.o does not)
+getaddrinfo.o: CFLAGS+=$(PTHREAD_CFLAGS)
+getaddrinfo_shlib.o: CFLAGS+=$(PTHREAD_CFLAGS)
+
 # thread.o and thread_shlib.o need PTHREAD_CFLAGS (but thread_srv.o does not)
 thread.o: CFLAGS+=$(PTHREAD_CFLAGS)
 thread_shlib.o: CFLAGS+=$(PTHREAD_CFLAGS)
diff --git a/src/port/getaddrinfo.c b/src/port/getaddrinfo.c
index b0ca4c778e..3284c6eb52 100644
--- a/src/port/getaddrinfo.c
+++ b/src/port/getaddrinfo.c
@@ -34,6 +34,14 @@
 #include "port/pg_bswap.h"


+#ifdef FRONTEND
+static int    pqGethostbyname(const char *name,
+                            struct hostent *resultbuf,
+                            char *buffer, size_t buflen,
+                            struct hostent **result,
+                            int *herrno);
+#endif
+
 #ifdef WIN32
 /*
  * The native routines may or may not exist on the Windows platform we are on,
@@ -394,3 +402,39 @@ getnameinfo(const struct sockaddr *sa, int salen,

     return 0;
 }
+
+/*
+ * Wrapper around gethostbyname() or gethostbyname_r() to mimic
+ * POSIX gethostbyname_r() behaviour, if it is not available or required.
+ */
+#ifdef FRONTEND
+static int
+pqGethostbyname(const char *name,
+                struct hostent *resultbuf,
+                char *buffer, size_t buflen,
+                struct hostent **result,
+                int *herrno)
+{
+#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_GETHOSTBYNAME_R)
+
+    /*
+     * broken (well early POSIX draft) gethostbyname_r() which returns 'struct
+     * hostent *'
+     */
+    *result = gethostbyname_r(name, resultbuf, buffer, buflen, herrno);
+    return (*result == NULL) ? -1 : 0;
+#else
+
+    /* no gethostbyname_r(), just use gethostbyname() */
+    *result = gethostbyname(name);
+
+    if (*result != NULL)
+        *herrno = h_errno;
+
+    if (*result != NULL)
+        return 0;
+    else
+        return -1;
+#endif
+}
+#endif                            /* FRONTEND */
diff --git a/src/port/thread.c b/src/port/thread.c
index c1040d4e24..a4c1672c89 100644
--- a/src/port/thread.c
+++ b/src/port/thread.c
@@ -76,41 +76,3 @@ pqGetpwuid(uid_t uid, struct passwd *resultbuf, char *buffer,
 #endif
 }
 #endif
-
-/*
- * Wrapper around gethostbyname() or gethostbyname_r() to mimic
- * POSIX gethostbyname_r() behaviour, if it is not available or required.
- * This function is called _only_ by our getaddrinfo() portability function.
- */
-#ifndef HAVE_GETADDRINFO
-int
-pqGethostbyname(const char *name,
-                struct hostent *resultbuf,
-                char *buffer, size_t buflen,
-                struct hostent **result,
-                int *herrno)
-{
-#if defined(FRONTEND) && defined(ENABLE_THREAD_SAFETY) && defined(HAVE_GETHOSTBYNAME_R)
-
-    /*
-     * broken (well early POSIX draft) gethostbyname_r() which returns 'struct
-     * hostent *'
-     */
-    *result = gethostbyname_r(name, resultbuf, buffer, buflen, herrno);
-    return (*result == NULL) ? -1 : 0;
-#else
-
-    /* no gethostbyname_r(), just use gethostbyname() */
-    *result = gethostbyname(name);
-
-    if (*result != NULL)
-        *herrno = h_errno;
-
-    if (*result != NULL)
-        return 0;
-    else
-        return -1;
-#endif
-}
-
-#endif
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index b0d6988aff..4d72a6a8c1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -18,6 +18,7 @@
 #include <sys/param.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
+#include <pwd.h>
 #include <unistd.h>
 #ifdef HAVE_SYS_SELECT_H
 #include <sys/select.h>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index f210ccbde8..3503605a7d 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -10,6 +10,7 @@
 #include <ctype.h>
 #include <limits.h>
 #include <math.h>
+#include <pwd.h>
 #include <signal.h>
 #ifndef WIN32
 #include <unistd.h>                /* for write() */
diff --git a/src/include/port.h b/src/include/port.h
index df81fa97c6..3f1716188b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -14,7 +14,6 @@
 #define PG_PORT_H

 #include <ctype.h>
-#include <pwd.h>

 /*
  * Windows has enough specialized port stuff that we push most of it off
@@ -478,10 +477,10 @@ extern char *dlerror(void);
 #define RTLD_GLOBAL 0
 #endif

-/* thread.h */
+/* thread.c */
 #ifndef WIN32
-extern int    pqGetpwuid(uid_t uid, struct passwd *resultbuf, char *buffer,
-                       size_t buflen, struct passwd **result);
+extern bool pg_get_user_name(uid_t user_id, char *buffer, size_t buflen);
+extern bool pg_get_user_home_dir(uid_t user_id, char *buffer, size_t buflen);
 #endif

 extern void pg_qsort(void *base, size_t nel, size_t elsize,
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 8d2e4e5db4..aa4534578a 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -35,7 +35,6 @@
 #ifndef  MAXHOSTNAMELEN
 #include <netdb.h>                /* for MAXHOSTNAMELEN on some */
 #endif
-#include <pwd.h>
 #endif

 #include "common/md5.h"
@@ -1091,14 +1090,17 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)


 /*
- * pg_fe_getauthname
+ * pg_fe_getusername
  *
- * 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 append a suitable error message to *errorMessage if that's not NULL.
+ * Returns a pointer to malloc'd space containing the name of the
+ * specified user_id.  If there is an error, return NULL, and append
+ * a suitable error message to *errorMessage if that's not NULL.
+ *
+ * Caution: on Windows, the user_id argument is ignored, and we always
+ * fetch the current user's name.
  */
 char *
-pg_fe_getauthname(PQExpBuffer errorMessage)
+pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage)
 {
     char       *result = NULL;
     const char *name = NULL;
@@ -1108,17 +1110,13 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
     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

     /*
      * Some users are using configure --enable-thread-safety-force, so we
      * might as well do the locking within our library to protect
-     * pqGetpwuid(). In fact, application developers can use getpwuid() in
+     * getpwuid(). In fact, application developers can use getpwuid() in
      * their application if they use the locking call we provide, or install
      * their own locking function using PQregisterThreadLock().
      */
@@ -1132,21 +1130,10 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
                           libpq_gettext("user name lookup failure: error code %lu\n"),
                           GetLastError());
 #else
-    pwerr = pqGetpwuid(user_id, &pwdstr, pwdbuf, sizeof(pwdbuf), &pw);
-    if (pw != NULL)
-        name = pw->pw_name;
+    if (pg_get_user_name(user_id, pwdbuf, sizeof(pwdbuf)))
+        name = pwdbuf;
     else if (errorMessage)
-    {
-        if (pwerr != 0)
-            appendPQExpBuffer(errorMessage,
-                              libpq_gettext("could not look up local user ID %d: %s\n"),
-                              (int) user_id,
-                              strerror_r(pwerr, pwdbuf, sizeof(pwdbuf)));
-        else
-            appendPQExpBuffer(errorMessage,
-                              libpq_gettext("local user with ID %d does not exist\n"),
-                              (int) user_id);
-    }
+        appendPQExpBuffer(errorMessage, "%s\n", pwdbuf);
 #endif

     if (name)
@@ -1162,6 +1149,23 @@ pg_fe_getauthname(PQExpBuffer errorMessage)
     return result;
 }

+/*
+ * pg_fe_getauthname
+ *
+ * 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 append a suitable error message to *errorMessage if that's not NULL.
+ */
+char *
+pg_fe_getauthname(PQExpBuffer errorMessage)
+{
+#ifdef WIN32
+    return pg_fe_getusername(0, errorMessage);
+#else
+    return pg_fe_getusername(geteuid(), errorMessage);
+#endif
+}
+

 /*
  * PQencryptPassword -- exported routine to encrypt a password with MD5
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 16d5e1da0f..f22b3fe648 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -20,6 +20,7 @@

 /* Prototypes for functions in fe-auth.c */
 extern int    pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn);
+extern char *pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);

 /* Mechanisms in fe-auth-scram.c */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a12e0180fd..5fc16be849 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2813,10 +2813,7 @@ keep_going:                        /* We will come back to here until there is
                     IS_AF_UNIX(conn->raddr.addr.ss_family))
                 {
 #ifndef WIN32
-                    char        pwdbuf[BUFSIZ];
-                    struct passwd pass_buf;
-                    struct passwd *pass;
-                    int            passerr;
+                    char       *remote_username;
 #endif
                     uid_t        uid;
                     gid_t        gid;
@@ -2839,28 +2836,20 @@ keep_going:                        /* We will come back to here until there is
                     }

 #ifndef WIN32
-                    passerr = pqGetpwuid(uid, &pass_buf, pwdbuf, sizeof(pwdbuf), &pass);
-                    if (pass == NULL)
-                    {
-                        if (passerr != 0)
-                            appendPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("could not look up local user ID %d: %s\n"),
-                                              (int) uid,
-                                              strerror_r(passerr, sebuf, sizeof(sebuf)));
-                        else
-                            appendPQExpBuffer(&conn->errorMessage,
-                                              libpq_gettext("local user with ID %d does not exist\n"),
-                                              (int) uid);
-                        goto error_return;
-                    }
+                    remote_username = pg_fe_getusername(uid,
+                                                        &conn->errorMessage);
+                    if (remote_username == NULL)
+                        goto error_return;    /* message already logged */

-                    if (strcmp(pass->pw_name, conn->requirepeer) != 0)
+                    if (strcmp(remote_username, conn->requirepeer) != 0)
                     {
                         appendPQExpBuffer(&conn->errorMessage,
                                           libpq_gettext("requirepeer specifies \"%s\", but actual peer user name is
\"%s\"\n"),
-                                          conn->requirepeer, pass->pw_name);
+                                          conn->requirepeer, remote_username);
+                        free(remote_username);
                         goto error_return;
                     }
+                    free(remote_username);
 #else                            /* WIN32 */
                     /* should have failed with ENOSYS above */
                     Assert(false);
@@ -7271,16 +7260,7 @@ pqGetHomeDirectory(char *buf, int bufsize)

     home = getenv("HOME");
     if (home == NULL || home[0] == '\0')
-    {
-        char        pwdbuf[BUFSIZ];
-        struct passwd pwdstr;
-        struct passwd *pwd = NULL;
-
-        (void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
-        if (pwd == NULL)
-            return false;
-        home = pwd->pw_dir;
-    }
+        return pg_get_user_home_dir(geteuid(), buf, bufsize);
     strlcpy(buf, home, bufsize);
     return true;
 #else
diff --git a/src/port/path.c b/src/port/path.c
index 5ac26f4bcf..69bb8fe40b 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -815,16 +815,7 @@ get_home_path(char *ret_path)

     home = getenv("HOME");
     if (home == NULL || home[0] == '\0')
-    {
-        char        pwdbuf[BUFSIZ];
-        struct passwd pwdstr;
-        struct passwd *pwd = NULL;
-
-        (void) pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pwd);
-        if (pwd == NULL)
-            return false;
-        home = pwd->pw_dir;
-    }
+        return pg_get_user_home_dir(geteuid(), ret_path, MAXPGPATH);
     strlcpy(ret_path, home, MAXPGPATH);
     return true;
 #else
diff --git a/src/port/thread.c b/src/port/thread.c
index a4c1672c89..e2a570ec40 100644
--- a/src/port/thread.c
+++ b/src/port/thread.c
@@ -49,6 +49,7 @@
  *    One thread-safe solution for gethostbyname() might be to use getaddrinfo().
  */

+#ifndef WIN32

 /*
  * Wrapper around getpwuid() or getpwuid_r() to mimic POSIX getpwuid_r()
@@ -60,8 +61,7 @@
  * error during lookup: returns an errno code, *result is NULL
  * (caller should *not* assume that the errno variable is set)
  */
-#ifndef WIN32
-int
+static int
 pqGetpwuid(uid_t uid, struct passwd *resultbuf, char *buffer,
            size_t buflen, struct passwd **result)
 {
@@ -75,4 +75,74 @@ pqGetpwuid(uid_t uid, struct passwd *resultbuf, char *buffer,
     return (*result == NULL) ? errno : 0;
 #endif
 }
-#endif
+
+/*
+ * pg_get_user_name - get the name of the user with the given ID
+ *
+ * On success, the user name is returned into the buffer (of size buflen),
+ * and "true" is returned.  On failure, a localized error message is
+ * returned into the buffer, and "false" is returned.
+ */
+bool
+pg_get_user_name(uid_t user_id, char *buffer, size_t buflen)
+{
+    char        pwdbuf[BUFSIZ];
+    struct passwd pwdstr;
+    struct passwd *pw = NULL;
+    int            pwerr;
+
+    pwerr = pqGetpwuid(user_id, &pwdstr, pwdbuf, sizeof(pwdbuf), &pw);
+    if (pw != NULL)
+    {
+        strlcpy(buffer, pw->pw_name, buflen);
+        return true;
+    }
+    if (pwerr != 0)
+        snprintf(buffer, buflen,
+                 _("could not look up local user ID %d: %s"),
+                 (int) user_id,
+                 strerror_r(pwerr, pwdbuf, sizeof(pwdbuf)));
+    else
+        snprintf(buffer, buflen,
+                 _("local user with ID %d does not exist"),
+                 (int) user_id);
+    return false;
+}
+
+/*
+ * pg_get_user_home_dir - get the home directory of the user with the given ID
+ *
+ * On success, the directory path is returned into the buffer (of size buflen),
+ * and "true" is returned.  On failure, a localized error message is
+ * returned into the buffer, and "false" is returned.
+ *
+ * Note that this does not incorporate the common behavior of checking
+ * $HOME first, since it's independent of which user_id is queried.
+ */
+bool
+pg_get_user_home_dir(uid_t user_id, char *buffer, size_t buflen)
+{
+    char        pwdbuf[BUFSIZ];
+    struct passwd pwdstr;
+    struct passwd *pw = NULL;
+    int            pwerr;
+
+    pwerr = pqGetpwuid(user_id, &pwdstr, pwdbuf, sizeof(pwdbuf), &pw);
+    if (pw != NULL)
+    {
+        strlcpy(buffer, pw->pw_dir, buflen);
+        return true;
+    }
+    if (pwerr != 0)
+        snprintf(buffer, buflen,
+                 _("could not look up local user ID %d: %s"),
+                 (int) user_id,
+                 strerror_r(pwerr, pwdbuf, sizeof(pwdbuf)));
+    else
+        snprintf(buffer, buflen,
+                 _("local user with ID %d does not exist"),
+                 (int) user_id);
+    return false;
+}
+
+#endif                            /* !WIN32 */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index deb62c1c7b..ec3546d0c0 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -106,7 +106,7 @@ sub mkvcbuild
       pread.c preadv.c pwrite.c pwritev.c pg_bitutils.c
       pg_strong_random.c pgcheckdir.c pgmkdirp.c pgsleep.c pgstrcasecmp.c
       pqsignal.c mkdtemp.c qsort.c qsort_arg.c bsearch_arg.c quotes.c system.c
-      strerror.c tar.c thread.c
+      strerror.c tar.c
       win32env.c win32error.c win32ntdll.c
       win32security.c win32setlocale.c win32stat.c);