From 41a4e45957e19e962352848fdcd8316353d30379 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 20 Jan 2010 21:15:21 +0000
Subject: [PATCH] Add user-specific .pg_service.conf file

This extends the existing pg_service.conf facility to first look for a
service definition file in the user's home directory.
---
 doc/src/sgml/libpq.sgml           |  51 +++++-
 src/interfaces/libpq/fe-connect.c | 280 +++++++++++++++++-------------
 2 files changed, 207 insertions(+), 124 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index cfa87d0adae..a5386cf333d 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.293 2010/01/20 00:42:28 rhaas Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.294 2010/01/20 21:15:21 petere Exp $ -->
 
 <chapter id="libpq">
  <title><application>libpq</application> - C Library</title>
@@ -5791,6 +5791,18 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGSERVICEFILE</envar></primary>
+      </indexterm>
+      <envar>PGSERVICEFILE</envar> specifies the name of the per-user
+      connection service file.  If not set, it defaults
+      to <filename>~/.pg_service.conf</>
+      (see <xref linkend="libpq-pgservice">).
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
@@ -5987,7 +5999,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
        <primary><envar>PGSYSCONFDIR</envar></primary>
       </indexterm>
       <envar>PGSYSCONFDIR</envar> sets the directory containing the
-      <filename>pg_service.conf</> file.
+      <filename>pg_service.conf</> file and in a future version
+      possibly other system-wide configuration files.
      </para>
     </listitem>
 
@@ -6063,6 +6076,9 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
   <indexterm zone="libpq-pgservice">
    <primary>pg_service.conf</primary>
   </indexterm>
+  <indexterm zone="libpq-pgservice">
+   <primary>.pg_service.conf</primary>
+  </indexterm>
 
   <para>
    The connection service file allows libpq connection parameters to be
@@ -6074,12 +6090,31 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
   </para>
 
   <para>
-   To use this feature, copy
-   <filename>share/pg_service.conf.sample</filename> to
-   <filename>etc/pg_service.conf</filename> and edit the file to add
-   service names and parameters. This file can be used for client-only
-   installs too. The file's location can also be specified by the
-   <envar>PGSYSCONFDIR</envar> environment variable.
+   The connection service file can be a per-user service file
+   at <filename>~/.pg_service.conf</filename> or the location
+   specified by the environment variable <envar>PGSERVICEFILE</envar>,
+   or it can be a system-wide file
+   at <filename>etc/pg_service.conf</filename> or in the directory
+   specified by the environment variable
+   <envar>PGSYSCONFDIR</envar>.  If service definitions with the same
+   name exist in the user and the system file, the user file takes
+   precedence.
+  </para>
+
+  <para>
+   The file uses an <quote>INI file</quote> format where the section
+   name is the service name and the parameters are connection
+   parameters; see <xref linkend="libpq-connect"> for a list.  For
+   example:
+<programlisting>
+# comment
+[mydb]
+host=somehost
+port=5433
+user=admin
+</programlisting>
+   An example file is provided at
+   <filename>share/pg_service.conf.sample</filename>.
   </para>
  </sect1>
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 125d93cf0b1..4d7be078c07 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.383 2010/01/15 09:19:10 heikki Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.384 2010/01/20 21:15:21 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -272,6 +272,11 @@ static void defaultNoticeReceiver(void *arg, const PGresult *res);
 static void defaultNoticeProcessor(void *arg, const char *message);
 static int parseServiceInfo(PQconninfoOption *options,
 				 PQExpBuffer errorMessage);
+static int parseServiceFile(const char *serviceFile,
+							const char *service,
+							PQconninfoOption *options,
+							PQExpBuffer errorMessage,
+							bool *group_found);
 static char *pwdfMatchesString(char *buf, char *token);
 static char *PasswordFromFile(char *hostname, char *port, char *dbname,
 				 char *username);
