/*-------------------------------------------------------------------------
*
* fe - connect . c
* functions related to setting up a connection to the backend
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
*
* IDENTIFICATION
* src / interfaces / libpq / fe - connect . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres_fe.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <limits.h>
#include <netdb.h>
#include <time.h>
#include <unistd.h>
#include "common/base64.h"
#include "common/ip.h"
#include "common/link-canary.h"
#include "common/scram-common.h"
#include "common/string.h"
#include "fe-auth.h"
#include "fe-auth-oauth.h"
#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#include "pg_config_paths.h"
#include "port/pg_bswap.h"
#ifdef WIN32
#include "win32.h"
#ifdef _WIN32_IE
#undef _WIN32_IE
#endif
#define _WIN32_IE 0 x0500
#ifdef near
#undef near
#endif
#define near
#include <shlobj.h>
#include <mstcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <pwd.h>
#endif
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#ifdef USE_LDAP
#ifdef WIN32
#include <winldap.h>
#else
/* OpenLDAP deprecates RFC 1823, but we want standard conformance */
#define LDAP_DEPRECATED 1
#include <ldap.h>
typedef struct timeval LDAP_TIMEVAL;
#endif
static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
PQExpBuffer errorMessage);
#endif
#ifndef WIN32
#define PGPASSFILE ".pgpass"
#else
#define PGPASSFILE "pgpass.conf"
#endif
/*
* Pre - 9 . 0 servers will return this SQLSTATE if asked to set
* application_name in a startup packet . We hard - wire the value rather
* than looking into errcodes . h since it reflects historical behavior
* rather than that of the current code .
*/
#define ERRCODE_APPNAME_UNKNOWN "42704"
/* This is part of the protocol so just define it */
#define ERRCODE_INVALID_PASSWORD "28P01"
/* This too */
#define ERRCODE_CANNOT_CONNECT_NOW "57P03"
/*
* Cope with the various platform - specific ways to spell TCP keepalive socket
* options . This doesn ' t cover Windows , which as usual does its own thing .
*/
#if defined (TCP_KEEPIDLE)
/* TCP_KEEPIDLE is the name of this option on Linux and *BSD */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPIDLE
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPIDLE"
#elif defined (TCP_KEEPALIVE_THRESHOLD)
/* TCP_KEEPALIVE_THRESHOLD is the name of this option on Solaris >= 11 */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPALIVE_THRESHOLD
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPALIVE_THRESHOLD"
#elif defined (TCP_KEEPALIVE) && defined (__darwin__)
/* TCP_KEEPALIVE is the name of this option on macOS */
/* Caution: Solaris has this symbol but it means something different */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPALIVE
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPALIVE"
#endif
/*
* fall back options if they are not specified by arguments or defined
* by environment variables
*/
#define DefaultHost "localhost"
#define DefaultOption ""
#ifdef USE_SSL
#define DefaultChannelBinding "prefer"
#else
#define DefaultChannelBinding "disable"
#endif
#define DefaultTargetSessionAttrs "any"
#define DefaultLoadBalanceHosts "disable"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
#define DefaultSSLCertMode "allow"
#else
#define DefaultSSLMode "disable"
#define DefaultSSLCertMode "disable"
#endif
#define DefaultSSLNegotiation "postgres"
#ifdef ENABLE_GSS
#include "fe-gssapi-common.h"
#define DefaultGSSMode "prefer"
#else
#define DefaultGSSMode "disable"
#endif
/* ----------
* 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 value for the username is treated specially in conninfo_add_defaults .
* If the value is not obtained any other way , the username is determined
* by pg_fe_getauthname ( ) .
*
* 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
* " * " Password field - hide value
* " D " Debug option - don ' t show by default
*
* NB : Server - side clients - - dblink , postgres_fdw , libpqrcv - - use dispchar to
* determine which options to expose to end users , and how . Changing dispchar
* has compatibility and security implications for those clients . For example ,
* postgres_fdw will attach a " * " option to USER MAPPING instead of the default
* SERVER , and it disallows setting " D " options entirely .
*
* PQconninfoOptions [ ] is a constant static array that we use to initialize
* a dynamically allocated working copy . All the " val " fields in
* PQconninfoOptions [ ] * must * be NULL . In a working copy , non - null " val "
* fields point to malloc ' d strings that should be freed when the working
* array is freed ( see PQconninfoFree ) .
*
* The first part of each struct is identical to the one in libpq - fe . h ,
* which is required since we memcpy ( ) data between the two !
* - - - - - - - - - -
*/
typedef struct _internalPQconninfoOption
{
char *keyword; /* The keyword of the option */
char *envvar; /* Fallback environment variable name */
char *compiled; /* Fallback compiled in default value */
char *val; /* Option's current value, or NULL */
char *label; /* Label for field in connect dialog */
char *dispchar; /* Indicates how to display this field in a
* connect dialog . Values are : " " Display
* entered value as is " * " Password field -
* hide value " D " Debug option - don ' t show
* by default */
int dispsize; /* Field size in characters for dialog */
/* ---
* Anything above this comment must be synchronized with
* PQconninfoOption in libpq - fe . h , since we memcpy ( ) data
* between them !
* - - -
*/
off_t connofs; /* Offset into PGconn struct, -1 if not there */
} internalPQconninfoOption;
static const internalPQconninfoOption PQconninfoOptions[] = {
{"service" , "PGSERVICE" , NULL, NULL,
"Database-Service" , "" , 20 ,
offsetof(struct pg_conn, pgservice)},
{"user" , "PGUSER" , NULL, NULL,
"Database-User" , "" , 20 ,
offsetof(struct pg_conn, pguser)},
{"password" , "PGPASSWORD" , NULL, NULL,
"Database-Password" , "*" , 20 ,
offsetof(struct pg_conn, pgpass)},
{"passfile" , "PGPASSFILE" , NULL, NULL,
"Database-Password-File" , "" , 64 ,
offsetof(struct pg_conn, pgpassfile)},
{"channel_binding" , "PGCHANNELBINDING" , DefaultChannelBinding, NULL,
"Channel-Binding" , "" , 8 , /* sizeof("require") == 8 */
offsetof(struct pg_conn, channel_binding)},
{"connect_timeout" , "PGCONNECT_TIMEOUT" , NULL, NULL,
"Connect-timeout" , "" , 10 , /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, connect_timeout)},
{"dbname" , "PGDATABASE" , NULL, NULL,
"Database-Name" , "" , 20 ,
offsetof(struct pg_conn, dbName)},
{"host" , "PGHOST" , NULL, NULL,
"Database-Host" , "" , 40 ,
offsetof(struct pg_conn, pghost)},
{"hostaddr" , "PGHOSTADDR" , NULL, NULL,
"Database-Host-IP-Address" , "" , 45 ,
offsetof(struct pg_conn, pghostaddr)},
{"port" , "PGPORT" , DEF_PGPORT_STR, NULL,
"Database-Port" , "" , 6 ,
offsetof(struct pg_conn, pgport)},
{"client_encoding" , "PGCLIENTENCODING" , NULL, NULL,
"Client-Encoding" , "" , 10 ,
offsetof(struct pg_conn, client_encoding_initial)},
{"options" , "PGOPTIONS" , DefaultOption, NULL,
"Backend-Options" , "" , 40 ,
offsetof(struct pg_conn, pgoptions)},
{"application_name" , "PGAPPNAME" , NULL, NULL,
"Application-Name" , "" , 64 ,
offsetof(struct pg_conn, appname)},
{"fallback_application_name" , NULL, NULL, NULL,
"Fallback-Application-Name" , "" , 64 ,
offsetof(struct pg_conn, fbappname)},
{"keepalives" , NULL, NULL, NULL,
"TCP-Keepalives" , "" , 1 , /* should be just '0' or '1' */
offsetof(struct pg_conn, keepalives)},
{"keepalives_idle" , NULL, NULL, NULL,
"TCP-Keepalives-Idle" , "" , 10 , /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, keepalives_idle)},
{"keepalives_interval" , NULL, NULL, NULL,
"TCP-Keepalives-Interval" , "" , 10 , /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, keepalives_interval)},
{"keepalives_count" , NULL, NULL, NULL,
"TCP-Keepalives-Count" , "" , 10 , /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, keepalives_count)},
{"tcp_user_timeout" , NULL, NULL, NULL,
"TCP-User-Timeout" , "" , 10 , /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, pgtcp_user_timeout)},
/*
* ssl options are allowed even without client SSL support because the
* client can still handle SSL modes " disable " and " allow " . Other
* parameters have no effect on non - SSL connections , so there is no reason
* to exclude them since none of them are mandatory .
*/
{"sslmode" , "PGSSLMODE" , DefaultSSLMode, NULL,
"SSL-Mode" , "" , 12 , /* sizeof("verify-full") == 12 */
offsetof(struct pg_conn, sslmode)},
{"sslnegotiation" , "PGSSLNEGOTIATION" , DefaultSSLNegotiation, NULL,
"SSL-Negotiation" , "" , 9 , /* sizeof("postgres") == 9 */
offsetof(struct pg_conn, sslnegotiation)},
{"sslcompression" , "PGSSLCOMPRESSION" , "0" , NULL,
"SSL-Compression" , "" , 1 ,
offsetof(struct pg_conn, sslcompression)},
{"sslcert" , "PGSSLCERT" , NULL, NULL,
"SSL-Client-Cert" , "" , 64 ,
offsetof(struct pg_conn, sslcert)},
{"sslkey" , "PGSSLKEY" , NULL, NULL,
"SSL-Client-Key" , "" , 64 ,
offsetof(struct pg_conn, sslkey)},
{"sslcertmode" , "PGSSLCERTMODE" , NULL, NULL,
"SSL-Client-Cert-Mode" , "" , 8 , /* sizeof("disable") == 8 */
offsetof(struct pg_conn, sslcertmode)},
{"sslpassword" , NULL, NULL, NULL,
"SSL-Client-Key-Password" , "*" , 20 ,
offsetof(struct pg_conn, sslpassword)},
{"sslrootcert" , "PGSSLROOTCERT" , NULL, NULL,
"SSL-Root-Certificate" , "" , 64 ,
offsetof(struct pg_conn, sslrootcert)},
{"sslcrl" , "PGSSLCRL" , NULL, NULL,
"SSL-Revocation-List" , "" , 64 ,
offsetof(struct pg_conn, sslcrl)},
{"sslcrldir" , "PGSSLCRLDIR" , NULL, NULL,
"SSL-Revocation-List-Dir" , "" , 64 ,
offsetof(struct pg_conn, sslcrldir)},
{"sslsni" , "PGSSLSNI" , "1" , NULL,
"SSL-SNI" , "" , 1 ,
offsetof(struct pg_conn, sslsni)},
{"requirepeer" , "PGREQUIREPEER" , NULL, NULL,
"Require-Peer" , "" , 10 ,
offsetof(struct pg_conn, requirepeer)},
{"require_auth" , "PGREQUIREAUTH" , NULL, NULL,
"Require-Auth" , "" , 14 , /* sizeof("scram-sha-256") == 14 */
offsetof(struct pg_conn, require_auth)},
{"min_protocol_version" , "PGMINPROTOCOLVERSION" ,
NULL, NULL,
"Min-Protocol-Version" , "" , 6 , /* sizeof("latest") = 6 */
offsetof(struct pg_conn, min_protocol_version)},
{"max_protocol_version" , "PGMAXPROTOCOLVERSION" ,
NULL, NULL,
"Max-Protocol-Version" , "" , 6 , /* sizeof("latest") = 6 */
offsetof(struct pg_conn, max_protocol_version)},
{"ssl_min_protocol_version" , "PGSSLMINPROTOCOLVERSION" , "TLSv1.2" , NULL,
"SSL-Minimum-Protocol-Version" , "" , 8 , /* sizeof("TLSv1.x") == 8 */
offsetof(struct pg_conn, ssl_min_protocol_version)},
{"ssl_max_protocol_version" , "PGSSLMAXPROTOCOLVERSION" , NULL, NULL,
"SSL-Maximum-Protocol-Version" , "" , 8 , /* sizeof("TLSv1.x") == 8 */
offsetof(struct pg_conn, ssl_max_protocol_version)},
/*
* As with SSL , all GSS options are exposed even in builds that don ' t have
* support .
*/
{"gssencmode" , "PGGSSENCMODE" , DefaultGSSMode, NULL,
"GSSENC-Mode" , "" , 8 , /* sizeof("disable") == 8 */
offsetof(struct pg_conn, gssencmode)},
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname" , "PGKRBSRVNAME" , PG_KRB_SRVNAM, NULL,
"Kerberos-service-name" , "" , 20 ,
offsetof(struct pg_conn, krbsrvname)},
{"gsslib" , "PGGSSLIB" , NULL, NULL,
"GSS-library" , "" , 7 , /* sizeof("gssapi") == 7 */
offsetof(struct pg_conn, gsslib)},
{"gssdelegation" , "PGGSSDELEGATION" , "0" , NULL,
"GSS-delegation" , "" , 1 ,
offsetof(struct pg_conn, gssdelegation)},
{"replication" , NULL, NULL, NULL,
"Replication" , "D" , 5 ,
offsetof(struct pg_conn, replication)},
{"target_session_attrs" , "PGTARGETSESSIONATTRS" ,
DefaultTargetSessionAttrs, NULL,
"Target-Session-Attrs" , "" , 15 , /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
{"load_balance_hosts" , "PGLOADBALANCEHOSTS" ,
DefaultLoadBalanceHosts, NULL,
"Load-Balance-Hosts" , "" , 8 , /* sizeof("disable") = 8 */
offsetof(struct pg_conn, load_balance_hosts)},
{"scram_client_key" , NULL, NULL, NULL, "SCRAM-Client-Key" , "D" , SCRAM_MAX_KEY_LEN * 2 ,
offsetof(struct pg_conn, scram_client_key)},
{"scram_server_key" , NULL, NULL, NULL, "SCRAM-Server-Key" , "D" , SCRAM_MAX_KEY_LEN * 2 ,
offsetof(struct pg_conn, scram_server_key)},
/* OAuth v2 */
{"oauth_issuer" , NULL, NULL, NULL,
"OAuth-Issuer" , "" , 40 ,
offsetof(struct pg_conn, oauth_issuer)},
{"oauth_client_id" , NULL, NULL, NULL,
"OAuth-Client-ID" , "" , 40 ,
offsetof(struct pg_conn, oauth_client_id)},
{"oauth_client_secret" , NULL, NULL, NULL,
"OAuth-Client-Secret" , "*" , 40 ,
offsetof(struct pg_conn, oauth_client_secret)},
{"oauth_scope" , NULL, NULL, NULL,
"OAuth-Scope" , "" , 15 ,
offsetof(struct pg_conn, oauth_scope)},
{"sslkeylogfile" , NULL, NULL, NULL,
"SSL-Key-Log-File" , "D" , 64 ,
offsetof(struct pg_conn, sslkeylogfile)},
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0 }
};
static const PQEnvironmentOption EnvironmentOptions[] =
{
/* common user-interface settings */
{
"PGDATESTYLE" , "datestyle"
},
{
"PGTZ" , "timezone"
},
/* internal performance-related settings */
{
"PGGEQO" , "geqo"
},
{
NULL, NULL
}
};
static const pg_fe_sasl_mech *supported_sasl_mechs[] =
{
&pg_scram_mech,
&pg_oauth_mech,
};
#define SASL_MECHANISM_COUNT lengthof(supported_sasl_mechs)
/* The connection URI must start with either of the following designators: */
static const char uri_designator[] = "postgresql://";
static const char short_uri_designator[] = "postgres://";
static bool connectOptions1(PGconn *conn, const char *conninfo);
static bool init_allowed_encryption_methods(PGconn *conn);
#if defined (USE_SSL) || defined (ENABLE_GSS)
static int encryption_negotiation_failed(PGconn *conn);
#endif
static bool connection_failed(PGconn *conn);
static bool select_next_encryption_method(PGconn *conn, bool have_valid_connection);
static PGPing internal_ping(PGconn *conn);
static void pqFreeCommandQueue(PGcmdQueueEntry *queue);
static bool fillPGconn(PGconn *conn, PQconninfoOption *connOptions);
static void freePGconn(PGconn *conn);
static void release_conn_addrinfo(PGconn *conn);
static int store_conn_addrinfo(PGconn *conn, struct addrinfo *addrlist);
static void sendTerminateConn(PGconn *conn);
static PQconninfoOption *conninfo_init(PQExpBuffer errorMessage);
static PQconninfoOption *parse_connection_string(const char *connstr,
PQExpBuffer errorMessage, bool use_defaults);
static int uri_prefix_length(const char *connstr);
static bool recognized_connection_string(const char *connstr);
static PQconninfoOption *conninfo_parse(const char *conninfo,
PQExpBuffer errorMessage, bool use_defaults);
static PQconninfoOption *conninfo_array_parse(const char *const *keywords,
const char *const *values, PQExpBuffer errorMessage,
bool use_defaults, int expand_dbname);
static bool conninfo_add_defaults(PQconninfoOption *options,
PQExpBuffer errorMessage);
static PQconninfoOption *conninfo_uri_parse(const char *uri,
PQExpBuffer errorMessage, bool use_defaults);
static bool conninfo_uri_parse_options(PQconninfoOption *options,
const char *uri, PQExpBuffer errorMessage);
static bool conninfo_uri_parse_params(char *params,
PQconninfoOption *connOptions,
PQExpBuffer errorMessage);
static char *conninfo_uri_decode(const char *str, PQExpBuffer errorMessage);
static bool get_hexdigit(char digit, int *value);
static const char *conninfo_getval(PQconninfoOption *connOptions,
const char *keyword);
static PQconninfoOption *conninfo_storeval(PQconninfoOption *connOptions,
const char *keyword, const char *value,
PQExpBuffer errorMessage, bool ignoreMissing, bool uri_decode);
static PQconninfoOption *conninfo_find(PQconninfoOption *connOptions,
const char *keyword);
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, const char *token);
static char *passwordFromFile(const char *hostname, const char *port, const char *dbname,
const char *username, const char *pgpassfile);
static void pgpassfileWarning(PGconn *conn);
static void default_threadlock(int acquire);
static bool sslVerifyProtocolVersion(const char *version);
static bool sslVerifyProtocolRange(const char *min, const char *max);
static bool pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, const char *context);
/* global variable because fe-auth.c needs to access it */
pgthreadlock_t pg_g_threadlock = default_threadlock;
/*
* pqDropConnection
*
* Close any physical connection to the server , and reset associated
* state inside the connection object . We don ' t release state that
* would be needed to reconnect , though , nor local state that might still
* be useful later .
*
* We can always flush the output buffer , since there ' s no longer any hope
* of sending that data . However , unprocessed input data might still be
* valuable , so the caller must tell us whether to flush that or not .
*/
void
pqDropConnection(PGconn *conn, bool flushInput)
{
/* Drop any SSL state */
pqsecure_close(conn);
/* Close the socket itself */
if (conn->sock != PGINVALID_SOCKET)
closesocket(conn->sock);
conn->sock = PGINVALID_SOCKET;
/* Optionally discard any unread data */
if (flushInput)
conn->inStart = conn->inCursor = conn->inEnd = 0 ;
/* Always discard any unsent data */
conn->outCount = 0 ;
/* Likewise, discard any pending pipelined commands */
pqFreeCommandQueue(conn->cmd_queue_head);
conn->cmd_queue_head = conn->cmd_queue_tail = NULL;
pqFreeCommandQueue(conn->cmd_queue_recycle);
conn->cmd_queue_recycle = NULL;
/* Free authentication/encryption state */
if (conn->cleanup_async_auth)
{
/*
* Any in - progress async authentication should be torn down first so
* that cleanup_async_auth ( ) can depend on the other authentication
* state if necessary .
*/
conn->cleanup_async_auth(conn);
conn->cleanup_async_auth = NULL;
}
conn->async_auth = NULL;
/* cleanup_async_auth() should have done this, but make sure */
conn->altsock = PGINVALID_SOCKET;
#ifdef ENABLE_GSS
{
OM_uint32 min_s;
if (conn->gcred != GSS_C_NO_CREDENTIAL)
{
gss_release_cred(&min_s, &conn->gcred);
conn->gcred = GSS_C_NO_CREDENTIAL;
}
if (conn->gctx)
gss_delete_sec_context(&min_s, &conn->gctx, GSS_C_NO_BUFFER);
if (conn->gtarg_nam)
gss_release_name(&min_s, &conn->gtarg_nam);
if (conn->gss_SendBuffer)
{
free(conn->gss_SendBuffer);
conn->gss_SendBuffer = NULL;
}
if (conn->gss_RecvBuffer)
{
free(conn->gss_RecvBuffer);
conn->gss_RecvBuffer = NULL;
}
if (conn->gss_ResultBuffer)
{
free(conn->gss_ResultBuffer);
conn->gss_ResultBuffer = NULL;
}
conn->gssenc = false ;
}
#endif
#ifdef ENABLE_SSPI
if (conn->sspitarget)
{
free(conn->sspitarget);
conn->sspitarget = NULL;
}
if (conn->sspicred)
{
FreeCredentialsHandle(conn->sspicred);
free(conn->sspicred);
conn->sspicred = NULL;
}
if (conn->sspictx)
{
DeleteSecurityContext(conn->sspictx);
free(conn->sspictx);
conn->sspictx = NULL;
}
conn->usesspi = 0 ;
#endif
if (conn->sasl_state)
{
conn->sasl->free(conn->sasl_state);
conn->sasl_state = NULL;
}
}
/*
* pqFreeCommandQueue
* Free all the entries of PGcmdQueueEntry queue passed .
*/
static void
pqFreeCommandQueue(PGcmdQueueEntry *queue)
{
while (queue != NULL)
{
PGcmdQueueEntry *cur = queue;
queue = cur->next;
free(cur->query);
free(cur);
}
}
/*
* pqDropServerData
*
* Clear all connection state data that was received from ( or deduced about )
* the server . This is essential to do between connection attempts to
* different servers , else we may incorrectly hold over some data from the
* old server .
*
* It would be better to merge this into pqDropConnection , perhaps , but
* right now we cannot because that function is called immediately on
* detection of connection loss ( cf . pqReadData , for instance ) . This data
* should be kept until we are actually starting a new connection .
*/
static void
pqDropServerData(PGconn *conn)
{
PGnotify *notify;
pgParameterStatus *pstatus;
/* Forget pending notifies */
notify = conn->notifyHead;
while (notify != NULL)
{
PGnotify *prev = notify;
notify = notify->next;
free(prev);
}
conn->notifyHead = conn->notifyTail = NULL;
/* Reset ParameterStatus data, as well as variables deduced from it */
pstatus = conn->pstatus;
while (pstatus != NULL)
{
pgParameterStatus *prev = pstatus;
pstatus = pstatus->next;
free(prev);
}
conn->pstatus = NULL;
conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false ;
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->sversion = 0 ;
/* Drop large-object lookup data */
free(conn->lobjfuncs);
conn->lobjfuncs = NULL;
/* Reset assorted other per-connection state */
conn->last_sqlstate[0 ] = '\0' ;
conn->pversion_negotiated = false ;
conn->auth_req_received = false ;
conn->client_finished_auth = false ;
conn->password_needed = false ;
conn->gssapi_used = false ;
conn->write_failed = false ;
free(conn->write_err_msg);
conn->write_err_msg = NULL;
conn->oauth_want_retry = false ;
/*
* Cancel connections need to retain their be_pid and be_cancel_key across
* PQcancelReset invocations , otherwise they would not have access to the
* secret token of the connection they are supposed to cancel .
*/
if (!conn->cancelRequest)
{
conn->be_pid = 0 ;
if (conn->be_cancel_key != NULL)
{
free(conn->be_cancel_key);
conn->be_cancel_key = NULL;
}
conn->be_cancel_key_len = 0 ;
}
}
/*
* Connecting to a Database
*
* There are now six different ways a user of this API can connect to the
* database . Two are not recommended for use in new code , because of their
* lack of extensibility with respect to the passing of options to the
* backend . These are PQsetdb and PQsetdbLogin ( the former now being a macro
* to the latter ) .
*
* If it is desired to connect in a synchronous ( blocking ) manner , use the
* function PQconnectdb or PQconnectdbParams . The former accepts a string of
* option = value pairs ( or a URI ) which must be parsed ; the latter takes two
* NULL terminated arrays instead .
*
* To connect in an asynchronous ( non - blocking ) manner , use the functions
* PQconnectStart or PQconnectStartParams ( which differ in the same way as
* PQconnectdb and PQconnectdbParams ) and PQconnectPoll .
*
* The non - exported functions pqConnectDBStart , pqConnectDBComplete are
* part of the connection procedure implementation .
*/
/*
* PQconnectdbParams
*
* establishes a connection to a postgres backend through the postmaster
* using connection information in two arrays .
*
* The keywords array is defined as
*
* const char * params [ ] = { " option1 " , " option2 " , NULL }
*
* The values array is defined as
*
* const char * values [ ] = { " value1 " , " value2 " , NULL }
*
* Returns a PGconn * which is needed for all subsequent libpq calls , or NULL
* if a memory allocation failed .
* If the status field of the connection returned is CONNECTION_BAD ,
* then some fields may be null ' ed out instead of having valid values .
*
* You should call PQfinish ( if conn is not NULL ) regardless of whether this
* call succeeded .
*/
PGconn *
PQconnectdbParams(const char *const *keywords,
const char *const *values,
int expand_dbname)
{
PGconn *conn = PQconnectStartParams(keywords, values, expand_dbname);
if (conn && conn->status != CONNECTION_BAD)
(void ) pqConnectDBComplete(conn);
return conn;
}
/*
* PQpingParams
*
* check server status , accepting parameters identical to PQconnectdbParams
*/
PGPing
PQpingParams(const char *const *keywords,
const char *const *values,
int expand_dbname)
{
PGconn *conn = PQconnectStartParams(keywords, values, expand_dbname);
PGPing ret;
ret = internal_ping(conn);
PQfinish(conn);
return ret;
}
/*
* PQconnectdb
*
* establishes a connection to a postgres backend through the postmaster
* using connection information in a string .
*
* The conninfo string is either a whitespace - separated list of
*
* option = value
*
* definitions or a URI ( refer to the documentation for details . ) Value
* might be a single value containing no whitespaces or a single quoted
* string . If a single quote should appear anywhere in the value , it must be
* escaped with a backslash like \ '
*
* Returns a PGconn * which is needed for all subsequent libpq calls , or NULL
* if a memory allocation failed .
* If the status field of the connection returned is CONNECTION_BAD ,
* then some fields may be null ' ed out instead of having valid values .
*
* You should call PQfinish ( if conn is not NULL ) regardless of whether this
* call succeeded .
*/
PGconn *
PQconnectdb(const char *conninfo)
{
PGconn *conn = PQconnectStart(conninfo);
if (conn && conn->status != CONNECTION_BAD)
(void ) pqConnectDBComplete(conn);
return conn;
}
/*
* PQping
*
* check server status , accepting parameters identical to PQconnectdb
*/
PGPing
PQping(const char *conninfo)
{
PGconn *conn = PQconnectStart(conninfo);
PGPing ret;
ret = internal_ping(conn);
PQfinish(conn);
return ret;
}
/*
* PQconnectStartParams
*
* Begins the establishment of a connection to a postgres backend through the
* postmaster using connection information in a struct .
*
* See comment for PQconnectdbParams for the definition of the string format .
*
* Returns a PGconn * . If NULL is returned , a malloc error has occurred , and
* you should not attempt to proceed with this connection . If the status
* field of the connection returned is CONNECTION_BAD , an error has
* occurred . In this case you should call PQfinish on the result , ( perhaps
* inspecting the error message first ) . Other fields of the structure may not
* be valid if that occurs . If the status field is not CONNECTION_BAD , then
* this stage has succeeded - call PQconnectPoll , using select ( 2 ) to see when
* this is necessary .
*
* See PQconnectPoll for more info .
*/
PGconn *
PQconnectStartParams(const char *const *keywords,
const char *const *values,
int expand_dbname)
{
PGconn *conn;
PQconninfoOption *connOptions;
/*
* Allocate memory for the conn structure . Note that we also expect this
* to initialize conn - > errorMessage to empty . All subsequent steps during
* connection initialization will only append to that buffer .
*/
conn = pqMakeEmptyPGconn();
if (conn == NULL)
return NULL;
/*
* Parse the conninfo arrays
*/
connOptions = conninfo_array_parse(keywords, values,
&conn->errorMessage,
true , expand_dbname);
if (connOptions == NULL)
{
conn->status = CONNECTION_BAD;
/* errorMessage is already set */
return conn;
}
/*
* Move option values into conn structure
*/
if (!fillPGconn(conn, connOptions))
{
PQconninfoFree(connOptions);
return conn;
}
/*
* Free the option info - all is in conn now
*/
PQconninfoFree(connOptions);
/*
* Compute derived options
*/
if (!pqConnectOptions2(conn))
return conn;
/*
* Connect to the database
*/
if (!pqConnectDBStart(conn))
{
/* Just in case we failed to set it in pqConnectDBStart */
conn->status = CONNECTION_BAD;
}
return conn;
}
/*
* PQconnectStart
*
* Begins the establishment of a connection to a postgres backend through the
* postmaster using connection information in a string .
*
* See comment for PQconnectdb for the definition of the string format .
*
* Returns a PGconn * . If NULL is returned , a malloc error has occurred , and
* you should not attempt to proceed with this connection . If the status
* field of the connection returned is CONNECTION_BAD , an error has
* occurred . In this case you should call PQfinish on the result , ( perhaps
* inspecting the error message first ) . Other fields of the structure may not
* be valid if that occurs . If the status field is not CONNECTION_BAD , then
* this stage has succeeded - call PQconnectPoll , using select ( 2 ) to see when
* this is necessary .
*
* See PQconnectPoll for more info .
*/
PGconn *
PQconnectStart(const char *conninfo)
{
PGconn *conn;
/*
* Allocate memory for the conn structure . Note that we also expect this
* to initialize conn - > errorMessage to empty . All subsequent steps during
* connection initialization will only append to that buffer .
*/
conn = pqMakeEmptyPGconn();
if (conn == NULL)
return NULL;
/*
* Parse the conninfo string
*/
if (!connectOptions1(conn, conninfo))
return conn;
/*
* Compute derived options
*/
if (!pqConnectOptions2(conn))
return conn;
/*
* Connect to the database
*/
if (!pqConnectDBStart(conn))
{
/* Just in case we failed to set it in pqConnectDBStart */
conn->status = CONNECTION_BAD;
}
return conn;
}
/*
* Move option values into conn structure
*
* Don ' t put anything cute here - - - intelligence should be in
* pqConnectOptions2 . . .
*
* Returns true on success . On failure , returns false and sets error message .
*/
static bool
fillPGconn(PGconn *conn, PQconninfoOption *connOptions)
{
const internalPQconninfoOption *option;
for (option = PQconninfoOptions; option->keyword; option++)
{
if (option->connofs >= 0 )
{
const char *tmp = conninfo_getval(connOptions, option->keyword);
if (tmp)
{
char **connmember = (char **) ((char *) conn + option->connofs);
free(*connmember);
*connmember = strdup(tmp);
if (*connmember == NULL)
{
libpq_append_conn_error(conn, "out of memory" );
return false ;
}
}
}
}
return true ;
}
/*
* Copy over option values from srcConn to dstConn
*
* Don ' t put anything cute here - - - intelligence should be in
* pqConnectOptions2 . . .
*
* Returns true on success . On failure , returns false and sets error message of
* dstConn .
*/
bool
pqCopyPGconn(PGconn *srcConn, PGconn *dstConn)
{
const internalPQconninfoOption *option;
/* copy over connection options */
for (option = PQconninfoOptions; option->keyword; option++)
{
if (option->connofs >= 0 )
{
const char **tmp = (const char **) ((char *) srcConn + option->connofs);
if (*tmp)
{
char **dstConnmember = (char **) ((char *) dstConn + option->connofs);
if (*dstConnmember)
free(*dstConnmember);
*dstConnmember = strdup(*tmp);
if (*dstConnmember == NULL)
{
libpq_append_conn_error(dstConn, "out of memory" );
return false ;
}
}
}
}
return true ;
}
/*
* connectOptions1
*
* Internal subroutine to set up connection parameters given an already -
* created PGconn and a conninfo string . Derived settings should be
* processed by calling pqConnectOptions2 next . ( We split them because
* PQsetdbLogin overrides defaults in between . )
*
* Returns true if OK , false if trouble ( in which case errorMessage is set
* and so is conn - > status ) .
*/
static bool
connectOptions1(PGconn *conn, const char *conninfo)
{
PQconninfoOption *connOptions;
/*
* Parse the conninfo string
*/
connOptions = parse_connection_string(conninfo, &conn->errorMessage, true );
if (connOptions == NULL)
{
conn->status = CONNECTION_BAD;
/* errorMessage is already set */
return false ;
}
/*
* Move option values into conn structure
*/
if (!fillPGconn(conn, connOptions))
{
conn->status = CONNECTION_BAD;
PQconninfoFree(connOptions);
return false ;
}
/*
* Free the option info - all is in conn now
*/
PQconninfoFree(connOptions);
return true ;
}
/*
* Count the number of elements in a simple comma - separated list .
*/
static int
count_comma_separated_elems(const char *input)
{
int n;
n = 1 ;
for (; *input != '\0' ; input++)
{
if (*input == ',' )
n++;
}
return n;
}
/*
* Parse a simple comma - separated list .
*
* On each call , returns a malloc ' d copy of the next element , and sets * more
* to indicate whether there are any more elements in the list after this ,
* and updates * startptr to point to the next element , if any .
*
* On out of memory , returns NULL .
*/
static char *
parse_comma_separated_list(char **startptr, bool *more)
{
char *p;
char *s = *startptr;
char *e;
size_t len;
/*
* Search for the end of the current element ; a comma or end - of - string
* acts as a terminator .
*/
e = s;
while (*e != '\0' && *e != ',' )
++e;
*more = (*e == ',' );
len = e - s;
p = (char *) malloc(sizeof (char ) * (len + 1 ));
if (p)
{
memcpy(p, s, len);
p[len] = '\0' ;
}
*startptr = e + 1 ;
return p;
}
/*
* Initializes the prng_state field of the connection . We want something
* unpredictable , so if possible , use high - quality random bits for the
* seed . Otherwise , fall back to a seed based on the connection address ,
* timestamp and PID .
*/
static void
libpq_prng_init(PGconn *conn)
{
uint64 rseed;
struct timeval tval = {0 };
if (pg_prng_strong_seed(&conn->prng_state))
return ;
gettimeofday(&tval, NULL);
rseed = ((uintptr_t) conn) ^
((uint64) getpid()) ^
((uint64) tval.tv_usec) ^
((uint64) tval.tv_sec);
pg_prng_seed(&conn->prng_state, rseed);
}
/*
* Fills the connection ' s allowed_sasl_mechs list with all supported SASL
* mechanisms .
*/
static inline void
fill_allowed_sasl_mechs(PGconn *conn)
{
/*---
* We only support two mechanisms at the moment , so rather than deal with a
* linked list , conn - > allowed_sasl_mechs is an array of static length . We
* rely on the compile - time assertion here to keep us honest .
*
* To add a new mechanism to require_auth ,
* - add it to supported_sasl_mechs ,
* - update the length of conn - > allowed_sasl_mechs ,
* - handle the new mechanism name in the require_auth portion of
* pqConnectOptions2 ( ) , below .
*/
StaticAssertDecl(lengthof(conn->allowed_sasl_mechs) == SASL_MECHANISM_COUNT,
"conn->allowed_sasl_mechs[] is not sufficiently large for holding all supported SASL mechanisms" );
for (int i = 0 ; i < SASL_MECHANISM_COUNT; i++)
conn->allowed_sasl_mechs[i] = supported_sasl_mechs[i];
}
/*
* Clears the connection ' s allowed_sasl_mechs list .
*/
static inline void
clear_allowed_sasl_mechs(PGconn *conn)
{
for (int i = 0 ; i < lengthof(conn->allowed_sasl_mechs); i++)
conn->allowed_sasl_mechs[i] = NULL;
}
/*
* Helper routine that searches the static allowed_sasl_mechs list for a
* specific mechanism .
*/
static inline int
index_of_allowed_sasl_mech(PGconn *conn, const pg_fe_sasl_mech *mech)
{
for (int i = 0 ; i < lengthof(conn->allowed_sasl_mechs); i++)
{
if (conn->allowed_sasl_mechs[i] == mech)
return i;
}
return -1 ;
}
/*
* pqConnectOptions2
*
* Compute derived connection options after absorbing all user - supplied info .
*
* Returns true if OK , false if trouble ( in which case errorMessage is set
* and so is conn - > status ) .
*/
bool
pqConnectOptions2(PGconn *conn)
{
int i;
/*
* Allocate memory for details about each host to which we might possibly
* try to connect . For that , count the number of elements in the hostaddr
* or host options . If neither is given , assume one host .
*/
conn->whichhost = 0 ;
if (conn->pghostaddr && conn->pghostaddr[0 ] != '\0' )
conn->nconnhost = count_comma_separated_elems(conn->pghostaddr);
else if (conn->pghost && conn->pghost[0 ] != '\0' )
conn->nconnhost = count_comma_separated_elems(conn->pghost);
else
conn->nconnhost = 1 ;
conn->connhost = (pg_conn_host *)
calloc(conn->nconnhost, sizeof (pg_conn_host));
if (conn->connhost == NULL)
goto oom_error;
/*
* We now have one pg_conn_host structure per possible host . Fill in the
* host and hostaddr fields for each , by splitting the parameter strings .
*/
if (conn->pghostaddr != NULL && conn->pghostaddr[0 ] != '\0' )
{
char *s = conn->pghostaddr;
bool more = true ;
for (i = 0 ; i < conn->nconnhost && more; i++)
{
conn->connhost[i].hostaddr = parse_comma_separated_list(&s, &more);
if (conn->connhost[i].hostaddr == NULL)
goto oom_error;
}
/*
* If hostaddr was given , the array was allocated according to the
* number of elements in the hostaddr list , so it really should be the
* right size .
*/
Assert(!more);
Assert(i == conn->nconnhost);
}
if (conn->pghost != NULL && conn->pghost[0 ] != '\0' )
{
char *s = conn->pghost;
bool more = true ;
for (i = 0 ; i < conn->nconnhost && more; i++)
{
conn->connhost[i].host = parse_comma_separated_list(&s, &more);
if (conn->connhost[i].host == NULL)
goto oom_error;
}
/* Check for wrong number of host items. */
if (more || i != conn->nconnhost)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "could not match %d host names to %d hostaddr values" ,
count_comma_separated_elems(conn->pghost), conn->nconnhost);
return false ;
}
}
/*
* Now , for each host slot , identify the type of address spec , and fill in
* the default address if nothing was given .
*/
for (i = 0 ; i < conn->nconnhost; i++)
{
pg_conn_host *ch = &conn->connhost[i];
if (ch->hostaddr != NULL && ch->hostaddr[0 ] != '\0' )
ch->type = CHT_HOST_ADDRESS;
else if (ch->host != NULL && ch->host[0 ] != '\0' )
{
ch->type = CHT_HOST_NAME;
if (is_unixsock_path(ch->host))
ch->type = CHT_UNIX_SOCKET;
}
else
{
free(ch->host);
/*
* This bit selects the default host location . If you change
* this , see also pg_regress .
*/
if (DEFAULT_PGSOCKET_DIR[0 ])
{
ch->host = strdup(DEFAULT_PGSOCKET_DIR);
ch->type = CHT_UNIX_SOCKET;
}
else
{
ch->host = strdup(DefaultHost);
ch->type = CHT_HOST_NAME;
}
if (ch->host == NULL)
goto oom_error;
}
}
/*
* Next , work out the port number corresponding to each host name .
*
* Note : unlike the above for host names , this could leave the port fields
* as null or empty strings . We will substitute DEF_PGPORT whenever we
* read such a port field .
*/
if (conn->pgport != NULL && conn->pgport[0 ] != '\0' )
{
char *s = conn->pgport;
bool more = true ;
for (i = 0 ; i < conn->nconnhost && more; i++)
{
conn->connhost[i].port = parse_comma_separated_list(&s, &more);
if (conn->connhost[i].port == NULL)
goto oom_error;
}
/*
* If exactly one port was given , use it for every host . Otherwise ,
* there must be exactly as many ports as there were hosts .
*/
if (i == 1 && !more)
{
for (i = 1 ; i < conn->nconnhost; i++)
{
conn->connhost[i].port = strdup(conn->connhost[0 ].port);
if (conn->connhost[i].port == NULL)
goto oom_error;
}
}
else if (more || i != conn->nconnhost)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "could not match %d port numbers to %d hosts" ,
count_comma_separated_elems(conn->pgport), conn->nconnhost);
return false ;
}
}
/*
* If user name was not given , fetch it . ( Most likely , the fetch will
* fail , since the only way we get here is if pg_fe_getauthname ( ) failed
* during conninfo_add_defaults ( ) . But now we want an error message . )
*/
if (conn->pguser == NULL || conn->pguser[0 ] == '\0' )
{
free(conn->pguser);
conn->pguser = pg_fe_getauthname(&conn->errorMessage);
if (!conn->pguser)
{
conn->status = CONNECTION_BAD;
return false ;
}
}
/*
* If database name was not given , default it to equal user name
*/
if (conn->dbName == NULL || conn->dbName[0 ] == '\0' )
{
free(conn->dbName);
conn->dbName = strdup(conn->pguser);
if (!conn->dbName)
goto oom_error;
}
/*
* If password was not given , try to look it up in password file . Note
* that the result might be different for each host / port pair .
*/
if (conn->pgpass == NULL || conn->pgpass[0 ] == '\0' )
{
/* If password file wasn't specified, use ~/PGPASSFILE */
if (conn->pgpassfile == NULL || conn->pgpassfile[0 ] == '\0' )
{
char homedir[MAXPGPATH];
if (pqGetHomeDirectory(homedir, sizeof (homedir)))
{
free(conn->pgpassfile);
conn->pgpassfile = malloc(MAXPGPATH);
if (!conn->pgpassfile)
goto oom_error;
snprintf(conn->pgpassfile, MAXPGPATH, "%s/%s" ,
homedir, PGPASSFILE);
}
}
if (conn->pgpassfile != NULL && conn->pgpassfile[0 ] != '\0' )
{
for (i = 0 ; i < conn->nconnhost; i++)
{
/*
* Try to get a password for this host from file . We use host
* for the hostname search key if given , else hostaddr ( at
* least one of them is guaranteed nonempty by now ) .
*/
const char *pwhost = conn->connhost[i].host;
if (pwhost == NULL || pwhost[0 ] == '\0' )
pwhost = conn->connhost[i].hostaddr;
conn->connhost[i].password =
passwordFromFile(pwhost,
conn->connhost[i].port,
conn->dbName,
conn->pguser,
conn->pgpassfile);
}
}
}
/*
* parse and validate require_auth option
*/
if (conn->require_auth && conn->require_auth[0 ])
{
char *s = conn->require_auth;
bool first,
more;
bool negated = false ;
/*
* By default , start from an empty set of allowed methods and
* mechanisms , and add to it .
*/
conn->auth_required = true ;
conn->allowed_auth_methods = 0 ;
clear_allowed_sasl_mechs(conn);
for (first = true , more = true ; more; first = false )
{
char *method,
*part;
uint32 bits = 0 ;
const pg_fe_sasl_mech *mech = NULL;
part = parse_comma_separated_list(&s, &more);
if (part == NULL)
goto oom_error;
/*
* Check for negation , e . g . ' ! password ' . If one element is
* negated , they all have to be .
*/
method = part;
if (*method == '!' )
{
if (first)
{
/*
* Switch to a permissive set of allowed methods and
* mechanisms , and subtract from it .
*/
conn->auth_required = false ;
conn->allowed_auth_methods = -1 ;
fill_allowed_sasl_mechs(conn);
}
else if (!negated)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "negative require_auth method \" %s\" cannot be mixed with non-negative methods" ,
method);
free(part);
return false ;
}
negated = true ;
method++;
}
else if (negated)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "require_auth method \" %s\" cannot be mixed with negative methods" ,
method);
free(part);
return false ;
}
/*
* First group : methods that can be handled solely with the
* authentication request codes .
*/
if (strcmp(method, "password" ) == 0 )
{
bits = (1 << AUTH_REQ_PASSWORD);
}
else if (strcmp(method, "md5" ) == 0 )
{
bits = (1 << AUTH_REQ_MD5);
}
else if (strcmp(method, "gss" ) == 0 )
{
bits = (1 << AUTH_REQ_GSS);
bits |= (1 << AUTH_REQ_GSS_CONT);
}
else if (strcmp(method, "sspi" ) == 0 )
{
bits = (1 << AUTH_REQ_SSPI);
bits |= (1 << AUTH_REQ_GSS_CONT);
}
/*
* Next group : SASL mechanisms . All of these use the same request
* codes , so the list of allowed mechanisms is tracked separately .
*
* supported_sasl_mechs must contain all mechanisms handled here .
*/
else if (strcmp(method, "scram-sha-256" ) == 0 )
{
mech = &pg_scram_mech;
}
else if (strcmp(method, "oauth" ) == 0 )
{
mech = &pg_oauth_mech;
}
/*
* Final group : meta - options .
*/
else if (strcmp(method, "none" ) == 0 )
{
/*
* Special case : let the user explicitly allow ( or disallow )
* connections where the server does not send an explicit
* authentication challenge , such as " trust " and " cert " auth .
*/
if (negated) /* "!none" */
{
if (conn->auth_required)
goto duplicate;
conn->auth_required = true ;
}
else /* "none" */
{
if (!conn->auth_required)
goto duplicate;
conn->auth_required = false ;
}
free(part);
continue ; /* avoid the bitmask manipulation below */
}
else
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"require_auth" , method);
free(part);
return false ;
}
if (mech)
{
/*
* Update the mechanism set only . The method bitmask will be
* updated for SASL further down .
*/
Assert(!bits);
if (negated)
{
/* Remove the existing mechanism from the list. */
i = index_of_allowed_sasl_mech(conn, mech);
if (i < 0 )
goto duplicate;
conn->allowed_sasl_mechs[i] = NULL;
}
else
{
/*
* Find a space to put the new mechanism ( after making
* sure it ' s not already there ) .
*/
i = index_of_allowed_sasl_mech(conn, mech);
if (i >= 0 )
goto duplicate;
i = index_of_allowed_sasl_mech(conn, NULL);
if (i < 0 )
{
/* Should not happen; the pointer list is corrupted. */
Assert(false );
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn,
"internal error: no space in allowed_sasl_mechs" );
free(part);
return false ;
}
conn->allowed_sasl_mechs[i] = mech;
}
}
else
{
/* Update the method bitmask. */
Assert(bits);
if (negated)
{
if ((conn->allowed_auth_methods & bits) == 0 )
goto duplicate;
conn->allowed_auth_methods &= ~bits;
}
else
{
if ((conn->allowed_auth_methods & bits) == bits)
goto duplicate;
conn->allowed_auth_methods |= bits;
}
}
free(part);
continue ;
duplicate:
/*
* A duplicated method probably indicates a typo in a setting
* where typos are extremely risky .
*/
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "require_auth method \" %s\" is specified more than once" ,
part);
free(part);
return false ;
}
/*
* Finally , allow SASL authentication requests if ( and only if ) we ' ve
* allowed any mechanisms .
*/
{
bool allowed = false ;
const uint32 sasl_bits =
(1 << AUTH_REQ_SASL)
| (1 << AUTH_REQ_SASL_CONT)
| (1 << AUTH_REQ_SASL_FIN);
for (i = 0 ; i < lengthof(conn->allowed_sasl_mechs); i++)
{
if (conn->allowed_sasl_mechs[i])
{
allowed = true ;
break ;
}
}
/*
* For the standard case , add the SASL bits to the ( default - empty )
* set if needed . For the negated case , remove them .
*/
if (!negated && allowed)
conn->allowed_auth_methods |= sasl_bits;
else if (negated && !allowed)
conn->allowed_auth_methods &= ~sasl_bits;
}
}
/*
* validate channel_binding option
*/
if (conn->channel_binding)
{
if (strcmp(conn->channel_binding, "disable" ) != 0
&& strcmp(conn->channel_binding, "prefer" ) != 0
&& strcmp(conn->channel_binding, "require" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"channel_binding" , conn->channel_binding);
return false ;
}
}
else
{
conn->channel_binding = strdup(DefaultChannelBinding);
if (!conn->channel_binding)
goto oom_error;
}
#ifndef USE_SSL
/*
* sslrootcert = system is not supported . Since setting this changes the
* default sslmode , check this _ before_ we validate sslmode , to avoid
* confusing the user with errors for an option they may not have set .
*/
if (conn->sslrootcert
&& strcmp(conn->sslrootcert, "system" ) == 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "%s value \" %s\" invalid when SSL support is not compiled in" ,
"sslrootcert" , conn->sslrootcert);
return false ;
}
#endif
/*
* validate sslmode option
*/
if (conn->sslmode)
{
if (strcmp(conn->sslmode, "disable" ) != 0
&& strcmp(conn->sslmode, "allow" ) != 0
&& strcmp(conn->sslmode, "prefer" ) != 0
&& strcmp(conn->sslmode, "require" ) != 0
&& strcmp(conn->sslmode, "verify-ca" ) != 0
&& strcmp(conn->sslmode, "verify-full" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"sslmode" , conn->sslmode);
return false ;
}
#ifndef USE_SSL
switch (conn->sslmode[0 ])
{
case 'a' : /* "allow" */
case 'p' : /* "prefer" */
/*
* warn user that an SSL connection will never be negotiated
* since SSL was not compiled in ?
*/
break ;
case 'r' : /* "require" */
case 'v' : /* "verify-ca" or "verify-full" */
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "%s value \" %s\" invalid when SSL support is not compiled in" ,
"sslmode" , conn->sslmode);
return false ;
}
#endif
}
else
{
conn->sslmode = strdup(DefaultSSLMode);
if (!conn->sslmode)
goto oom_error;
}
/*
* validate sslnegotiation option , default is " postgres " for the postgres
* style negotiated connection with an extra round trip but more options .
*/
if (conn->sslnegotiation)
{
if (strcmp(conn->sslnegotiation, "postgres" ) != 0
&& strcmp(conn->sslnegotiation, "direct" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"sslnegotiation" , conn->sslnegotiation);
return false ;
}
#ifndef USE_SSL
if (conn->sslnegotiation[0 ] != 'p' )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "%s value \" %s\" invalid when SSL support is not compiled in" ,
"sslnegotiation" , conn->sslnegotiation);
return false ;
}
#endif
/*
* Don ' t allow direct SSL negotiation with sslmode = ' prefer ' , because
* that poses a risk of unintentional fallback to plaintext connection
* when connecting to a pre - v17 server that does not support direct
* SSL connections . To keep things simple , don ' t allow it with
* sslmode = ' allow ' or sslmode = ' disable ' either . If a user goes through
* the trouble of setting sslnegotiation = ' direct ' , they probably
* intend to use SSL , and sslmode = disable or allow is probably a user
* mistake anyway .
*/
if (conn->sslnegotiation[0 ] == 'd' &&
conn->sslmode[0 ] != 'r' && conn->sslmode[0 ] != 'v' )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "weak sslmode \" %s\" may not be used with sslnegotiation=direct (use \" require\", \" verify-ca\", or \" verify-full\")" ,
conn->sslmode);
return false ;
}
}
else
{
conn->sslnegotiation = strdup(DefaultSSLNegotiation);
if (!conn->sslnegotiation)
goto oom_error;
}
#ifdef USE_SSL
/*
* If sslrootcert = system , make sure our chosen sslmode is compatible .
*/
if (conn->sslrootcert
&& strcmp(conn->sslrootcert, "system" ) == 0
&& strcmp(conn->sslmode, "verify-full" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "weak sslmode \" %s\" may not be used with sslrootcert=system (use \" verify-full\")" ,
conn->sslmode);
return false ;
}
#endif
/*
* Validate TLS protocol versions for ssl_min_protocol_version and
* ssl_max_protocol_version .
*/
if (!sslVerifyProtocolVersion(conn->ssl_min_protocol_version))
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid \" %s\" value: \" %s\"" ,
"ssl_min_protocol_version" ,
conn->ssl_min_protocol_version);
return false ;
}
if (!sslVerifyProtocolVersion(conn->ssl_max_protocol_version))
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid \" %s\" value: \" %s\"" ,
"ssl_max_protocol_version" ,
conn->ssl_max_protocol_version);
return false ;
}
/*
* Check if the range of SSL protocols defined is correct . This is done
* at this early step because this is independent of the SSL
* implementation used , and this avoids unnecessary cycles with an
* already - built SSL context when the connection is being established , as
* it would be doomed anyway .
*/
if (!sslVerifyProtocolRange(conn->ssl_min_protocol_version,
conn->ssl_max_protocol_version))
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid SSL protocol version range" );
return false ;
}
/*
* validate sslcertmode option
*/
if (conn->sslcertmode)
{
if (strcmp(conn->sslcertmode, "disable" ) != 0 &&
strcmp(conn->sslcertmode, "allow" ) != 0 &&
strcmp(conn->sslcertmode, "require" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"sslcertmode" , conn->sslcertmode);
return false ;
}
#ifndef USE_SSL
if (strcmp(conn->sslcertmode, "require" ) == 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "%s value \" %s\" invalid when SSL support is not compiled in" ,
"sslcertmode" , conn->sslcertmode);
return false ;
}
#endif
#ifndef HAVE_SSL_CTX_SET_CERT_CB
/*
* Without a certificate callback , the current implementation can ' t
* figure out if a certificate was actually requested , so " require " is
* useless .
*/
if (strcmp(conn->sslcertmode, "require" ) == 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "%s value \" %s\" is not supported (check OpenSSL version)" ,
"sslcertmode" , conn->sslcertmode);
return false ;
}
#endif
}
else
{
conn->sslcertmode = strdup(DefaultSSLCertMode);
if (!conn->sslcertmode)
goto oom_error;
}
/*
* validate gssencmode option
*/
if (conn->gssencmode)
{
if (strcmp(conn->gssencmode, "disable" ) != 0 &&
strcmp(conn->gssencmode, "prefer" ) != 0 &&
strcmp(conn->gssencmode, "require" ) != 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" , "gssencmode" , conn->gssencmode);
return false ;
}
#ifndef ENABLE_GSS
if (strcmp(conn->gssencmode, "require" ) == 0 )
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "gssencmode value \" %s\" invalid when GSSAPI support is not compiled in" ,
conn->gssencmode);
return false ;
}
#endif
}
else
{
conn->gssencmode = strdup(DefaultGSSMode);
if (!conn->gssencmode)
goto oom_error;
}
/*
* validate target_session_attrs option , and set target_server_type
*/
if (conn->target_session_attrs)
{
if (strcmp(conn->target_session_attrs, "any" ) == 0 )
conn->target_server_type = SERVER_TYPE_ANY;
else if (strcmp(conn->target_session_attrs, "read-write" ) == 0 )
conn->target_server_type = SERVER_TYPE_READ_WRITE;
else if (strcmp(conn->target_session_attrs, "read-only" ) == 0 )
conn->target_server_type = SERVER_TYPE_READ_ONLY;
else if (strcmp(conn->target_session_attrs, "primary" ) == 0 )
conn->target_server_type = SERVER_TYPE_PRIMARY;
else if (strcmp(conn->target_session_attrs, "standby" ) == 0 )
conn->target_server_type = SERVER_TYPE_STANDBY;
else if (strcmp(conn->target_session_attrs, "prefer-standby" ) == 0 )
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;
else
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"target_session_attrs" ,
conn->target_session_attrs);
return false ;
}
}
else
conn->target_server_type = SERVER_TYPE_ANY;
if (conn->scram_client_key)
{
int len;
len = pg_b64_dec_len(strlen(conn->scram_client_key));
conn->scram_client_key_binary = malloc(len);
if (!conn->scram_client_key_binary)
goto oom_error;
len = pg_b64_decode(conn->scram_client_key, strlen(conn->scram_client_key),
conn->scram_client_key_binary, len);
if (len < 0 )
{
libpq_append_conn_error(conn, "invalid SCRAM client key" );
return false ;
}
if (len != SCRAM_MAX_KEY_LEN)
{
libpq_append_conn_error(conn, "invalid SCRAM client key length: %d" , len);
return false ;
}
conn->scram_client_key_len = len;
}
if (conn->scram_server_key)
{
int len;
len = pg_b64_dec_len(strlen(conn->scram_server_key));
conn->scram_server_key_binary = malloc(len);
if (!conn->scram_server_key_binary)
goto oom_error;
len = pg_b64_decode(conn->scram_server_key, strlen(conn->scram_server_key),
conn->scram_server_key_binary, len);
if (len < 0 )
{
libpq_append_conn_error(conn, "invalid SCRAM server key" );
return false ;
}
if (len != SCRAM_MAX_KEY_LEN)
{
libpq_append_conn_error(conn, "invalid SCRAM server key length: %d" , len);
return false ;
}
conn->scram_server_key_len = len;
}
/*
* validate load_balance_hosts option , and set load_balance_type
*/
if (conn->load_balance_hosts)
{
if (strcmp(conn->load_balance_hosts, "disable" ) == 0 )
conn->load_balance_type = LOAD_BALANCE_DISABLE;
else if (strcmp(conn->load_balance_hosts, "random" ) == 0 )
conn->load_balance_type = LOAD_BALANCE_RANDOM;
else
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
"load_balance_hosts" ,
conn->load_balance_hosts);
return false ;
}
}
else
conn->load_balance_type = LOAD_BALANCE_DISABLE;
if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
{
libpq_prng_init(conn);
/*
* This is the " inside - out " variant of the Fisher - Yates shuffle
* algorithm . Notionally , we append each new value to the array and
* then swap it with a randomly - chosen array element ( possibly
* including itself , else we fail to generate permutations with the
* last integer last ) . The swap step can be optimized by combining it
* with the insertion .
*/
for (i = 1 ; i < conn->nconnhost; i++)
{
int j = pg_prng_uint64_range(&conn->prng_state, 0 , i);
pg_conn_host temp = conn->connhost[j];
conn->connhost[j] = conn->connhost[i];
conn->connhost[i] = temp;
}
}
if (conn->min_protocol_version)
{
if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version" ))
{
conn->status = CONNECTION_BAD;
return false ;
}
}
else
{
conn->min_pversion = PG_PROTOCOL_EARLIEST;
}
if (conn->max_protocol_version)
{
if (!pqParseProtocolVersion(conn->max_protocol_version, &conn->max_pversion, conn, "max_protocol_version" ))
{
conn->status = CONNECTION_BAD;
return false ;
}
}
else
{
/*
* To not break connecting to older servers / poolers that do not yet
* support NegotiateProtocolVersion , default to the 3 . 0 protocol at
* least for a while longer . Except when min_protocol_version is set
* to something larger , then we might as well default to the latest .
*/
if (conn->min_pversion > PG_PROTOCOL(3 , 0 ))
conn->max_pversion = PG_PROTOCOL_LATEST;
else
conn->max_pversion = PG_PROTOCOL(3 , 0 );
}
if (conn->min_pversion > conn->max_pversion)
{
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "\" %s\" is greater than \" %s\"" , "min_protocol_version" , "max_protocol_version" );
return false ;
}
/*
* Resolve special " auto " client_encoding from the locale
*/
if (conn->client_encoding_initial &&
strcmp(conn->client_encoding_initial, "auto" ) == 0 )
{
free(conn->client_encoding_initial);
conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true )));
if (!conn->client_encoding_initial)
goto oom_error;
}
/*
* Only if we get this far is it appropriate to try to connect . ( We need a
* state flag , rather than just the boolean result of this function , in
* case someone tries to PQreset ( ) the PGconn . )
*/
conn->options_valid = true ;
return true ;
oom_error:
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "out of memory" );
return false ;
}
/*
* PQconndefaults
*
* Construct a default connection options array , which identifies all the
* available options and shows any default values that are available from the
* environment etc . On error ( eg out of memory ) , NULL is returned .
*
* Using this function , an application may determine all possible options
* and their current default values .
*
* NOTE : as of PostgreSQL 7 . 0 , the returned array is dynamically allocated
* and should be freed when no longer needed via PQconninfoFree ( ) . ( In prior
* versions , the returned array was static , but that ' s not thread - safe . )
* Pre - 7 . 0 applications that use this function will see a small memory leak
* until they are updated to call PQconninfoFree .
*/
PQconninfoOption *
PQconndefaults(void )
{
PQExpBufferData errorBuf;
PQconninfoOption *connOptions;
/* We don't actually report any errors here, but callees want a buffer */
initPQExpBuffer(&errorBuf);
if (PQExpBufferDataBroken(errorBuf))
return NULL; /* out of memory already :-( */
connOptions = conninfo_init(&errorBuf);
if (connOptions != NULL)
{
/* pass NULL errorBuf to ignore errors */
if (!conninfo_add_defaults(connOptions, NULL))
{
PQconninfoFree(connOptions);
connOptions = NULL;
}
}
termPQExpBuffer(&errorBuf);
return connOptions;
}
/* ----------------
* 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 only the errorMessage is likely to be useful .
* - - - - - - - - - - - - - - - -
*/
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;
/*
* Allocate memory for the conn structure . Note that we also expect this
* to initialize conn - > errorMessage to empty . All subsequent steps during
* connection initialization will only append to that buffer .
*/
conn = pqMakeEmptyPGconn();
if (conn == NULL)
return NULL;
/*
* If the dbName parameter contains what looks like a connection string ,
* parse it into conn struct using connectOptions1 .
*/
if (dbName && recognized_connection_string(dbName))
{
if (!connectOptions1(conn, dbName))
return conn;
}
else
{
/*
* Old - style path : first , parse an empty conninfo string in order to
* set up the same defaults that PQconnectdb ( ) would use .
*/
if (!connectOptions1(conn, "" ))
return conn;
/* Insert dbName parameter value into struct */
if (dbName && dbName[0 ] != '\0' )
{
free(conn->dbName);
conn->dbName = strdup(dbName);
if (!conn->dbName)
goto oom_error;
}
}
/*
* Insert remaining parameters into struct , overriding defaults ( as well
* as any conflicting data from dbName taken as a conninfo ) .
*/
if (pghost && pghost[0 ] != '\0' )
{
free(conn->pghost);
conn->pghost = strdup(pghost);
if (!conn->pghost)
goto oom_error;
}
if (pgport && pgport[0 ] != '\0' )
{
free(conn->pgport);
conn->pgport = strdup(pgport);
if (!conn->pgport)
goto oom_error;
}
if (pgoptions && pgoptions[0 ] != '\0' )
{
free(conn->pgoptions);
conn->pgoptions = strdup(pgoptions);
if (!conn->pgoptions)
goto oom_error;
}
if (login && login[0 ] != '\0' )
{
free(conn->pguser);
conn->pguser = strdup(login);
if (!conn->pguser)
goto oom_error;
}
if (pwd && pwd[0 ] != '\0' )
{
free(conn->pgpass);
conn->pgpass = strdup(pwd);
if (!conn->pgpass)
goto oom_error;
}
/*
* Compute derived options
*/
if (!pqConnectOptions2(conn))
return conn;
/*
* Connect to the database
*/
if (pqConnectDBStart(conn))
(void ) pqConnectDBComplete(conn);
return conn;
oom_error:
conn->status = CONNECTION_BAD;
libpq_append_conn_error(conn, "out of memory" );
return conn;
}
/* ----------
* connectNoDelay -
* Sets the TCP_NODELAY socket option .
* Returns 1 if successful , 0 if not .
* - - - - - - - - - -
*/
static int
connectNoDelay(PGconn *conn)
{
#ifdef TCP_NODELAY
int on = 1 ;
if (setsockopt(conn->sock, IPPROTO_TCP, TCP_NODELAY,
(char *) &on,
sizeof (on)) < 0 )
{
char sebuf[PG_STRERROR_R_BUFLEN];
libpq_append_conn_error(conn, "could not set socket to TCP no delay mode: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
return 0 ;
}
#endif
return 1 ;
}
/* ----------
* Write currently connected IP address into host_addr ( of len host_addr_len ) .
* If unable to , set it to the empty string .
* - - - - - - - - - -
*/
static void
getHostaddr(PGconn *conn, char *host_addr, int host_addr_len)
{
struct sockaddr_storage *addr = &conn->raddr.addr;
if (addr->ss_family == AF_INET)
{
if (pg_inet_net_ntop(AF_INET,
&((struct sockaddr_in *) addr)->sin_addr.s_addr,
32 ,
host_addr, host_addr_len) == NULL)
host_addr[0 ] = '\0' ;
}
else if (addr->ss_family == AF_INET6)
{
if (pg_inet_net_ntop(AF_INET6,
&((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr,
128 ,
host_addr, host_addr_len) == NULL)
host_addr[0 ] = '\0' ;
}
else
host_addr[0 ] = '\0' ;
}
/*
* emitHostIdentityInfo -
* Speculatively append " connection to server so - and - so failed : " to
* conn - > errorMessage once we ' ve identified the current connection target
* address . This ensures that any subsequent error message will be properly
* attributed to the server we couldn ' t connect to . conn - > raddr must be
* valid , and the result of getHostaddr ( ) must be supplied .
*/
static void
emitHostIdentityInfo(PGconn *conn, const char *host_addr)
{
if (conn->raddr.addr.ss_family == AF_UNIX)
{
char service[NI_MAXHOST];
pg_getnameinfo_all(&conn->raddr.addr, conn->raddr.salen,
NULL, 0 ,
service, sizeof (service),
NI_NUMERICSERV);
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("connection to server on socket \" %s\" failed: " ),
service);
}
else
{
const char *displayed_host;
const char *displayed_port;
/* To which host and port were we actually connecting? */
if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
displayed_host = conn->connhost[conn->whichhost].hostaddr;
else
displayed_host = conn->connhost[conn->whichhost].host;
displayed_port = conn->connhost[conn->whichhost].port;
if (displayed_port == NULL || displayed_port[0 ] == '\0' )
displayed_port = DEF_PGPORT_STR;
/*
* If the user did not supply an IP address using ' hostaddr ' , and
* ' host ' was missing or does not match our lookup , display the
* looked - up IP address .
*/
if (conn->connhost[conn->whichhost].type != CHT_HOST_ADDRESS &&
host_addr[0 ] &&
strcmp(displayed_host, host_addr) != 0 )
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("connection to server at \" %s\" (%s), port %s failed: " ),
displayed_host, host_addr,
displayed_port);
else
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("connection to server at \" %s\", port %s failed: " ),
displayed_host,
displayed_port);
}
}
/* ----------
* connectFailureMessage -
* create a friendly error message on connection failure ,
* using the given errno value . Use this for error cases that
* imply that there ' s no server there .
* - - - - - - - - - -
*/
static void
connectFailureMessage(PGconn *conn, int errorno)
{
char sebuf[PG_STRERROR_R_BUFLEN];
appendPQExpBuffer(&conn->errorMessage,
"%s\n" ,
SOCK_STRERROR(errorno, sebuf, sizeof (sebuf)));
if (conn->raddr.addr.ss_family == AF_UNIX)
libpq_append_conn_error(conn, "\tIs the server running locally and accepting connections on that socket?" );
else
libpq_append_conn_error(conn, "\tIs the server running on that host and accepting TCP/IP connections?" );
}
/*
* Should we use keepalives ? Returns 1 if yes , 0 if no , and - 1 if
* conn - > keepalives is set to a value which is not parseable as an
* integer .
*/
static int
useKeepalives(PGconn *conn)
{
int val;
if (conn->keepalives == NULL)
return 1 ;
if (!pqParseIntParam(conn->keepalives, &val, conn, "keepalives" ))
return -1 ;
return val != 0 ? 1 : 0 ;
}
#ifndef WIN32
/*
* Set the keepalive idle timer .
*/
static int
setKeepalivesIdle(PGconn *conn)
{
int idle;
if (conn->keepalives_idle == NULL)
return 1 ;
if (!pqParseIntParam(conn->keepalives_idle, &idle, conn,
"keepalives_idle" ))
return 0 ;
if (idle < 0 )
idle = 0 ;
#ifdef PG_TCP_KEEPALIVE_IDLE
if (setsockopt(conn->sock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
(char *) &idle, sizeof (idle)) < 0 )
{
char sebuf[PG_STRERROR_R_BUFLEN];
libpq_append_conn_error(conn, "%s(%s) failed: %s" ,
"setsockopt" ,
PG_TCP_KEEPALIVE_IDLE_STR,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
return 0 ;
}
#endif
return 1 ;
}
/*
* Set the keepalive interval .
*/
static int
setKeepalivesInterval(PGconn *conn)
{
int interval;
if (conn->keepalives_interval == NULL)
return 1 ;
if (!pqParseIntParam(conn->keepalives_interval, &interval, conn,
"keepalives_interval" ))
return 0 ;
if (interval < 0 )
interval = 0 ;
#ifdef TCP_KEEPINTVL
if (setsockopt(conn->sock, IPPROTO_TCP, TCP_KEEPINTVL,
(char *) &interval, sizeof (interval)) < 0 )
{
char sebuf[PG_STRERROR_R_BUFLEN];
libpq_append_conn_error(conn, "%s(%s) failed: %s" ,
"setsockopt" ,
"TCP_KEEPINTVL" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
return 0 ;
}
#endif
return 1 ;
}
/*
* Set the count of lost keepalive packets that will trigger a connection
* break .
*/
static int
setKeepalivesCount(PGconn *conn)
{
int count;
if (conn->keepalives_count == NULL)
return 1 ;
if (!pqParseIntParam(conn->keepalives_count, &count, conn,
"keepalives_count" ))
return 0 ;
if (count < 0 )
count = 0 ;
#ifdef TCP_KEEPCNT
if (setsockopt(conn->sock, IPPROTO_TCP, TCP_KEEPCNT,
(char *) &count, sizeof (count)) < 0 )
{
char sebuf[PG_STRERROR_R_BUFLEN];
libpq_append_conn_error(conn, "%s(%s) failed: %s" ,
"setsockopt" ,
"TCP_KEEPCNT" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
return 0 ;
}
#endif
return 1 ;
}
#else /* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
/*
* Enable keepalives and set the keepalive values on Win32 ,
* where they are always set in one batch .
*
* CAUTION : This needs to be signal safe , since it ' s used by PQcancel .
*/
int
pqSetKeepalivesWin32(pgsocket sock, int idle, int interval)
{
struct tcp_keepalive ka;
DWORD retsize;
if (idle <= 0 )
idle = 2 * 60 * 60 ; /* 2 hours = default */
if (interval <= 0 )
interval = 1 ; /* 1 second = default */
ka.onoff = 1 ;
ka.keepalivetime = idle * 1000 ;
ka.keepaliveinterval = interval * 1000 ;
if (WSAIoctl(sock,
SIO_KEEPALIVE_VALS,
(LPVOID) &ka,
sizeof (ka),
NULL,
0 ,
&retsize,
NULL,
NULL)
!= 0 )
return 0 ;
return 1 ;
}
static int
prepKeepalivesWin32(PGconn *conn)
{
int idle = -1 ;
int interval = -1 ;
if (conn->keepalives_idle &&
!pqParseIntParam(conn->keepalives_idle, &idle, conn,
"keepalives_idle" ))
return 0 ;
if (conn->keepalives_interval &&
!pqParseIntParam(conn->keepalives_interval, &interval, conn,
"keepalives_interval" ))
return 0 ;
if (!pqSetKeepalivesWin32(conn->sock, idle, interval))
{
libpq_append_conn_error(conn, "%s(%s) failed: error code %d" ,
"WSAIoctl" , "SIO_KEEPALIVE_VALS" ,
WSAGetLastError());
return 0 ;
}
return 1 ;
}
#endif /* SIO_KEEPALIVE_VALS */
#endif /* WIN32 */
/*
* Set the TCP user timeout .
*/
static int
setTCPUserTimeout(PGconn *conn)
{
int timeout;
if (conn->pgtcp_user_timeout == NULL)
return 1 ;
if (!pqParseIntParam(conn->pgtcp_user_timeout, &timeout, conn,
"tcp_user_timeout" ))
return 0 ;
if (timeout < 0 )
timeout = 0 ;
#ifdef TCP_USER_TIMEOUT
if (setsockopt(conn->sock, IPPROTO_TCP, TCP_USER_TIMEOUT,
(char *) &timeout, sizeof (timeout)) < 0 )
{
char sebuf[256 ];
libpq_append_conn_error(conn, "%s(%s) failed: %s" ,
"setsockopt" ,
"TCP_USER_TIMEOUT" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
return 0 ;
}
#endif
return 1 ;
}
/* ----------
* pqConnectDBStart -
* Begin the process of making a connection to the backend .
*
* Returns 1 if successful , 0 if not .
* - - - - - - - - - -
*/
int
pqConnectDBStart(PGconn *conn)
{
if (!conn)
return 0 ;
if (!conn->options_valid)
goto connect_errReturn;
/*
* Check for bad linking to backend - internal versions of src / common
* functions ( see comments in link - canary . c for the reason we need this ) .
* Nobody but developers should see this message , so we don ' t bother
* translating it .
*/
if (!pg_link_canary_is_frontend())
{
appendPQExpBufferStr(&conn->errorMessage,
"libpq is incorrectly linked to backend functions\n" );
goto connect_errReturn;
}
/* Ensure our buffers are empty */
conn->inStart = conn->inCursor = conn->inEnd = 0 ;
conn->outCount = 0 ;
/*
* Set up to try to connect to the first host . ( Setting whichhost = - 1 is
* a bit of a cheat , but PQconnectPoll will advance it to 0 before
* anything else looks at it . )
*
* Cancel requests are special though , they should only try one host and
* address , and these fields have already been set up in PQcancelCreate ,
* so leave these fields alone for cancel requests .
*/
if (!conn->cancelRequest)
{
conn->whichhost = -1 ;
conn->try_next_host = true ;
conn->try_next_addr = false ;
}
conn->status = CONNECTION_NEEDED;
/* Also reset the target_server_type state if needed */
if (conn->target_server_type == SERVER_TYPE_PREFER_STANDBY_PASS2)
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;
/*
* The code for processing CONNECTION_NEEDED state is in PQconnectPoll ( ) ,
* so that it can easily be re - executed if needed again during the
* asynchronous startup process . However , we must run it once here ,
* because callers expect a success return from this routine to mean that
* we are in PGRES_POLLING_WRITING connection state .
*/
if (PQconnectPoll(conn) == PGRES_POLLING_WRITING)
return 1 ;
connect_errReturn:
/*
* If we managed to open a socket , close it immediately rather than
* waiting till PQfinish . ( The application cannot have gotten the socket
* from PQsocket yet , so this doesn ' t risk breaking anything . )
*/
pqDropConnection(conn, true );
conn->status = CONNECTION_BAD;
return 0 ;
}
/*
* pqConnectDBComplete
*
* Block and complete a connection .
*
* Returns 1 on success , 0 on failure .
*/
int
pqConnectDBComplete(PGconn *conn)
{
PostgresPollingStatusType flag = PGRES_POLLING_WRITING;
pg_usec_time_t end_time = -1 ;
int timeout = 0 ;
int last_whichhost = -2 ; /* certainly different from whichhost */
int last_whichaddr = -2 ; /* certainly different from whichaddr */
if (conn == NULL || conn->status == CONNECTION_BAD)
return 0 ;
/*
* Set up a time limit , if connect_timeout is greater than zero .
*/
if (conn->connect_timeout != NULL)
{
if (!pqParseIntParam(conn->connect_timeout, &timeout, conn,
"connect_timeout" ))
{
/* mark the connection as bad to report the parsing failure */
conn->status = CONNECTION_BAD;
return 0 ;
}
}
for (;;)
{
int ret = 0 ;
/*
* ( Re ) start the connect_timeout timer if it ' s active and we are
* considering a different host than we were last time through . If
* we ' ve already succeeded , though , needn ' t recalculate .
*/
if (flag != PGRES_POLLING_OK &&
timeout > 0 &&
(conn->whichhost != last_whichhost ||
conn->whichaddr != last_whichaddr))
{
end_time = PQgetCurrentTimeUSec() + (pg_usec_time_t) timeout * 1000000 ;
last_whichhost = conn->whichhost;
last_whichaddr = conn->whichaddr;
}
/*
* Wait , if necessary . Note that the initial state ( just after
* PQconnectStart ) is to wait for the socket to select for writing .
*/
switch (flag)
{
case PGRES_POLLING_OK:
return 1 ; /* success! */
case PGRES_POLLING_READING:
ret = pqWaitTimed(1 , 0 , conn, end_time);
if (ret == -1 )
{
/* hard failure, eg select() problem, aborts everything */
conn->status = CONNECTION_BAD;
return 0 ;
}
break ;
case PGRES_POLLING_WRITING:
ret = pqWaitTimed(0 , 1 , conn, end_time);
if (ret == -1 )
{
/* hard failure, eg select() problem, aborts everything */
conn->status = CONNECTION_BAD;
return 0 ;
}
break ;
default :
/* Just in case we failed to set it in PQconnectPoll */
conn->status = CONNECTION_BAD;
return 0 ;
}
if (ret == 1 ) /* connect_timeout elapsed */
{
/*
* Give up on current server / address , try the next one .
*/
conn->try_next_addr = true ;
conn->status = CONNECTION_NEEDED;
}
/*
* Now try to advance the state machine .
*/
if (conn->cancelRequest)
flag = PQcancelPoll((PGcancelConn *) conn);
else
flag = PQconnectPoll(conn);
}
}
/* ----------------
* PQconnectPoll
*
* Poll an asynchronous connection .
*
* Returns a PostgresPollingStatusType .
* Before calling this function , use select ( 2 ) to determine when data
* has arrived . .
*
* You must call PQfinish whether or not this fails .
*
* This function and PQconnectStart are intended to allow connections to be
* made without blocking the execution of your program on remote I / O . However ,
* there are a number of caveats :
*
* o If you call PQtrace , ensure that the stream object into which you trace
* will not block .
* o If you do not supply an IP address for the remote host ( i . e . you
* supply a host name instead ) then PQconnectStart will block on
* getaddrinfo . You will be fine if using Unix sockets ( i . e . by
* supplying neither a host name nor a host address ) .
* o If your backend wants to use Kerberos authentication then you must
* supply both a host name and a host address , otherwise this function
* may block on gethostname .
*
* - - - - - - - - - - - - - - - -
*/
PostgresPollingStatusType
PQconnectPoll(PGconn *conn)
{
bool reset_connection_state_machine = false ;
bool need_new_connection = false ;
PGresult *res;
char sebuf[PG_STRERROR_R_BUFLEN];
int optval;
if (conn == NULL)
return PGRES_POLLING_FAILED;
/* Get the new data */
switch (conn->status)
{
/*
* We really shouldn ' t have been polled in these two cases , but we
* can handle it .
*/
case CONNECTION_BAD:
return PGRES_POLLING_FAILED;
case CONNECTION_OK:
return PGRES_POLLING_OK;
/* These are reading states */
case CONNECTION_AWAITING_RESPONSE:
case CONNECTION_AUTH_OK:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_CHECK_STANDBY:
{
/* Load waiting data */
int n = pqReadData(conn);
if (n < 0 )
goto error_return;
if (n == 0 )
return PGRES_POLLING_READING;
break ;
}
/* These are writing states, so we just proceed. */
case CONNECTION_STARTED:
case CONNECTION_MADE:
break ;
/* Special cases: proceed without waiting. */
case CONNECTION_SSL_STARTUP:
case CONNECTION_NEEDED:
case CONNECTION_GSS_STARTUP:
case CONNECTION_CHECK_TARGET:
case CONNECTION_AUTHENTICATING:
break ;
default :
libpq_append_conn_error(conn, "invalid connection state, probably indicative of memory corruption" );
goto error_return;
}
keep_going: /* We will come back to here until there is
* nothing left to do. */
/* Time to advance to next address, or next host if no more addresses? */
if (conn->try_next_addr)
{
if (conn->whichaddr < conn->naddr)
{
conn->whichaddr++;
reset_connection_state_machine = true ;
}
else
conn->try_next_host = true ;
conn->try_next_addr = false ;
}
/* Time to advance to next connhost[] entry? */
if (conn->try_next_host)
{
pg_conn_host *ch;
struct addrinfo hint;
struct addrinfo *addrlist;
int thisport;
int ret;
char portstr[MAXPGPATH];
if (conn->whichhost + 1 < conn->nconnhost)
conn->whichhost++;
else
{
/*
* Oops , no more hosts .
*
* If we are trying to connect in " prefer - standby " mode , then drop
* the standby requirement and start over . Don ' t do this for
* cancel requests though , since we are certain the list of
* servers won ' t change as the target_server_type option is not
* applicable to those connections .
*
* Otherwise , an appropriate error message is already set up , so
* we just need to set the right status .
*/
if (conn->target_server_type == SERVER_TYPE_PREFER_STANDBY &&
conn->nconnhost > 0 &&
!conn->cancelRequest)
{
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY_PASS2;
conn->whichhost = 0 ;
}
else
goto error_return;
}
/* Drop any address info for previous host */
release_conn_addrinfo(conn);
/*
* Look up info for the new host . On failure , log the problem in
* conn - > errorMessage , then loop around to try the next host . ( Note
* we don ' t clear try_next_host until we ' ve succeeded . )
*/
ch = &conn->connhost[conn->whichhost];
/* Initialize hint structure */
MemSet(&hint, 0 , sizeof (hint));
hint.ai_socktype = SOCK_STREAM;
hint.ai_family = AF_UNSPEC;
/* Figure out the port number we're going to use. */
if (ch->port == NULL || ch->port[0 ] == '\0' )
thisport = DEF_PGPORT;
else
{
if (!pqParseIntParam(ch->port, &thisport, conn, "port" ))
goto error_return;
if (thisport < 1 || thisport > 65535 )
{
libpq_append_conn_error(conn, "invalid port number: \" %s\"" , ch->port);
goto keep_going;
}
}
snprintf(portstr, sizeof (portstr), "%d" , thisport);
/* Use pg_getaddrinfo_all() to resolve the address */
switch (ch->type)
{
case CHT_HOST_NAME:
ret = pg_getaddrinfo_all(ch->host, portstr, &hint,
&addrlist);
if (ret || !addrlist)
{
libpq_append_conn_error(conn, "could not translate host name \" %s\" to address: %s" ,
ch->host, gai_strerror(ret));
goto keep_going;
}
break ;
case CHT_HOST_ADDRESS:
hint.ai_flags = AI_NUMERICHOST;
ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint,
&addrlist);
if (ret || !addrlist)
{
libpq_append_conn_error(conn, "could not parse network address \" %s\": %s" ,
ch->hostaddr, gai_strerror(ret));
goto keep_going;
}
break ;
case CHT_UNIX_SOCKET:
hint.ai_family = AF_UNIX;
UNIXSOCK_PATH(portstr, thisport, ch->host);
if (strlen(portstr) >= UNIXSOCK_PATH_BUFLEN)
{
libpq_append_conn_error(conn, "Unix-domain socket path \" %s\" is too long (maximum %d bytes)" ,
portstr,
(int ) (UNIXSOCK_PATH_BUFLEN - 1 ));
goto keep_going;
}
/*
* NULL hostname tells pg_getaddrinfo_all to parse the service
* name as a Unix - domain socket path .
*/
ret = pg_getaddrinfo_all(NULL, portstr, &hint,
&addrlist);
if (ret || !addrlist)
{
libpq_append_conn_error(conn, "could not translate Unix-domain socket path \" %s\" to address: %s" ,
portstr, gai_strerror(ret));
goto keep_going;
}
break ;
}
/*
* Store a copy of the addrlist in private memory so we can perform
* randomization for load balancing .
*/
ret = store_conn_addrinfo(conn, addrlist);
pg_freeaddrinfo_all(hint.ai_family, addrlist);
if (ret)
goto error_return; /* message already logged */
/*
* If random load balancing is enabled we shuffle the addresses .
*/
if (conn->load_balance_type == LOAD_BALANCE_RANDOM)
{
/*
* This is the " inside - out " variant of the Fisher - Yates shuffle
* algorithm . Notionally , we append each new value to the array
* and then swap it with a randomly - chosen array element ( possibly
* including itself , else we fail to generate permutations with
* the last integer last ) . The swap step can be optimized by
* combining it with the insertion .
*
* We don ' t need to initialize conn - > prng_state here , because that
* already happened in pqConnectOptions2 .
*/
for (int i = 1 ; i < conn->naddr; i++)
{
int j = pg_prng_uint64_range(&conn->prng_state, 0 , i);
AddrInfo temp = conn->addr[j];
conn->addr[j] = conn->addr[i];
conn->addr[i] = temp;
}
}
reset_connection_state_machine = true ;
conn->try_next_host = false ;
}
/* Reset connection state machine? */
if (reset_connection_state_machine)
{
/*
* ( Re ) initialize our connection control variables for a set of
* connection attempts to a single server address . These variables
* must persist across individual connection attempts , but we must
* reset them when we start to consider a new server .
*/
conn->pversion = conn->max_pversion;
conn->send_appname = true ;
conn->failed_enc_methods = 0 ;
conn->current_enc_method = 0 ;
conn->allowed_enc_methods = 0 ;
reset_connection_state_machine = false ;
need_new_connection = true ;
}
/* Force a new connection (perhaps to the same server as before)? */
if (need_new_connection)
{
/* Drop any existing connection */
pqDropConnection(conn, true );
/* Reset all state obtained from old server */
pqDropServerData(conn);
/* Drop any PGresult we might have, too */
conn->asyncStatus = PGASYNC_IDLE;
conn->xactStatus = PQTRANS_IDLE;
conn->pipelineStatus = PQ_PIPELINE_OFF;
pqClearAsyncResult(conn);
/* Reset conn->status to put the state machine in the right state */
conn->status = CONNECTION_NEEDED;
need_new_connection = false ;
}
/*
* Decide what to do next , if server rejects SSL or GSS negotiation , but
* the connection is still valid . If there are no options left , error out
* with ' msg ' .
*/
#define ENCRYPTION_NEGOTIATION_FAILED(msg) \
do { \
switch (encryption_negotiation_failed(conn)) \
{ \
case 0 : \
libpq_append_conn_error(conn, (msg)); \
goto error_return; \
case 1 : \
conn->status = CONNECTION_MADE; \
return PGRES_POLLING_WRITING; \
case 2 : \
need_new_connection = true ; \
goto keep_going; \
} \
} while (0 );
/*
* Decide what to do next , if connection fails . If there are no options
* left , return with an error . The error message has already been written
* to the connection ' s error buffer .
*/
#define CONNECTION_FAILED() \
do { \
if (connection_failed(conn)) \
{ \
need_new_connection = true ; \
goto keep_going; \
} \
else \
goto error_return; \
} while (0 );
/* Now try to advance the state machine for this connection */
switch (conn->status)
{
case CONNECTION_NEEDED:
{
/*
* Try to initiate a connection to one of the addresses
* returned by pg_getaddrinfo_all ( ) . conn - > whichaddr is the
* next one to try .
*
* The extra level of braces here is historical . It ' s not
* worth reindenting this whole switch case to remove ' em .
*/
{
char host_addr[NI_MAXHOST];
int sock_type;
AddrInfo *addr_cur;
/*
* Advance to next possible host , if we ' ve tried all of
* the addresses for the current host .
*/
if (conn->whichaddr == conn->naddr)
{
conn->try_next_host = true ;
goto keep_going;
}
addr_cur = &conn->addr[conn->whichaddr];
/* Remember current address for possible use later */
memcpy(&conn->raddr, &addr_cur->addr, sizeof (SockAddr));
#ifdef ENABLE_GSS
/*
* Before establishing the connection , check if it ' s
* doomed to fail because gssencmode = ' require ' but GSSAPI
* is not available .
*/
if (conn->gssencmode[0 ] == 'r' )
{
if (conn->raddr.addr.ss_family == AF_UNIX)
{
libpq_append_conn_error(conn,
"GSSAPI encryption required but it is not supported over a local socket" );
goto error_return;
}
if (conn->gcred == GSS_C_NO_CREDENTIAL)
{
if (!pg_GSS_have_cred_cache(&conn->gcred))
{
libpq_append_conn_error(conn,
"GSSAPI encryption required but no credential cache" );
goto error_return;
}
}
}
#endif
/*
* Choose the encryption method to try first . Do this
* before establishing the connection , so that if none of
* the modes allowed by the connections options are
* available , we can error out before establishing the
* connection .
*/
if (!init_allowed_encryption_methods(conn))
goto error_return;
/*
* Set connip , too . Note we purposely ignore strdup
* failure ; not a big problem if it fails .
*/
if (conn->connip != NULL)
{
free(conn->connip);
conn->connip = NULL;
}
getHostaddr(conn, host_addr, NI_MAXHOST);
if (host_addr[0 ])
conn->connip = strdup(host_addr);
/* Try to create the socket */
sock_type = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
/*
* Atomically mark close - on - exec , if possible on this
* platform , so that there isn ' t a window where a
* subprogram executed by another thread inherits the
* socket . See fallback code below .
*/
sock_type |= SOCK_CLOEXEC;
#endif
#ifdef SOCK_NONBLOCK
/*
* We might as well skip a system call for nonblocking
* mode too , if we can .
*/
sock_type |= SOCK_NONBLOCK;
#endif
conn->sock = socket(addr_cur->family, sock_type, 0 );
if (conn->sock == PGINVALID_SOCKET)
{
int errorno = SOCK_ERRNO;
/*
* Silently ignore socket ( ) failure if we have more
* addresses to try ; this reduces useless chatter in
* cases where the address list includes both IPv4 and
* IPv6 but kernel only accepts one family .
*/
if (conn->whichaddr < conn->naddr ||
conn->whichhost + 1 < conn->nconnhost)
{
conn->try_next_addr = true ;
goto keep_going;
}
emitHostIdentityInfo(conn, host_addr);
libpq_append_conn_error(conn, "could not create socket: %s" ,
SOCK_STRERROR(errorno, sebuf, sizeof (sebuf)));
goto error_return;
}
/*
* Once we ' ve identified a target address , all errors
* except the preceding socket ( ) - failure case should be
* prefixed with host - identity information . ( If the
* connection succeeds , the contents of conn - > errorMessage
* won ' t matter , so this is harmless . )
*/
emitHostIdentityInfo(conn, host_addr);
/*
* Select socket options : no delay of outgoing data for
* TCP sockets , nonblock mode , close - on - exec . Try the
* next address if any of this fails .
*/
if (addr_cur->family != AF_UNIX)
{
if (!connectNoDelay(conn))
{
/* error message already created */
conn->try_next_addr = true ;
goto keep_going;
}
}
#ifndef SOCK_NONBLOCK
if (!pg_set_noblock(conn->sock))
{
libpq_append_conn_error(conn, "could not set socket to nonblocking mode: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
conn->try_next_addr = true ;
goto keep_going;
}
#endif
#ifndef SOCK_CLOEXEC
#ifdef F_SETFD
if (fcntl(conn->sock, F_SETFD, FD_CLOEXEC) == -1 )
{
libpq_append_conn_error(conn, "could not set socket to close-on-exec mode: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
conn->try_next_addr = true ;
goto keep_going;
}
#endif /* F_SETFD */
#endif
if (addr_cur->family != AF_UNIX)
{
#ifndef WIN32
int on = 1 ;
#endif
int usekeepalives = useKeepalives(conn);
int err = 0 ;
if (usekeepalives < 0 )
{
/* error is already reported */
err = 1 ;
}
else if (usekeepalives == 0 )
{
/* Do nothing */
}
#ifndef WIN32
else if (setsockopt(conn->sock,
SOL_SOCKET, SO_KEEPALIVE,
(char *) &on, sizeof (on)) < 0 )
{
libpq_append_conn_error(conn, "%s(%s) failed: %s" ,
"setsockopt" ,
"SO_KEEPALIVE" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
err = 1 ;
}
else if (!setKeepalivesIdle(conn)
|| !setKeepalivesInterval(conn)
|| !setKeepalivesCount(conn))
err = 1 ;
#else /* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
else if (!prepKeepalivesWin32(conn))
err = 1 ;
#endif /* SIO_KEEPALIVE_VALS */
#endif /* WIN32 */
else if (!setTCPUserTimeout(conn))
err = 1 ;
if (err)
{
conn->try_next_addr = true ;
goto keep_going;
}
}
/*----------
* We have three methods of blocking SIGPIPE during
* send ( ) calls to this socket :
*
* - setsockopt ( sock , SO_NOSIGPIPE )
* - send ( sock , . . . , MSG_NOSIGNAL )
* - setting the signal mask to SIG_IGN during send ( )
*
* The third method requires three syscalls per send ,
* so we prefer either of the first two , but they are
* less portable . The state is tracked in the following
* members of PGconn :
*
* conn - > sigpipe_so - we have set up SO_NOSIGPIPE
* conn - > sigpipe_flag - we ' re specifying MSG_NOSIGNAL
*
* If we can use SO_NOSIGPIPE , then set sigpipe_so here
* and we ' re done . Otherwise , set sigpipe_flag so that
* we will try MSG_NOSIGNAL on sends . If we get an error
* with MSG_NOSIGNAL , we ' ll clear that flag and revert to
* signal masking .
* - - - - - - - - - -
*/
conn->sigpipe_so = false ;
#ifdef MSG_NOSIGNAL
conn->sigpipe_flag = true ;
#else
conn->sigpipe_flag = false ;
#endif /* MSG_NOSIGNAL */
#ifdef SO_NOSIGPIPE
optval = 1 ;
if (setsockopt(conn->sock, SOL_SOCKET, SO_NOSIGPIPE,
(char *) &optval, sizeof (optval)) == 0 )
{
conn->sigpipe_so = true ;
conn->sigpipe_flag = false ;
}
#endif /* SO_NOSIGPIPE */
/*
* Start / make connection . This should not block , since we
* are in nonblock mode . If it does , well , too bad .
*/
if (connect(conn->sock, (struct sockaddr *) &addr_cur->addr.addr,
addr_cur->addr.salen) < 0 )
{
if (SOCK_ERRNO == EINPROGRESS ||
#ifdef WIN32
SOCK_ERRNO == EWOULDBLOCK ||
#endif
SOCK_ERRNO == EINTR)
{
/*
* This is fine - we ' re in non - blocking mode , and
* the connection is in progress . Tell caller to
* wait for write - ready on socket .
*/
conn->status = CONNECTION_STARTED;
return PGRES_POLLING_WRITING;
}
/* otherwise, trouble */
}
else
{
/*
* Hm , we ' re connected already - - - seems the " nonblock
* connection " wasn ' t . Advance the state machine and
* go do the next stuff .
*/
conn->status = CONNECTION_STARTED;
goto keep_going;
}
/*
* This connection failed . Add the error report to
* conn - > errorMessage , then try the next address if any .
*/
connectFailureMessage(conn, SOCK_ERRNO);
conn->try_next_addr = true ;
goto keep_going;
}
}
case CONNECTION_STARTED:
{
socklen_t optlen = sizeof (optval);
/*
* Write ready , since we ' ve made it here , so the connection
* has been made . . . or has failed .
*/
/*
* Now check ( using getsockopt ) that there is not an error
* state waiting for us on the socket .
*/
if (getsockopt(conn->sock, SOL_SOCKET, SO_ERROR,
(char *) &optval, &optlen) == -1 )
{
libpq_append_conn_error(conn, "could not get socket error status: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
goto error_return;
}
else if (optval != 0 )
{
/*
* When using a nonblocking connect , we will typically see
* connect failures at this point , so provide a friendly
* error message .
*/
connectFailureMessage(conn, optval);
/*
* Try the next address if any , just as in the case where
* connect ( ) returned failure immediately .
*/
conn->try_next_addr = true ;
goto keep_going;
}
/* Fill in the client address */
conn->laddr.salen = sizeof (conn->laddr.addr);
if (getsockname(conn->sock,
(struct sockaddr *) &conn->laddr.addr,
&conn->laddr.salen) < 0 )
{
libpq_append_conn_error(conn, "could not get client address from socket: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
goto error_return;
}
/*
* Implement requirepeer check , if requested and it ' s a
* Unix - domain socket .
*/
if (conn->requirepeer && conn->requirepeer[0 ] &&
conn->raddr.addr.ss_family == AF_UNIX)
{
#ifndef WIN32
char *remote_username;
#endif
uid_t uid;
gid_t gid;
errno = 0 ;
if (getpeereid(conn->sock, &uid, &gid) != 0 )
{
/*
* Provide special error message if getpeereid is a
* stub
*/
if (errno == ENOSYS)
libpq_append_conn_error(conn, "requirepeer parameter is not supported on this platform" );
else
libpq_append_conn_error(conn, "could not get peer credentials: %s" ,
strerror_r(errno, sebuf, sizeof (sebuf)));
goto error_return;
}
#ifndef WIN32
remote_username = pg_fe_getusername(uid,
&conn->errorMessage);
if (remote_username == NULL)
goto error_return; /* message already logged */
if (strcmp(remote_username, conn->requirepeer) != 0 )
{
libpq_append_conn_error(conn, "requirepeer specifies \" %s\", but actual peer user name is \" %s\"" ,
conn->requirepeer, remote_username);
free(remote_username);
goto error_return;
}
free(remote_username);
#else /* WIN32 */
/* should have failed with ENOSYS above */
Assert(false );
#endif /* WIN32 */
}
/*
* Make sure we can write before advancing to next step .
*/
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
case CONNECTION_MADE:
{
char *startpacket;
int packetlen;
#ifdef ENABLE_GSS
/*
* If GSSAPI encryption is enabled , send a packet to the
* server asking for GSSAPI Encryption and proceed with GSSAPI
* handshake . We will come back here after GSSAPI encryption
* has been established , with conn - > gctx set .
*/
if (conn->current_enc_method == ENC_GSSAPI && !conn->gctx)
{
ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
if (pqPacketSend(conn, 0 , &pv, sizeof (pv)) != STATUS_OK)
{
libpq_append_conn_error(conn, "could not send GSSAPI negotiation packet: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
goto error_return;
}
/* Ok, wait for response */
conn->status = CONNECTION_GSS_STARTUP;
return PGRES_POLLING_READING;
}
#endif
#ifdef USE_SSL
/*
* If SSL is enabled , start the SSL negotiation . We will come
* back here after SSL encryption has been established , with
* ssl_in_use set .
*/
if (conn->current_enc_method == ENC_SSL && !conn->ssl_in_use)
{
/*
* If traditional postgres SSL negotiation is used , send
* the SSL request . In direct negotiation , jump straight
* into the SSL handshake .
*/
if (conn->sslnegotiation[0 ] == 'p' )
{
ProtocolVersion pv;
/*
* Send the SSL request packet .
*
* Theoretically , this could block , but it really
* shouldn ' t since we only got here if the socket is
* write - ready .
*/
pv = pg_hton32(NEGOTIATE_SSL_CODE);
if (pqPacketSend(conn, 0 , &pv, sizeof (pv)) != STATUS_OK)
{
libpq_append_conn_error(conn, "could not send SSL negotiation packet: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
goto error_return;
}
/* Ok, wait for response */
conn->status = CONNECTION_SSL_STARTUP;
return PGRES_POLLING_READING;
}
else
{
Assert(conn->sslnegotiation[0 ] == 'd' );
conn->status = CONNECTION_SSL_STARTUP;
return PGRES_POLLING_WRITING;
}
}
#endif /* USE_SSL */
/*
* For cancel requests this is as far as we need to go in the
* connection establishment . Now we can actually send our
* cancellation request .
*/
if (conn->cancelRequest)
{
if (PQsendCancelRequest(conn) != STATUS_OK)
{
libpq_append_conn_error(conn, "could not send cancel packet: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
goto error_return;
}
conn->status = CONNECTION_AWAITING_RESPONSE;
return PGRES_POLLING_READING;
}
/*
* We have now established encryption , or we are happy to
* proceed without .
*/
/* Build the startup packet. */
startpacket = pqBuildStartupPacket3(conn, &packetlen,
EnvironmentOptions);
if (!startpacket)
{
libpq_append_conn_error(conn, "out of memory" );
goto error_return;
}
/*
* Send the startup packet .
*
* Theoretically , this could block , but it really shouldn ' t
* since we only got here if the socket is write - ready .
*/
if (pqPacketSend(conn, 0 , startpacket, packetlen) != STATUS_OK)
{
libpq_append_conn_error(conn, "could not send startup packet: %s" ,
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof (sebuf)));
free(startpacket);
goto error_return;
}
free(startpacket);
conn->status = CONNECTION_AWAITING_RESPONSE;
return PGRES_POLLING_READING;
}
/*
* Handle SSL negotiation : wait for postmaster messages and
* respond as necessary .
*/
case CONNECTION_SSL_STARTUP:
{
#ifdef USE_SSL
PostgresPollingStatusType pollres;
/*
* On first time through with traditional SSL negotiation , get
* the postmaster ' s response to our SSLRequest packet . With
* sslnegotiation = ' direct ' , go straight to initiating SSL .
*/
if (!conn->ssl_in_use && conn->sslnegotiation[0 ] == 'p' )
{
/*
* We use pqReadData here since it has the logic to
* distinguish no - data - yet from connection closure . Since
* conn - > ssl isn ' t set , a plain recv ( ) will occur .
*/
char SSLok;
int rdresult;
rdresult = pqReadData(conn);
if (rdresult < 0 )
{
/* errorMessage is already filled in */
goto error_return;
}
if (rdresult == 0 )
{
/* caller failed to wait for data */
return PGRES_POLLING_READING;
}
if (pqGetc(&SSLok, conn) < 0 )
{
/* should not happen really */
return PGRES_POLLING_READING;
}
if (SSLok == 'S' )
{
if (conn->Pfdebug)
pqTraceOutputCharResponse(conn, "SSLResponse" ,
SSLok);
/* mark byte consumed */
conn->inStart = conn->inCursor;
}
else if (SSLok == 'N' )
{
if (conn->Pfdebug)
pqTraceOutputCharResponse(conn, "SSLResponse" ,
SSLok);
/* mark byte consumed */
conn->inStart = conn->inCursor;
/*
* The connection is still valid , so if it ' s OK to
* continue without SSL , we can proceed using this
* connection . Otherwise return with an error .
*/
ENCRYPTION_NEGOTIATION_FAILED(libpq_gettext("server does not support SSL, but SSL was required" ));
}
else if (SSLok == 'E' )
{
/*
* Server failure of some sort , such as failure to
* fork a backend process . Don ' t bother retrieving
* the error message ; we should not trust it as the
* server has not been authenticated yet .
*/
libpq_append_conn_error(conn, "server sent an error response during SSL exchange" );
goto error_return;
}
else
{
libpq_append_conn_error(conn, "received invalid response to SSL negotiation: %c" ,
SSLok);
goto error_return;
}
}
/*
* Begin or continue the SSL negotiation process .
*/
pollres = pqsecure_open_client(conn);
if (pollres == PGRES_POLLING_OK)
{
/*
* At this point we should have no data already buffered .
* If we do , it was received before we performed the SSL
* handshake , so it wasn ' t encrypted and indeed may have
* been injected by a man - in - the - middle .
*/
if (conn->inCursor != conn->inEnd)
{
libpq_append_conn_error(conn, "received unencrypted data after SSL response" );
goto error_return;
}
/* SSL handshake done, ready to send startup packet */
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
if (pollres == PGRES_POLLING_FAILED)
{
/*
* SSL handshake failed . We will retry with a plaintext
* connection , if permitted by sslmode .
*/
CONNECTION_FAILED();
}
/* Else, return POLLING_READING or POLLING_WRITING status */
return pollres;
#else /* !USE_SSL */
/* can't get here */
goto error_return;
#endif /* USE_SSL */
}
case CONNECTION_GSS_STARTUP:
{
#ifdef ENABLE_GSS
PostgresPollingStatusType pollres;
/*
* If we haven ' t yet , get the postmaster ' s response to our
* negotiation packet
*/
if (!conn->gctx)
{
char gss_ok;
int rdresult = pqReadData(conn);
if (rdresult < 0 )
/* pqReadData fills in error message */
goto error_return;
else if (rdresult == 0 )
/* caller failed to wait for data */
return PGRES_POLLING_READING;
if (pqGetc(&gss_ok, conn) < 0 )
/* shouldn't happen... */
return PGRES_POLLING_READING;
if (gss_ok == 'E' )
{
/*
* Server failure of some sort , possibly protocol
* version support failure . Don ' t bother retrieving
* the error message ; we should not trust it anyway as
* the server has not authenticated yet .
*
* Note that unlike on an error response to
* SSLRequest , we allow falling back to SSL or
* plaintext connection here . GSS support was
* introduced in PostgreSQL version 12 , so an error
* response might mean that we are connecting to a
* pre - v12 server .
*/
libpq_append_conn_error(conn, "server sent an error response during GSS encryption exchange" );
CONNECTION_FAILED();
}
/* mark byte consumed */
conn->inStart = conn->inCursor;
if (gss_ok == 'N' )
{
if (conn->Pfdebug)
pqTraceOutputCharResponse(conn, "GSSENCResponse" ,
gss_ok);
/*
* The connection is still valid , so if it ' s OK to
* continue without GSS , we can proceed using this
* connection . Otherwise return with an error .
*/
ENCRYPTION_NEGOTIATION_FAILED(libpq_gettext("server doesn't support GSSAPI encryption, but it was required" ));
}
else if (gss_ok != 'G' )
{
libpq_append_conn_error(conn, "received invalid response to GSSAPI negotiation: %c" ,
gss_ok);
goto error_return;
}
if (conn->Pfdebug)
pqTraceOutputCharResponse(conn, "GSSENCResponse" ,
gss_ok);
}
/* Begin or continue GSSAPI negotiation */
pollres = pqsecure_open_gss(conn);
if (pollres == PGRES_POLLING_OK)
{
/*
* At this point we should have no data already buffered .
* If we do , it was received before we performed the GSS
* handshake , so it wasn ' t encrypted and indeed may have
* been injected by a man - in - the - middle .
*/
if (conn->inCursor != conn->inEnd)
{
libpq_append_conn_error(conn, "received unencrypted data after GSSAPI encryption response" );
goto error_return;
}
/* All set for startup packet */
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (pollres == PGRES_POLLING_FAILED)
{
/*
* GSS handshake failed . We will retry with an SSL or
* plaintext connection , if permitted by the options .
*/
CONNECTION_FAILED();
}
/* Else, return POLLING_READING or POLLING_WRITING status */
return pollres;
#else /* !ENABLE_GSS */
/* unreachable */
goto error_return;
#endif /* ENABLE_GSS */
}
/*
* Handle authentication exchange : wait for postmaster messages
* and respond as necessary .
*/
case CONNECTION_AWAITING_RESPONSE:
{
char beresp;
int msgLength;
int avail;
AuthRequest areq;
int res;
bool async;
/*
* Scan the message from current point ( note that if we find
* the message is incomplete , we will return without advancing
* inStart , and resume here next time ) .
*/
conn->inCursor = conn->inStart;
/* Read type byte */
if (pqGetc(&beresp, conn))
{
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
/*
* Validate message type : we expect only an authentication
* request , NegotiateProtocolVersion , or an error here .
* Anything else probably means it ' s not Postgres on the other
* end at all .
*/
if (beresp != PqMsg_AuthenticationRequest &&
beresp != PqMsg_ErrorResponse &&
beresp != PqMsg_NegotiateProtocolVersion)
{
libpq_append_conn_error(conn, "expected authentication request from server, but received %c" ,
beresp);
goto error_return;
}
/* Read message length word */
if (pqGetInt(&msgLength, 4 , conn))
{
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
/*
* Try to validate message length before using it .
*
* Authentication requests can ' t be very large , although GSS
* auth requests may not be that small . Same for
* NegotiateProtocolVersion .
*
* Errors can be a little larger , but not huge . If we see a
* large apparent length in an error , it means we ' re really
* talking to a pre - 3 . 0 - protocol server ; cope . ( Before
* version 14 , the server also used the old protocol for
* errors that happened before processing the startup packet . )
*/
if (beresp == PqMsg_AuthenticationRequest &&
(msgLength < 8 || msgLength > 2000 ))
{
libpq_append_conn_error(conn, "received invalid authentication request" );
goto error_return;
}
if (beresp == PqMsg_NegotiateProtocolVersion &&
(msgLength < 8 || msgLength > 2000 ))
{
libpq_append_conn_error(conn, "received invalid protocol negotiation message" );
goto error_return;
}
#define MAX_ERRLEN 30000
if (beresp == PqMsg_ErrorResponse &&
(msgLength < 8 || msgLength > MAX_ERRLEN))
{
/* Handle error from a pre-3.0 server */
conn->inCursor = conn->inStart + 1 ; /* reread data */
if (pqGets_append(&conn->errorMessage, conn))
{
/*
* We may not have authenticated the server yet , so
* don ' t let the buffer grow forever .
*/
avail = conn->inEnd - conn->inCursor;
if (avail > MAX_ERRLEN)
{
libpq_append_conn_error(conn, "received invalid error message" );
goto error_return;
}
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
/* OK, we read the message; mark data consumed */
pqParseDone(conn, conn->inCursor);
/*
* Before 7 . 2 , the postmaster didn ' t always end its
* messages with a newline , so add one if needed to
* conform to libpq conventions .
*/
if (conn->errorMessage.len == 0 ||
conn->errorMessage.data[conn->errorMessage.len - 1 ] != '\n' )
{
appendPQExpBufferChar(&conn->errorMessage, '\n' );
}
goto error_return;
}
#undef MAX_ERRLEN
/*
* Can ' t process if message body isn ' t all here yet .
*
* After this check passes , any further EOF during parsing
* implies that the server sent a bad / truncated message .
* Reading more bytes won ' t help in that case , so don ' t return
* PGRES_POLLING_READING after this point .
*/
msgLength -= 4 ;
avail = conn->inEnd - conn->inCursor;
if (avail < msgLength)
{
/*
* Before returning , try to enlarge the input buffer if
* needed to hold the whole message ; see notes in
* pqParseInput3 .
*/
if (pqCheckInBufferSpace(conn->inCursor + (size_t) msgLength,
conn))
goto error_return;
/* We'll come back when there is more data */
return PGRES_POLLING_READING;
}
/* Handle errors. */
if (beresp == PqMsg_ErrorResponse)
{
if (pqGetErrorNotice3(conn, true ))
{
libpq_append_conn_error(conn, "received invalid error message" );
goto error_return;
}
/* OK, we read the message; mark data consumed */
pqParseDone(conn, conn->inCursor);
/*
* If error is " cannot connect now " , try the next host if
* any ( but we don ' t want to consider additional addresses
* for this host , nor is there much point in changing SSL
* or GSS mode ) . This is helpful when dealing with
* standby servers that might not be in hot - standby state .
*/
if (strcmp(conn->last_sqlstate,
ERRCODE_CANNOT_CONNECT_NOW) == 0 )
{
conn->try_next_host = true ;
goto keep_going;
}
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
CONNECTION_FAILED();
}
/* Handle NegotiateProtocolVersion */
else if (beresp == PqMsg_NegotiateProtocolVersion)
{
if (conn->pversion_negotiated)
{
libpq_append_conn_error(conn, "received duplicate protocol negotiation message" );
goto error_return;
}
if (pqGetNegotiateProtocolVersion3(conn))
{
/* pqGetNegotiateProtocolVersion3 set error already */
goto error_return;
}
conn->pversion_negotiated = true ;
/* OK, we read the message; mark data consumed */
pqParseDone(conn, conn->inCursor);
goto keep_going;
}
/* It is an authentication request. */
conn->auth_req_received = true ;
/* Get the type of request. */
if (pqGetInt((int *) &areq, 4 , conn))
{
/* can't happen because we checked the length already */
libpq_append_conn_error(conn, "received invalid authentication request" );
goto error_return;
}
msgLength -= 4 ;
/*
* Process the rest of the authentication request message , and
* respond to it if necessary .
*
* Note that conn - > pghost must be non - NULL if we are going to
* avoid the Kerberos code doing a hostname look - up .
*/
res = pg_fe_sendauth(areq, msgLength, conn, &async);
if (async && (res == STATUS_OK))
{
/*
* We ' ll come back later once we ' re ready to respond .
* Don ' t consume the request yet .
*/
conn->status = CONNECTION_AUTHENTICATING;
goto keep_going;
}
/*
* OK , we have processed the message ; mark data consumed . We
* don ' t call pqParseDone here because we already traced this
* message inside pg_fe_sendauth .
*/
conn->inStart = conn->inCursor;
if (res != STATUS_OK)
{
/*
* OAuth connections may perform two - step discovery , where
* the first connection is a dummy .
*/
if (conn->sasl == &pg_oauth_mech && conn->oauth_want_retry)
{
need_new_connection = true ;
goto keep_going;
}
goto error_return;
}
/*
* Just make sure that any data sent by pg_fe_sendauth is
* flushed out . Although this theoretically could block , it
* really shouldn ' t since we don ' t send large auth responses .
*/
if (pqFlush(conn))
goto error_return;
if (areq == AUTH_REQ_OK)
{
/* We are done with authentication exchange */
conn->status = CONNECTION_AUTH_OK;
/*
* Set asyncStatus so that PQgetResult will think that
* what comes back next is the result of a query . See
* below .
*/
conn->asyncStatus = PGASYNC_BUSY;
}
/* Look to see if we have more data yet. */
goto keep_going;
}
case CONNECTION_AUTHENTICATING:
{
PostgresPollingStatusType status;
if (!conn->async_auth || !conn->cleanup_async_auth)
{
/* programmer error; should not happen */
libpq_append_conn_error(conn,
"internal error: async authentication has no handler" );
goto error_return;
}
/* Drive some external authentication work. */
status = conn->async_auth(conn);
if (status == PGRES_POLLING_FAILED)
goto error_return;
if (status == PGRES_POLLING_OK)
{
/* Done. Tear down the async implementation. */
conn->cleanup_async_auth(conn);
conn->cleanup_async_auth = NULL;
/*
* Cleanup must unset altsock , both as an indication that
* it ' s been released , and to stop pqSocketCheck from
* looking at the wrong socket after async auth is done .
*/
if (conn->altsock != PGINVALID_SOCKET)
{
Assert(false );
libpq_append_conn_error(conn,
"internal error: async cleanup did not release polling socket" );
goto error_return;
}
/*
* Reenter the authentication exchange with the server . We
* didn ' t consume the message that started external
* authentication , so it ' ll be reprocessed as if we just
* received it .
*/
conn->status = CONNECTION_AWAITING_RESPONSE;
goto keep_going;
}
/*
* Caller needs to poll some more . conn - > async_auth ( ) should
* have assigned an altsock to poll on .
*/
if (conn->altsock == PGINVALID_SOCKET)
{
Assert(false );
libpq_append_conn_error(conn,
"internal error: async authentication did not set a socket for polling" );
goto error_return;
}
return status;
}
case CONNECTION_AUTH_OK:
{
/*
* 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 are ParameterStatus and BackendKeyData
* messages . ) 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 , by setting
* asyncStatus = PGASYNC_BUSY ( done above ) .
*/
if (PQisBusy(conn))
return PGRES_POLLING_READING;
res = PQgetResult(conn);
/*
* NULL return indicating we have gone to IDLE state is
* expected
*/
if (res)
{
if (res->resultStatus != PGRES_FATAL_ERROR)
libpq_append_conn_error(conn, "unexpected message from server during startup" );
else if (conn->send_appname &&
(conn->appname || conn->fbappname))
{
/*
* If we tried to send application_name , check to see
* if the error is about that - - - pre - 9 . 0 servers will
* reject it at this stage of the process . If so ,
* close the connection and retry without sending
* application_name . We could possibly get a false
* SQLSTATE match here and retry uselessly , but there
* seems no great harm in that ; we ' ll just get the
* same error again if it ' s unrelated .
*/
const char *sqlstate;
sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (sqlstate &&
strcmp(sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0 )
{
PQclear(res);
conn->send_appname = false ;
need_new_connection = true ;
goto keep_going;
}
}
/*
* if the resultStatus is FATAL , then conn - > errorMessage
* already has a copy of the error ; needn ' t copy it back .
* But add a newline if it ' s not there already , since
* postmaster error messages may not have one .
*/
if (conn->errorMessage.len <= 0 ||
conn->errorMessage.data[conn->errorMessage.len - 1 ] != '\n' )
appendPQExpBufferChar(&conn->errorMessage, '\n' );
PQclear(res);
goto error_return;
}
/* Almost there now ... */
conn->status = CONNECTION_CHECK_TARGET;
goto keep_going;
}
case CONNECTION_CHECK_TARGET:
{
/*
* If a read - write , read - only , primary , or standby connection
* is required , see if we have one .
*/
if (conn->target_server_type == SERVER_TYPE_READ_WRITE ||
conn->target_server_type == SERVER_TYPE_READ_ONLY)
{
bool read_only_server;
/*
* If the server didn ' t report
* " default_transaction_read_only " or " in_hot_standby " at
* startup , we must determine its state by sending the
* query " SHOW transaction_read_only " . This GUC exists in
* all server versions that support 3 . 0 protocol .
*/
if (conn->default_transaction_read_only == PG_BOOL_UNKNOWN ||
conn->in_hot_standby == PG_BOOL_UNKNOWN)
{
/*
* We use PQsendQueryContinue so that
* conn - > errorMessage does not get cleared . We need
* to preserve any error messages related to previous
* hosts we have tried and failed to connect to .
*/
conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn,
"SHOW transaction_read_only" ))
goto error_return;
/* We'll return to this state when we have the answer */
conn->status = CONNECTION_CHECK_WRITABLE;
return PGRES_POLLING_READING;
}
/* OK, we can make the test */
read_only_server =
(conn->default_transaction_read_only == PG_BOOL_YES ||
conn->in_hot_standby == PG_BOOL_YES);
if ((conn->target_server_type == SERVER_TYPE_READ_WRITE) ?
read_only_server : !read_only_server)
{
/* Wrong server state, reject and try the next host */
if (conn->target_server_type == SERVER_TYPE_READ_WRITE)
libpq_append_conn_error(conn, "session is read-only" );
else
libpq_append_conn_error(conn, "session is not read-only" );
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/*
* Try next host if any , but we don ' t want to consider
* additional addresses for this host .
*/
conn->try_next_host = true ;
goto keep_going;
}
}
else if (conn->target_server_type == SERVER_TYPE_PRIMARY ||
conn->target_server_type == SERVER_TYPE_STANDBY ||
conn->target_server_type == SERVER_TYPE_PREFER_STANDBY)
{
/*
* If the server didn ' t report " in_hot_standby " at
* startup , we must determine its state by sending the
* query " SELECT pg_catalog . pg_is_in_recovery ( ) " . Servers
* before 9 . 0 don ' t have that function , but by the same
* token they don ' t have any standby mode , so we may just
* assume the result .
*/
if (conn->sversion < 90000 )
conn->in_hot_standby = PG_BOOL_NO;
if (conn->in_hot_standby == PG_BOOL_UNKNOWN)
{
/*
* We use PQsendQueryContinue so that
* conn - > errorMessage does not get cleared . We need
* to preserve any error messages related to previous
* hosts we have tried and failed to connect to .
*/
conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn,
"SELECT pg_catalog.pg_is_in_recovery()" ))
goto error_return;
/* We'll return to this state when we have the answer */
conn->status = CONNECTION_CHECK_STANDBY;
return PGRES_POLLING_READING;
}
/* OK, we can make the test */
if ((conn->target_server_type == SERVER_TYPE_PRIMARY) ?
(conn->in_hot_standby == PG_BOOL_YES) :
(conn->in_hot_standby == PG_BOOL_NO))
{
/* Wrong server state, reject and try the next host */
if (conn->target_server_type == SERVER_TYPE_PRIMARY)
libpq_append_conn_error(conn, "server is in hot standby mode" );
else
libpq_append_conn_error(conn, "server is not in hot standby mode" );
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/*
* Try next host if any , but we don ' t want to consider
* additional addresses for this host .
*/
conn->try_next_host = true ;
goto keep_going;
}
}
/* Don't hold onto any OAuth tokens longer than necessary. */
pqClearOAuthToken(conn);
/*
* For non cancel requests we can release the address list
* now . For cancel requests we never actually resolve
* addresses and instead the addrinfo exists for the lifetime
* of the connection .
*/
if (!conn->cancelRequest)
release_conn_addrinfo(conn);
/*
* Contents of conn - > errorMessage are no longer interesting
* ( and it seems some clients expect it to be empty after a
* successful connection ) .
*/
pqClearConnErrorState(conn);
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
}
case CONNECTION_CONSUME:
{
/*
* This state just makes sure the connection is idle after
* we ' ve obtained the result of a SHOW or SELECT query . Once
* we ' re clear , return to CONNECTION_CHECK_TARGET state to
* decide what to do next . We must transiently set status =
* CONNECTION_OK in order to use the result - consuming
* subroutines .
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
if (PQisBusy(conn))
{
conn->status = CONNECTION_CONSUME;
return PGRES_POLLING_READING;
}
/* Call PQgetResult() again until we get a NULL result */
res = PQgetResult(conn);
if (res != NULL)
{
PQclear(res);
conn->status = CONNECTION_CONSUME;
return PGRES_POLLING_READING;
}
conn->status = CONNECTION_CHECK_TARGET;
goto keep_going;
}
case CONNECTION_CHECK_WRITABLE:
{
/*
* Waiting for result of " SHOW transaction_read_only " . We
* must transiently set status = CONNECTION_OK in order to use
* the result - consuming subroutines .
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
if (PQisBusy(conn))
{
conn->status = CONNECTION_CHECK_WRITABLE;
return PGRES_POLLING_READING;
}
res = PQgetResult(conn);
if (res && PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1 )
{
char *val = PQgetvalue(res, 0 , 0 );
/*
* " transaction_read_only = on " proves that at least one
* of default_transaction_read_only and in_hot_standby is
* on , but we don ' t actually know which . We don ' t care
* though for the purpose of identifying a read - only
* session , so satisfy the CONNECTION_CHECK_TARGET code by
* claiming they are both on . On the other hand , if it ' s
* a read - write session , they are certainly both off .
*/
if (strncmp(val, "on" , 2 ) == 0 )
{
conn->default_transaction_read_only = PG_BOOL_YES;
conn->in_hot_standby = PG_BOOL_YES;
}
else
{
conn->default_transaction_read_only = PG_BOOL_NO;
conn->in_hot_standby = PG_BOOL_NO;
}
PQclear(res);
/* Finish reading messages before continuing */
conn->status = CONNECTION_CONSUME;
goto keep_going;
}
/* Something went wrong with "SHOW transaction_read_only". */
PQclear(res);
/* Append error report to conn->errorMessage. */
libpq_append_conn_error(conn, "\" %s\" failed" ,
"SHOW transaction_read_only" );
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/* Try next host. */
conn->try_next_host = true ;
goto keep_going;
}
case CONNECTION_CHECK_STANDBY:
{
/*
* Waiting for result of " SELECT pg_is_in_recovery ( ) " . We
* must transiently set status = CONNECTION_OK in order to use
* the result - consuming subroutines .
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
if (PQisBusy(conn))
{
conn->status = CONNECTION_CHECK_STANDBY;
return PGRES_POLLING_READING;
}
res = PQgetResult(conn);
if (res && PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1 )
{
char *val = PQgetvalue(res, 0 , 0 );
if (strncmp(val, "t" , 1 ) == 0 )
conn->in_hot_standby = PG_BOOL_YES;
else
conn->in_hot_standby = PG_BOOL_NO;
PQclear(res);
/* Finish reading messages before continuing */
conn->status = CONNECTION_CONSUME;
goto keep_going;
}
/* Something went wrong with "SELECT pg_is_in_recovery()". */
PQclear(res);
/* Append error report to conn->errorMessage. */
libpq_append_conn_error(conn, "\" %s\" failed" ,
"SELECT pg_is_in_recovery()" );
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/* Try next host. */
conn->try_next_host = true ;
goto keep_going;
}
default :
libpq_append_conn_error(conn,
"invalid connection state %d, probably indicative of memory corruption" ,
conn->status);
goto error_return;
}
/* Unreachable */
error_return:
/*
* We used to close the socket at this point , but that makes it awkward
* for those above us if they wish to remove this socket from their own
* records ( an fd_set for example ) . We ' ll just have this socket closed
* when PQfinish is called ( which is compulsory even after an error , since
* the connection structure must be freed ) .
*/
conn->status = CONNECTION_BAD;
return PGRES_POLLING_FAILED;
}
/*
* Initialize the state machine for negotiating encryption
*/
static bool
init_allowed_encryption_methods(PGconn *conn)
{
if (conn->raddr.addr.ss_family == AF_UNIX)
{
/* Don't request SSL or GSSAPI over Unix sockets */
conn->allowed_enc_methods &= ~(ENC_SSL | ENC_GSSAPI);
/*
* XXX : we probably should not do this . sslmode = require works
* differently
*/
if (conn->gssencmode[0 ] == 'r' )
{
libpq_append_conn_error(conn,
"GSSAPI encryption required but it is not supported over a local socket" );
conn->allowed_enc_methods = 0 ;
conn->current_enc_method = ENC_ERROR;
return false ;
}
conn->allowed_enc_methods = ENC_PLAINTEXT;
conn->current_enc_method = ENC_PLAINTEXT;
return true ;
}
/* initialize based on sslmode and gssencmode */
conn->allowed_enc_methods = 0 ;
#ifdef USE_SSL
/* sslmode anything but 'disable', and GSSAPI not required */
if (conn->sslmode[0 ] != 'd' && conn->gssencmode[0 ] != 'r' )
{
conn->allowed_enc_methods |= ENC_SSL;
}
#endif
#ifdef ENABLE_GSS
if (conn->gssencmode[0 ] != 'd' )
conn->allowed_enc_methods |= ENC_GSSAPI;
#endif
if ((conn->sslmode[0 ] == 'd' || conn->sslmode[0 ] == 'p' || conn->sslmode[0 ] == 'a' ) &&
(conn->gssencmode[0 ] == 'd' || conn->gssencmode[0 ] == 'p' ))
{
conn->allowed_enc_methods |= ENC_PLAINTEXT;
}
return select_next_encryption_method(conn, false );
}
/*
* Out - of - line portion of the ENCRYPTION_NEGOTIATION_FAILED ( ) macro in the
* PQconnectPoll state machine .
*
* Return value :
* 0 : connection failed and we are out of encryption methods to try . return an error
* 1 : Retry with next connection method . The TCP connection is still valid and in
* known state , so we can proceed with the negotiating next method without
* reconnecting .
* 2 : Disconnect , and retry with next connection method .
*
* conn - > current_enc_method is updated to the next method to try .
*/
#if defined (USE_SSL) || defined (ENABLE_GSS)
static int
encryption_negotiation_failed(PGconn *conn)
{
Assert((conn->failed_enc_methods & conn->current_enc_method) == 0 );
conn->failed_enc_methods |= conn->current_enc_method;
if (select_next_encryption_method(conn, true ))
{
/* An existing connection cannot be reused for direct SSL */
if (conn->current_enc_method == ENC_SSL && conn->sslnegotiation[0 ] == 'd' )
return 2 ;
else
return 1 ;
}
else
return 0 ;
}
#endif
/*
* Out - of - line portion of the CONNECTION_FAILED ( ) macro
*
* Returns true , if we should reconnect and retry with a different encryption
* method . conn - > current_enc_method is updated to the next method to try .
*/
static bool
connection_failed(PGconn *conn)
{
Assert((conn->failed_enc_methods & conn->current_enc_method) == 0 );
conn->failed_enc_methods |= conn->current_enc_method;
return select_next_encryption_method(conn, false );
}
/*
* Choose the next encryption method to try . If this is a retry ,
* conn - > failed_enc_methods has already been updated . The function sets
* conn - > current_enc_method to the next method to try . Returns false if no
* encryption methods remain .
*/
static bool
select_next_encryption_method(PGconn *conn, bool have_valid_connection)
{
int remaining_methods;
#define SELECT_NEXT_METHOD(method) \
do { \
if ((remaining_methods & method) != 0 ) \
{ \
conn->current_enc_method = method; \
return true ; \
} \
} while (false )
remaining_methods = conn->allowed_enc_methods & ~conn->failed_enc_methods;
/*
* Try GSSAPI before SSL
*/
#ifdef ENABLE_GSS
if ((remaining_methods & ENC_GSSAPI) != 0 )
{
/*
* If GSSAPI encryption is enabled , then call pg_GSS_have_cred_cache ( )
* which will return true if we can acquire credentials ( and give us a
* handle to use in conn - > gcred ) , and then send a packet to the server
* asking for GSSAPI Encryption ( and skip past SSL negotiation and
* regular startup below ) .
*/
if (!conn->gctx)
{
if (!pg_GSS_have_cred_cache(&conn->gcred))
{
conn->allowed_enc_methods &= ~ENC_GSSAPI;
remaining_methods &= ~ENC_GSSAPI;
if (conn->gssencmode[0 ] == 'r' )
{
libpq_append_conn_error(conn,
"GSSAPI encryption required but no credential cache" );
}
}
}
}
SELECT_NEXT_METHOD(ENC_GSSAPI);
#endif
/*
* The order between SSL encryption and plaintext depends on sslmode . With
* sslmode = allow , try plaintext connection before SSL . With
* sslmode = prefer , it ' s the other way round . With other modes , we only try
* plaintext or SSL connections so the order they ' re listed here doesn ' t
* matter .
*/
if (conn->sslmode[0 ] == 'a' )
SELECT_NEXT_METHOD(ENC_PLAINTEXT);
SELECT_NEXT_METHOD(ENC_SSL);
if (conn->sslmode[0 ] != 'a' )
SELECT_NEXT_METHOD(ENC_PLAINTEXT);
/* No more options */
conn->current_enc_method = ENC_ERROR;
return false ;
#undef SELECT_NEXT_METHOD
}
/*
* internal_ping
* Determine if a server is running and if we can connect to it .
*
* The argument is a connection that ' s been started , but not completed .
*/
static PGPing
internal_ping(PGconn *conn)
{
/* Say "no attempt" if we never got to PQconnectPoll */
if (!conn || !conn->options_valid)
return PQPING_NO_ATTEMPT;
/* Attempt to complete the connection */
if (conn->status != CONNECTION_BAD)
(void ) pqConnectDBComplete(conn);
/* Definitely OK if we succeeded */
if (conn->status != CONNECTION_BAD)
return PQPING_OK;
/*
* Here begins the interesting part of " ping " : determine the cause of the
* failure in sufficient detail to decide what to return . We do not want
* to report that the server is not up just because we didn ' t have a valid
* password , for example . In fact , any sort of authentication request
* implies the server is up . ( We need this check since the libpq side of
* things might have pulled the plug on the connection before getting an
* error as such from the postmaster . )
*/
if (conn->auth_req_received)
return PQPING_OK;
/*
* If we failed to get any ERROR response from the postmaster , report
* PQPING_NO_RESPONSE . This result could be somewhat misleading for a
* pre - 7 . 4 server , since it won ' t send back a SQLSTATE , but those are long
* out of support . Another corner case where the server could return a
* failure without a SQLSTATE is fork failure , but PQPING_NO_RESPONSE
* isn ' t totally unreasonable for that anyway . We expect that every other
* failure case in a modern server will produce a report with a SQLSTATE .
*
* NOTE : whenever we get around to making libpq generate SQLSTATEs for
* client - side errors , we should either not store those into
* last_sqlstate , or add an extra flag so we can tell client - side errors
* apart from server - side ones .
*/
if (strlen(conn->last_sqlstate) != 5 )
return PQPING_NO_RESPONSE;
/*
* Report PQPING_REJECT if server says it ' s not accepting connections .
*/
if (strcmp(conn->last_sqlstate, ERRCODE_CANNOT_CONNECT_NOW) == 0 )
return PQPING_REJECT;
/*
* Any other SQLSTATE can be taken to indicate that the server is up .
* Presumably it didn ' t like our username , password , or database name ; or
* perhaps it had some transient failure , but that should not be taken as
* meaning " it ' s down " .
*/
return PQPING_OK;
}
/*
* pqMakeEmptyPGconn
* - create a PGconn data structure with ( as yet ) no interesting data
*/
PGconn *
pqMakeEmptyPGconn(void )
{
PGconn *conn;
#ifdef WIN32
/*
* Make sure socket support is up and running in this process .
*
* Note : the Windows documentation says that we should eventually do a
* matching WSACleanup ( ) call , but experience suggests that that is at
* least as likely to cause problems as fix them . So we don ' t .
*/
static bool wsastartup_done = false ;
if (!wsastartup_done)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2 , 2 ), &wsaData) != 0 )
return NULL;
wsastartup_done = true ;
}
/* Forget any earlier error */
WSASetLastError(0 );
#endif /* WIN32 */
conn = (PGconn *) malloc(sizeof (PGconn));
if (conn == NULL)
return conn;
/* Zero all pointers and booleans */
MemSet(conn, 0 , sizeof (PGconn));
/* install default notice hooks */
conn->noticeHooks.noticeRec = defaultNoticeReceiver;
conn->noticeHooks.noticeProc = defaultNoticeProcessor;
conn->status = CONNECTION_BAD;
conn->asyncStatus = PGASYNC_IDLE;
conn->pipelineStatus = PQ_PIPELINE_OFF;
conn->xactStatus = PQTRANS_IDLE;
conn->options_valid = false ;
conn->nonblocking = false ;
conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false ; /* unless server says differently */
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS;
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
conn->altsock = PGINVALID_SOCKET;
conn->Pfdebug = NULL;
/*
* We try to send at least 8 K at a time , which is the usual size of pipe
* buffers on Unix systems . That way , when we are sending a large amount
* of data , we avoid incurring extra kernel context swaps for partial
* bufferloads . The output buffer is initially made 16 K in size , and we
* try to dump it after accumulating 8 K .
*
* With the same goal of minimizing context swaps , the input buffer will
* be enlarged anytime it has less than 8 K free , so we initially allocate
* twice that .
*/
conn->inBufSize = 16 * 1024 ;
conn->inBuffer = (char *) malloc(conn->inBufSize);
conn->outBufSize = 16 * 1024 ;
conn->outBuffer = (char *) malloc(conn->outBufSize);
conn->rowBufLen = 32 ;
conn->rowBuf = (PGdataValue *) malloc(conn->rowBufLen * sizeof (PGdataValue));
initPQExpBuffer(&conn->errorMessage);
initPQExpBuffer(&conn->workBuffer);
if (conn->inBuffer == NULL ||
conn->outBuffer == NULL ||
conn->rowBuf == NULL ||
PQExpBufferBroken(&conn->errorMessage) ||
PQExpBufferBroken(&conn->workBuffer))
{
/* out of memory already :-( */
freePGconn(conn);
conn = NULL;
}
return conn;
}
/*
* freePGconn
* - free an idle ( closed ) PGconn data structure
*
* NOTE : this should not overlap any functionality with pqClosePGconn ( ) .
* Clearing / resetting of transient state belongs there ; what we do here is
* release data that is to be held for the life of the PGconn structure .
* If a value ought to be cleared / freed during PQreset ( ) , do it there not here .
*/
static void
freePGconn(PGconn *conn)
{
/* let any event procs clean up their state data */
for (int i = 0 ; i < conn->nEvents; i++)
{
PGEventConnDestroy evt;
evt.conn = conn;
(void ) conn->events[i].proc(PGEVT_CONNDESTROY, &evt,
conn->events[i].passThrough);
free(conn->events[i].name);
}
/* free everything not freed in pqClosePGconn */
free(conn->pghost);
free(conn->pghostaddr);
free(conn->pgport);
free(conn->connect_timeout);
free(conn->pgtcp_user_timeout);
free(conn->client_encoding_initial);
free(conn->pgoptions);
free(conn->appname);
free(conn->fbappname);
free(conn->dbName);
free(conn->replication);
free(conn->pgservice);
free(conn->pguser);
if (conn->pgpass)
{
explicit_bzero(conn->pgpass, strlen(conn->pgpass));
free(conn->pgpass);
}
free(conn->pgpassfile);
free(conn->channel_binding);
free(conn->keepalives);
free(conn->keepalives_idle);
free(conn->keepalives_interval);
free(conn->keepalives_count);
free(conn->sslmode);
free(conn->sslnegotiation);
free(conn->sslcompression);
free(conn->sslkey);
free(conn->sslcert);
if (conn->sslpassword)
{
explicit_bzero(conn->sslpassword, strlen(conn->sslpassword));
free(conn->sslpassword);
}
free(conn->sslcertmode);
free(conn->sslrootcert);
free(conn->sslcrl);
free(conn->sslcrldir);
free(conn->sslsni);
free(conn->requirepeer);
free(conn->gssencmode);
free(conn->krbsrvname);
free(conn->gsslib);
free(conn->gssdelegation);
free(conn->min_protocol_version);
free(conn->max_protocol_version);
free(conn->ssl_min_protocol_version);
free(conn->ssl_max_protocol_version);
free(conn->target_session_attrs);
free(conn->require_auth);
free(conn->load_balance_hosts);
free(conn->scram_client_key);
free(conn->scram_server_key);
free(conn->sslkeylogfile);
free(conn->oauth_issuer);
free(conn->oauth_issuer_id);
free(conn->oauth_discovery_uri);
free(conn->oauth_client_id);
free(conn->oauth_client_secret);
free(conn->oauth_scope);
/* Note that conn->Pfdebug is not ours to close or free */
free(conn->events);
pqReleaseConnHosts(conn);
free(conn->connip);
release_conn_addrinfo(conn);
free(conn->scram_client_key_binary);
free(conn->scram_server_key_binary);
/* if this is a cancel connection, be_cancel_key may still be allocated */
free(conn->be_cancel_key);
free(conn->inBuffer);
free(conn->outBuffer);
free(conn->rowBuf);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
free(conn);
}
/*
* pqReleaseConnHosts
* - Free the host list in the PGconn .
*/
void
pqReleaseConnHosts(PGconn *conn)
{
if (conn->connhost)
{
for (int i = 0 ; i < conn->nconnhost; ++i)
{
free(conn->connhost[i].host);
free(conn->connhost[i].hostaddr);
free(conn->connhost[i].port);
if (conn->connhost[i].password != NULL)
{
explicit_bzero(conn->connhost[i].password,
strlen(conn->connhost[i].password));
free(conn->connhost[i].password);
}
}
free(conn->connhost);
conn->connhost = NULL;
}
}
/*
* store_conn_addrinfo
* - copy addrinfo to PGconn object
*
* Copies the addrinfos from addrlist to the PGconn object such that the
* addrinfos can be manipulated by libpq . Returns a positive integer on
* failure , otherwise zero .
*/
static int
store_conn_addrinfo(PGconn *conn, struct addrinfo *addrlist)
{
struct addrinfo *ai = addrlist;
conn->whichaddr = 0 ;
conn->naddr = 0 ;
while (ai)
{
ai = ai->ai_next;
conn->naddr++;
}
conn->addr = calloc(conn->naddr, sizeof (AddrInfo));
if (conn->addr == NULL)
{
libpq_append_conn_error(conn, "out of memory" );
return 1 ;
}
ai = addrlist;
for (int i = 0 ; i < conn->naddr; i++)
{
conn->addr[i].family = ai->ai_family;
memcpy(&conn->addr[i].addr.addr, ai->ai_addr,
ai->ai_addrlen);
conn->addr[i].addr.salen = ai->ai_addrlen;
ai = ai->ai_next;
}
return 0 ;
}
/*
* release_conn_addrinfo
* - Free any addrinfo list in the PGconn .
*/
static void
release_conn_addrinfo(PGconn *conn)
{
if (conn->addr)
{
free(conn->addr);
conn->addr = NULL;
}
}
/*
* sendTerminateConn
* - Send a terminate message to backend .
*/
static void
sendTerminateConn(PGconn *conn)
{
/*
* The Postgres cancellation protocol does not have a notion of a
* Terminate message , so don ' t send one .
*/
if (conn->cancelRequest)
return ;
/*
* Note that the protocol doesn ' t allow us to send Terminate messages
* during the startup phase .
*/
if (conn->sock != PGINVALID_SOCKET && conn->status == CONNECTION_OK)
{
/*
* Try to send " close connection " message to backend . Ignore any
* error .
*/
pqPutMsgStart(PqMsg_Terminate, conn);
pqPutMsgEnd(conn);
(void ) pqFlush(conn);
}
}
/*
* pqClosePGconn
* - properly close a connection to the backend
*
* This should reset or release all transient state , but NOT the connection
* parameters . On exit , the PGconn should be in condition to start a fresh
* connection with the same parameters ( see PQreset ( ) ) .
*/
void
pqClosePGconn(PGconn *conn)
{
/*
* If possible , send Terminate message to close the connection politely .
*/
sendTerminateConn(conn);
/*
* Must reset the blocking status so a possible reconnect will work .
*
* Don ' t call PQsetnonblocking ( ) because it will fail if it ' s unable to
* flush the connection .
*/
conn->nonblocking = false ;
/*
* Close the connection , reset all transient state , flush I / O buffers .
* Note that this includes clearing conn ' s error state ; we ' re no longer
* interested in any failures associated with the old connection , and we
* want a clean slate for any new connection attempt .
*/
pqDropConnection(conn, true );
conn->status = CONNECTION_BAD; /* Well, not really _bad_ - just absent */
conn->asyncStatus = PGASYNC_IDLE;
conn->xactStatus = PQTRANS_IDLE;
conn->pipelineStatus = PQ_PIPELINE_OFF;
pqClearOAuthToken(conn);
pqClearAsyncResult(conn); /* deallocate result */
pqClearConnErrorState(conn);
/*
* Release addrinfo , but since cancel requests never change their addrinfo
* we don ' t do that . Otherwise we would have to rebuild it during a
* PQcancelReset .
*/
if (!conn->cancelRequest)
release_conn_addrinfo(conn);
/* Reset all state obtained from server, too */
pqDropServerData(conn);
}
/*
* 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)
{
pqClosePGconn(conn);
freePGconn(conn);
}
}
/*
* PQreset : resets the connection to the backend by closing the
* existing connection and creating a new one .
*/
void
PQreset(PGconn *conn)
{
if (conn)
{
pqClosePGconn(conn);
if (pqConnectDBStart(conn) && pqConnectDBComplete(conn))
{
/*
* Notify event procs of successful reset .
*/
int i;
for (i = 0 ; i < conn->nEvents; i++)
{
PGEventConnReset evt;
evt.conn = conn;
(void ) conn->events[i].proc(PGEVT_CONNRESET, &evt,
conn->events[i].passThrough);
}
}
}
}
/*
* PQresetStart :
* resets the connection to the backend
* closes the existing connection and makes a new one
* Returns 1 on success , 0 on failure .
*/
int
PQresetStart(PGconn *conn)
{
if (conn)
{
pqClosePGconn(conn);
return pqConnectDBStart(conn);
}
return 0 ;
}
/*
* PQresetPoll :
* resets the connection to the backend
* closes the existing connection and makes a new one
*/
PostgresPollingStatusType
PQresetPoll(PGconn *conn)
{
if (conn)
{
PostgresPollingStatusType status = PQconnectPoll(conn);
if (status == PGRES_POLLING_OK)
{
/*
* Notify event procs of successful reset .
*/
int i;
for (i = 0 ; i < conn->nEvents; i++)
{
PGEventConnReset evt;
evt.conn = conn;
(void ) conn->events[i].proc(PGEVT_CONNRESET, &evt,
conn->events[i].passThrough);
}
}
return status;
}
return PGRES_POLLING_FAILED;
}
/*
* pqPacketSend ( ) - - convenience routine to send a message to server .
*
* pack_type : the single - byte message type code . ( Pass zero for startup
* packets , which have no message type code . )
*
* buf , buf_len : contents of message . The given length includes only what
* is in buf ; the message type and message length fields are added here .
*
* RETURNS : STATUS_ERROR if the write fails , STATUS_OK otherwise .
* SIDE_EFFECTS : may block .
*/
int
pqPacketSend(PGconn *conn, char pack_type,
const void *buf, size_t buf_len)
{
/* Start the message. */
if (pqPutMsgStart(pack_type, conn))
return STATUS_ERROR;
/* Send the message body. */
if (pqPutnchar(buf, buf_len, conn))
return STATUS_ERROR;
/* Finish the message. */
if (pqPutMsgEnd(conn))
return STATUS_ERROR;
/* Flush to ensure backend gets it. */
if (pqFlush(conn))
return STATUS_ERROR;
return STATUS_OK;
}
#ifdef USE_LDAP
#define LDAP_URL "ldap://"
#define LDAP_DEF_PORT 389
#define PGLDAP_TIMEOUT 2
#define ld_is_sp_tab(x) ((x) == ' ' || (x) == '\t' )
#define ld_is_nl_cr(x) ((x) == '\r' || (x) == '\n' )
/*
* ldapServiceLookup
*
* Search the LDAP URL passed as first argument , treat the result as a
* string of connection options that are parsed and added to the array of
* options passed as second argument .
*
* LDAP URLs must conform to RFC 1959 without escape sequences .
* ldap : //host:port/dn?attributes?scope?filter?extensions
*
* Returns
* 0 if the lookup was successful ,
* 1 if the connection to the LDAP server could be established but
* the search was unsuccessful ,
* 2 if a connection could not be established , and
* 3 if a fatal error occurred .
*
* An error message is appended to * errorMessage for return codes 1 and 3 .
*/
static int
ldapServiceLookup(const char *purl, PQconninfoOption *options,
PQExpBuffer errorMessage)
{
int port = LDAP_DEF_PORT,
scope,
rc,
size,
state,
oldstate,
i;
#ifndef WIN32
int msgid;
#endif
bool found_keyword;
char *url,
*hostname,
*portstr,
*endptr,
*dn,
*scopestr,
*filter,
*result,
*p,
*p1 = NULL,
*optname = NULL,
*optval = NULL;
char *attrs[2 ] = {NULL, NULL};
LDAP *ld = NULL;
LDAPMessage *res,
*entry;
struct berval **values;
LDAP_TIMEVAL time = {PGLDAP_TIMEOUT, 0 };
if ((url = strdup(purl)) == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
return 3 ;
}
/*
* Parse URL components , check for correctness . Basically , url has ' \ 0 '
* placed at component boundaries and variables are pointed at each
* component .
*/
if (pg_strncasecmp(url, LDAP_URL, strlen(LDAP_URL)) != 0 )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": scheme must be ldap://", purl);
free(url);
return 3 ;
}
/* hostname */
hostname = url + strlen(LDAP_URL);
if (*hostname == '/' ) /* no hostname? */
hostname = DefaultHost; /* the default */
/* dn, "distinguished name" */
p = strchr(url + strlen(LDAP_URL), '/' );
if (p == NULL || *(p + 1 ) == '\0' || *(p + 1 ) == '?' )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": missing distinguished name" ,
purl);
free(url);
return 3 ;
}
*p = '\0' ; /* terminate hostname */
dn = p + 1 ;
/* attribute */
if ((p = strchr(dn, '?' )) == NULL || *(p + 1 ) == '\0' || *(p + 1 ) == '?' )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": must have exactly one attribute" ,
purl);
free(url);
return 3 ;
}
*p = '\0' ;
attrs[0 ] = p + 1 ;
/* scope */
if ((p = strchr(attrs[0 ], '?' )) == NULL || *(p + 1 ) == '\0' || *(p + 1 ) == '?' )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": must have search scope (base/one/sub)" ,
purl);
free(url);
return 3 ;
}
*p = '\0' ;
scopestr = p + 1 ;
/* filter */
if ((p = strchr(scopestr, '?' )) == NULL || *(p + 1 ) == '\0' || *(p + 1 ) == '?' )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": no filter" ,
purl);
free(url);
return 3 ;
}
*p = '\0' ;
filter = p + 1 ;
if ((p = strchr(filter, '?' )) != NULL)
*p = '\0' ;
/* port number? */
if ((p1 = strchr(hostname, ':' )) != NULL)
{
long lport;
*p1 = '\0' ;
portstr = p1 + 1 ;
errno = 0 ;
lport = strtol(portstr, &endptr, 10 );
if (*portstr == '\0' || *endptr != '\0' || errno || lport < 0 || lport > 65535 )
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": invalid port number" ,
purl);
free(url);
return 3 ;
}
port = (int ) lport;
}
/* Allow only one attribute */
if (strchr(attrs[0 ], ',' ) != NULL)
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": must have exactly one attribute" ,
purl);
free(url);
return 3 ;
}
/* set scope */
if (pg_strcasecmp(scopestr, "base" ) == 0 )
scope = LDAP_SCOPE_BASE;
else if (pg_strcasecmp(scopestr, "one" ) == 0 )
scope = LDAP_SCOPE_ONELEVEL;
else if (pg_strcasecmp(scopestr, "sub" ) == 0 )
scope = LDAP_SCOPE_SUBTREE;
else
{
libpq_append_error(errorMessage,
"invalid LDAP URL \" %s\": must have search scope (base/one/sub)" ,
purl);
free(url);
return 3 ;
}
/* initialize LDAP structure */
if ((ld = ldap_init(hostname, port)) == NULL)
{
libpq_append_error(errorMessage, "could not create LDAP structure" );
free(url);
return 3 ;
}
/*
* Perform an explicit anonymous bind .
*
* LDAP does not require that an anonymous bind is performed explicitly ,
* but we want to distinguish between the case where LDAP bind does not
* succeed within PGLDAP_TIMEOUT seconds ( return 2 to continue parsing the
* service control file ) and the case where querying the LDAP server fails
* ( return 1 to end parsing ) .
*
* Unfortunately there is no way of setting a timeout that works for both
* Windows and OpenLDAP .
*/
#ifdef WIN32
/* the nonstandard ldap_connect function performs an anonymous bind */
if (ldap_connect(ld, &time) != LDAP_SUCCESS)
{
/* error or timeout in ldap_connect */
free(url);
ldap_unbind(ld);
return 2 ;
}
#else /* !WIN32 */
/* in OpenLDAP, use the LDAP_OPT_NETWORK_TIMEOUT option */
if (ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &time) != LDAP_SUCCESS)
{
free(url);
ldap_unbind(ld);
return 3 ;
}
/* anonymous bind */
if ((msgid = ldap_simple_bind(ld, NULL, NULL)) == -1 )
{
/* error or network timeout */
free(url);
ldap_unbind(ld);
return 2 ;
}
/* wait some time for the connection to succeed */
res = NULL;
if ((rc = ldap_result(ld, msgid, LDAP_MSG_ALL, &time, &res)) == -1 ||
res == NULL)
{
/* error or timeout */
if (res != NULL)
ldap_msgfree(res);
free(url);
ldap_unbind(ld);
return 2 ;
}
ldap_msgfree(res);
/* reset timeout */
time.tv_sec = -1 ;
if (ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &time) != LDAP_SUCCESS)
{
free(url);
ldap_unbind(ld);
return 3 ;
}
#endif /* WIN32 */
/* search */
res = NULL;
if ((rc = ldap_search_st(ld, dn, scope, filter, attrs, 0 , &time, &res))
!= LDAP_SUCCESS)
{
if (res != NULL)
ldap_msgfree(res);
libpq_append_error(errorMessage, "lookup on LDAP server failed: %s" , ldap_err2string(rc));
ldap_unbind(ld);
free(url);
return 1 ;
}
/* complain if there was not exactly one result */
if ((rc = ldap_count_entries(ld, res)) != 1 )
{
if (rc > 1 )
libpq_append_error(errorMessage, "more than one entry found on LDAP lookup" );
else
libpq_append_error(errorMessage, "no entry found on LDAP lookup" );
ldap_msgfree(res);
ldap_unbind(ld);
free(url);
return 1 ;
}
/* get entry */
if ((entry = ldap_first_entry(ld, res)) == NULL)
{
/* should never happen */
libpq_append_error(errorMessage, "no entry found on LDAP lookup" );
ldap_msgfree(res);
ldap_unbind(ld);
free(url);
return 1 ;
}
/* get values */
if ((values = ldap_get_values_len(ld, entry, attrs[0 ])) == NULL)
{
libpq_append_error(errorMessage, "attribute has no values on LDAP lookup" );
ldap_msgfree(res);
ldap_unbind(ld);
free(url);
return 1 ;
}
ldap_msgfree(res);
free(url);
if (values[0 ] == NULL)
{
libpq_append_error(errorMessage, "attribute has no values on LDAP lookup" );
ldap_value_free_len(values);
ldap_unbind(ld);
return 1 ;
}
/* concatenate values into a single string with newline terminators */
size = 1 ; /* for the trailing null */
for (i = 0 ; values[i] != NULL; i++)
{
if (values[i]->bv_len >= INT_MAX ||
size > (INT_MAX - (values[i]->bv_len + 1 )))
{
libpq_append_error(errorMessage,
"connection info string size exceeds the maximum allowed (%d)" ,
INT_MAX);
ldap_value_free_len(values);
ldap_unbind(ld);
return 3 ;
}
size += values[i]->bv_len + 1 ;
}
if ((result = malloc(size)) == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
ldap_value_free_len(values);
ldap_unbind(ld);
return 3 ;
}
p = result;
for (i = 0 ; values[i] != NULL; i++)
{
memcpy(p, values[i]->bv_val, values[i]->bv_len);
p += values[i]->bv_len;
*(p++) = '\n' ;
}
*p = '\0' ;
ldap_value_free_len(values);
ldap_unbind(ld);
/* parse result string */
oldstate = state = 0 ;
for (p = result; *p != '\0' ; ++p)
{
switch (state)
{
case 0 : /* between entries */
if (!ld_is_sp_tab(*p) && !ld_is_nl_cr(*p))
{
optname = p;
state = 1 ;
}
break ;
case 1 : /* in option name */
if (ld_is_sp_tab(*p))
{
*p = '\0' ;
state = 2 ;
}
else if (ld_is_nl_cr(*p))
{
libpq_append_error(errorMessage,
"missing \" =\" after \" %s\" in connection info string" ,
optname);
free(result);
return 3 ;
}
else if (*p == '=' )
{
*p = '\0' ;
state = 3 ;
}
break ;
case 2 : /* after option name */
if (*p == '=' )
{
state = 3 ;
}
else if (!ld_is_sp_tab(*p))
{
libpq_append_error(errorMessage,
"missing \" =\" after \" %s\" in connection info string" ,
optname);
free(result);
return 3 ;
}
break ;
case 3 : /* before option value */
if (*p == '\' ')
{
optval = p + 1 ;
p1 = p + 1 ;
state = 5 ;
}
else if (ld_is_nl_cr(*p))
{
optval = optname + strlen(optname); /* empty */
state = 0 ;
}
else if (!ld_is_sp_tab(*p))
{
optval = p;
state = 4 ;
}
break ;
case 4 : /* in unquoted option value */
if (ld_is_sp_tab(*p) || ld_is_nl_cr(*p))
{
*p = '\0' ;
state = 0 ;
}
break ;
case 5 : /* in quoted option value */
if (*p == '\' ')
{
*p1 = '\0' ;
state = 0 ;
}
else if (*p == '\\' )
state = 6 ;
else
*(p1++) = *p;
break ;
case 6 : /* in quoted option value after escape */
*(p1++) = *p;
state = 5 ;
break ;
}
if (state == 0 && oldstate != 0 )
{
found_keyword = false ;
for (i = 0 ; options[i].keyword; i++)
{
if (strcmp(options[i].keyword, optname) == 0 )
{
if (options[i].val == NULL)
{
options[i].val = strdup(optval);
if (!options[i].val)
{
libpq_append_error(errorMessage, "out of memory" );
free(result);
return 3 ;
}
}
found_keyword = true ;
break ;
}
}
if (!found_keyword)
{
libpq_append_error(errorMessage, "invalid connection option \" %s\"" , optname);
free(result);
return 1 ;
}
optname = NULL;
optval = NULL;
}
oldstate = state;
}
free(result);
if (state == 5 || state == 6 )
{
libpq_append_error(errorMessage,
"unterminated quoted string in connection info string" );
return 3 ;
}
return 0 ;
}
#endif /* USE_LDAP */
/*
* parseServiceInfo : if a service name has been given , look it up and absorb
* connection options from it into * options .
*
* Returns 0 on success , nonzero on failure . On failure , if errorMessage
* isn ' t null , also store an error message there . ( Note : the only reason
* this function and related ones don ' t dump core on errorMessage = = NULL
* is the undocumented fact that appendPQExpBuffer does nothing when passed
* a null PQExpBuffer pointer . )
*/
static int
parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
{
const char *service = conninfo_getval(options, "service" );
char serviceFile[MAXPGPATH];
char *env;
bool group_found = false ;
int status;
struct stat stat_buf;
/*
* We have to special - case the environment variable PGSERVICE here , since
* this is and should be called before inserting environment defaults for
* other connection options .
*/
if (service == NULL)
service = getenv("PGSERVICE" );
/* If no service name given, nothing to do */
if (service == NULL)
return 0 ;
/*
* Try PGSERVICEFILE if specified , else try ~ / . pg_service . conf ( if that
* exists ) .
*/
if ((env = getenv("PGSERVICEFILE" )) != NULL)
strlcpy(serviceFile, env, sizeof (serviceFile));
else
{
char homedir[MAXPGPATH];
if (!pqGetHomeDirectory(homedir, sizeof (homedir)))
goto next_file;
snprintf(serviceFile, MAXPGPATH, "%s/%s" , homedir, ".pg_service.conf" );
if (stat(serviceFile, &stat_buf) != 0 )
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);
if (stat(serviceFile, &stat_buf) != 0 )
goto last_file;
status = parseServiceFile(serviceFile, service, options, errorMessage, &group_found);
if (status != 0 )
return status;
last_file:
if (!group_found)
{
libpq_append_error(errorMessage, "definition of service \" %s\" not found" , service);
return 3 ;
}
return 0 ;
}
static int
parseServiceFile(const char *serviceFile,
const char *service,
PQconninfoOption *options,
PQExpBuffer errorMessage,
bool *group_found)
{
int result = 0 ,
linenr = 0 ,
i;
FILE *f;
char *line;
char buf[1024 ];
*group_found = false ;
f = fopen(serviceFile, "r" );
if (f == NULL)
{
libpq_append_error(errorMessage, "service file \" %s\" not found" , serviceFile);
return 1 ;
}
while ((line = fgets(buf, sizeof (buf), f)) != NULL)
{
int len;
linenr++;
if (strlen(line) >= sizeof (buf) - 1 )
{
libpq_append_error(errorMessage,
"line %d too long in service file \" %s\"" ,
linenr,
serviceFile);
result = 2 ;
goto exit ;
}
/* ignore whitespace at end of line, especially the newline */
len = strlen(line);
while (len > 0 && isspace((unsigned char ) line[len - 1 ]))
line[--len] = '\0' ;
/* ignore leading whitespace too */
while (*line && isspace((unsigned char ) line[0 ]))
line++;
/* ignore comments and empty lines */
if (line[0 ] == '\0' || line[0 ] == '#' )
continue ;
/* Check for right groupname */
if (line[0 ] == '[' )
{
if (*group_found)
{
/* end of desired group reached; return success */
goto exit ;
}
if (strncmp(line + 1 , service, strlen(service)) == 0 &&
line[strlen(service) + 1 ] == ']' )
*group_found = true ;
else
*group_found = false ;
}
else
{
if (*group_found)
{
/*
* 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 rc = 2, go on reading for fallback */
switch (rc)
{
case 0 :
goto exit ;
case 1 :
case 3 :
result = 3 ;
goto exit ;
case 2 :
continue ;
}
}
#endif
key = line;
val = strchr(line, '=' );
if (val == NULL)
{
libpq_append_error(errorMessage,
"syntax error in service file \" %s\", line %d" ,
serviceFile,
linenr);
result = 3 ;
goto exit ;
}
*val++ = '\0' ;
if (strcmp(key, "service" ) == 0 )
{
libpq_append_error(errorMessage,
"nested service specifications not supported in service file \" %s\", line %d" ,
serviceFile,
linenr);
result = 3 ;
goto exit ;
}
/*
* 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 (options[i].val == NULL)
options[i].val = strdup(val);
if (!options[i].val)
{
libpq_append_error(errorMessage, "out of memory" );
result = 3 ;
goto exit ;
}
found_keyword = true ;
break ;
}
}
if (!found_keyword)
{
libpq_append_error(errorMessage,
"syntax error in service file \" %s\", line %d" ,
serviceFile,
linenr);
result = 3 ;
goto exit ;
}
}
}
}
exit :
fclose(f);
return result;
}
/*
* PQconninfoParse
*
* Parse a string like PQconnectdb ( ) would do and return the
* resulting connection options array . NULL is returned on failure .
* The result contains only options specified directly in the string ,
* not any possible default values .
*
* If errmsg isn ' t NULL , * errmsg is set to NULL on success , or a malloc ' d
* string on failure ( use PQfreemem to free it ) . In out - of - memory conditions
* both * errmsg and the result could be NULL .
*
* NOTE : the returned array is dynamically allocated and should
* be freed when no longer needed via PQconninfoFree ( ) .
*/
PQconninfoOption *
PQconninfoParse(const char *conninfo, char **errmsg)
{
PQExpBufferData errorBuf;
PQconninfoOption *connOptions;
if (errmsg)
*errmsg = NULL; /* default */
initPQExpBuffer(&errorBuf);
if (PQExpBufferDataBroken(errorBuf))
return NULL; /* out of memory already :-( */
connOptions = parse_connection_string(conninfo, &errorBuf, false );
if (connOptions == NULL && errmsg)
*errmsg = errorBuf.data;
else
termPQExpBuffer(&errorBuf);
return connOptions;
}
/*
* Build a working copy of the constant PQconninfoOptions array .
*/
static PQconninfoOption *
conninfo_init(PQExpBuffer errorMessage)
{
PQconninfoOption *options;
PQconninfoOption *opt_dest;
const internalPQconninfoOption *cur_opt;
/*
* Get enough memory for all options in PQconninfoOptions , even if some
* end up being filtered out .
*/
options = (PQconninfoOption *) malloc(sizeof (PQconninfoOption) * sizeof (PQconninfoOptions) / sizeof (PQconninfoOptions[0 ]));
if (options == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
return NULL;
}
opt_dest = options;
for (cur_opt = PQconninfoOptions; cur_opt->keyword; cur_opt++)
{
/* Only copy the public part of the struct, not the full internal */
memcpy(opt_dest, cur_opt, sizeof (PQconninfoOption));
opt_dest++;
}
MemSet(opt_dest, 0 , sizeof (PQconninfoOption));
return options;
}
/*
* Connection string parser
*
* Returns a malloc ' d PQconninfoOption array , if parsing is successful .
* Otherwise , NULL is returned and an error message is added to errorMessage .
*
* If use_defaults is true , default values are filled in ( from a service file ,
* environment variables , etc ) .
*/
static PQconninfoOption *
parse_connection_string(const char *connstr, PQExpBuffer errorMessage,
bool use_defaults)
{
/* Parse as URI if connection string matches URI prefix */
if (uri_prefix_length(connstr) != 0 )
return conninfo_uri_parse(connstr, errorMessage, use_defaults);
/* Parse as default otherwise */
return conninfo_parse(connstr, errorMessage, use_defaults);
}
/*
* Checks if connection string starts with either of the valid URI prefix
* designators .
*
* Returns the URI prefix length , 0 if the string doesn ' t contain a URI prefix .
*
* XXX this is duplicated in psql / common . c .
*/
static int
uri_prefix_length(const char *connstr)
{
if (strncmp(connstr, uri_designator,
sizeof (uri_designator) - 1 ) == 0 )
return sizeof (uri_designator) - 1 ;
if (strncmp(connstr, short_uri_designator,
sizeof (short_uri_designator) - 1 ) == 0 )
return sizeof (short_uri_designator) - 1 ;
return 0 ;
}
/*
* Recognized connection string either starts with a valid URI prefix or
* contains a " = " in it .
*
* Must be consistent with parse_connection_string : anything for which this
* returns true should at least look like it ' s parseable by that routine .
*
* XXX this is duplicated in psql / common . c
*/
static bool
recognized_connection_string(const char *connstr)
{
return uri_prefix_length(connstr) != 0 || strchr(connstr, '=' ) != NULL;
}
/*
* Subroutine for parse_connection_string
*
* Deal with a string containing key = value pairs .
*/
static PQconninfoOption *
conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
bool use_defaults)
{
char *pname;
char *pval;
char *buf;
char *cp;
char *cp2;
PQconninfoOption *options;
/* Make a working copy of PQconninfoOptions */
options = conninfo_init(errorMessage);
if (options == NULL)
return NULL;
/* Need a modifiable copy of the input string */
if ((buf = strdup(conninfo)) == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
PQconninfoFree(options);
return NULL;
}
cp = buf;
while (*cp)
{
/* Skip blanks before the parameter name */
if (isspace((unsigned char ) *cp))
{
cp++;
continue ;
}
/* Get the parameter name */
pname = cp;
while (*cp)
{
if (*cp == '=' )
break ;
if (isspace((unsigned char ) *cp))
{
*cp++ = '\0' ;
while (*cp)
{
if (!isspace((unsigned char ) *cp))
break ;
cp++;
}
break ;
}
cp++;
}
/* Check that there is a following '=' */
if (*cp != '=' )
{
libpq_append_error(errorMessage,
"missing \" =\" after \" %s\" in connection info string" ,
pname);
PQconninfoFree(options);
free(buf);
return NULL;
}
*cp++ = '\0' ;
/* Skip blanks after the '=' */
while (*cp)
{
if (!isspace((unsigned char ) *cp))
break ;
cp++;
}
/* Get the parameter value */
pval = cp;
if (*cp != '\' ')
{
cp2 = pval;
while (*cp)
{
if (isspace((unsigned char ) *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' )
{
libpq_append_error(errorMessage, "unterminated quoted string in connection info string" );
PQconninfoFree(options);
free(buf);
return NULL;
}
if (*cp == '\\' )
{
cp++;
if (*cp != '\0' )
*cp2++ = *cp++;
continue ;
}
if (*cp == '\' ')
{
*cp2 = '\0' ;
cp++;
break ;
}
*cp2++ = *cp++;
}
}
/*
* Now that we have the name and the value , store the record .
*/
if (!conninfo_storeval(options, pname, pval, errorMessage, false , false ))
{
PQconninfoFree(options);
free(buf);
return NULL;
}
}
/* Done with the modifiable input string */
free(buf);
/*
* Add in defaults if the caller wants that .
*/
if (use_defaults)
{
if (!conninfo_add_defaults(options, errorMessage))
{
PQconninfoFree(options);
return NULL;
}
}
return options;
}
/*
* Conninfo array parser routine
*
* If successful , a malloc ' d PQconninfoOption array is returned .
* If not successful , NULL is returned and an error message is
* appended to errorMessage .
* Defaults are supplied ( from a service file , environment variables , etc )
* for unspecified options , but only if use_defaults is true .
*
* If expand_dbname is non - zero , and the value passed for the first occurrence
* of " dbname " keyword is a connection string ( as indicated by
* recognized_connection_string ) then parse and process it , overriding any
* previously processed conflicting keywords . Subsequent keywords will take
* precedence , however . In - tree programs generally specify expand_dbname = true ,
* so command - line arguments naming a database can use a connection string .
* Some code acquires arbitrary database names from known - literal sources like
* PQdb ( ) , PQconninfoParse ( ) and pg_database . datname . When connecting to such
* a database , in - tree code first wraps the name in a connection string .
*/
static PQconninfoOption *
conninfo_array_parse(const char *const *keywords, const char *const *values,
PQExpBuffer errorMessage, bool use_defaults,
int expand_dbname)
{
PQconninfoOption *options;
PQconninfoOption *dbname_options = NULL;
PQconninfoOption *option;
int i = 0 ;
/*
* If expand_dbname is non - zero , check keyword " dbname " to see if val is
* actually a recognized connection string .
*/
while (expand_dbname && keywords[i])
{
const char *pname = keywords[i];
const char *pvalue = values[i];
/* first find "dbname" if any */
if (strcmp(pname, "dbname" ) == 0 && pvalue)
{
/*
* If value is a connection string , parse it , but do not use
* defaults here - - those get picked up later . We only want to
* override for those parameters actually passed .
*/
if (recognized_connection_string(pvalue))
{
dbname_options = parse_connection_string(pvalue, errorMessage, false );
if (dbname_options == NULL)
return NULL;
}
break ;
}
++i;
}
/* Make a working copy of PQconninfoOptions */
options = conninfo_init(errorMessage);
if (options == NULL)
{
PQconninfoFree(dbname_options);
return NULL;
}
/* Parse the keywords/values arrays */
i = 0 ;
while (keywords[i])
{
const char *pname = keywords[i];
const char *pvalue = values[i];
if (pvalue != NULL && pvalue[0 ] != '\0' )
{
/* Search for the param record */
for (option = options; option->keyword != NULL; option++)
{
if (strcmp(option->keyword, pname) == 0 )
break ;
}
/* Check for invalid connection option */
if (option->keyword == NULL)
{
libpq_append_error(errorMessage, "invalid connection option \" %s\"" , pname);
PQconninfoFree(options);
PQconninfoFree(dbname_options);
return NULL;
}
/*
* If we are on the first dbname parameter , and we have a parsed
* connection string , copy those parameters across , overriding any
* existing previous settings .
*/
if (strcmp(pname, "dbname" ) == 0 && dbname_options)
{
PQconninfoOption *str_option;
for (str_option = dbname_options; str_option->keyword != NULL; str_option++)
{
if (str_option->val != NULL)
{
int k;
for (k = 0 ; options[k].keyword; k++)
{
if (strcmp(options[k].keyword, str_option->keyword) == 0 )
{
free(options[k].val);
options[k].val = strdup(str_option->val);
if (!options[k].val)
{
libpq_append_error(errorMessage, "out of memory" );
PQconninfoFree(options);
PQconninfoFree(dbname_options);
return NULL;
}
break ;
}
}
}
}
/*
* Forget the parsed connection string , so that any subsequent
* dbname parameters will not be expanded .
*/
PQconninfoFree(dbname_options);
dbname_options = NULL;
}
else
{
/*
* Store the value , overriding previous settings
*/
free(option->val);
option->val = strdup(pvalue);
if (!option->val)
{
libpq_append_error(errorMessage, "out of memory" );
PQconninfoFree(options);
PQconninfoFree(dbname_options);
return NULL;
}
}
}
++i;
}
PQconninfoFree(dbname_options);
/*
* Add in defaults if the caller wants that .
*/
if (use_defaults)
{
if (!conninfo_add_defaults(options, errorMessage))
{
PQconninfoFree(options);
return NULL;
}
}
return options;
}
/*
* Add the default values for any unspecified options to the connection
* options array .
*
* Defaults are obtained from a service file , environment variables , etc .
*
* Returns true if successful , otherwise false ; errorMessage , if supplied ,
* is filled in upon failure . Note that failure to locate a default value
* is not an error condition here - - - we just leave the option ' s value as
* NULL .
*/
static bool
conninfo_add_defaults(PQconninfoOption *options, PQExpBuffer errorMessage)
{
PQconninfoOption *option;
PQconninfoOption *sslmode_default = NULL,
*sslrootcert = NULL;
char *tmp;
/*
* If there ' s a service spec , use it to obtain any not - explicitly - given
* parameters . Ignore error if no error message buffer is passed because
* there is no way to pass back the failure message .
*/
if (parseServiceInfo(options, errorMessage) != 0 && errorMessage)
return false ;
/*
* Get the fallback resources for parameters not specified in the conninfo
* string nor the service .
*/
for (option = options; option->keyword != NULL; option++)
{
if (strcmp(option->keyword, "sslrootcert" ) == 0 )
sslrootcert = option; /* save for later */
if (option->val != NULL)
continue ; /* Value was in conninfo or service */
/*
* Try to get the environment variable fallback
*/
if (option->envvar != NULL)
{
if ((tmp = getenv(option->envvar)) != NULL)
{
option->val = strdup(tmp);
if (!option->val)
{
if (errorMessage)
libpq_append_error(errorMessage, "out of memory" );
return false ;
}
continue ;
}
}
/*
* Interpret the deprecated PGREQUIRESSL environment variable . Per
* tradition , translate values starting with " 1 " to sslmode = require ,
* and ignore other values . Given both PGREQUIRESSL = 1 and PGSSLMODE ,
* PGSSLMODE takes precedence ; the opposite was true before v9 . 3 .
*/
if (strcmp(option->keyword, "sslmode" ) == 0 )
{
const char *requiresslenv = getenv("PGREQUIRESSL" );
if (requiresslenv != NULL && requiresslenv[0 ] == '1' )
{
option->val = strdup("require" );
if (!option->val)
{
if (errorMessage)
libpq_append_error(errorMessage, "out of memory" );
return false ;
}
continue ;
}
/*
* sslmode is not specified . Let it be filled in with the compiled
* default for now , but if sslrootcert = system , we ' ll override the
* default later before returning .
*/
sslmode_default = option;
}
/*
* No environment variable specified or the variable isn ' t set - try
* compiled - in default
*/
if (option->compiled != NULL)
{
option->val = strdup(option->compiled);
if (!option->val)
{
if (errorMessage)
libpq_append_error(errorMessage, "out of memory" );
return false ;
}
continue ;
}
/*
* Special handling for " user " option . Note that if pg_fe_getauthname
* fails , we just leave the value as NULL ; there ' s no need for this to
* be an error condition if the caller provides a user name . The only
* reason we do this now at all is so that callers of PQconndefaults
* will see a correct default ( barring error , of course ) .
*/
if (strcmp(option->keyword, "user" ) == 0 )
{
option->val = pg_fe_getauthname(NULL);
continue ;
}
}
/*
* Special handling for sslrootcert = system with no sslmode explicitly
* defined . In this case we want to strengthen the default sslmode to
* verify - full .
*/
if (sslmode_default && sslrootcert)
{
if (sslrootcert->val && strcmp(sslrootcert->val, "system" ) == 0 )
{
free(sslmode_default->val);
sslmode_default->val = strdup("verify-full" );
if (!sslmode_default->val)
{
if (errorMessage)
libpq_append_error(errorMessage, "out of memory" );
return false ;
}
}
}
return true ;
}
/*
* Subroutine for parse_connection_string
*
* Deal with a URI connection string .
*/
static PQconninfoOption *
conninfo_uri_parse(const char *uri, PQExpBuffer errorMessage,
bool use_defaults)
{
PQconninfoOption *options;
/* Make a working copy of PQconninfoOptions */
options = conninfo_init(errorMessage);
if (options == NULL)
return NULL;
if (!conninfo_uri_parse_options(options, uri, errorMessage))
{
PQconninfoFree(options);
return NULL;
}
/*
* Add in defaults if the caller wants that .
*/
if (use_defaults)
{
if (!conninfo_add_defaults(options, errorMessage))
{
PQconninfoFree(options);
return NULL;
}
}
return options;
}
/*
* conninfo_uri_parse_options
* Actual URI parser .
*
* If successful , returns true while the options array is filled with parsed
* options from the URI .
* If not successful , returns false and fills errorMessage accordingly .
*
* Parses the connection URI string in ' uri ' according to the URI syntax ( RFC
* 3986 ) :
*
* postgresql : //[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
*
* where " netloc " is a hostname , an IPv4 address , or an IPv6 address surrounded
* by literal square brackets . As an extension , we also allow multiple
* netloc [ : port ] specifications , separated by commas :
*
* postgresql : //[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]an>
*
* Any of the URI parts might use percent - encoding ( % xy ) .
*/
static bool
conninfo_uri_parse_options(PQconninfoOption *options, const char *uri,
PQExpBuffer errorMessage)
{
int prefix_len;
char *p;
char *buf = NULL;
char *start;
char prevchar = '\0' ;
char *user = NULL;
char *host = NULL;
bool retval = false ;
PQExpBufferData hostbuf;
PQExpBufferData portbuf;
initPQExpBuffer(&hostbuf);
initPQExpBuffer(&portbuf);
if (PQExpBufferDataBroken(hostbuf) || PQExpBufferDataBroken(portbuf))
{
libpq_append_error(errorMessage, "out of memory" );
goto cleanup;
}
/* need a modifiable copy of the input URI */
buf = strdup(uri);
if (buf == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
goto cleanup;
}
start = buf;
/* Skip the URI prefix */
prefix_len = uri_prefix_length(uri);
if (prefix_len == 0 )
{
/* Should never happen */
libpq_append_error(errorMessage,
"invalid URI propagated to internal parser routine: \" %s\"" ,
uri);
goto cleanup;
}
start += prefix_len;
p = start;
/* Look ahead for possible user credentials designator */
while (*p && *p != '@' && *p != '/' )
++p;
if (*p == '@' )
{
/*
* Found username / password designator , so URI should be of the form
* " scheme : //user[:password]@[netloc]".
*/
user = start;
p = user;
while (*p != ':' && *p != '@' )
++p;
/* Save last char and cut off at end of user name */
prevchar = *p;
*p = '\0' ;
if (*user &&
!conninfo_storeval(options, "user" , user,
errorMessage, false , true ))
goto cleanup;
if (prevchar == ':' )
{
const char *password = p + 1 ;
while (*p != '@' )
++p;
*p = '\0' ;
if (*password &&
!conninfo_storeval(options, "password" , password,
errorMessage, false , true ))
goto cleanup;
}
/* Advance past end of parsed user name or password token */
++p;
}
else
{
/*
* No username / password designator found . Reset to start of URI .
*/
p = start;
}
/*
* There may be multiple netloc [ : port ] pairs , each separated from the next
* by a comma . When we initially enter this loop , " p " has been
* incremented past optional URI credential information at this point and
* now points at the " netloc " part of the URI . On subsequent loop
* iterations , " p " has been incremented past the comma separator and now
* points at the start of the next " netloc " .
*/
for (;;)
{
/*
* Look for IPv6 address .
*/
if (*p == '[' )
{
host = ++p;
while (*p && *p != ']' )
++p;
if (!*p)
{
libpq_append_error(errorMessage,
"end of string reached when looking for matching \" ]\" in IPv6 host address in URI: \" %s\"" ,
uri);
goto cleanup;
}
if (p == host)
{
libpq_append_error(errorMessage,
"IPv6 host address may not be empty in URI: \" %s\"" ,
uri);
goto cleanup;
}
/* Cut off the bracket and advance */
*(p++) = '\0' ;
/*
* The address may be followed by a port specifier or a slash or a
* query or a separator comma .
*/
if (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',' )
{
libpq_append_error(errorMessage,
"unexpected character \" %c\" at position %d in URI (expected \" :\" or \" /\"): \" %s\"" ,
*p, (int ) (p - buf + 1 ), uri);
goto cleanup;
}
}
else
{
/* not an IPv6 address: DNS-named or IPv4 netloc */
host = p;
/*
* Look for port specifier ( colon ) or end of host specifier
* ( slash ) or query ( question mark ) or host separator ( comma ) .
*/
while (*p && *p != ':' && *p != '/' && *p != '?' && *p != ',' )
++p;
}
/* Save the hostname terminator before we null it */
prevchar = *p;
*p = '\0' ;
appendPQExpBufferStr(&hostbuf, host);
if (prevchar == ':' )
{
const char *port = ++p; /* advance past host terminator */
while (*p && *p != '/' && *p != '?' && *p != ',' )
++p;
prevchar = *p;
*p = '\0' ;
appendPQExpBufferStr(&portbuf, port);
}
if (prevchar != ',' )
break ;
++p; /* advance past comma separator */
appendPQExpBufferChar(&hostbuf, ',' );
appendPQExpBufferChar(&portbuf, ',' );
}
/* Save final values for host and port. */
if (PQExpBufferDataBroken(hostbuf) || PQExpBufferDataBroken(portbuf))
goto cleanup;
if (hostbuf.data[0 ] &&
!conninfo_storeval(options, "host" , hostbuf.data,
errorMessage, false , true ))
goto cleanup;
if (portbuf.data[0 ] &&
!conninfo_storeval(options, "port" , portbuf.data,
errorMessage, false , true ))
goto cleanup;
if (prevchar && prevchar != '?' )
{
const char *dbname = ++p; /* advance past host terminator */
/* Look for query parameters */
while (*p && *p != '?' )
++p;
prevchar = *p;
*p = '\0' ;
/*
* Avoid setting dbname to an empty string , as it forces the default
* value ( username ) and ignores $ PGDATABASE , as opposed to not setting
* it at all .
*/
if (*dbname &&
!conninfo_storeval(options, "dbname" , dbname,
errorMessage, false , true ))
goto cleanup;
}
if (prevchar)
{
++p; /* advance past terminator */
if (!conninfo_uri_parse_params(p, options, errorMessage))
goto cleanup;
}
/* everything parsed okay */
retval = true ;
cleanup:
termPQExpBuffer(&hostbuf);
termPQExpBuffer(&portbuf);
free(buf);
return retval;
}
/*
* Connection URI parameters parser routine
*
* If successful , returns true while connOptions is filled with parsed
* parameters . Otherwise , returns false and fills errorMessage appropriately .
*
* Destructively modifies ' params ' buffer .
*/
static bool
conninfo_uri_parse_params(char *params,
PQconninfoOption *connOptions,
PQExpBuffer errorMessage)
{
while (*params)
{
char *keyword = params;
char *value = NULL;
char *p = params;
bool malloced = false ;
int oldmsglen;
/*
* Scan the params string for ' = ' and ' & ' , marking the end of keyword
* and value respectively .
*/
for (;;)
{
if (*p == '=' )
{
/* Was there '=' already? */
if (value != NULL)
{
libpq_append_error(errorMessage,
"extra key/value separator \" =\" in URI query parameter: \" %s\"" ,
keyword);
return false ;
}
/* Cut off keyword, advance to value */
*p++ = '\0' ;
value = p;
}
else if (*p == '&' || *p == '\0' )
{
/*
* If not at the end , cut off value and advance ; leave p
* pointing to start of the next parameter , if any .
*/
if (*p != '\0' )
*p++ = '\0' ;
/* Was there '=' at all? */
if (value == NULL)
{
libpq_append_error(errorMessage,
"missing key/value separator \" =\" in URI query parameter: \" %s\"" ,
keyword);
return false ;
}
/* Got keyword and value, go process them. */
break ;
}
else
++p; /* Advance over all other bytes. */
}
keyword = conninfo_uri_decode(keyword, errorMessage);
if (keyword == NULL)
{
/* conninfo_uri_decode already set an error message */
return false ;
}
value = conninfo_uri_decode(value, errorMessage);
if (value == NULL)
{
/* conninfo_uri_decode already set an error message */
free(keyword);
return false ;
}
malloced = true ;
/*
* Special keyword handling for improved JDBC compatibility .
*/
if (strcmp(keyword, "ssl" ) == 0 &&
strcmp(value, "true" ) == 0 )
{
free(keyword);
free(value);
malloced = false ;
keyword = "sslmode" ;
value = "require" ;
}
/*
* Store the value if the corresponding option exists ; ignore
* otherwise . At this point both keyword and value are not
* URI - encoded .
*/
oldmsglen = errorMessage->len;
if (!conninfo_storeval(connOptions, keyword, value,
errorMessage, true , false ))
{
/* Insert generic message if conninfo_storeval didn't give one. */
if (errorMessage->len == oldmsglen)
libpq_append_error(errorMessage,
"invalid URI query parameter: \" %s\"" ,
keyword);
/* And fail. */
if (malloced)
{
free(keyword);
free(value);
}
return false ;
}
if (malloced)
{
free(keyword);
free(value);
}
/* Proceed to next key=value pair, if any */
params = p;
}
return true ;
}
/*
* Connection URI decoder routine
*
* If successful , returns the malloc ' d decoded string .
* If not successful , returns NULL and fills errorMessage accordingly .
*
* The string is decoded by replacing any percent - encoded tokens with
* corresponding characters , while preserving any non - encoded characters . A
* percent - encoded token is a character triplet : a percent sign , followed by a
* pair of hexadecimal digits ( 0 - 9 A - F ) , where lower - and upper - case letters are
* treated identically .
*/
static char *
conninfo_uri_decode(const char *str, PQExpBuffer errorMessage)
{
char *buf; /* result */
char *p; /* output location */
const char *q = str; /* input location */
buf = malloc(strlen(str) + 1 );
if (buf == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
return NULL;
}
p = buf;
/* skip leading whitespaces */
for (const char *s = q; *s == ' ' ; s++)
{
q++;
continue ;
}
for (;;)
{
if (*q != '%' )
{
/* if found a whitespace or NUL, the string ends */
if (*q == ' ' || *q == '\0' )
goto end;
/* copy character */
*(p++) = *(q++);
}
else
{
int hi;
int lo;
int c;
++q; /* skip the percent sign itself */
/*
* Possible EOL will be caught by the first call to
* get_hexdigit ( ) , so we never dereference an invalid q pointer .
*/
if (!(get_hexdigit(*q++, &hi) && get_hexdigit(*q++, &lo)))
{
libpq_append_error(errorMessage,
"invalid percent-encoded token: \" %s\"" ,
str);
free(buf);
return NULL;
}
c = (hi << 4 ) | lo;
if (c == 0 )
{
libpq_append_error(errorMessage,
"forbidden value %%00 in percent-encoded value: \" %s\"" ,
str);
free(buf);
return NULL;
}
*(p++) = c;
}
}
end:
/* skip trailing whitespaces */
for (const char *s = q; *s == ' ' ; s++)
{
q++;
continue ;
}
/* Not at the end of the string yet? Fail. */
if (*q != '\0' )
{
libpq_append_error(errorMessage,
"unexpected spaces found in \" %s\", use percent-encoded spaces (%%20) instead" ,
str);
free(buf);
return NULL;
}
/* Copy NUL terminator */
*p = '\0' ;
return buf;
}
/*
* Convert hexadecimal digit character to its integer value .
*
* If successful , returns true and value is filled with digit ' s base 16 value .
* If not successful , returns false .
*
* Lower - and upper - case letters in the range A - F are treated identically .
*/
static bool
get_hexdigit(char digit, int *value)
{
if ('0' <= digit && digit <= '9' )
*value = digit - '0' ;
else if ('A' <= digit && digit <= 'F' )
*value = digit - 'A' + 10 ;
else if ('a' <= digit && digit <= 'f' )
*value = digit - 'a' + 10 ;
else
return false ;
return true ;
}
/*
* Find an option value corresponding to the keyword in the connOptions array .
*
* If successful , returns a pointer to the corresponding option ' s value .
* If not successful , returns NULL .
*/
static const char *
conninfo_getval(PQconninfoOption *connOptions,
const char *keyword)
{
PQconninfoOption *option;
option = conninfo_find(connOptions, keyword);
return option ? option->val : NULL;
}
/*
* Store a ( new ) value for an option corresponding to the keyword in
* connOptions array .
*
* If uri_decode is true , the value is URI - decoded . The keyword is always
* assumed to be non URI - encoded .
*
* If successful , returns a pointer to the corresponding PQconninfoOption ,
* which value is replaced with a strdup ' d copy of the passed value string .
* The existing value for the option is free ' d before replacing , if any .
*
* If not successful , returns NULL and fills errorMessage accordingly .
* However , if the reason of failure is an invalid keyword being passed and
* ignoreMissing is true , errorMessage will be left untouched .
*/
static PQconninfoOption *
conninfo_storeval(PQconninfoOption *connOptions,
const char *keyword, const char *value,
PQExpBuffer errorMessage, bool ignoreMissing,
bool uri_decode)
{
PQconninfoOption *option;
char *value_copy;
/*
* For backwards compatibility , requiressl = 1 gets translated to
* sslmode = require , and requiressl = 0 gets translated to sslmode = prefer
* ( which is the default for sslmode ) .
*/
if (strcmp(keyword, "requiressl" ) == 0 )
{
keyword = "sslmode" ;
if (value[0 ] == '1' )
value = "require" ;
else
value = "prefer" ;
}
option = conninfo_find(connOptions, keyword);
if (option == NULL)
{
if (!ignoreMissing)
libpq_append_error(errorMessage,
"invalid connection option \" %s\"" ,
keyword);
return NULL;
}
if (uri_decode)
{
value_copy = conninfo_uri_decode(value, errorMessage);
if (value_copy == NULL)
/* conninfo_uri_decode already set an error message */
return NULL;
}
else
{
value_copy = strdup(value);
if (value_copy == NULL)
{
libpq_append_error(errorMessage, "out of memory" );
return NULL;
}
}
free(option->val);
option->val = value_copy;
return option;
}
/*
* Find a PQconninfoOption option corresponding to the keyword in the
* connOptions array .
*
* If successful , returns a pointer to the corresponding PQconninfoOption
* structure .
* If not successful , returns NULL .
*/
static PQconninfoOption *
conninfo_find(PQconninfoOption *connOptions, const char *keyword)
{
PQconninfoOption *option;
for (option = connOptions; option->keyword != NULL; option++)
{
if (strcmp(option->keyword, keyword) == 0 )
return option;
}
return NULL;
}
/*
* Return the connection options used for the connection
*/
PQconninfoOption *
PQconninfo(PGconn *conn)
{
PQExpBufferData errorBuf;
PQconninfoOption *connOptions;
if (conn == NULL)
return NULL;
/*
* We don ' t actually report any errors here , but callees want a buffer ,
* and we prefer not to trash the conn ' s errorMessage .
*/
initPQExpBuffer(&errorBuf);
if (PQExpBufferDataBroken(errorBuf))
return NULL; /* out of memory already :-( */
connOptions = conninfo_init(&errorBuf);
if (connOptions != NULL)
{
const internalPQconninfoOption *option;
for (option = PQconninfoOptions; option->keyword; option++)
{
char **connmember;
if (option->connofs < 0 )
continue ;
connmember = (char **) ((char *) conn + option->connofs);
if (*connmember)
conninfo_storeval(connOptions, option->keyword, *connmember,
&errorBuf, true , false );
}
}
termPQExpBuffer(&errorBuf);
return connOptions;
}
void
PQconninfoFree(PQconninfoOption *connOptions)
{
if (connOptions == NULL)
return ;
for (PQconninfoOption *option = connOptions; option->keyword != NULL; option++)
free(option->val);
free(connOptions);
}
/* =========== accessor functions for PGconn ========= */
char *
PQdb(const PGconn *conn)
{
if (!conn)
return NULL;
return conn->dbName;
}
char *
PQuser(const PGconn *conn)
{
if (!conn)
return NULL;
return conn->pguser;
}
char *
PQpass(const PGconn *conn)
{
char *password = NULL;
if (!conn)
return NULL;
if (conn->connhost != NULL)
password = conn->connhost[conn->whichhost].password;
if (password == NULL)
password = conn->pgpass;
/* Historically we've returned "" not NULL for no password specified */
if (password == NULL)
password = "" ;
return password;
}
char *
PQhost(const PGconn *conn)
{
if (!conn)
return NULL;
if (conn->connhost != NULL)
{
/*
* Return the verbatim host value provided by user , or hostaddr in its
* lack .
*/
if (conn->connhost[conn->whichhost].host != NULL &&
conn->connhost[conn->whichhost].host[0 ] != '\0' )
return conn->connhost[conn->whichhost].host;
else if (conn->connhost[conn->whichhost].hostaddr != NULL &&
conn->connhost[conn->whichhost].hostaddr[0 ] != '\0' )
return conn->connhost[conn->whichhost].hostaddr;
}
return "" ;
}
char *
PQhostaddr(const PGconn *conn)
{
if (!conn)
return NULL;
/* Return the parsed IP address */
if (conn->connhost != NULL && conn->connip != NULL)
return conn->connip;
return "" ;
}
char *
PQport(const PGconn *conn)
{
if (!conn)
return NULL;
if (conn->connhost != NULL &&
conn->connhost[conn->whichhost].port != NULL &&
conn->connhost[conn->whichhost].port[0 ] != '\0' )
return conn->connhost[conn->whichhost].port;
return DEF_PGPORT_STR;
}
/*
* No longer does anything , but the function remains for API backwards
* compatibility .
*/
char *
PQtty(const PGconn *conn)
{
if (!conn)
return NULL;
return "" ;
}
char *
PQoptions(const PGconn *conn)
{
if (!conn)
return NULL;
return conn->pgoptions;
}
ConnStatusType
PQstatus(const PGconn *conn)
{
if (!conn)
return CONNECTION_BAD;
return conn->status;
}
PGTransactionStatusType
PQtransactionStatus(const PGconn *conn)
{
if (!conn || conn->status != CONNECTION_OK)
return PQTRANS_UNKNOWN;
if (conn->asyncStatus != PGASYNC_IDLE)
return PQTRANS_ACTIVE;
return conn->xactStatus;
}
const char *
PQparameterStatus(const PGconn *conn, const char *paramName)
{
const pgParameterStatus *pstatus;
if (!conn || !paramName)
return NULL;
for (pstatus = conn->pstatus; pstatus != NULL; pstatus = pstatus->next)
{
if (strcmp(pstatus->name, paramName) == 0 )
return pstatus->value;
}
return NULL;
}
int
PQprotocolVersion(const PGconn *conn)
{
if (!conn)
return 0 ;
if (conn->status == CONNECTION_BAD)
return 0 ;
return PG_PROTOCOL_MAJOR(conn->pversion);
}
int
PQfullProtocolVersion(const PGconn *conn)
{
if (!conn)
return 0 ;
if (conn->status == CONNECTION_BAD)
return 0 ;
return PG_PROTOCOL_FULL(conn->pversion);
}
int
PQserverVersion(const PGconn *conn)
{
if (!conn)
return 0 ;
if (conn->status == CONNECTION_BAD)
return 0 ;
return conn->sversion;
}
char *
PQerrorMessage(const PGconn *conn)
{
if (!conn)
return libpq_gettext("connection pointer is NULL\n" );
/*
* The errorMessage buffer might be marked " broken " due to having
* previously failed to allocate enough memory for the message . In that
* case , tell the application we ran out of memory .
*/
if (PQExpBufferBroken(&conn->errorMessage))
return libpq_gettext("out of memory\n" );
return conn->errorMessage.data;
}
/*
* In Windows , socket values are unsigned , and an invalid socket value
* ( INVALID_SOCKET ) is ~ 0 , which equals - 1 in comparisons ( with no compiler
* warning ) . Ideally we would return an unsigned value for PQsocket ( ) on
* Windows , but that would cause the function ' s return value to differ from
* Unix , so we just return - 1 for invalid sockets .
* http : //msdn.microsoft.com/en-us/library/windows/desktop/cc507522%28v=vs.85%29.aspx
* http : //stackoverflow.com/questions/10817252/why-is-invalid-socket-defined-as-0-in-winsock2-h-c
*/
int
PQsocket(const PGconn *conn)
{
if (!conn)
return -1 ;
if (conn->altsock != PGINVALID_SOCKET)
return conn->altsock;
return (conn->sock != PGINVALID_SOCKET) ? conn->sock : -1 ;
}
int
PQbackendPID(const PGconn *conn)
{
if (!conn || conn->status != CONNECTION_OK)
return 0 ;
return conn->be_pid;
}
PGpipelineStatus
PQpipelineStatus(const PGconn *conn)
{
if (!conn)
return PQ_PIPELINE_OFF;
return conn->pipelineStatus;
}
int
PQconnectionNeedsPassword(const PGconn *conn)
{
char *password;
if (!conn)
return false ;
password = PQpass(conn);
if (conn->password_needed &&
(password == NULL || password[0 ] == '\0' ))
return true ;
else
return false ;
}
int
PQconnectionUsedPassword(const PGconn *conn)
{
if (!conn)
return false ;
if (conn->password_needed)
return true ;
else
return false ;
}
int
PQconnectionUsedGSSAPI(const PGconn *conn)
{
if (!conn)
return false ;
if (conn->gssapi_used)
return true ;
else
return false ;
}
int
PQclientEncoding(const PGconn *conn)
{
if (!conn || conn->status != CONNECTION_OK)
return -1 ;
return conn->client_encoding;
}
int
PQsetClientEncoding(PGconn *conn, const char *encoding)
{
char qbuf[128 ];
static const char query[] = "set client_encoding to '%s'" ;
PGresult *res;
int status;
if (!conn || conn->status != CONNECTION_OK)
return -1 ;
if (!encoding)
return -1 ;
/* Resolve special "auto" value from the locale */
if (strcmp(encoding, "auto" ) == 0 )
encoding = pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true ));
/* check query buffer overflow */
if (sizeof (qbuf) < (sizeof (query) + strlen(encoding)))
return -1 ;
/* ok, now send a query */
sprintf(qbuf, query, encoding);
res = PQexec(conn, qbuf);
if (res == NULL)
return -1 ;
if (res->resultStatus != PGRES_COMMAND_OK)
status = -1 ;
else
{
/*
* We rely on the backend to report the parameter value , and we ' ll
* change state at that time .
*/
status = 0 ; /* everything is ok */
}
PQclear(res);
return status;
}
PGVerbosity
PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity)
{
PGVerbosity old;
if (!conn)
return PQERRORS_DEFAULT;
old = conn->verbosity;
conn->verbosity = verbosity;
return old;
}
PGContextVisibility
PQsetErrorContextVisibility(PGconn *conn, PGContextVisibility show_context)
{
PGContextVisibility old;
if (!conn)
return PQSHOW_CONTEXT_ERRORS;
old = conn->show_context;
conn->show_context = show_context;
return old;
}
PQnoticeReceiver
PQsetNoticeReceiver(PGconn *conn, PQnoticeReceiver proc, void *arg)
{
PQnoticeReceiver old;
if (conn == NULL)
return NULL;
old = conn->noticeHooks.noticeRec;
if (proc)
{
conn->noticeHooks.noticeRec = proc;
conn->noticeHooks.noticeRecArg = arg;
}
return old;
}
PQnoticeProcessor
PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg)
{
PQnoticeProcessor old;
if (conn == NULL)
return NULL;
old = conn->noticeHooks.noticeProc;
if (proc)
{
conn->noticeHooks.noticeProc = proc;
conn->noticeHooks.noticeProcArg = arg;
}
return old;
}
/*
* The default notice message receiver just gets the standard notice text
* and sends it to the notice processor . This two - level setup exists
* mostly for backwards compatibility ; perhaps we should deprecate use of
* PQsetNoticeProcessor ?
*/
static void
defaultNoticeReceiver(void *arg, const PGresult *res)
{
(void ) arg; /* not used */
if (res->noticeHooks.noticeProc != NULL)
res->noticeHooks.noticeProc(res->noticeHooks.noticeProcArg,
PQresultErrorMessage(res));
}
/*
* The default notice message processor just prints the
* message on stderr . Applications can override this if they
* want the messages to go elsewhere ( a window , for example ) .
* Note that simply discarding notices is probably a bad idea .
*/
static void
defaultNoticeProcessor(void *arg, const char *message)
{
(void ) arg; /* not used */
/* Note: we expect the supplied string to end with a newline already. */
fprintf(stderr, "%s" , message);
}
/*
* returns a pointer to the next token or NULL if the current
* token doesn ' t match
*/
static char *
pwdfMatchesString(char *buf, const char *token)
{
char *tbuf;
const char *ttok;
bool bslash = false ;
if (buf == NULL || token == NULL)
return NULL;
tbuf = buf;
ttok = token;
if (tbuf[0 ] == '*' && tbuf[1 ] == ':' )
return tbuf + 2 ;
while (*tbuf != 0 )
{
if (*tbuf == '\\' && !bslash)
{
tbuf++;
bslash = true ;
}
if (*tbuf == ':' && *ttok == 0 && !bslash)
return tbuf + 1 ;
bslash = false ;
if (*ttok == 0 )
return NULL;
if (*tbuf == *ttok)
{
tbuf++;
ttok++;
}
else
return NULL;
}
return NULL;
}
/* Get a password from the password file. Return value is malloc'd. */
static char *
passwordFromFile(const char *hostname, const char *port, const char *dbname,
const char *username, const char *pgpassfile)
{
FILE *fp;
#ifndef WIN32
struct stat stat_buf;
#endif
PQExpBufferData buf;
if (dbname == NULL || dbname[0 ] == '\0' )
return NULL;
if (username == NULL || username[0 ] == '\0' )
return NULL;
/* 'localhost' matches pghost of '' or the default socket directory */
if (hostname == NULL || hostname[0 ] == '\0' )
hostname = DefaultHost;
else if (is_unixsock_path(hostname))
/*
* We should probably use canonicalize_path ( ) , but then we have to
* bring path . c into libpq , and it doesn ' t seem worth it .
*/
if (strcmp(hostname, DEFAULT_PGSOCKET_DIR) == 0 )
hostname = DefaultHost;
if (port == NULL || port[0 ] == '\0' )
port = DEF_PGPORT_STR;
/* If password file cannot be opened, ignore it. */
fp = fopen(pgpassfile, "r" );
if (fp == NULL)
return NULL;
#ifndef WIN32
if (fstat(fileno(fp), &stat_buf) != 0 )
{
fclose(fp);
return NULL;
}
if (!S_ISREG(stat_buf.st_mode))
{
fprintf(stderr,
libpq_gettext("WARNING: password file \" %s\" is not a plain file\n" ),
pgpassfile);
fclose(fp);
return NULL;
}
/* If password file is insecure, alert the user and ignore it. */
if (stat_buf.st_mode & (S_IRWXG | S_IRWXO))
{
fprintf(stderr,
libpq_gettext("WARNING: password file \" %s\" has group or world access; permissions should be u=rw (0600) or less\n" ),
pgpassfile);
fclose(fp);
return NULL;
}
#else
/*
* On Win32 , the directory is protected , so we don ' t have to check the
* file .
*/
#endif
/* Use an expansible buffer to accommodate any reasonable line length */
initPQExpBuffer(&buf);
while (!feof(fp) && !ferror(fp))
{
/* Make sure there's a reasonable amount of room in the buffer */
if (!enlargePQExpBuffer(&buf, 128 ))
break ;
/* Read some data, appending it to what we already have */
if (fgets(buf.data + buf.len, buf.maxlen - buf.len, fp) == NULL)
break ;
buf.len += strlen(buf.data + buf.len);
/* If we don't yet have a whole line, loop around to read more */
if (!(buf.len > 0 && buf.data[buf.len - 1 ] == '\n' ) && !feof(fp))
continue ;
/* ignore comments */
if (buf.data[0 ] != '#' )
{
char *t = buf.data;
int len;
/* strip trailing newline and carriage return */
len = pg_strip_crlf(t);
if (len > 0 &&
(t = pwdfMatchesString(t, hostname)) != NULL &&
(t = pwdfMatchesString(t, port)) != NULL &&
(t = pwdfMatchesString(t, dbname)) != NULL &&
(t = pwdfMatchesString(t, username)) != NULL)
{
/* Found a match. */
char *ret,
*p1,
*p2;
ret = strdup(t);
fclose(fp);
explicit_bzero(buf.data, buf.maxlen);
termPQExpBuffer(&buf);
if (!ret)
{
/* Out of memory. XXX: an error message would be nice. */
return NULL;
}
/* De-escape password. */
for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0' ; ++p1, ++p2)
{
if (*p1 == '\\' && p1[1 ] != '\0' )
++p1;
*p2 = *p1;
}
*p2 = '\0' ;
return ret;
}
}
/* No match, reset buffer to prepare for next line. */
buf.len = 0 ;
}
fclose(fp);
explicit_bzero(buf.data, buf.maxlen);
termPQExpBuffer(&buf);
return NULL;
}
/*
* If the connection failed due to bad password , we should mention
* if we got the password from the pgpassfile .
*/
static void
pgpassfileWarning(PGconn *conn)
{
/* If it was 'invalid authorization', add pgpassfile mention */
/* only works with >= 9.0 servers */
if (conn->password_needed &&
conn->connhost[conn->whichhost].password != NULL &&
conn->result)
{
const char *sqlstate = PQresultErrorField(conn->result,
PG_DIAG_SQLSTATE);
if (sqlstate && strcmp(sqlstate, ERRCODE_INVALID_PASSWORD) == 0 )
libpq_append_conn_error(conn, "password retrieved from file \" %s\"" ,
conn->pgpassfile);
}
}
/*
* Check if the SSL protocol value given in input is valid or not .
* This is used as a sanity check routine for the connection parameters
* ssl_min_protocol_version and ssl_max_protocol_version .
*/
static bool
sslVerifyProtocolVersion(const char *version)
{
/*
* An empty string and a NULL value are considered valid as it is
* equivalent to ignoring the parameter .
*/
if (!version || strlen(version) == 0 )
return true ;
if (pg_strcasecmp(version, "TLSv1" ) == 0 ||
pg_strcasecmp(version, "TLSv1.1" ) == 0 ||
pg_strcasecmp(version, "TLSv1.2" ) == 0 ||
pg_strcasecmp(version, "TLSv1.3" ) == 0 )
return true ;
/* anything else is wrong */
return false ;
}
/*
* Ensure that the SSL protocol range given in input is correct . The check
* is performed on the input string to keep it TLS backend agnostic . Input
* to this function is expected verified with sslVerifyProtocolVersion ( ) .
*/
static bool
sslVerifyProtocolRange(const char *min, const char *max)
{
Assert(sslVerifyProtocolVersion(min) &&
sslVerifyProtocolVersion(max));
/* If at least one of the bounds is not set, the range is valid */
if (min == NULL || max == NULL || strlen(min) == 0 || strlen(max) == 0 )
return true ;
/*
* If the minimum version is the lowest one we accept , then all options
* for the maximum are valid .
*/
if (pg_strcasecmp(min, "TLSv1" ) == 0 )
return true ;
/*
* The minimum bound is valid , and cannot be TLSv1 , so using TLSv1 for the
* maximum is incorrect .
*/
if (pg_strcasecmp(max, "TLSv1" ) == 0 )
return false ;
/*
* At this point we know that we have a mix of TLSv1 . 1 through 1 . 3
* versions .
*/
if (pg_strcasecmp(min, max) > 0 )
return false ;
return true ;
}
/*
* Obtain user ' s home directory , return in given buffer
*
* On Unix , this actually returns the user ' s home directory . On Windows
* it returns the PostgreSQL - specific application data folder .
*
* This is essentially the same as get_home_path ( ) , but we don ' t use that
* because we don ' t want to pull path . c into libpq ( it pollutes application
* namespace ) .
*
* Returns true on success , false on failure to obtain the directory name .
*
* CAUTION : although in most situations failure is unexpected , there are users
* who like to run applications in a home - directory - less environment . On
* failure , you almost certainly DO NOT want to report an error . Just act as
* though whatever file you were hoping to find in the home directory isn ' t
* there ( which it isn ' t ) .
*/
bool
pqGetHomeDirectory(char *buf, int bufsize)
{
#ifndef WIN32
const char *home;
home = getenv("HOME" );
if (home && home[0 ])
{
strlcpy(buf, home, bufsize);
return true ;
}
else
{
struct passwd pwbuf;
struct passwd *pw;
char tmpbuf[1024 ];
int rc;
rc = getpwuid_r(geteuid(), &pwbuf, tmpbuf, sizeof tmpbuf, &pw);
if (rc != 0 || !pw)
return false ;
strlcpy(buf, pw->pw_dir, bufsize);
return true ;
}
#else
char tmppath[MAX_PATH];
ZeroMemory(tmppath, sizeof (tmppath));
if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0 , tmppath) != S_OK)
return false ;
snprintf(buf, bufsize, "%s/postgresql" , tmppath);
return true ;
#endif
}
/*
* Parse and try to interpret " value " as an integer value , and if successful ,
* store it in * result , complaining if there is any trailing garbage or an
* overflow . This allows any number of leading and trailing whitespaces .
*/
bool
pqParseIntParam(const char *value, int *result, PGconn *conn,
const char *context)
{
char *end;
long numval;
Assert(value != NULL);
*result = 0 ;
/* strtol(3) skips leading whitespaces */
errno = 0 ;
numval = strtol(value, &end, 10 );
/*
* If no progress was done during the parsing or an error happened , fail .
* This tests properly for overflows of the result .
*/
if (value == end || errno != 0 || numval != (int ) numval)
goto error;
/*
* Skip any trailing whitespace ; if anything but whitespace remains before
* the terminating character , fail
*/
while (*end != '\0' && isspace((unsigned char ) *end))
end++;
if (*end != '\0' )
goto error;
*result = numval;
return true ;
error:
libpq_append_conn_error(conn, "invalid integer value \" %s\" for connection option \" %s\"" ,
value, context);
return false ;
}
/*
* Parse and try to interpret " value " as a ProtocolVersion value , and if
* successful , store it in * result .
*/
static bool
pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn,
const char *context)
{
if (strcmp(value, "latest" ) == 0 )
{
*result = PG_PROTOCOL_LATEST;
return true ;
}
if (strcmp(value, "3.0" ) == 0 )
{
*result = PG_PROTOCOL(3 , 0 );
return true ;
}
/* 3.1 never existed, we went straight from 3.0 to 3.2 */
if (strcmp(value, "3.2" ) == 0 )
{
*result = PG_PROTOCOL(3 , 2 );
return true ;
}
libpq_append_conn_error(conn, "invalid %s value: \" %s\"" ,
context, value);
return false ;
}
/*
* To keep the API consistent , the locking stubs are always provided , even
* if they are not required .
*
* Since we neglected to provide any error - return convention in the
* pgthreadlock_t API , we can ' t do much except Assert upon failure of any
* mutex primitive . Fortunately , such failures appear to be nonexistent in
* the field .
*/
static void
default_threadlock(int acquire)
{
static pthread_mutex_t singlethread_lock = PTHREAD_MUTEX_INITIALIZER;
if (acquire)
{
if (pthread_mutex_lock(&singlethread_lock))
Assert(false );
}
else
{
if (pthread_mutex_unlock(&singlethread_lock))
Assert(false );
}
}
pgthreadlock_t
PQregisterThreadLock(pgthreadlock_t newhandler)
{
pgthreadlock_t prev = pg_g_threadlock;
if (newhandler)
pg_g_threadlock = newhandler;
else
pg_g_threadlock = default_threadlock;
return prev;
}
Messung V0.5 in Prozent C=93 H=94 G=93
¤ Dauer der Verarbeitung: 0.534 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland