postgresql/src/interfaces/libpq/fe-connect.c
Marc G. Fournier bd029bcb4a From: Tom Lane <tgl@sss.pgh.pa.us>
The attached patches respond to discussion that was on pgsql-hackers
around the beginning of June (see thread "libpgtcl bug (and symptomatic
treatment)").  The changes are:

1. Remove code in connectDB that throws away the password after making
a connection.  This doesn't really add much security IMHO --- a bad guy
with access to your client's address space can likely extract the
password anyway, to say nothing of what he might do directly.  And
there's the serious shortcoming that it prevents PQreset() from working
if the database requires a password.

2. Fix coredump problem: fe_sendauth did not guard against being handed
a NULL password pointer.  (This is the proximate cause of the coredump-
during-PQreset problem that Magosanyi Arpad complained of last month.)

3. Remove highly questionable "error recovery" logic in libpgtcl's
pg_exec statement.

I believe the consensus of the discussion last month was in favor of
#1 and #3, but I'm just now getting around to making the change.
I realized that #2 was a bug in process of looking at the change.
1998-07-09 03:32:10 +00:00

1473 lines
31 KiB
C

/*-------------------------------------------------------------------------
*
* fe-connect.c--
* functions related to setting up a connection to the backend
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.74 1998/07/09 03:32:10 scrappy Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdlib.h>
#include <sys/types.h>
#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h> /* for isspace() */
#include "postgres.h"
#include "fe-auth.h"
#include "fe-connect.h"
#include "libpq-fe.h"
#ifndef HAVE_STRDUP
#include "strdup.h"
#endif
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
static ConnStatusType connectDB(PGconn *conn);
static PGconn *makeEmptyPGconn(void);
static void freePGconn(PGconn *conn);
static void closePGconn(PGconn *conn);
static int conninfo_parse(const char *conninfo, char *errorMessage);
static char *conninfo_getval(char *keyword);
static void conninfo_free(void);
/* XXX Why is this not static? */
void PQsetenv(PGconn *conn);
#define NOTIFYLIST_INITIAL_SIZE 10
#define NOTIFYLIST_GROWBY 10
/* ----------
* Definition of the conninfo parameters and their fallback resources.
* If Environment-Var and Compiled-in are specified as NULL, no
* fallback is available. If after all no value can be determined
* for an option, an error is returned.
*
* The values for dbname and user are treated special in conninfo_parse.
* If the Compiled-in resource is specified as a NULL value, the
* user is determined by fe_getauthname() and for dbname the user
* name is copied.
*
* The Label and Disp-Char entries are provided for applications that
* want to use PQconndefaults() to create a generic database connection
* dialog. Disp-Char is defined as follows:
* "" Normal input field
* ----------
*/
static PQconninfoOption PQconninfoOptions[] = {
/* ----------------------------------------------------------------- */
/* Option-name Environment-Var Compiled-in Current value */
/* Label Disp-Char */
/* ----------------- --------------- --------------- --------------- */
/* "authtype" is ignored as it is no longer used. */
{"authtype", "PGAUTHTYPE", DefaultAuthtype, NULL,
"Database-Authtype", "", 20},
{"user", "PGUSER", NULL, NULL,
"Database-User", "", 20},
{"password", "PGPASSWORD", DefaultPassword, NULL,
"Database-Password", "", 20},
{"dbname", "PGDATABASE", NULL, NULL,
"Database-Name", "", 20},
{"host", "PGHOST", NULL, NULL,
"Database-Host", "", 40},
{"port", "PGPORT", DEF_PGPORT, NULL,
"Database-Port", "", 6},
{"tty", "PGTTY", DefaultTty, NULL,
"Backend-Debug-TTY", "D", 40},
{"options", "PGOPTIONS", DefaultOption, NULL,
"Backend-Debug-Options", "D", 40},
/* ----------------- --------------- --------------- --------------- */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
};
struct EnvironmentOptions
{
const char *envName,
*pgName;
} EnvironmentOptions[] =
{
/* common user-interface settings */
{
"PGDATESTYLE", "datestyle"
},
{
"PGTZ", "timezone"
},
#ifdef MB
{
"PGCLIENTENCODING", "client_encoding"
},
#endif
/* internal performance-related settings */
{
"PGCOSTHEAP", "cost_heap"
},
{
"PGCOSTINDEX", "cost_index"
},
{
"PGRPLANS", "r_plans"
},
{
"PGGEQO", "geqo"
},
{
NULL
}
};
/* ----------------
* PQconnectdb
*
* establishes a connection to a postgres backend through the postmaster
* using connection information in a string.
*
* The conninfo string is a list of
*
* option = value
*
* definitions. Value might be a single value containing no whitespaces
* or a single quoted string. If a single quote should appear everywhere
* in the value, it must be escaped with a backslash like \'
*
* Returns a PGconn* which is needed for all subsequent libpq calls
* if the status field of the connection returned is CONNECTION_BAD,
* then some fields may be null'ed out instead of having valid values
* ----------------
*/
PGconn *
PQconnectdb(const char *conninfo)
{
PGconn *conn;
char *tmp;
/* ----------
* Allocate memory for the conn structure
* ----------
*/
conn = makeEmptyPGconn();
if (conn == NULL)
{
fprintf(stderr,
"FATAL: PQconnectdb() -- unable to allocate memory for a PGconn");
return (PGconn *) NULL;
}
/* ----------
* Parse the conninfo string and save settings in conn structure
* ----------
*/
if (conninfo_parse(conninfo, conn->errorMessage) < 0)
{
conn->status = CONNECTION_BAD;
conninfo_free();
return conn;
}
tmp = conninfo_getval("host");
conn->pghost = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("port");
conn->pgport = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("tty");
conn->pgtty = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("options");
conn->pgoptions = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("dbname");
conn->dbName = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("user");
conn->pguser = tmp ? strdup(tmp) : NULL;
tmp = conninfo_getval("password");
conn->pgpass = tmp ? strdup(tmp) : NULL;
/* ----------
* Free the connection info - all is in conn now
* ----------
*/
conninfo_free();
/* ----------
* Connect to the database
* ----------
*/
conn->status = connectDB(conn);
return conn;
}
/* ----------------
* PQconndefaults
*
* Parse an empty string like PQconnectdb() would do and return the
* address of the connection options structure. Using this function
* an application might determine all possible options and their
* current default values.
* ----------------
*/
PQconninfoOption *
PQconndefaults(void)
{
char errorMessage[ERROR_MSG_LENGTH];
conninfo_parse("", errorMessage);
return PQconninfoOptions;
}
/* ----------------
* PQsetdbLogin
*
* establishes a connection to a postgres backend through the postmaster
* at the specified host and port.
*
* returns a PGconn* which is needed for all subsequent libpq calls
* if the status field of the connection returned is CONNECTION_BAD,
* then some fields may be null'ed out instead of having valid values
*
* Uses these environment variables:
*
* PGHOST identifies host to which to connect if <pghost> argument
* is NULL or a null string.
*
* PGPORT identifies TCP port to which to connect if <pgport> argument
* is NULL or a null string.
*
* PGTTY identifies tty to which to send messages if <pgtty> argument
* is NULL or a null string.
*
* PGOPTIONS identifies connection options if <pgoptions> argument is
* NULL or a null string.
*
* PGUSER Postgres username to associate with the connection.
*
* PGPASSWORD The user's password.
*
* PGDATABASE name of database to which to connect if <pgdatabase>
* argument is NULL or a null string
*
* None of the above need be defined. There are defaults for all of them.
*
* To support "delimited identifiers" for database names, only convert
* the database name to lower case if it is not surrounded by double quotes.
* Otherwise, strip the double quotes but leave the reset of the string intact.
* - thomas 1997-11-08
*
* ----------------
*/
PGconn *
PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions, const char *pgtty, const char *dbName, const char *login, const char *pwd)
{
PGconn *conn;
char *tmp;
/* An error message from some service we call. */
bool error = FALSE;
/* We encountered an error that prevents successful completion */
int i;
conn = makeEmptyPGconn();
if (conn == NULL)
{
fprintf(stderr,
"FATAL: PQsetdbLogin() -- unable to allocate memory for a PGconn");
return (PGconn *) NULL;
}
if ((pghost == NULL) || pghost[0] == '\0')
{
if ((tmp = getenv("PGHOST")) != NULL)
conn->pghost = strdup(tmp);
}
else
conn->pghost = strdup(pghost);
if ((pgport == NULL) || pgport[0] == '\0')
{
if ((tmp = getenv("PGPORT")) == NULL)
tmp = DEF_PGPORT;
conn->pgport = strdup(tmp);
}
else
conn->pgport = strdup(pgport);
if ((pgtty == NULL) || pgtty[0] == '\0')
{
if ((tmp = getenv("PGTTY")) == NULL)
tmp = DefaultTty;
conn->pgtty = strdup(tmp);
}
else
conn->pgtty = strdup(pgtty);
if ((pgoptions == NULL) || pgoptions[0] == '\0')
{
if ((tmp = getenv("PGOPTIONS")) == NULL)
tmp = DefaultOption;
conn->pgoptions = strdup(tmp);
}
else
conn->pgoptions = strdup(pgoptions);
if (login)
conn->pguser = strdup(login);
else if ((tmp = getenv("PGUSER")) != NULL)
conn->pguser = strdup(tmp);
else
conn->pguser = fe_getauthname(conn->errorMessage);
if (conn->pguser == NULL)
{
error = TRUE;
sprintf(conn->errorMessage,
"FATAL: PQsetdbLogin(): Unable to determine a Postgres username!\n");
}
if (pwd)
conn->pgpass = strdup(pwd);
else if ((tmp = getenv("PGPASSWORD")) != NULL)
conn->pgpass = strdup(tmp);
else
conn->pgpass = strdup(DefaultPassword);
if ((dbName == NULL) || dbName[0] == '\0')
{
if ((tmp = getenv("PGDATABASE")) != NULL)
conn->dbName = strdup(tmp);
else if (conn->pguser)
conn->dbName = strdup(conn->pguser);
}
else
conn->dbName = strdup(dbName);
if (conn->dbName)
{
/*
* if the database name is surrounded by double-quotes, then
* don't convert case
*/
if (*conn->dbName == '"')
{
strcpy(conn->dbName, conn->dbName + 1);
conn->dbName[strlen(conn->dbName) - 1] = '\0';
}
else
for (i = 0; conn->dbName[i]; i++)
if (isascii((unsigned char)conn->dbName[i]) &&
isupper(conn->dbName[i]))
conn->dbName[i] = tolower(conn->dbName[i]);
}
if (error)
conn->status = CONNECTION_BAD;
else
conn->status = connectDB(conn);
return conn;
}
/*
* update_db_info -
* get all additional infos out of dbName
*
*/
static int
update_db_info(PGconn *conn)
{
char *tmp, *old = conn->dbName;
if (strchr(conn->dbName, '@') != NULL)
{
/* old style: dbname[@server][:port] */
tmp = strrchr(conn->dbName, ':');
if (tmp != NULL) /* port number given */
{
conn->pgport = strdup(tmp + 1);
*tmp = '\0';
}
tmp = strrchr(conn->dbName, '@');
if (tmp != NULL) /* host name given */
{
conn->pghost = strdup(tmp + 1);
*tmp = '\0';
}
conn->dbName = strdup(old);
free(old);
}
else
{
int offset;
/*
* only allow protocols tcp and unix
*/
if (strncmp(conn->dbName, "tcp:", 4) == 0)
offset = 4;
else if (strncmp(conn->dbName, "unix:", 5) == 0)
offset = 5;
else return 0;
if (strncmp(conn->dbName + offset, "postgresql://", strlen("postgresql://")) == 0)
{
/* new style: <tcp|unix>:postgresql://server[:port][/dbname][?options] */
offset += strlen("postgresql://");
tmp = strrchr(conn->dbName + offset, '?');
if (tmp != NULL) /* options given */
{
conn->pgoptions = strdup(tmp + 1);
*tmp = '\0';
}
tmp = strrchr(conn->dbName + offset, '/');
if (tmp != NULL) /* database name given */
{
conn->dbName = strdup(tmp + 1);
*tmp = '\0';
}
else
{
if ((tmp = getenv("PGDATABASE")) != NULL)
conn->dbName = strdup(tmp);
else if (conn->pguser)
conn->dbName = strdup(conn->pguser);
}
tmp = strrchr(old + offset, ':');
if (tmp != NULL) /* port number given */
{
conn->pgport = strdup(tmp + 1);
*tmp = '\0';
}
if (strncmp(old, "unix:", 5) == 0)
{
conn->pghost = NULL;
if (strcmp(old + offset, "localhost") != 0)
{
(void) sprintf(conn->errorMessage,
"connectDB() -- non-tcp access only possible on localhost\n");
return 1;
}
}
else conn->pghost = strdup(old + offset);
free(old);
}
}
return 0;
}
/*
* connectDB -
* make a connection to the backend so it is ready to receive queries.
* return CONNECTION_OK if successful, CONNECTION_BAD if not.
*
*/
static ConnStatusType
connectDB(PGconn *conn)
{
PGresult *res;
struct hostent *hp;
StartupPacket sp;
AuthRequest areq;
int laddrlen = sizeof(SockAddr);
int portno,
family;
char beresp;
int on = 1;
/*
* parse dbName to get all additional info in it, if any
*/
if (update_db_info(conn) != 0)
goto connect_errReturn;
/*
* Initialize the startup packet.
*/
MemSet((char *) &sp, 0, sizeof(StartupPacket));
sp.protoVersion = (ProtocolVersion) htonl(PG_PROTOCOL_LATEST);
strncpy(sp.user, conn->pguser, SM_USER);
strncpy(sp.database, conn->dbName, SM_DATABASE);
strncpy(sp.tty, conn->pgtty, SM_TTY);
if (conn->pgoptions)
strncpy(sp.options, conn->pgoptions, SM_OPTIONS);
/*
* Open a connection to postmaster/backend.
*/
if (conn->pghost != NULL)
{
hp = gethostbyname(conn->pghost);
if ((hp == NULL) || (hp->h_addrtype != AF_INET))
{
(void) sprintf(conn->errorMessage,
"connectDB() -- unknown hostname: %s\n",
conn->pghost);
goto connect_errReturn;
}
family = AF_INET;
}
else {
hp = NULL;
family = AF_UNIX;
}
MemSet((char *) &conn->raddr, 0, sizeof(conn->raddr));
conn->raddr.sa.sa_family = family;
portno = atoi(conn->pgport);
if (family == AF_INET)
{
memmove((char *) &(conn->raddr.in.sin_addr),
(char *) hp->h_addr,
hp->h_length);
conn->raddr.in.sin_port = htons((unsigned short) (portno));
conn->raddr_len = sizeof(struct sockaddr_in);
}
#ifndef WIN32
else
conn->raddr_len = UNIXSOCK_PATH(conn->raddr.un, portno);
#endif
/* Connect to the server */
if ((conn->sock = socket(family, SOCK_STREAM, 0)) < 0)
{
(void) sprintf(conn->errorMessage,
"connectDB() -- socket() failed: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
if (connect(conn->sock, &conn->raddr.sa, conn->raddr_len) < 0)
{
(void) sprintf(conn->errorMessage,
"connectDB() failed: Is the postmaster running and accepting%s connections at '%s' on port '%s'?\n",
conn->pghost ? " TCP/IP(with -i)" : "",
conn->pghost ? conn->pghost : "UNIX Socket",
conn->pgport);
goto connect_errReturn;
}
/*
* Set the right options.
* We need nonblocking I/O, and we don't want delay of outgoing data.
*/
#ifndef WIN32
if (fcntl(conn->sock, F_SETFL, O_NONBLOCK) < 0)
#else
if (ioctlsocket(conn->sock,FIONBIO, &on) != 0)
#endif
{
(void) sprintf(conn->errorMessage,
"connectDB() -- fcntl() failed: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
if (family == AF_INET)
{
struct protoent *pe;
pe = getprotobyname("TCP");
if (pe == NULL)
{
(void) sprintf(conn->errorMessage,
"connectDB(): getprotobyname failed\n");
goto connect_errReturn;
}
if (setsockopt(conn->sock, pe->p_proto, TCP_NODELAY,
#ifdef WIN32
(char *)
#endif
&on,
sizeof(on)) < 0)
{
(void) sprintf(conn->errorMessage,
"connectDB() -- setsockopt failed: errno=%d\n%s\n",
errno, strerror(errno));
#ifdef WIN32
printf("Winsock error: %i\n",WSAGetLastError());
#endif
goto connect_errReturn;
}
}
/* Fill in the client address */
if (getsockname(conn->sock, &conn->laddr.sa, &laddrlen) < 0)
{
(void) sprintf(conn->errorMessage,
"connectDB() -- getsockname() failed: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
/* Ensure our buffers are empty */
conn->inStart = conn->inCursor = conn->inEnd = 0;
conn->outCount = 0;
/* Send the startup packet. */
if (packetSend(conn, (char *) &sp, sizeof(StartupPacket)) != STATUS_OK)
{
sprintf(conn->errorMessage,
"connectDB() -- couldn't send startup packet: errno=%d\n%s\n",
errno, strerror(errno));
goto connect_errReturn;
}
/*
* Perform the authentication exchange:
* wait for backend messages and respond as necessary.
* We fall out of this loop when done talking to the postmaster.
*/
for (;;)
{
/* Wait for some data to arrive (or for the channel to close) */
if (pqWait(TRUE, FALSE, conn))
goto connect_errReturn;
/* Load data, or detect EOF */
if (pqReadData(conn) < 0)
goto connect_errReturn;
/* Scan the message.
* If we run out of data, loop around to try again.
*/
conn->inCursor = conn->inStart;
if (pqGetc(&beresp, conn))
continue; /* no data yet */
/* Handle errors. */
if (beresp == 'E')
{
if (pqGets(conn->errorMessage, sizeof(conn->errorMessage), conn))
continue;
goto connect_errReturn;
}
/* Otherwise it should be an authentication request. */
if (beresp != 'R')
{
(void) sprintf(conn->errorMessage,
"connectDB() -- expected authentication request\n");
goto connect_errReturn;
}
/* Get the type of request. */
if (pqGetInt((int *) &areq, 4, conn))
continue;
/* Get the password salt if there is one. */
if (areq == AUTH_REQ_CRYPT)
{
if (pqGetnchar(conn->salt, sizeof(conn->salt), conn))
continue;
}
/* OK, we successfully read the message; mark data consumed */
conn->inStart = conn->inCursor;
/* Respond to the request if necessary. */
if (fe_sendauth(areq, conn, conn->pghost, conn->pgpass,
conn->errorMessage) != STATUS_OK)
goto connect_errReturn;
if (pqFlush(conn))
goto connect_errReturn;
/* Are we done? */
if (areq == AUTH_REQ_OK)
break;
}
/*
* Now we expect to hear from the backend.
* A ReadyForQuery message indicates that startup is successful,
* but we might also get an Error message indicating failure.
* (Notice messages indicating nonfatal warnings are also allowed
* by the protocol, as is a BackendKeyData message.)
* Easiest way to handle this is to let PQgetResult() read the messages.
* We just have to fake it out about the state of the connection.
*/
conn->status = CONNECTION_OK;
conn->asyncStatus = PGASYNC_BUSY;
res = PQgetResult(conn);
/* NULL return indicating we have gone to IDLE state is expected */
if (res) {
if (res->resultStatus != PGRES_FATAL_ERROR)
sprintf(conn->errorMessage,
"connectDB() -- unexpected message during startup\n");
PQclear(res);
goto connect_errReturn;
}
/* Given the new protocol that sends a ReadyForQuery message
* after successful backend startup, it should no longer be
* necessary to send an empty query to test for startup.
*/
#if 0
/*
* Send a blank query to make sure everything works; in
* particular, that the database exists.
*/
res = PQexec(conn, " ");
if (res == NULL || res->resultStatus != PGRES_EMPTY_QUERY)
{
/* PQexec has put error message in conn->errorMessage */
closePGconn(conn);
PQclear(res);
goto connect_errReturn;
}
PQclear(res);
#endif
/* Post-connection housekeeping.
* Send environment variables to server
*/
PQsetenv(conn);
return CONNECTION_OK;
connect_errReturn:
if (conn->sock >= 0)
{
#ifdef WIN32
closesocket(conn->sock);
#else
close(conn->sock);
#endif
conn->sock = -1;
}
return CONNECTION_BAD;
}
void
PQsetenv(PGconn *conn)
{
struct EnvironmentOptions *eo;
char setQuery[80]; /* mjl: size okay? XXX */
for (eo = EnvironmentOptions; eo->envName; eo++)
{
const char *val;
if ((val = getenv(eo->envName)))
{
PGresult *res;
if (strcasecmp(val, "default") == 0)
sprintf(setQuery, "SET %s = %.60s", eo->pgName, val);
else
sprintf(setQuery, "SET %s = '%.60s'", eo->pgName, val);
#ifdef CONNECTDEBUG
printf("Use environment variable %s to send %s\n", eo->envName, setQuery);
#endif
res = PQexec(conn, setQuery);
PQclear(res); /* Don't care? */
}
}
} /* PQsetenv() */
/*
* makeEmptyPGconn
* - create a PGconn data structure with (as yet) no interesting data
*/
static PGconn *
makeEmptyPGconn(void)
{
PGconn *conn = (PGconn *) malloc(sizeof(PGconn));
if (conn == NULL)
return conn;
/* Zero all pointers */
MemSet((char *) conn, 0, sizeof(PGconn));
conn->status = CONNECTION_BAD;
conn->asyncStatus = PGASYNC_IDLE;
conn->notifyList = DLNewList();
conn->sock = -1;
conn->inBufSize = 8192;
conn->inBuffer = (char *) malloc(conn->inBufSize);
conn->outBufSize = 8192;
conn->outBuffer = (char *) malloc(conn->outBufSize);
if (conn->inBuffer == NULL || conn->outBuffer == NULL)
{
freePGconn(conn);
conn = NULL;
}
return conn;
}
/*
* freePGconn
* - free the PGconn data structure
*
*/
static void
freePGconn(PGconn *conn)
{
if (!conn)
return;
PQclearAsyncResult(conn); /* deallocate result and curTuple */
if (conn->sock >= 0)
#ifdef WIN32
closesocket(conn->sock);
#else
close(conn->sock);
#endif
if (conn->pghost)
free(conn->pghost);
if (conn->pgport)
free(conn->pgport);
if (conn->pgtty)
free(conn->pgtty);
if (conn->pgoptions)
free(conn->pgoptions);
if (conn->dbName)
free(conn->dbName);
if (conn->pguser)
free(conn->pguser);
if (conn->pgpass)
free(conn->pgpass);
/* Note that conn->Pfdebug is not ours to close or free */
if (conn->notifyList)
DLFreeList(conn->notifyList);
if (conn->lobjfuncs)
free(conn->lobjfuncs);
if (conn->inBuffer)
free(conn->inBuffer);
if (conn->outBuffer)
free(conn->outBuffer);
free(conn);
}
/*
closePGconn
- properly close a connection to the backend
*/
static void
closePGconn(PGconn *conn)
{
if (conn->sock >= 0)
{
/*
* Try to send close message.
* If connection is already gone, that's cool. No reason for kernel
* to kill us when we try to write to it. So ignore SIGPIPE signals.
*/
#ifndef WIN32
#if defined(USE_POSIX_SIGNALS)
struct sigaction ignore_action;
struct sigaction oldaction;
ignore_action.sa_handler = SIG_IGN;
sigemptyset(&ignore_action.sa_mask);
ignore_action.sa_flags = 0;
sigaction(SIGPIPE, (struct sigaction *) & ignore_action, &oldaction);
(void) pqPuts("X", conn);
(void) pqFlush(conn);
sigaction(SIGPIPE, &oldaction, NULL);
#else
void (*oldsignal)(int);
oldsignal = signal(SIGPIPE, SIG_IGN);
(void) pqPuts("X", conn);
(void) pqFlush(conn);
signal(SIGPIPE, oldsignal);
#endif
#endif /* Win32 uses no signals at all */
}
/*
* Close the connection, reset all transient state, flush I/O buffers.
*/
if (conn->sock >= 0)
#ifdef WIN32
closesocket(conn->sock);
#else
close(conn->sock);
#endif
conn->sock = -1;
conn->status = CONNECTION_BAD; /* Well, not really _bad_ - just
* absent */
conn->asyncStatus = PGASYNC_IDLE;
PQclearAsyncResult(conn); /* deallocate result and curTuple */
if (conn->lobjfuncs)
free(conn->lobjfuncs);
conn->lobjfuncs = NULL;
conn->inStart = conn->inCursor = conn->inEnd = 0;
conn->outCount = 0;
}
/*
PQfinish:
properly close a connection to the backend
also frees the PGconn data structure so it shouldn't be re-used
after this
*/
void
PQfinish(PGconn *conn)
{
if (!conn)
fprintf(stderr, "PQfinish() -- pointer to PGconn is null\n");
else
{
closePGconn(conn);
freePGconn(conn);
}
}
/* PQreset :
resets the connection to the backend
closes the existing connection and makes a new one
*/
void
PQreset(PGconn *conn)
{
if (!conn)
fprintf(stderr, "PQreset() -- pointer to PGconn is null\n");
else
{
closePGconn(conn);
conn->status = connectDB(conn);
}
}
/*
* PQrequestCancel: attempt to request cancellation of the current operation.
*
* The return value is TRUE if the cancel request was successfully
* dispatched, FALSE if not (in which case errorMessage is set).
* Note: successful dispatch is no guarantee that there will be any effect at
* the backend. The application must read the operation result as usual.
*
* CAUTION: we want this routine to be safely callable from a signal handler
* (for example, an application might want to call it in a SIGINT handler).
* This means we cannot use any C library routine that might be non-reentrant.
* malloc/free are often non-reentrant, and anything that might call them is
* just as dangerous. We avoid sprintf here for that reason. Building up
* error messages with strcpy/strcat is tedious but should be quite safe.
*/
int
PQrequestCancel(PGconn *conn)
{
int tmpsock = -1;
struct {
uint32 packetlen;
CancelRequestPacket cp;
} crp;
/* Check we have an open connection */
if (!conn)
return FALSE;
if (conn->sock < 0)
{
strcpy(conn->errorMessage,
"PQrequestCancel() -- connection is not open\n");
return FALSE;
}
/*
* We need to open a temporary connection to the postmaster.
* Use the information saved by connectDB to do this with
* only kernel calls.
*/
if ((tmpsock = socket(conn->raddr.sa.sa_family, SOCK_STREAM, 0)) < 0)
{
strcpy(conn->errorMessage, "PQrequestCancel() -- socket() failed: ");
goto cancel_errReturn;
}
if (connect(tmpsock, &conn->raddr.sa, conn->raddr_len) < 0)
{
strcpy(conn->errorMessage, "PQrequestCancel() -- connect() failed: ");
goto cancel_errReturn;
}
/*
* We needn't set nonblocking I/O or NODELAY options here.
*/
/* Create and send the cancel request packet. */
crp.packetlen = htonl((uint32) sizeof(crp));
crp.cp.cancelRequestCode = (MsgType) htonl(CANCEL_REQUEST_CODE);
crp.cp.backendPID = htonl(conn->be_pid);
crp.cp.cancelAuthCode = htonl(conn->be_key);
if (send(tmpsock, (char*) &crp, sizeof(crp), 0) != (int) sizeof(crp))
{
strcpy(conn->errorMessage, "PQrequestCancel() -- send() failed: ");
goto cancel_errReturn;
}
/* Sent it, done */
#ifdef WIN32
closesocket(tmpsock);
#else
close(tmpsock);
#endif
return TRUE;
cancel_errReturn:
strcat(conn->errorMessage, strerror(errno));
strcat(conn->errorMessage, "\n");
if (tmpsock >= 0)
{
#ifdef WIN32
closesocket(tmpsock);
#else
close(tmpsock);
#endif
}
return FALSE;
}
/*
* PacketSend() -- send a single-packet message.
* this is like PacketSend(), defined in backend/libpq/pqpacket.c
*
* RETURNS: STATUS_ERROR if the write fails, STATUS_OK otherwise.
* SIDE_EFFECTS: may block.
*/
int
packetSend(PGconn *conn, const char *buf, size_t len)
{
/* Send the total packet size. */
if (pqPutInt(4 + len, 4, conn))
return STATUS_ERROR;
/* Send the packet itself. */
if (pqPutnchar(buf, len, conn))
return STATUS_ERROR;
if (pqFlush(conn))
return STATUS_ERROR;
return STATUS_OK;
}
/* ----------------
* Conninfo parser routine
* ----------------
*/
static int
conninfo_parse(const char *conninfo, char *errorMessage)
{
char *pname;
char *pval;
char *buf;
char *tmp;
char *cp;
char *cp2;
PQconninfoOption *option;
char errortmp[ERROR_MSG_LENGTH];
conninfo_free();
if ((buf = strdup(conninfo)) == NULL)
{
strcpy(errorMessage,
"FATAL: cannot allocate memory for copy of conninfo string\n");
return -1;
}
cp = buf;
while (*cp)
{
/* Skip blanks before the parameter name */
if (isspace(*cp))
{
cp++;
continue;
}
/* Get the parameter name */
pname = cp;
while (*cp)
{
if (*cp == '=')
break;
if (isspace(*cp))
{
*cp++ = '\0';
while (*cp)
{
if (!isspace(*cp))
break;
cp++;
}
break;
}
cp++;
}
/* Check that there is a following '=' */
if (*cp != '=')
{
sprintf(errorMessage,
"ERROR: PQconnectdb() - Missing '=' after '%s' in conninfo\n",
pname);
free(buf);
return -1;
}
*cp++ = '\0';
/* Skip blanks after the '=' */
while (*cp)
{
if (!isspace(*cp))
break;
cp++;
}
pval = cp;
if (*cp != '\'')
{
cp2 = pval;
while (*cp)
{
if (isspace(*cp))
{
*cp++ = '\0';
break;
}
if (*cp == '\\')
{
cp++;
if (*cp != '\0')
*cp2++ = *cp++;
}
else
*cp2++ = *cp++;
}
*cp2 = '\0';
}
else
{
cp2 = pval;
cp++;
for (;;)
{
if (*cp == '\0')
{
sprintf(errorMessage,
"ERROR: PQconnectdb() - unterminated quoted string in conninfo\n");
free(buf);
return -1;
}
if (*cp == '\\')
{
cp++;
if (*cp != '\0')
*cp2++ = *cp++;
continue;
}
if (*cp == '\'')
{
*cp2 = '\0';
cp++;
break;
}
*cp2++ = *cp++;
}
}
/* ----------
* Now we have the name and the value. Search
* for the param record.
* ----------
*/
for (option = PQconninfoOptions; option->keyword != NULL; option++)
{
if (!strcmp(option->keyword, pname))
break;
}
if (option->keyword == NULL)
{
sprintf(errorMessage,
"ERROR: PQconnectdb() - unknown option '%s'\n",
pname);
free(buf);
return -1;
}
/* ----------
* Store the value
* ----------
*/
option->val = strdup(pval);
}
free(buf);
/* ----------
* Get the fallback resources for parameters not specified
* in the conninfo string.
* ----------
*/
for (option = PQconninfoOptions; option->keyword != NULL; option++)
{
if (option->val != NULL)
continue; /* Value was in conninfo */
/* ----------
* Try to get the environment variable fallback
* ----------
*/
if (option->environ != NULL)
{
if ((tmp = getenv(option->environ)) != NULL)
{
option->val = strdup(tmp);
continue;
}
}
/* ----------
* No environment variable specified or this one isn't set -
* try compiled in
* ----------
*/
if (option->compiled != NULL)
{
option->val = strdup(option->compiled);
continue;
}
/* ----------
* Special handling for user
* ----------
*/
if (!strcmp(option->keyword, "user"))
{
tmp = fe_getauthname(errortmp);
if (tmp)
option->val = strdup(tmp);
}
/* ----------
* Special handling for dbname
* ----------
*/
if (!strcmp(option->keyword, "dbname"))
{
tmp = conninfo_getval("user");
if (tmp)
option->val = strdup(tmp);
}
}
return 0;
}
static char *
conninfo_getval(char *keyword)
{
PQconninfoOption *option;
for (option = PQconninfoOptions; option->keyword != NULL; option++)
{
if (!strcmp(option->keyword, keyword))
return option->val;
}
return NULL;
}
static void
conninfo_free()
{
PQconninfoOption *option;
for (option = PQconninfoOptions; option->keyword != NULL; option++)
{
if (option->val != NULL)
{
free(option->val);
option->val = NULL;
}
}
}
/* =========== accessor functions for PGconn ========= */
char *
PQdb(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQdb() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->dbName;
}
char *
PQuser(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQuser() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->pguser;
}
char *
PQhost(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQhost() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->pghost;
}
char *
PQoptions(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQoptions() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->pgoptions;
}
char *
PQtty(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQtty() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->pgtty;
}
char *
PQport(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQport() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->pgport;
}
ConnStatusType
PQstatus(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQstatus() -- pointer to PGconn is null\n");
return CONNECTION_BAD;
}
return conn->status;
}
char *
PQerrorMessage(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQerrorMessage() -- pointer to PGconn is null\n");
return (char *) NULL;
}
return conn->errorMessage;
}
int
PQsocket(PGconn *conn)
{
if (!conn)
{
fprintf(stderr, "PQsocket() -- pointer to PGconn is null\n");
return -1;
}
return conn->sock;
}
void
PQtrace(PGconn *conn, FILE *debug_port)
{
if (conn == NULL ||
conn->status == CONNECTION_BAD)
return;
PQuntrace(conn);
conn->Pfdebug = debug_port;
}
void
PQuntrace(PGconn *conn)
{
/* note: better allow untrace even when connection bad */
if (conn == NULL)
return;
if (conn->Pfdebug)
{
fflush(conn->Pfdebug);
conn->Pfdebug = NULL;
}
}