@@ -3095,9 +3100,10 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 {
 	char	   *service = conninfo_getval(options, "service");
 	char		serviceFile[MAXPGPATH];
+	char	   *env;
 	bool		group_found = false;
-	int			linenr = 0,
-				i;
+	int			status;
+	struct stat stat_buf;
 
 	/*
 	 * We have to special-case the environment variable PGSERVICE here, since
@@ -3107,154 +3113,196 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
 	if (service == NULL)
 		service = getenv("PGSERVICE");
 
+	if (service == NULL)
+		return 0;
+
+	if ((env = getenv("PGSERVICEFILE")) != NULL)
+		strlcpy(serviceFile, env, sizeof(serviceFile));
+	else
+	{
+		char		homedir[MAXPGPATH];
+
+		if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
+		{
+			printfPQExpBuffer(errorMessage, libpq_gettext("could not get home directory to locate service definition file"));
+			return 1;
+		}
+		snprintf(serviceFile, MAXPGPATH, "%s/%s", homedir, ".pg_service.conf");
+		errno = 0;
+		if (stat(serviceFile, &stat_buf) != 0 && errno == ENOENT)
+			goto next_file;
+	}
+
+	status = parseServiceFile(serviceFile, service, options, errorMessage, &group_found);
+	if (group_found || status != 0)
+		return status;
+
+next_file:
 	/*
 	 * This could be used by any application so we can't use the binary
 	 * location to find our config files.
 	 */
 	snprintf(serviceFile, MAXPGPATH, "%s/pg_service.conf",
 			 getenv("PGSYSCONFDIR") ? getenv("PGSYSCONFDIR") : SYSCONFDIR);
+	errno = 0;
+	if (stat(serviceFile, &stat_buf) != 0 && errno == ENOENT)
+		goto last_file;
+
+	status = parseServiceFile(serviceFile, service, options, errorMessage, &group_found);
+	if (status != 0)
+		return status;
+
+last_file:
+	if (!group_found)
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("definition of service \"%s\" not found\n"), service);
+		return 3;
+	}
+
+	return 0;
+}
 
-	if (service != NULL)
+static int
+parseServiceFile(const char *serviceFile,
+				 const char *service,
+				 PQconninfoOption *options,
+				 PQExpBuffer errorMessage,
+				 bool *group_found)
+{	
+	int			linenr = 0,
+				i;
+	FILE	   *f;
+	char		buf[MAXBUFSIZE],
+			   *line;
+
+	f = fopen(serviceFile, "r");
+	if (f == NULL)
+	{
+		printfPQExpBuffer(errorMessage, libpq_gettext("service file \"%s\" not found\n"),
+						  serviceFile);
+		return 1;
+	}
+
+	while ((line = fgets(buf, sizeof(buf), f)) != NULL)
 	{
-		FILE	   *f;
-		char		buf[MAXBUFSIZE],
-				   *line;
+		linenr++;
 
-		f = fopen(serviceFile, "r");
-		if (f == NULL)
+		if (strlen(line) >= sizeof(buf) - 1)
 		{
-			printfPQExpBuffer(errorMessage, libpq_gettext("service file \"%s\" not found\n"),
+			fclose(f);
+			printfPQExpBuffer(errorMessage,
+							  libpq_gettext("line %d too long in service file \"%s\"\n"),
+							  linenr,
 							  serviceFile);
-			return 1;
+			return 2;
 		}
 
-		while ((line = fgets(buf, sizeof(buf), f)) != NULL)
-		{
-			linenr++;
-
-			if (strlen(line) >= sizeof(buf) - 1)
-			{
-				fclose(f);
-				printfPQExpBuffer(errorMessage,
-								  libpq_gettext("line %d too long in service file \"%s\"\n"),
-								  linenr,
-								  serviceFile);
-				return 2;
-			}
+		/* ignore EOL at end of line */
+		if (strlen(line) && line[strlen(line) - 1] == '\n')
+			line[strlen(line) - 1] = 0;
 
-			/* ignore EOL at end of line */
-			if (strlen(line) && line[strlen(line) - 1] == '\n')
-				line[strlen(line) - 1] = 0;
+		/* ignore leading blanks */
+		while (*line && isspace((unsigned char) line[0]))
+			line++;
 
-			/* ignore leading blanks */
-			while (*line && isspace((unsigned char) line[0]))
-				line++;
-
-			/* ignore comments and empty lines */
-			if (strlen(line) == 0 || line[0] == '#')
-				continue;
+		/* ignore comments and empty lines */
+		if (strlen(line) == 0 || line[0] == '#')
+			continue;
 
-			/* Check for right groupname */
-			if (line[0] == '[')
+		/* Check for right groupname */
+		if (line[0] == '[')
+		{
+			if (*group_found)
 			{
-				if (group_found)
-				{
-					/* group info already read */
-					fclose(f);
-					return 0;
-				}
-
-				if (strncmp(line + 1, service, strlen(service)) == 0 &&
-					line[strlen(service) + 1] == ']')
-					group_found = true;
-				else
-					group_found = false;
+				/* group info already read */
+				fclose(f);
+				return 0;
 			}
+
+			if (strncmp(line + 1, service, strlen(service)) == 0 &&
+				line[strlen(service) + 1] == ']')
+				*group_found = true;
 			else
+				*group_found = false;
+		}
+		else
+		{
+			if (*group_found)
 			{
-				if (group_found)
-				{
-					/*
-					 * Finally, we are in the right group and can parse the
-					 * line
-					 */
-					char	   *key,
-							   *val;
-					bool		found_keyword;
+				/*
+				 * Finally, we are in the right group and can parse
+				 * the line
+				 */
+				char	   *key,
+						   *val;
+				bool		found_keyword;
 
 #ifdef USE_LDAP
-					if (strncmp(line, "ldap", 4) == 0)
-					{
-						int			rc = ldapServiceLookup(line, options, errorMessage);
+				if (strncmp(line, "ldap", 4) == 0)
+				{
+					int			rc = ldapServiceLookup(line, options, errorMessage);
 
-						/* if rc = 2, go on reading for fallback */
-						switch (rc)
-						{
-							case 0:
-								fclose(f);
-								return 0;
-							case 1:
-							case 3:
-								fclose(f);
-								return 3;
-							case 2:
-								continue;
-						}
+					/* if rc = 2, go on reading for fallback */
+					switch (rc)
+					{
+						case 0:
+							fclose(f);
+							return 0;
+						case 1:
+						case 3:
+							fclose(f);
+							return 3;
+						case 2:
+							continue;
 					}
+				}
 #endif
 
-					key = line;
-					val = strchr(line, '=');
-					if (val == NULL)
-					{
-						printfPQExpBuffer(errorMessage,
-										  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
-										  serviceFile,
-										  linenr);
-						fclose(f);
-						return 3;
-					}
-					*val++ = '\0';
+				key = line;
+				val = strchr(line, '=');
+				if (val == NULL)
+				{
+					printfPQExpBuffer(errorMessage,
+									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
+									  serviceFile,
+									  linenr);
+					fclose(f);
+					return 3;
+				}
+				*val++ = '\0';
 
-					/*
-					 * Set the parameter --- but don't override any previous
-					 * explicit setting.
-					 */
-					found_keyword = false;
-					for (i = 0; options[i].keyword; i++)
+				/*
+				 * Set the parameter --- but don't override any previous
+				 * explicit setting.
+				 */
+				found_keyword = false;
+				for (i = 0; options[i].keyword; i++)
+				{
+					if (strcmp(options[i].keyword, key) == 0)
 					{
-						if (strcmp(options[i].keyword, key) == 0)
-						{
-							if (options[i].val == NULL)
-								options[i].val = strdup(val);
-							found_keyword = true;
-							break;
-						}
+						if (options[i].val == NULL)
+							options[i].val = strdup(val);
+						found_keyword = true;
+						break;
 					}
+				}
 
-					if (!found_keyword)
-					{
-						printfPQExpBuffer(errorMessage,
-										  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
-										  serviceFile,
-										  linenr);
-						fclose(f);
-						return 3;
-					}
+				if (!found_keyword)
+				{
+					printfPQExpBuffer(errorMessage,
+									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
+									  serviceFile,
+									  linenr);
+					fclose(f);
+					return 3;
 				}
 			}
 		}
-
-		fclose(f);
-
-		if (!group_found)
-		{
-			printfPQExpBuffer(errorMessage,
-							  libpq_gettext("definition of service \"%s\" not found\n"), service);
-			return 3;
-		}
 	}
 
+	fclose(f);
+
 	return 0;
 }
 
-- 
GitLab