Commit 41a4e459 authored by Peter Eisentraut's avatar Peter Eisentraut

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.
parent e8aae273
<!-- $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"> <chapter id="libpq">
<title><application>libpq</application> - C Library</title> <title><application>libpq</application> - C Library</title>
...@@ -5791,6 +5791,18 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) ...@@ -5791,6 +5791,18 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para> </para>
</listitem> </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> <listitem>
<para> <para>
<indexterm> <indexterm>
...@@ -5987,7 +5999,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) ...@@ -5987,7 +5999,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<primary><envar>PGSYSCONFDIR</envar></primary> <primary><envar>PGSYSCONFDIR</envar></primary>
</indexterm> </indexterm>
<envar>PGSYSCONFDIR</envar> sets the directory containing the <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> </para>
</listitem> </listitem>
...@@ -6063,6 +6076,9 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) ...@@ -6063,6 +6076,9 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<indexterm zone="libpq-pgservice"> <indexterm zone="libpq-pgservice">
<primary>pg_service.conf</primary> <primary>pg_service.conf</primary>
</indexterm> </indexterm>
<indexterm zone="libpq-pgservice">
<primary>.pg_service.conf</primary>
</indexterm>
<para> <para>
The connection service file allows libpq connection parameters to be The connection service file allows libpq connection parameters to be
...@@ -6074,12 +6090,31 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) ...@@ -6074,12 +6090,31 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para> </para>
<para> <para>
To use this feature, copy The connection service file can be a per-user service file
<filename>share/pg_service.conf.sample</filename> to at <filename>~/.pg_service.conf</filename> or the location
<filename>etc/pg_service.conf</filename> and edit the file to add specified by the environment variable <envar>PGSERVICEFILE</envar>,
service names and parameters. This file can be used for client-only or it can be a system-wide file
installs too. The file's location can also be specified by the at <filename>etc/pg_service.conf</filename> or in the directory
<envar>PGSYSCONFDIR</envar> environment variable. 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> </para>
</sect1> </sect1>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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); ...@@ -272,6 +272,11 @@ static void defaultNoticeReceiver(void *arg, const PGresult *res);
static void defaultNoticeProcessor(void *arg, const char *message); static void defaultNoticeProcessor(void *arg, const char *message);
static int parseServiceInfo(PQconninfoOption *options, static int parseServiceInfo(PQconninfoOption *options,
PQExpBuffer errorMessage); 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 *pwdfMatchesString(char *buf, char *token);
static char *PasswordFromFile(char *hostname, char *port, char *dbname, static char *PasswordFromFile(char *hostname, char *port, char *dbname,
char *username); char *username);
...@@ -3095,9 +3100,10 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage) ...@@ -3095,9 +3100,10 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
{ {
char *service = conninfo_getval(options, "service"); char *service = conninfo_getval(options, "service");
char serviceFile[MAXPGPATH]; char serviceFile[MAXPGPATH];
char *env;
bool group_found = false; bool group_found = false;
int linenr = 0, int status;
i; struct stat stat_buf;
/* /*
* We have to special-case the environment variable PGSERVICE here, since * We have to special-case the environment variable PGSERVICE here, since
...@@ -3107,154 +3113,196 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage) ...@@ -3107,154 +3113,196 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
if (service == NULL) if (service == NULL)
service = getenv("PGSERVICE"); 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 * This could be used by any application so we can't use the binary
* location to find our config files. * location to find our config files.
*/ */
snprintf(serviceFile, MAXPGPATH, "%s/pg_service.conf", snprintf(serviceFile, MAXPGPATH, "%s/pg_service.conf",
getenv("PGSYSCONFDIR") ? getenv("PGSYSCONFDIR") : SYSCONFDIR); 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; linenr++;
char buf[MAXBUFSIZE],
*line;
f = fopen(serviceFile, "r"); if (strlen(line) >= sizeof(buf) - 1)
if (f == NULL)
{ {
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); serviceFile);
return 1; return 2;
} }
while ((line = fgets(buf, sizeof(buf), f)) != NULL) /* ignore EOL at end of line */
{ if (strlen(line) && line[strlen(line) - 1] == '\n')
linenr++; line[strlen(line) - 1] = 0;
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 */ /* ignore leading blanks */
if (strlen(line) && line[strlen(line) - 1] == '\n') while (*line && isspace((unsigned char) line[0]))
line[strlen(line) - 1] = 0; line++;
/* ignore leading blanks */ /* ignore comments and empty lines */
while (*line && isspace((unsigned char) line[0])) if (strlen(line) == 0 || line[0] == '#')
line++; continue;
/* ignore comments and empty lines */
if (strlen(line) == 0 || line[0] == '#')
continue;
/* Check for right groupname */ /* Check for right groupname */
if (line[0] == '[') if (line[0] == '[')
{
if (*group_found)
{ {
if (group_found) /* group info already read */
{ fclose(f);
/* group info already read */ return 0;
fclose(f);
return 0;
}
if (strncmp(line + 1, service, strlen(service)) == 0 &&
line[strlen(service) + 1] == ']')
group_found = true;
else
group_found = false;
} }
if (strncmp(line + 1, service, strlen(service)) == 0 &&
line[strlen(service) + 1] == ']')
*group_found = true;
else else
*group_found = false;
}
else
{
if (*group_found)
{ {
if (group_found) /*
{ * Finally, we are in the right group and can parse
/* * the line
* Finally, we are in the right group and can parse the */
* line char *key,
*/ *val;
char *key, bool found_keyword;
*val;
bool found_keyword;
#ifdef USE_LDAP #ifdef USE_LDAP
if (strncmp(line, "ldap", 4) == 0) if (strncmp(line, "ldap", 4) == 0)
{ {
int rc = ldapServiceLookup(line, options, errorMessage); int rc = ldapServiceLookup(line, options, errorMessage);
/* if rc = 2, go on reading for fallback */ /* if rc = 2, go on reading for fallback */
switch (rc) switch (rc)
{ {
case 0: case 0:
fclose(f); fclose(f);
return 0; return 0;
case 1: case 1:
case 3: case 3:
fclose(f); fclose(f);
return 3; return 3;
case 2: case 2:
continue; continue;
}
} }
}
#endif #endif
key = line; key = line;
val = strchr(line, '='); val = strchr(line, '=');
if (val == NULL) if (val == NULL)
{ {
printfPQExpBuffer(errorMessage, printfPQExpBuffer(errorMessage,
libpq_gettext("syntax error in service file \"%s\", line %d\n"), libpq_gettext("syntax error in service file \"%s\", line %d\n"),
serviceFile, serviceFile,
linenr); linenr);
fclose(f); fclose(f);
return 3; return 3;
} }
*val++ = '\0'; *val++ = '\0';
/* /*
* Set the parameter --- but don't override any previous * Set the parameter --- but don't override any previous
* explicit setting. * explicit setting.
*/ */
found_keyword = false; found_keyword = false;
for (i = 0; options[i].keyword; i++) 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);
if (options[i].val == NULL) found_keyword = true;
options[i].val = strdup(val); break;
found_keyword = true;
break;
}
} }
}
if (!found_keyword) if (!found_keyword)
{ {
printfPQExpBuffer(errorMessage, printfPQExpBuffer(errorMessage,
libpq_gettext("syntax error in service file \"%s\", line %d\n"), libpq_gettext("syntax error in service file \"%s\", line %d\n"),
serviceFile, serviceFile,
linenr); linenr);
fclose(f); fclose(f);
return 3; 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; return 0;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment