Move code for backend startup to separate file
This is code that runs in the backend process after forking, rather than postmaster. Move it out of postmaster.c for clarity. Reviewed-by: Tristan Partin, Andres Freund Discussion: https://www.postgresql.org/message-id/7a59b073-5b5b-151e-7ed3-8b01ff7ce9ef@iki.fi
This commit is contained in:
parent
aafc05de1b
commit
05c3980e7f
|
@ -58,6 +58,7 @@
|
||||||
#include "storage/pg_shmem.h"
|
#include "storage/pg_shmem.h"
|
||||||
#include "storage/pmsignal.h"
|
#include "storage/pmsignal.h"
|
||||||
#include "storage/proc.h"
|
#include "storage/proc.h"
|
||||||
|
#include "tcop/backend_startup.h"
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/datetime.h"
|
#include "utils/datetime.h"
|
||||||
|
|
|
@ -95,10 +95,8 @@
|
||||||
#include "common/file_utils.h"
|
#include "common/file_utils.h"
|
||||||
#include "common/ip.h"
|
#include "common/ip.h"
|
||||||
#include "common/pg_prng.h"
|
#include "common/pg_prng.h"
|
||||||
#include "common/string.h"
|
|
||||||
#include "lib/ilist.h"
|
#include "lib/ilist.h"
|
||||||
#include "libpq/libpq.h"
|
#include "libpq/libpq.h"
|
||||||
#include "libpq/pqformat.h"
|
|
||||||
#include "libpq/pqsignal.h"
|
#include "libpq/pqsignal.h"
|
||||||
#include "pg_getopt.h"
|
#include "pg_getopt.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
|
@ -117,13 +115,11 @@
|
||||||
#include "storage/ipc.h"
|
#include "storage/ipc.h"
|
||||||
#include "storage/pmsignal.h"
|
#include "storage/pmsignal.h"
|
||||||
#include "storage/proc.h"
|
#include "storage/proc.h"
|
||||||
|
#include "tcop/backend_startup.h"
|
||||||
#include "tcop/tcopprot.h"
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/builtins.h"
|
|
||||||
#include "utils/datetime.h"
|
#include "utils/datetime.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/pidfile.h"
|
#include "utils/pidfile.h"
|
||||||
#include "utils/ps_status.h"
|
|
||||||
#include "utils/timeout.h"
|
|
||||||
#include "utils/timestamp.h"
|
#include "utils/timestamp.h"
|
||||||
#include "utils/varlena.h"
|
#include "utils/varlena.h"
|
||||||
|
|
||||||
|
@ -382,7 +378,7 @@ static WaitEventSet *pm_wait_set;
|
||||||
|
|
||||||
#ifdef USE_SSL
|
#ifdef USE_SSL
|
||||||
/* Set when and if SSL has been initialized properly */
|
/* Set when and if SSL has been initialized properly */
|
||||||
static bool LoadedSSL = false;
|
bool LoadedSSL = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_BONJOUR
|
#ifdef USE_BONJOUR
|
||||||
|
@ -404,9 +400,7 @@ static void process_pm_pmsignal(void);
|
||||||
static void process_pm_child_exit(void);
|
static void process_pm_child_exit(void);
|
||||||
static void process_pm_reload_request(void);
|
static void process_pm_reload_request(void);
|
||||||
static void process_pm_shutdown_request(void);
|
static void process_pm_shutdown_request(void);
|
||||||
static void process_startup_packet_die(SIGNAL_ARGS);
|
|
||||||
static void dummy_handler(SIGNAL_ARGS);
|
static void dummy_handler(SIGNAL_ARGS);
|
||||||
static void StartupPacketTimeoutHandler(void);
|
|
||||||
static void CleanupBackend(int pid, int exitstatus);
|
static void CleanupBackend(int pid, int exitstatus);
|
||||||
static bool CleanupBackgroundWorker(int pid, int exitstatus);
|
static bool CleanupBackgroundWorker(int pid, int exitstatus);
|
||||||
static void HandleChildCrash(int pid, int exitstatus, const char *procname);
|
static void HandleChildCrash(int pid, int exitstatus, const char *procname);
|
||||||
|
@ -414,24 +408,9 @@ static void LogChildExit(int lev, const char *procname,
|
||||||
int pid, int exitstatus);
|
int pid, int exitstatus);
|
||||||
static void PostmasterStateMachine(void);
|
static void PostmasterStateMachine(void);
|
||||||
|
|
||||||
/* Return value of canAcceptConnections() */
|
|
||||||
typedef enum CAC_state
|
|
||||||
{
|
|
||||||
CAC_OK,
|
|
||||||
CAC_STARTUP,
|
|
||||||
CAC_SHUTDOWN,
|
|
||||||
CAC_RECOVERY,
|
|
||||||
CAC_NOTCONSISTENT,
|
|
||||||
CAC_TOOMANY,
|
|
||||||
} CAC_state;
|
|
||||||
|
|
||||||
static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
|
|
||||||
static void ExitPostmaster(int status) pg_attribute_noreturn();
|
static void ExitPostmaster(int status) pg_attribute_noreturn();
|
||||||
static int ServerLoop(void);
|
static int ServerLoop(void);
|
||||||
static int BackendStartup(ClientSocket *client_sock);
|
static int BackendStartup(ClientSocket *client_sock);
|
||||||
static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
|
|
||||||
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
|
|
||||||
static void processCancelRequest(Port *port, void *pkt);
|
|
||||||
static void report_fork_failure_to_client(ClientSocket *client_sock, int errnum);
|
static void report_fork_failure_to_client(ClientSocket *client_sock, int errnum);
|
||||||
static CAC_state canAcceptConnections(int backend_type);
|
static CAC_state canAcceptConnections(int backend_type);
|
||||||
static bool RandomCancelKey(int32 *cancel_key);
|
static bool RandomCancelKey(int32 *cancel_key);
|
||||||
|
@ -1847,412 +1826,14 @@ ServerLoop(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Read a client's startup packet and do something according to it.
|
|
||||||
*
|
|
||||||
* Returns STATUS_OK or STATUS_ERROR, or might call ereport(FATAL) and
|
|
||||||
* not return at all.
|
|
||||||
*
|
|
||||||
* (Note that ereport(FATAL) stuff is sent to the client, so only use it
|
|
||||||
* if that's what you want. Return STATUS_ERROR if you don't want to
|
|
||||||
* send anything to the client, which would typically be appropriate
|
|
||||||
* if we detect a communications failure.)
|
|
||||||
*
|
|
||||||
* Set ssl_done and/or gss_done when negotiation of an encrypted layer
|
|
||||||
* (currently, TLS or GSSAPI) is completed. A successful negotiation of either
|
|
||||||
* encryption layer sets both flags, but a rejected negotiation sets only the
|
|
||||||
* flag for that layer, since the client may wish to try the other one. We
|
|
||||||
* should make no assumption here about the order in which the client may make
|
|
||||||
* requests.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
|
|
||||||
{
|
|
||||||
int32 len;
|
|
||||||
char *buf;
|
|
||||||
ProtocolVersion proto;
|
|
||||||
MemoryContext oldcontext;
|
|
||||||
|
|
||||||
pq_startmsgread();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Grab the first byte of the length word separately, so that we can tell
|
|
||||||
* whether we have no data at all or an incomplete packet. (This might
|
|
||||||
* sound inefficient, but it's not really, because of buffering in
|
|
||||||
* pqcomm.c.)
|
|
||||||
*/
|
|
||||||
if (pq_getbytes((char *) &len, 1) == EOF)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* If we get no data at all, don't clutter the log with a complaint;
|
|
||||||
* such cases often occur for legitimate reasons. An example is that
|
|
||||||
* we might be here after responding to NEGOTIATE_SSL_CODE, and if the
|
|
||||||
* client didn't like our response, it'll probably just drop the
|
|
||||||
* connection. Service-monitoring software also often just opens and
|
|
||||||
* closes a connection without sending anything. (So do port
|
|
||||||
* scanners, which may be less benign, but it's not really our job to
|
|
||||||
* notice those.)
|
|
||||||
*/
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pq_getbytes(((char *) &len) + 1, 3) == EOF)
|
|
||||||
{
|
|
||||||
/* Got a partial length word, so bleat about that */
|
|
||||||
if (!ssl_done && !gss_done)
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("incomplete startup packet")));
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = pg_ntoh32(len);
|
|
||||||
len -= 4;
|
|
||||||
|
|
||||||
if (len < (int32) sizeof(ProtocolVersion) ||
|
|
||||||
len > MAX_STARTUP_PACKET_LENGTH)
|
|
||||||
{
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("invalid length of startup packet")));
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allocate space to hold the startup packet, plus one extra byte that's
|
|
||||||
* initialized to be zero. This ensures we will have null termination of
|
|
||||||
* all strings inside the packet.
|
|
||||||
*/
|
|
||||||
buf = palloc(len + 1);
|
|
||||||
buf[len] = '\0';
|
|
||||||
|
|
||||||
if (pq_getbytes(buf, len) == EOF)
|
|
||||||
{
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("incomplete startup packet")));
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
pq_endmsgread();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The first field is either a protocol version number or a special
|
|
||||||
* request code.
|
|
||||||
*/
|
|
||||||
port->proto = proto = pg_ntoh32(*((ProtocolVersion *) buf));
|
|
||||||
|
|
||||||
if (proto == CANCEL_REQUEST_CODE)
|
|
||||||
{
|
|
||||||
if (len != sizeof(CancelRequestPacket))
|
|
||||||
{
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("invalid length of startup packet")));
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
processCancelRequest(port, buf);
|
|
||||||
/* Not really an error, but we don't want to proceed further */
|
|
||||||
return STATUS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (proto == NEGOTIATE_SSL_CODE && !ssl_done)
|
|
||||||
{
|
|
||||||
char SSLok;
|
|
||||||
|
|
||||||
#ifdef USE_SSL
|
|
||||||
/* No SSL when disabled or on Unix sockets */
|
|
||||||
if (!LoadedSSL || port->laddr.addr.ss_family == AF_UNIX)
|
|
||||||
SSLok = 'N';
|
|
||||||
else
|
|
||||||
SSLok = 'S'; /* Support for SSL */
|
|
||||||
#else
|
|
||||||
SSLok = 'N'; /* No support for SSL */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
retry1:
|
|
||||||
if (send(port->sock, &SSLok, 1, 0) != 1)
|
|
||||||
{
|
|
||||||
if (errno == EINTR)
|
|
||||||
goto retry1; /* if interrupted, just retry */
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode_for_socket_access(),
|
|
||||||
errmsg("failed to send SSL negotiation response: %m")));
|
|
||||||
return STATUS_ERROR; /* close the connection */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_SSL
|
|
||||||
if (SSLok == 'S' && secure_open_server(port) == -1)
|
|
||||||
return STATUS_ERROR;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
* We report this case to the client.
|
|
||||||
*/
|
|
||||||
if (pq_buffer_has_data())
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("received unencrypted data after SSL request"),
|
|
||||||
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* regular startup packet, cancel, etc packet should follow, but not
|
|
||||||
* another SSL negotiation request, and a GSS request should only
|
|
||||||
* follow if SSL was rejected (client may negotiate in either order)
|
|
||||||
*/
|
|
||||||
return ProcessStartupPacket(port, true, SSLok == 'S');
|
|
||||||
}
|
|
||||||
else if (proto == NEGOTIATE_GSS_CODE && !gss_done)
|
|
||||||
{
|
|
||||||
char GSSok = 'N';
|
|
||||||
|
|
||||||
#ifdef ENABLE_GSS
|
|
||||||
/* No GSSAPI encryption when on Unix socket */
|
|
||||||
if (port->laddr.addr.ss_family != AF_UNIX)
|
|
||||||
GSSok = 'G';
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (send(port->sock, &GSSok, 1, 0) != 1)
|
|
||||||
{
|
|
||||||
if (errno == EINTR)
|
|
||||||
continue;
|
|
||||||
ereport(COMMERROR,
|
|
||||||
(errcode_for_socket_access(),
|
|
||||||
errmsg("failed to send GSSAPI negotiation response: %m")));
|
|
||||||
return STATUS_ERROR; /* close the connection */
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ENABLE_GSS
|
|
||||||
if (GSSok == 'G' && secure_open_gssapi(port) == -1)
|
|
||||||
return STATUS_ERROR;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
* We report this case to the client.
|
|
||||||
*/
|
|
||||||
if (pq_buffer_has_data())
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("received unencrypted data after GSSAPI encryption request"),
|
|
||||||
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* regular startup packet, cancel, etc packet should follow, but not
|
|
||||||
* another GSS negotiation request, and an SSL request should only
|
|
||||||
* follow if GSS was rejected (client may negotiate in either order)
|
|
||||||
*/
|
|
||||||
return ProcessStartupPacket(port, GSSok == 'G', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Could add additional special packet types here */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set FrontendProtocol now so that ereport() knows what format to send if
|
|
||||||
* we fail during startup.
|
|
||||||
*/
|
|
||||||
FrontendProtocol = proto;
|
|
||||||
|
|
||||||
/* Check that the major protocol version is in range. */
|
|
||||||
if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) ||
|
|
||||||
PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST))
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u",
|
|
||||||
PG_PROTOCOL_MAJOR(proto), PG_PROTOCOL_MINOR(proto),
|
|
||||||
PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST),
|
|
||||||
PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST),
|
|
||||||
PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now fetch parameters out of startup packet and save them into the Port
|
|
||||||
* structure.
|
|
||||||
*/
|
|
||||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
|
||||||
|
|
||||||
/* Handle protocol version 3 startup packet */
|
|
||||||
{
|
|
||||||
int32 offset = sizeof(ProtocolVersion);
|
|
||||||
List *unrecognized_protocol_options = NIL;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Scan packet body for name/option pairs. We can assume any string
|
|
||||||
* beginning within the packet body is null-terminated, thanks to
|
|
||||||
* zeroing extra byte above.
|
|
||||||
*/
|
|
||||||
port->guc_options = NIL;
|
|
||||||
|
|
||||||
while (offset < len)
|
|
||||||
{
|
|
||||||
char *nameptr = buf + offset;
|
|
||||||
int32 valoffset;
|
|
||||||
char *valptr;
|
|
||||||
|
|
||||||
if (*nameptr == '\0')
|
|
||||||
break; /* found packet terminator */
|
|
||||||
valoffset = offset + strlen(nameptr) + 1;
|
|
||||||
if (valoffset >= len)
|
|
||||||
break; /* missing value, will complain below */
|
|
||||||
valptr = buf + valoffset;
|
|
||||||
|
|
||||||
if (strcmp(nameptr, "database") == 0)
|
|
||||||
port->database_name = pstrdup(valptr);
|
|
||||||
else if (strcmp(nameptr, "user") == 0)
|
|
||||||
port->user_name = pstrdup(valptr);
|
|
||||||
else if (strcmp(nameptr, "options") == 0)
|
|
||||||
port->cmdline_options = pstrdup(valptr);
|
|
||||||
else if (strcmp(nameptr, "replication") == 0)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Due to backward compatibility concerns the replication
|
|
||||||
* parameter is a hybrid beast which allows the value to be
|
|
||||||
* either boolean or the string 'database'. The latter
|
|
||||||
* connects to a specific database which is e.g. required for
|
|
||||||
* logical decoding while.
|
|
||||||
*/
|
|
||||||
if (strcmp(valptr, "database") == 0)
|
|
||||||
{
|
|
||||||
am_walsender = true;
|
|
||||||
am_db_walsender = true;
|
|
||||||
}
|
|
||||||
else if (!parse_bool(valptr, &am_walsender))
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
||||||
errmsg("invalid value for parameter \"%s\": \"%s\"",
|
|
||||||
"replication",
|
|
||||||
valptr),
|
|
||||||
errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
|
|
||||||
}
|
|
||||||
else if (strncmp(nameptr, "_pq_.", 5) == 0)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Any option beginning with _pq_. is reserved for use as a
|
|
||||||
* protocol-level option, but at present no such options are
|
|
||||||
* defined.
|
|
||||||
*/
|
|
||||||
unrecognized_protocol_options =
|
|
||||||
lappend(unrecognized_protocol_options, pstrdup(nameptr));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Assume it's a generic GUC option */
|
|
||||||
port->guc_options = lappend(port->guc_options,
|
|
||||||
pstrdup(nameptr));
|
|
||||||
port->guc_options = lappend(port->guc_options,
|
|
||||||
pstrdup(valptr));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copy application_name to port if we come across it. This
|
|
||||||
* is done so we can log the application_name in the
|
|
||||||
* connection authorization message. Note that the GUC would
|
|
||||||
* be used but we haven't gone through GUC setup yet.
|
|
||||||
*/
|
|
||||||
if (strcmp(nameptr, "application_name") == 0)
|
|
||||||
{
|
|
||||||
port->application_name = pg_clean_ascii(valptr, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offset = valoffset + strlen(valptr) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we didn't find a packet terminator exactly at the end of the
|
|
||||||
* given packet length, complain.
|
|
||||||
*/
|
|
||||||
if (offset != len - 1)
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
|
||||||
errmsg("invalid startup packet layout: expected terminator as last byte")));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the client requested a newer protocol version or if the client
|
|
||||||
* requested any protocol options we didn't recognize, let them know
|
|
||||||
* the newest minor protocol version we do support and the names of
|
|
||||||
* any unrecognized options.
|
|
||||||
*/
|
|
||||||
if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) ||
|
|
||||||
unrecognized_protocol_options != NIL)
|
|
||||||
SendNegotiateProtocolVersion(unrecognized_protocol_options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check a user name was given. */
|
|
||||||
if (port->user_name == NULL || port->user_name[0] == '\0')
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
|
|
||||||
errmsg("no PostgreSQL user name specified in startup packet")));
|
|
||||||
|
|
||||||
/* The database defaults to the user name. */
|
|
||||||
if (port->database_name == NULL || port->database_name[0] == '\0')
|
|
||||||
port->database_name = pstrdup(port->user_name);
|
|
||||||
|
|
||||||
if (am_walsender)
|
|
||||||
MyBackendType = B_WAL_SENDER;
|
|
||||||
else
|
|
||||||
MyBackendType = B_BACKEND;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Normal walsender backends, e.g. for streaming replication, are not
|
|
||||||
* connected to a particular database. But walsenders used for logical
|
|
||||||
* replication need to connect to a specific database. We allow streaming
|
|
||||||
* replication commands to be issued even if connected to a database as it
|
|
||||||
* can make sense to first make a basebackup and then stream changes
|
|
||||||
* starting from that.
|
|
||||||
*/
|
|
||||||
if (am_walsender && !am_db_walsender)
|
|
||||||
port->database_name[0] = '\0';
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Done filling the Port structure
|
|
||||||
*/
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
|
||||||
|
|
||||||
return STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send a NegotiateProtocolVersion to the client. This lets the client know
|
|
||||||
* that they have requested a newer minor protocol version than we are able
|
|
||||||
* to speak. We'll speak the highest version we know about; the client can,
|
|
||||||
* of course, abandon the connection if that's a problem.
|
|
||||||
*
|
|
||||||
* We also include in the response a list of protocol options we didn't
|
|
||||||
* understand. This allows clients to include optional parameters that might
|
|
||||||
* be present either in newer protocol versions or third-party protocol
|
|
||||||
* extensions without fear of having to reconnect if those options are not
|
|
||||||
* understood, while at the same time making certain that the client is aware
|
|
||||||
* of which options were actually accepted.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
SendNegotiateProtocolVersion(List *unrecognized_protocol_options)
|
|
||||||
{
|
|
||||||
StringInfoData buf;
|
|
||||||
ListCell *lc;
|
|
||||||
|
|
||||||
pq_beginmessage(&buf, PqMsg_NegotiateProtocolVersion);
|
|
||||||
pq_sendint32(&buf, PG_PROTOCOL_LATEST);
|
|
||||||
pq_sendint32(&buf, list_length(unrecognized_protocol_options));
|
|
||||||
foreach(lc, unrecognized_protocol_options)
|
|
||||||
pq_sendstring(&buf, lfirst(lc));
|
|
||||||
pq_endmessage(&buf);
|
|
||||||
|
|
||||||
/* no need to flush, some other message will follow */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The client has sent a cancel request packet, not a normal
|
* The client has sent a cancel request packet, not a normal
|
||||||
* start-a-new-connection packet. Perform the necessary processing.
|
* start-a-new-connection packet. Perform the necessary processing.
|
||||||
* Nothing is sent back to the client.
|
* Nothing is sent back to the client.
|
||||||
*/
|
*/
|
||||||
static void
|
void
|
||||||
processCancelRequest(Port *port, void *pkt)
|
processCancelRequest(int backendPID, int32 cancelAuthCode)
|
||||||
{
|
{
|
||||||
CancelRequestPacket *canc = (CancelRequestPacket *) pkt;
|
|
||||||
int backendPID;
|
|
||||||
int32 cancelAuthCode;
|
|
||||||
Backend *bp;
|
Backend *bp;
|
||||||
|
|
||||||
#ifndef EXEC_BACKEND
|
#ifndef EXEC_BACKEND
|
||||||
|
@ -2261,9 +1842,6 @@ processCancelRequest(Port *port, void *pkt)
|
||||||
int i;
|
int i;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
backendPID = (int) pg_ntoh32(canc->backendPID);
|
|
||||||
cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* See if we have a matching backend. In the EXEC_BACKEND case, we can no
|
* See if we have a matching backend. In the EXEC_BACKEND case, we can no
|
||||||
* longer access the postmaster's own backend list, and must rely on the
|
* longer access the postmaster's own backend list, and must rely on the
|
||||||
|
@ -3955,12 +3533,6 @@ TerminateChildren(int signal)
|
||||||
signal_child(SlotSyncWorkerPID, signal);
|
signal_child(SlotSyncWorkerPID, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Information passed from postmaster to backend process */
|
|
||||||
typedef struct BackendStartupData
|
|
||||||
{
|
|
||||||
CAC_state canAcceptConnections;
|
|
||||||
} BackendStartupData;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BackendStartup -- start backend process
|
* BackendStartup -- start backend process
|
||||||
*
|
*
|
||||||
|
@ -4087,302 +3659,6 @@ report_fork_failure_to_client(ClientSocket *client_sock, int errnum)
|
||||||
} while (rc < 0 && errno == EINTR);
|
} while (rc < 0 && errno == EINTR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BackendInitialize -- initialize an interactive (postmaster-child)
|
|
||||||
* backend process, and collect the client's startup packet.
|
|
||||||
*
|
|
||||||
* returns: nothing. Will not return at all if there's any failure.
|
|
||||||
*
|
|
||||||
* Note: this code does not depend on having any access to shared memory.
|
|
||||||
* Indeed, our approach to SIGTERM/timeout handling *requires* that
|
|
||||||
* shared memory not have been touched yet; see comments within.
|
|
||||||
* In the EXEC_BACKEND case, we are physically attached to shared memory
|
|
||||||
* but have not yet set up most of our local pointers to shmem structures.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
BackendInitialize(ClientSocket *client_sock, CAC_state cac)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
int ret;
|
|
||||||
Port *port;
|
|
||||||
char remote_host[NI_MAXHOST];
|
|
||||||
char remote_port[NI_MAXSERV];
|
|
||||||
StringInfoData ps_data;
|
|
||||||
MemoryContext oldcontext;
|
|
||||||
|
|
||||||
/* Tell fd.c about the long-lived FD associated with the client_sock */
|
|
||||||
ReserveExternalFD();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PreAuthDelay is a debugging aid for investigating problems in the
|
|
||||||
* authentication cycle: it can be set in postgresql.conf to allow time to
|
|
||||||
* attach to the newly-forked backend with a debugger. (See also
|
|
||||||
* PostAuthDelay, which we allow clients to pass through PGOPTIONS, but it
|
|
||||||
* is not honored until after authentication.)
|
|
||||||
*/
|
|
||||||
if (PreAuthDelay > 0)
|
|
||||||
pg_usleep(PreAuthDelay * 1000000L);
|
|
||||||
|
|
||||||
/* This flag will remain set until InitPostgres finishes authentication */
|
|
||||||
ClientAuthInProgress = true; /* limit visibility of log messages */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize libpq and enable reporting of ereport errors to the client.
|
|
||||||
* Must do this now because authentication uses libpq to send messages.
|
|
||||||
*
|
|
||||||
* The Port structure and all data structures attached to it are allocated
|
|
||||||
* in TopMemoryContext, so that they survive into PostgresMain execution.
|
|
||||||
* We need not worry about leaking this storage on failure, since we
|
|
||||||
* aren't in the postmaster process anymore.
|
|
||||||
*/
|
|
||||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
|
||||||
port = MyProcPort = pq_init(client_sock);
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
|
||||||
|
|
||||||
whereToSendOutput = DestRemote; /* now safe to ereport to client */
|
|
||||||
|
|
||||||
/* set these to empty in case they are needed before we set them up */
|
|
||||||
port->remote_host = "";
|
|
||||||
port->remote_port = "";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We arrange to do _exit(1) if we receive SIGTERM or timeout while trying
|
|
||||||
* to collect the startup packet; while SIGQUIT results in _exit(2).
|
|
||||||
* Otherwise the postmaster cannot shutdown the database FAST or IMMED
|
|
||||||
* cleanly if a buggy client fails to send the packet promptly.
|
|
||||||
*
|
|
||||||
* Exiting with _exit(1) is only possible because we have not yet touched
|
|
||||||
* shared memory; therefore no outside-the-process state needs to get
|
|
||||||
* cleaned up.
|
|
||||||
*/
|
|
||||||
pqsignal(SIGTERM, process_startup_packet_die);
|
|
||||||
/* SIGQUIT handler was already set up by InitPostmasterChild */
|
|
||||||
InitializeTimeouts(); /* establishes SIGALRM handler */
|
|
||||||
sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get the remote host name and port for logging and status display.
|
|
||||||
*/
|
|
||||||
remote_host[0] = '\0';
|
|
||||||
remote_port[0] = '\0';
|
|
||||||
if ((ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
|
|
||||||
remote_host, sizeof(remote_host),
|
|
||||||
remote_port, sizeof(remote_port),
|
|
||||||
(log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
|
|
||||||
ereport(WARNING,
|
|
||||||
(errmsg_internal("pg_getnameinfo_all() failed: %s",
|
|
||||||
gai_strerror(ret))));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Save remote_host and remote_port in port structure (after this, they
|
|
||||||
* will appear in log_line_prefix data for log messages).
|
|
||||||
*/
|
|
||||||
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
|
||||||
port->remote_host = pstrdup(remote_host);
|
|
||||||
port->remote_port = pstrdup(remote_port);
|
|
||||||
|
|
||||||
/* And now we can issue the Log_connections message, if wanted */
|
|
||||||
if (Log_connections)
|
|
||||||
{
|
|
||||||
if (remote_port[0])
|
|
||||||
ereport(LOG,
|
|
||||||
(errmsg("connection received: host=%s port=%s",
|
|
||||||
remote_host,
|
|
||||||
remote_port)));
|
|
||||||
else
|
|
||||||
ereport(LOG,
|
|
||||||
(errmsg("connection received: host=%s",
|
|
||||||
remote_host)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we did a reverse lookup to name, we might as well save the results
|
|
||||||
* rather than possibly repeating the lookup during authentication.
|
|
||||||
*
|
|
||||||
* Note that we don't want to specify NI_NAMEREQD above, because then we'd
|
|
||||||
* get nothing useful for a client without an rDNS entry. Therefore, we
|
|
||||||
* must check whether we got a numeric IPv4 or IPv6 address, and not save
|
|
||||||
* it into remote_hostname if so. (This test is conservative and might
|
|
||||||
* sometimes classify a hostname as numeric, but an error in that
|
|
||||||
* direction is safe; it only results in a possible extra lookup.)
|
|
||||||
*/
|
|
||||||
if (log_hostname &&
|
|
||||||
ret == 0 &&
|
|
||||||
strspn(remote_host, "0123456789.") < strlen(remote_host) &&
|
|
||||||
strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host))
|
|
||||||
{
|
|
||||||
port->remote_hostname = pstrdup(remote_host);
|
|
||||||
}
|
|
||||||
MemoryContextSwitchTo(oldcontext);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ready to begin client interaction. We will give up and _exit(1) after
|
|
||||||
* a time delay, so that a broken client can't hog a connection
|
|
||||||
* indefinitely. PreAuthDelay and any DNS interactions above don't count
|
|
||||||
* against the time limit.
|
|
||||||
*
|
|
||||||
* Note: AuthenticationTimeout is applied here while waiting for the
|
|
||||||
* startup packet, and then again in InitPostgres for the duration of any
|
|
||||||
* authentication operations. So a hostile client could tie up the
|
|
||||||
* process for nearly twice AuthenticationTimeout before we kick him off.
|
|
||||||
*
|
|
||||||
* Note: because PostgresMain will call InitializeTimeouts again, the
|
|
||||||
* registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay
|
|
||||||
* since we never use it again after this function.
|
|
||||||
*/
|
|
||||||
RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
|
|
||||||
enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Receive the startup packet (which might turn out to be a cancel request
|
|
||||||
* packet).
|
|
||||||
*/
|
|
||||||
status = ProcessStartupPacket(port, false, false);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're going to reject the connection due to database state, say so
|
|
||||||
* now instead of wasting cycles on an authentication exchange. (This also
|
|
||||||
* allows a pg_ping utility to be written.)
|
|
||||||
*/
|
|
||||||
if (status == STATUS_OK)
|
|
||||||
{
|
|
||||||
switch (cac)
|
|
||||||
{
|
|
||||||
case CAC_STARTUP:
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
|
||||||
errmsg("the database system is starting up")));
|
|
||||||
break;
|
|
||||||
case CAC_NOTCONSISTENT:
|
|
||||||
if (EnableHotStandby)
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
|
||||||
errmsg("the database system is not yet accepting connections"),
|
|
||||||
errdetail("Consistent recovery state has not been yet reached.")));
|
|
||||||
else
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
|
||||||
errmsg("the database system is not accepting connections"),
|
|
||||||
errdetail("Hot standby mode is disabled.")));
|
|
||||||
break;
|
|
||||||
case CAC_SHUTDOWN:
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
|
||||||
errmsg("the database system is shutting down")));
|
|
||||||
break;
|
|
||||||
case CAC_RECOVERY:
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
|
||||||
errmsg("the database system is in recovery mode")));
|
|
||||||
break;
|
|
||||||
case CAC_TOOMANY:
|
|
||||||
ereport(FATAL,
|
|
||||||
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
|
|
||||||
errmsg("sorry, too many clients already")));
|
|
||||||
break;
|
|
||||||
case CAC_OK:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disable the timeout, and prevent SIGTERM again.
|
|
||||||
*/
|
|
||||||
disable_timeout(STARTUP_PACKET_TIMEOUT, false);
|
|
||||||
sigprocmask(SIG_SETMASK, &BlockSig, NULL);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* As a safety check that nothing in startup has yet performed
|
|
||||||
* shared-memory modifications that would need to be undone if we had
|
|
||||||
* exited through SIGTERM or timeout above, check that no on_shmem_exit
|
|
||||||
* handlers have been registered yet. (This isn't terribly bulletproof,
|
|
||||||
* since someone might misuse an on_proc_exit handler for shmem cleanup,
|
|
||||||
* but it's a cheap and helpful check. We cannot disallow on_proc_exit
|
|
||||||
* handlers unfortunately, since pq_init() already registered one.)
|
|
||||||
*/
|
|
||||||
check_on_shmem_exit_lists_are_empty();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Stop here if it was bad or a cancel packet. ProcessStartupPacket
|
|
||||||
* already did any appropriate error reporting.
|
|
||||||
*/
|
|
||||||
if (status != STATUS_OK)
|
|
||||||
proc_exit(0);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now that we have the user and database name, we can set the process
|
|
||||||
* title for ps. It's good to do this as early as possible in startup.
|
|
||||||
*/
|
|
||||||
initStringInfo(&ps_data);
|
|
||||||
if (am_walsender)
|
|
||||||
appendStringInfo(&ps_data, "%s ", GetBackendTypeDesc(B_WAL_SENDER));
|
|
||||||
appendStringInfo(&ps_data, "%s ", port->user_name);
|
|
||||||
if (port->database_name[0] != '\0')
|
|
||||||
appendStringInfo(&ps_data, "%s ", port->database_name);
|
|
||||||
appendStringInfoString(&ps_data, port->remote_host);
|
|
||||||
if (port->remote_port[0] != '\0')
|
|
||||||
appendStringInfo(&ps_data, "(%s)", port->remote_port);
|
|
||||||
|
|
||||||
init_ps_display(ps_data.data);
|
|
||||||
pfree(ps_data.data);
|
|
||||||
|
|
||||||
set_ps_display("initializing");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
BackendMain(char *startup_data, size_t startup_data_len)
|
|
||||||
{
|
|
||||||
BackendStartupData *bsdata = (BackendStartupData *) startup_data;
|
|
||||||
|
|
||||||
Assert(startup_data_len == sizeof(BackendStartupData));
|
|
||||||
Assert(MyClientSocket != NULL);
|
|
||||||
|
|
||||||
#ifdef EXEC_BACKEND
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Need to reinitialize the SSL library in the backend, since the context
|
|
||||||
* structures contain function pointers and cannot be passed through the
|
|
||||||
* parameter file.
|
|
||||||
*
|
|
||||||
* If for some reason reload fails (maybe the user installed broken key
|
|
||||||
* files), soldier on without SSL; that's better than all connections
|
|
||||||
* becoming impossible.
|
|
||||||
*
|
|
||||||
* XXX should we do this in all child processes? For the moment it's
|
|
||||||
* enough to do it in backend children.
|
|
||||||
*/
|
|
||||||
#ifdef USE_SSL
|
|
||||||
if (EnableSSL)
|
|
||||||
{
|
|
||||||
if (secure_initialize(false) == 0)
|
|
||||||
LoadedSSL = true;
|
|
||||||
else
|
|
||||||
ereport(LOG,
|
|
||||||
(errmsg("SSL configuration could not be loaded in child process")));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Perform additional initialization and collect startup packet */
|
|
||||||
BackendInitialize(MyClientSocket, bsdata->canAcceptConnections);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a per-backend PGPROC struct in shared memory. We must do this
|
|
||||||
* before we can use LWLocks or access any shared memory.
|
|
||||||
*/
|
|
||||||
InitProcess();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure we aren't in PostmasterContext anymore. (We can't delete it
|
|
||||||
* just yet, though, because InitPostgres will need the HBA data.)
|
|
||||||
*/
|
|
||||||
MemoryContextSwitchTo(TopMemoryContext);
|
|
||||||
|
|
||||||
PostgresMain(MyProcPort->database_name, MyProcPort->user_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ExitPostmaster -- cleanup
|
* ExitPostmaster -- cleanup
|
||||||
*
|
*
|
||||||
|
@ -4571,25 +3847,6 @@ process_pm_pmsignal(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* SIGTERM while processing startup packet.
|
|
||||||
*
|
|
||||||
* Running proc_exit() from a signal handler would be quite unsafe.
|
|
||||||
* However, since we have not yet touched shared memory, we can just
|
|
||||||
* pull the plug and exit without running any atexit handlers.
|
|
||||||
*
|
|
||||||
* One might be tempted to try to send a message, or log one, indicating
|
|
||||||
* why we are disconnecting. However, that would be quite unsafe in itself.
|
|
||||||
* Also, it seems undesirable to provide clues about the database's state
|
|
||||||
* to a client that has not yet completed authentication, or even sent us
|
|
||||||
* a startup packet.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
process_startup_packet_die(SIGNAL_ARGS)
|
|
||||||
{
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dummy signal handler
|
* Dummy signal handler
|
||||||
*
|
*
|
||||||
|
@ -4604,17 +3861,6 @@ dummy_handler(SIGNAL_ARGS)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Timeout while processing startup packet.
|
|
||||||
* As for process_startup_packet_die(), we exit via _exit(1).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
StartupPacketTimeoutHandler(void)
|
|
||||||
{
|
|
||||||
_exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate a random cancel key.
|
* Generate a random cancel key.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,6 +13,7 @@ top_builddir = ../../..
|
||||||
include $(top_builddir)/src/Makefile.global
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
OBJS = \
|
OBJS = \
|
||||||
|
backend_startup.o \
|
||||||
cmdtag.o \
|
cmdtag.o \
|
||||||
dest.o \
|
dest.o \
|
||||||
fastpath.o \
|
fastpath.o \
|
||||||
|
|
|
@ -0,0 +1,778 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* backend_startup.c
|
||||||
|
* Backend startup code
|
||||||
|
*
|
||||||
|
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
|
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* IDENTIFICATION
|
||||||
|
* src/backend/tcop/backend_startup.c
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "access/xlog.h"
|
||||||
|
#include "common/ip.h"
|
||||||
|
#include "common/string.h"
|
||||||
|
#include "libpq/libpq.h"
|
||||||
|
#include "libpq/libpq-be.h"
|
||||||
|
#include "libpq/pqformat.h"
|
||||||
|
#include "libpq/pqsignal.h"
|
||||||
|
#include "miscadmin.h"
|
||||||
|
#include "postmaster/postmaster.h"
|
||||||
|
#include "replication/walsender.h"
|
||||||
|
#include "storage/fd.h"
|
||||||
|
#include "storage/ipc.h"
|
||||||
|
#include "storage/proc.h"
|
||||||
|
#include "tcop/backend_startup.h"
|
||||||
|
#include "tcop/tcopprot.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
#include "utils/ps_status.h"
|
||||||
|
#include "utils/timeout.h"
|
||||||
|
|
||||||
|
static void BackendInitialize(ClientSocket *client_sock, CAC_state cac);
|
||||||
|
static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done);
|
||||||
|
static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options);
|
||||||
|
static void process_startup_packet_die(SIGNAL_ARGS);
|
||||||
|
static void StartupPacketTimeoutHandler(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Entry point for a new backend process.
|
||||||
|
*
|
||||||
|
* Initialize the connection, read the startup packet, authenticate the
|
||||||
|
* client, and start the main processing loop.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
BackendMain(char *startup_data, size_t startup_data_len)
|
||||||
|
{
|
||||||
|
BackendStartupData *bsdata = (BackendStartupData *) startup_data;
|
||||||
|
|
||||||
|
Assert(startup_data_len == sizeof(BackendStartupData));
|
||||||
|
Assert(MyClientSocket != NULL);
|
||||||
|
|
||||||
|
#ifdef EXEC_BACKEND
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Need to reinitialize the SSL library in the backend, since the context
|
||||||
|
* structures contain function pointers and cannot be passed through the
|
||||||
|
* parameter file.
|
||||||
|
*
|
||||||
|
* If for some reason reload fails (maybe the user installed broken key
|
||||||
|
* files), soldier on without SSL; that's better than all connections
|
||||||
|
* becoming impossible.
|
||||||
|
*
|
||||||
|
* XXX should we do this in all child processes? For the moment it's
|
||||||
|
* enough to do it in backend children.
|
||||||
|
*/
|
||||||
|
#ifdef USE_SSL
|
||||||
|
if (EnableSSL)
|
||||||
|
{
|
||||||
|
if (secure_initialize(false) == 0)
|
||||||
|
LoadedSSL = true;
|
||||||
|
else
|
||||||
|
ereport(LOG,
|
||||||
|
(errmsg("SSL configuration could not be loaded in child process")));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Perform additional initialization and collect startup packet */
|
||||||
|
BackendInitialize(MyClientSocket, bsdata->canAcceptConnections);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a per-backend PGPROC struct in shared memory. We must do this
|
||||||
|
* before we can use LWLocks or access any shared memory.
|
||||||
|
*/
|
||||||
|
InitProcess();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure we aren't in PostmasterContext anymore. (We can't delete it
|
||||||
|
* just yet, though, because InitPostgres will need the HBA data.)
|
||||||
|
*/
|
||||||
|
MemoryContextSwitchTo(TopMemoryContext);
|
||||||
|
|
||||||
|
PostgresMain(MyProcPort->database_name, MyProcPort->user_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BackendInitialize -- initialize an interactive (postmaster-child)
|
||||||
|
* backend process, and collect the client's startup packet.
|
||||||
|
*
|
||||||
|
* returns: nothing. Will not return at all if there's any failure.
|
||||||
|
*
|
||||||
|
* Note: this code does not depend on having any access to shared memory.
|
||||||
|
* Indeed, our approach to SIGTERM/timeout handling *requires* that
|
||||||
|
* shared memory not have been touched yet; see comments within.
|
||||||
|
* In the EXEC_BACKEND case, we are physically attached to shared memory
|
||||||
|
* but have not yet set up most of our local pointers to shmem structures.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
BackendInitialize(ClientSocket *client_sock, CAC_state cac)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
int ret;
|
||||||
|
Port *port;
|
||||||
|
char remote_host[NI_MAXHOST];
|
||||||
|
char remote_port[NI_MAXSERV];
|
||||||
|
StringInfoData ps_data;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
|
||||||
|
/* Tell fd.c about the long-lived FD associated with the client_sock */
|
||||||
|
ReserveExternalFD();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreAuthDelay is a debugging aid for investigating problems in the
|
||||||
|
* authentication cycle: it can be set in postgresql.conf to allow time to
|
||||||
|
* attach to the newly-forked backend with a debugger. (See also
|
||||||
|
* PostAuthDelay, which we allow clients to pass through PGOPTIONS, but it
|
||||||
|
* is not honored until after authentication.)
|
||||||
|
*/
|
||||||
|
if (PreAuthDelay > 0)
|
||||||
|
pg_usleep(PreAuthDelay * 1000000L);
|
||||||
|
|
||||||
|
/* This flag will remain set until InitPostgres finishes authentication */
|
||||||
|
ClientAuthInProgress = true; /* limit visibility of log messages */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize libpq and enable reporting of ereport errors to the client.
|
||||||
|
* Must do this now because authentication uses libpq to send messages.
|
||||||
|
*
|
||||||
|
* The Port structure and all data structures attached to it are allocated
|
||||||
|
* in TopMemoryContext, so that they survive into PostgresMain execution.
|
||||||
|
* We need not worry about leaking this storage on failure, since we
|
||||||
|
* aren't in the postmaster process anymore.
|
||||||
|
*/
|
||||||
|
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
||||||
|
port = MyProcPort = pq_init(client_sock);
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
whereToSendOutput = DestRemote; /* now safe to ereport to client */
|
||||||
|
|
||||||
|
/* set these to empty in case they are needed before we set them up */
|
||||||
|
port->remote_host = "";
|
||||||
|
port->remote_port = "";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We arrange to do _exit(1) if we receive SIGTERM or timeout while trying
|
||||||
|
* to collect the startup packet; while SIGQUIT results in _exit(2).
|
||||||
|
* Otherwise the postmaster cannot shutdown the database FAST or IMMED
|
||||||
|
* cleanly if a buggy client fails to send the packet promptly.
|
||||||
|
*
|
||||||
|
* Exiting with _exit(1) is only possible because we have not yet touched
|
||||||
|
* shared memory; therefore no outside-the-process state needs to get
|
||||||
|
* cleaned up.
|
||||||
|
*/
|
||||||
|
pqsignal(SIGTERM, process_startup_packet_die);
|
||||||
|
/* SIGQUIT handler was already set up by InitPostmasterChild */
|
||||||
|
InitializeTimeouts(); /* establishes SIGALRM handler */
|
||||||
|
sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the remote host name and port for logging and status display.
|
||||||
|
*/
|
||||||
|
remote_host[0] = '\0';
|
||||||
|
remote_port[0] = '\0';
|
||||||
|
if ((ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
|
||||||
|
remote_host, sizeof(remote_host),
|
||||||
|
remote_port, sizeof(remote_port),
|
||||||
|
(log_hostname ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV)) != 0)
|
||||||
|
ereport(WARNING,
|
||||||
|
(errmsg_internal("pg_getnameinfo_all() failed: %s",
|
||||||
|
gai_strerror(ret))));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Save remote_host and remote_port in port structure (after this, they
|
||||||
|
* will appear in log_line_prefix data for log messages).
|
||||||
|
*/
|
||||||
|
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
||||||
|
port->remote_host = pstrdup(remote_host);
|
||||||
|
port->remote_port = pstrdup(remote_port);
|
||||||
|
|
||||||
|
/* And now we can issue the Log_connections message, if wanted */
|
||||||
|
if (Log_connections)
|
||||||
|
{
|
||||||
|
if (remote_port[0])
|
||||||
|
ereport(LOG,
|
||||||
|
(errmsg("connection received: host=%s port=%s",
|
||||||
|
remote_host,
|
||||||
|
remote_port)));
|
||||||
|
else
|
||||||
|
ereport(LOG,
|
||||||
|
(errmsg("connection received: host=%s",
|
||||||
|
remote_host)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we did a reverse lookup to name, we might as well save the results
|
||||||
|
* rather than possibly repeating the lookup during authentication.
|
||||||
|
*
|
||||||
|
* Note that we don't want to specify NI_NAMEREQD above, because then we'd
|
||||||
|
* get nothing useful for a client without an rDNS entry. Therefore, we
|
||||||
|
* must check whether we got a numeric IPv4 or IPv6 address, and not save
|
||||||
|
* it into remote_hostname if so. (This test is conservative and might
|
||||||
|
* sometimes classify a hostname as numeric, but an error in that
|
||||||
|
* direction is safe; it only results in a possible extra lookup.)
|
||||||
|
*/
|
||||||
|
if (log_hostname &&
|
||||||
|
ret == 0 &&
|
||||||
|
strspn(remote_host, "0123456789.") < strlen(remote_host) &&
|
||||||
|
strspn(remote_host, "0123456789ABCDEFabcdef:") < strlen(remote_host))
|
||||||
|
{
|
||||||
|
port->remote_hostname = pstrdup(remote_host);
|
||||||
|
}
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ready to begin client interaction. We will give up and _exit(1) after
|
||||||
|
* a time delay, so that a broken client can't hog a connection
|
||||||
|
* indefinitely. PreAuthDelay and any DNS interactions above don't count
|
||||||
|
* against the time limit.
|
||||||
|
*
|
||||||
|
* Note: AuthenticationTimeout is applied here while waiting for the
|
||||||
|
* startup packet, and then again in InitPostgres for the duration of any
|
||||||
|
* authentication operations. So a hostile client could tie up the
|
||||||
|
* process for nearly twice AuthenticationTimeout before we kick him off.
|
||||||
|
*
|
||||||
|
* Note: because PostgresMain will call InitializeTimeouts again, the
|
||||||
|
* registration of STARTUP_PACKET_TIMEOUT will be lost. This is okay
|
||||||
|
* since we never use it again after this function.
|
||||||
|
*/
|
||||||
|
RegisterTimeout(STARTUP_PACKET_TIMEOUT, StartupPacketTimeoutHandler);
|
||||||
|
enable_timeout_after(STARTUP_PACKET_TIMEOUT, AuthenticationTimeout * 1000);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Receive the startup packet (which might turn out to be a cancel request
|
||||||
|
* packet).
|
||||||
|
*/
|
||||||
|
status = ProcessStartupPacket(port, false, false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're going to reject the connection due to database state, say so
|
||||||
|
* now instead of wasting cycles on an authentication exchange. (This also
|
||||||
|
* allows a pg_ping utility to be written.)
|
||||||
|
*/
|
||||||
|
if (status == STATUS_OK)
|
||||||
|
{
|
||||||
|
switch (cac)
|
||||||
|
{
|
||||||
|
case CAC_STARTUP:
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
||||||
|
errmsg("the database system is starting up")));
|
||||||
|
break;
|
||||||
|
case CAC_NOTCONSISTENT:
|
||||||
|
if (EnableHotStandby)
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
||||||
|
errmsg("the database system is not yet accepting connections"),
|
||||||
|
errdetail("Consistent recovery state has not been yet reached.")));
|
||||||
|
else
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
||||||
|
errmsg("the database system is not accepting connections"),
|
||||||
|
errdetail("Hot standby mode is disabled.")));
|
||||||
|
break;
|
||||||
|
case CAC_SHUTDOWN:
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
||||||
|
errmsg("the database system is shutting down")));
|
||||||
|
break;
|
||||||
|
case CAC_RECOVERY:
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_CANNOT_CONNECT_NOW),
|
||||||
|
errmsg("the database system is in recovery mode")));
|
||||||
|
break;
|
||||||
|
case CAC_TOOMANY:
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
|
||||||
|
errmsg("sorry, too many clients already")));
|
||||||
|
break;
|
||||||
|
case CAC_OK:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disable the timeout, and prevent SIGTERM again.
|
||||||
|
*/
|
||||||
|
disable_timeout(STARTUP_PACKET_TIMEOUT, false);
|
||||||
|
sigprocmask(SIG_SETMASK, &BlockSig, NULL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As a safety check that nothing in startup has yet performed
|
||||||
|
* shared-memory modifications that would need to be undone if we had
|
||||||
|
* exited through SIGTERM or timeout above, check that no on_shmem_exit
|
||||||
|
* handlers have been registered yet. (This isn't terribly bulletproof,
|
||||||
|
* since someone might misuse an on_proc_exit handler for shmem cleanup,
|
||||||
|
* but it's a cheap and helpful check. We cannot disallow on_proc_exit
|
||||||
|
* handlers unfortunately, since pq_init() already registered one.)
|
||||||
|
*/
|
||||||
|
check_on_shmem_exit_lists_are_empty();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stop here if it was bad or a cancel packet. ProcessStartupPacket
|
||||||
|
* already did any appropriate error reporting.
|
||||||
|
*/
|
||||||
|
if (status != STATUS_OK)
|
||||||
|
proc_exit(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that we have the user and database name, we can set the process
|
||||||
|
* title for ps. It's good to do this as early as possible in startup.
|
||||||
|
*/
|
||||||
|
initStringInfo(&ps_data);
|
||||||
|
if (am_walsender)
|
||||||
|
appendStringInfo(&ps_data, "%s ", GetBackendTypeDesc(B_WAL_SENDER));
|
||||||
|
appendStringInfo(&ps_data, "%s ", port->user_name);
|
||||||
|
if (port->database_name[0] != '\0')
|
||||||
|
appendStringInfo(&ps_data, "%s ", port->database_name);
|
||||||
|
appendStringInfoString(&ps_data, port->remote_host);
|
||||||
|
if (port->remote_port[0] != '\0')
|
||||||
|
appendStringInfo(&ps_data, "(%s)", port->remote_port);
|
||||||
|
|
||||||
|
init_ps_display(ps_data.data);
|
||||||
|
pfree(ps_data.data);
|
||||||
|
|
||||||
|
set_ps_display("initializing");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read a client's startup packet and do something according to it.
|
||||||
|
*
|
||||||
|
* Returns STATUS_OK or STATUS_ERROR, or might call ereport(FATAL) and
|
||||||
|
* not return at all.
|
||||||
|
*
|
||||||
|
* (Note that ereport(FATAL) stuff is sent to the client, so only use it
|
||||||
|
* if that's what you want. Return STATUS_ERROR if you don't want to
|
||||||
|
* send anything to the client, which would typically be appropriate
|
||||||
|
* if we detect a communications failure.)
|
||||||
|
*
|
||||||
|
* Set ssl_done and/or gss_done when negotiation of an encrypted layer
|
||||||
|
* (currently, TLS or GSSAPI) is completed. A successful negotiation of either
|
||||||
|
* encryption layer sets both flags, but a rejected negotiation sets only the
|
||||||
|
* flag for that layer, since the client may wish to try the other one. We
|
||||||
|
* should make no assumption here about the order in which the client may make
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
|
||||||
|
{
|
||||||
|
int32 len;
|
||||||
|
char *buf;
|
||||||
|
ProtocolVersion proto;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
|
||||||
|
pq_startmsgread();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Grab the first byte of the length word separately, so that we can tell
|
||||||
|
* whether we have no data at all or an incomplete packet. (This might
|
||||||
|
* sound inefficient, but it's not really, because of buffering in
|
||||||
|
* pqcomm.c.)
|
||||||
|
*/
|
||||||
|
if (pq_getbytes((char *) &len, 1) == EOF)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If we get no data at all, don't clutter the log with a complaint;
|
||||||
|
* such cases often occur for legitimate reasons. An example is that
|
||||||
|
* we might be here after responding to NEGOTIATE_SSL_CODE, and if the
|
||||||
|
* client didn't like our response, it'll probably just drop the
|
||||||
|
* connection. Service-monitoring software also often just opens and
|
||||||
|
* closes a connection without sending anything. (So do port
|
||||||
|
* scanners, which may be less benign, but it's not really our job to
|
||||||
|
* notice those.)
|
||||||
|
*/
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pq_getbytes(((char *) &len) + 1, 3) == EOF)
|
||||||
|
{
|
||||||
|
/* Got a partial length word, so bleat about that */
|
||||||
|
if (!ssl_done && !gss_done)
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("incomplete startup packet")));
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = pg_ntoh32(len);
|
||||||
|
len -= 4;
|
||||||
|
|
||||||
|
if (len < (int32) sizeof(ProtocolVersion) ||
|
||||||
|
len > MAX_STARTUP_PACKET_LENGTH)
|
||||||
|
{
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("invalid length of startup packet")));
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate space to hold the startup packet, plus one extra byte that's
|
||||||
|
* initialized to be zero. This ensures we will have null termination of
|
||||||
|
* all strings inside the packet.
|
||||||
|
*/
|
||||||
|
buf = palloc(len + 1);
|
||||||
|
buf[len] = '\0';
|
||||||
|
|
||||||
|
if (pq_getbytes(buf, len) == EOF)
|
||||||
|
{
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("incomplete startup packet")));
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
pq_endmsgread();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The first field is either a protocol version number or a special
|
||||||
|
* request code.
|
||||||
|
*/
|
||||||
|
port->proto = proto = pg_ntoh32(*((ProtocolVersion *) buf));
|
||||||
|
|
||||||
|
if (proto == CANCEL_REQUEST_CODE)
|
||||||
|
{
|
||||||
|
CancelRequestPacket *canc;
|
||||||
|
int backendPID;
|
||||||
|
int32 cancelAuthCode;
|
||||||
|
|
||||||
|
if (len != sizeof(CancelRequestPacket))
|
||||||
|
{
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("invalid length of startup packet")));
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
canc = (CancelRequestPacket *) buf;
|
||||||
|
backendPID = (int) pg_ntoh32(canc->backendPID);
|
||||||
|
cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode);
|
||||||
|
|
||||||
|
processCancelRequest(backendPID, cancelAuthCode);
|
||||||
|
/* Not really an error, but we don't want to proceed further */
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto == NEGOTIATE_SSL_CODE && !ssl_done)
|
||||||
|
{
|
||||||
|
char SSLok;
|
||||||
|
|
||||||
|
#ifdef USE_SSL
|
||||||
|
/* No SSL when disabled or on Unix sockets */
|
||||||
|
if (!LoadedSSL || port->laddr.addr.ss_family == AF_UNIX)
|
||||||
|
SSLok = 'N';
|
||||||
|
else
|
||||||
|
SSLok = 'S'; /* Support for SSL */
|
||||||
|
#else
|
||||||
|
SSLok = 'N'; /* No support for SSL */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
retry1:
|
||||||
|
if (send(port->sock, &SSLok, 1, 0) != 1)
|
||||||
|
{
|
||||||
|
if (errno == EINTR)
|
||||||
|
goto retry1; /* if interrupted, just retry */
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode_for_socket_access(),
|
||||||
|
errmsg("failed to send SSL negotiation response: %m")));
|
||||||
|
return STATUS_ERROR; /* close the connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SSL
|
||||||
|
if (SSLok == 'S' && secure_open_server(port) == -1)
|
||||||
|
return STATUS_ERROR;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* We report this case to the client.
|
||||||
|
*/
|
||||||
|
if (pq_buffer_has_data())
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("received unencrypted data after SSL request"),
|
||||||
|
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* regular startup packet, cancel, etc packet should follow, but not
|
||||||
|
* another SSL negotiation request, and a GSS request should only
|
||||||
|
* follow if SSL was rejected (client may negotiate in either order)
|
||||||
|
*/
|
||||||
|
return ProcessStartupPacket(port, true, SSLok == 'S');
|
||||||
|
}
|
||||||
|
else if (proto == NEGOTIATE_GSS_CODE && !gss_done)
|
||||||
|
{
|
||||||
|
char GSSok = 'N';
|
||||||
|
|
||||||
|
#ifdef ENABLE_GSS
|
||||||
|
/* No GSSAPI encryption when on Unix socket */
|
||||||
|
if (port->laddr.addr.ss_family != AF_UNIX)
|
||||||
|
GSSok = 'G';
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while (send(port->sock, &GSSok, 1, 0) != 1)
|
||||||
|
{
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
ereport(COMMERROR,
|
||||||
|
(errcode_for_socket_access(),
|
||||||
|
errmsg("failed to send GSSAPI negotiation response: %m")));
|
||||||
|
return STATUS_ERROR; /* close the connection */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_GSS
|
||||||
|
if (GSSok == 'G' && secure_open_gssapi(port) == -1)
|
||||||
|
return STATUS_ERROR;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
* We report this case to the client.
|
||||||
|
*/
|
||||||
|
if (pq_buffer_has_data())
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("received unencrypted data after GSSAPI encryption request"),
|
||||||
|
errdetail("This could be either a client-software bug or evidence of an attempted man-in-the-middle attack.")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* regular startup packet, cancel, etc packet should follow, but not
|
||||||
|
* another GSS negotiation request, and an SSL request should only
|
||||||
|
* follow if GSS was rejected (client may negotiate in either order)
|
||||||
|
*/
|
||||||
|
return ProcessStartupPacket(port, GSSok == 'G', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Could add additional special packet types here */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set FrontendProtocol now so that ereport() knows what format to send if
|
||||||
|
* we fail during startup.
|
||||||
|
*/
|
||||||
|
FrontendProtocol = proto;
|
||||||
|
|
||||||
|
/* Check that the major protocol version is in range. */
|
||||||
|
if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) ||
|
||||||
|
PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST))
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u",
|
||||||
|
PG_PROTOCOL_MAJOR(proto), PG_PROTOCOL_MINOR(proto),
|
||||||
|
PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST),
|
||||||
|
PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST),
|
||||||
|
PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now fetch parameters out of startup packet and save them into the Port
|
||||||
|
* structure.
|
||||||
|
*/
|
||||||
|
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
|
||||||
|
|
||||||
|
/* Handle protocol version 3 startup packet */
|
||||||
|
{
|
||||||
|
int32 offset = sizeof(ProtocolVersion);
|
||||||
|
List *unrecognized_protocol_options = NIL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan packet body for name/option pairs. We can assume any string
|
||||||
|
* beginning within the packet body is null-terminated, thanks to
|
||||||
|
* zeroing extra byte above.
|
||||||
|
*/
|
||||||
|
port->guc_options = NIL;
|
||||||
|
|
||||||
|
while (offset < len)
|
||||||
|
{
|
||||||
|
char *nameptr = buf + offset;
|
||||||
|
int32 valoffset;
|
||||||
|
char *valptr;
|
||||||
|
|
||||||
|
if (*nameptr == '\0')
|
||||||
|
break; /* found packet terminator */
|
||||||
|
valoffset = offset + strlen(nameptr) + 1;
|
||||||
|
if (valoffset >= len)
|
||||||
|
break; /* missing value, will complain below */
|
||||||
|
valptr = buf + valoffset;
|
||||||
|
|
||||||
|
if (strcmp(nameptr, "database") == 0)
|
||||||
|
port->database_name = pstrdup(valptr);
|
||||||
|
else if (strcmp(nameptr, "user") == 0)
|
||||||
|
port->user_name = pstrdup(valptr);
|
||||||
|
else if (strcmp(nameptr, "options") == 0)
|
||||||
|
port->cmdline_options = pstrdup(valptr);
|
||||||
|
else if (strcmp(nameptr, "replication") == 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Due to backward compatibility concerns the replication
|
||||||
|
* parameter is a hybrid beast which allows the value to be
|
||||||
|
* either boolean or the string 'database'. The latter
|
||||||
|
* connects to a specific database which is e.g. required for
|
||||||
|
* logical decoding while.
|
||||||
|
*/
|
||||||
|
if (strcmp(valptr, "database") == 0)
|
||||||
|
{
|
||||||
|
am_walsender = true;
|
||||||
|
am_db_walsender = true;
|
||||||
|
}
|
||||||
|
else if (!parse_bool(valptr, &am_walsender))
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||||
|
errmsg("invalid value for parameter \"%s\": \"%s\"",
|
||||||
|
"replication",
|
||||||
|
valptr),
|
||||||
|
errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\".")));
|
||||||
|
}
|
||||||
|
else if (strncmp(nameptr, "_pq_.", 5) == 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Any option beginning with _pq_. is reserved for use as a
|
||||||
|
* protocol-level option, but at present no such options are
|
||||||
|
* defined.
|
||||||
|
*/
|
||||||
|
unrecognized_protocol_options =
|
||||||
|
lappend(unrecognized_protocol_options, pstrdup(nameptr));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Assume it's a generic GUC option */
|
||||||
|
port->guc_options = lappend(port->guc_options,
|
||||||
|
pstrdup(nameptr));
|
||||||
|
port->guc_options = lappend(port->guc_options,
|
||||||
|
pstrdup(valptr));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy application_name to port if we come across it. This
|
||||||
|
* is done so we can log the application_name in the
|
||||||
|
* connection authorization message. Note that the GUC would
|
||||||
|
* be used but we haven't gone through GUC setup yet.
|
||||||
|
*/
|
||||||
|
if (strcmp(nameptr, "application_name") == 0)
|
||||||
|
{
|
||||||
|
port->application_name = pg_clean_ascii(valptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = valoffset + strlen(valptr) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we didn't find a packet terminator exactly at the end of the
|
||||||
|
* given packet length, complain.
|
||||||
|
*/
|
||||||
|
if (offset != len - 1)
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
|
errmsg("invalid startup packet layout: expected terminator as last byte")));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the client requested a newer protocol version or if the client
|
||||||
|
* requested any protocol options we didn't recognize, let them know
|
||||||
|
* the newest minor protocol version we do support and the names of
|
||||||
|
* any unrecognized options.
|
||||||
|
*/
|
||||||
|
if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) ||
|
||||||
|
unrecognized_protocol_options != NIL)
|
||||||
|
SendNegotiateProtocolVersion(unrecognized_protocol_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check a user name was given. */
|
||||||
|
if (port->user_name == NULL || port->user_name[0] == '\0')
|
||||||
|
ereport(FATAL,
|
||||||
|
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
|
||||||
|
errmsg("no PostgreSQL user name specified in startup packet")));
|
||||||
|
|
||||||
|
/* The database defaults to the user name. */
|
||||||
|
if (port->database_name == NULL || port->database_name[0] == '\0')
|
||||||
|
port->database_name = pstrdup(port->user_name);
|
||||||
|
|
||||||
|
if (am_walsender)
|
||||||
|
MyBackendType = B_WAL_SENDER;
|
||||||
|
else
|
||||||
|
MyBackendType = B_BACKEND;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normal walsender backends, e.g. for streaming replication, are not
|
||||||
|
* connected to a particular database. But walsenders used for logical
|
||||||
|
* replication need to connect to a specific database. We allow streaming
|
||||||
|
* replication commands to be issued even if connected to a database as it
|
||||||
|
* can make sense to first make a basebackup and then stream changes
|
||||||
|
* starting from that.
|
||||||
|
*/
|
||||||
|
if (am_walsender && !am_db_walsender)
|
||||||
|
port->database_name[0] = '\0';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Done filling the Port structure
|
||||||
|
*/
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
return STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send a NegotiateProtocolVersion to the client. This lets the client know
|
||||||
|
* that they have requested a newer minor protocol version than we are able
|
||||||
|
* to speak. We'll speak the highest version we know about; the client can,
|
||||||
|
* of course, abandon the connection if that's a problem.
|
||||||
|
*
|
||||||
|
* We also include in the response a list of protocol options we didn't
|
||||||
|
* understand. This allows clients to include optional parameters that might
|
||||||
|
* be present either in newer protocol versions or third-party protocol
|
||||||
|
* extensions without fear of having to reconnect if those options are not
|
||||||
|
* understood, while at the same time making certain that the client is aware
|
||||||
|
* of which options were actually accepted.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
SendNegotiateProtocolVersion(List *unrecognized_protocol_options)
|
||||||
|
{
|
||||||
|
StringInfoData buf;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
pq_beginmessage(&buf, PqMsg_NegotiateProtocolVersion);
|
||||||
|
pq_sendint32(&buf, PG_PROTOCOL_LATEST);
|
||||||
|
pq_sendint32(&buf, list_length(unrecognized_protocol_options));
|
||||||
|
foreach(lc, unrecognized_protocol_options)
|
||||||
|
pq_sendstring(&buf, lfirst(lc));
|
||||||
|
pq_endmessage(&buf);
|
||||||
|
|
||||||
|
/* no need to flush, some other message will follow */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SIGTERM while processing startup packet.
|
||||||
|
*
|
||||||
|
* Running proc_exit() from a signal handler would be quite unsafe.
|
||||||
|
* However, since we have not yet touched shared memory, we can just
|
||||||
|
* pull the plug and exit without running any atexit handlers.
|
||||||
|
*
|
||||||
|
* One might be tempted to try to send a message, or log one, indicating
|
||||||
|
* why we are disconnecting. However, that would be quite unsafe in itself.
|
||||||
|
* Also, it seems undesirable to provide clues about the database's state
|
||||||
|
* to a client that has not yet completed authentication, or even sent us
|
||||||
|
* a startup packet.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
process_startup_packet_die(SIGNAL_ARGS)
|
||||||
|
{
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Timeout while processing startup packet.
|
||||||
|
* As for process_startup_packet_die(), we exit via _exit(1).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
StartupPacketTimeoutHandler(void)
|
||||||
|
{
|
||||||
|
_exit(1);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (c) 2022-2024, PostgreSQL Global Development Group
|
# Copyright (c) 2022-2024, PostgreSQL Global Development Group
|
||||||
|
|
||||||
backend_sources += files(
|
backend_sources += files(
|
||||||
|
'backend_startup.c',
|
||||||
'cmdtag.c',
|
'cmdtag.c',
|
||||||
'dest.c',
|
'dest.c',
|
||||||
'fastpath.c',
|
'fastpath.c',
|
||||||
|
|
|
@ -52,6 +52,8 @@ extern PGDLLIMPORT int postmaster_alive_fds[2];
|
||||||
|
|
||||||
extern PGDLLIMPORT const char *progname;
|
extern PGDLLIMPORT const char *progname;
|
||||||
|
|
||||||
|
extern bool LoadedSSL;
|
||||||
|
|
||||||
extern void PostmasterMain(int argc, char *argv[]) pg_attribute_noreturn();
|
extern void PostmasterMain(int argc, char *argv[]) pg_attribute_noreturn();
|
||||||
extern void ClosePostmasterPorts(bool am_syslogger);
|
extern void ClosePostmasterPorts(bool am_syslogger);
|
||||||
extern void InitProcessGlobals(void);
|
extern void InitProcessGlobals(void);
|
||||||
|
@ -60,7 +62,7 @@ extern int MaxLivePostmasterChildren(void);
|
||||||
|
|
||||||
extern bool PostmasterMarkPIDForWorkerNotify(int);
|
extern bool PostmasterMarkPIDForWorkerNotify(int);
|
||||||
|
|
||||||
extern void BackendMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
|
extern void processCancelRequest(int backendPID, int32 cancelAuthCode);
|
||||||
|
|
||||||
#ifdef EXEC_BACKEND
|
#ifdef EXEC_BACKEND
|
||||||
extern Size ShmemBackendArraySize(void);
|
extern Size ShmemBackendArraySize(void);
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* backend_startup.h
|
||||||
|
* prototypes for backend_startup.c.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
|
||||||
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
|
*
|
||||||
|
* src/include/tcop/backend_startup.h
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#ifndef BACKEND_STARTUP_H
|
||||||
|
#define BACKEND_STARTUP_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CAC_state is passed from postmaster to the backend process, to indicate
|
||||||
|
* whether the connection should be accepted, or if the process should just
|
||||||
|
* send an error to the client and close the connection. Note that the
|
||||||
|
* connection can fail for various reasons even if postmaster passed CAC_OK.
|
||||||
|
*/
|
||||||
|
typedef enum CAC_state
|
||||||
|
{
|
||||||
|
CAC_OK,
|
||||||
|
CAC_STARTUP,
|
||||||
|
CAC_SHUTDOWN,
|
||||||
|
CAC_RECOVERY,
|
||||||
|
CAC_NOTCONSISTENT,
|
||||||
|
CAC_TOOMANY,
|
||||||
|
} CAC_state;
|
||||||
|
|
||||||
|
/* Information passed from postmaster to backend process in 'startup_data' */
|
||||||
|
typedef struct BackendStartupData
|
||||||
|
{
|
||||||
|
CAC_state canAcceptConnections;
|
||||||
|
} BackendStartupData;
|
||||||
|
|
||||||
|
extern void BackendMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
|
||||||
|
|
||||||
|
#endif /* BACKEND_STARTUP_H */
|
Loading…
Reference in New Issue