From 05c3980e7f473ac2061dad9bbb7a9f0ede0279d9 Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Mon, 18 Mar 2024 11:38:10 +0200 Subject: [PATCH] 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 --- src/backend/postmaster/launch_backend.c | 1 + src/backend/postmaster/postmaster.c | 762 +---------------------- src/backend/tcop/Makefile | 1 + src/backend/tcop/backend_startup.c | 778 ++++++++++++++++++++++++ src/backend/tcop/meson.build | 1 + src/include/postmaster/postmaster.h | 4 +- src/include/tcop/backend_startup.h | 41 ++ 7 files changed, 829 insertions(+), 759 deletions(-) create mode 100644 src/backend/tcop/backend_startup.c create mode 100644 src/include/tcop/backend_startup.h diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c index d159096a58..3b5f73524c 100644 --- a/src/backend/postmaster/launch_backend.c +++ b/src/backend/postmaster/launch_backend.c @@ -58,6 +58,7 @@ #include "storage/pg_shmem.h" #include "storage/pmsignal.h" #include "storage/proc.h" +#include "tcop/backend_startup.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/datetime.h" diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 9a82c3dafa..7f3170a8f0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -95,10 +95,8 @@ #include "common/file_utils.h" #include "common/ip.h" #include "common/pg_prng.h" -#include "common/string.h" #include "lib/ilist.h" #include "libpq/libpq.h" -#include "libpq/pqformat.h" #include "libpq/pqsignal.h" #include "pg_getopt.h" #include "pgstat.h" @@ -117,13 +115,11 @@ #include "storage/ipc.h" #include "storage/pmsignal.h" #include "storage/proc.h" +#include "tcop/backend_startup.h" #include "tcop/tcopprot.h" -#include "utils/builtins.h" #include "utils/datetime.h" #include "utils/memutils.h" #include "utils/pidfile.h" -#include "utils/ps_status.h" -#include "utils/timeout.h" #include "utils/timestamp.h" #include "utils/varlena.h" @@ -382,7 +378,7 @@ static WaitEventSet *pm_wait_set; #ifdef USE_SSL /* Set when and if SSL has been initialized properly */ -static bool LoadedSSL = false; +bool LoadedSSL = false; #endif #ifdef USE_BONJOUR @@ -404,9 +400,7 @@ static void process_pm_pmsignal(void); static void process_pm_child_exit(void); static void process_pm_reload_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 StartupPacketTimeoutHandler(void); static void CleanupBackend(int pid, int exitstatus); static bool CleanupBackgroundWorker(int pid, int exitstatus); 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); 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 int ServerLoop(void); 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 CAC_state canAcceptConnections(int backend_type); 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 * start-a-new-connection packet. Perform the necessary processing. * Nothing is sent back to the client. */ -static void -processCancelRequest(Port *port, void *pkt) +void +processCancelRequest(int backendPID, int32 cancelAuthCode) { - CancelRequestPacket *canc = (CancelRequestPacket *) pkt; - int backendPID; - int32 cancelAuthCode; Backend *bp; #ifndef EXEC_BACKEND @@ -2261,9 +1842,6 @@ processCancelRequest(Port *port, void *pkt) int i; #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 * longer access the postmaster's own backend list, and must rely on the @@ -3955,12 +3533,6 @@ TerminateChildren(int signal) signal_child(SlotSyncWorkerPID, signal); } -/* Information passed from postmaster to backend process */ -typedef struct BackendStartupData -{ - CAC_state canAcceptConnections; -} BackendStartupData; - /* * BackendStartup -- start backend process * @@ -4087,302 +3659,6 @@ report_fork_failure_to_client(ClientSocket *client_sock, int errnum) } 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 * @@ -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 * @@ -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. */ diff --git a/src/backend/tcop/Makefile b/src/backend/tcop/Makefile index f662a7dd1c..9119667345 100644 --- a/src/backend/tcop/Makefile +++ b/src/backend/tcop/Makefile @@ -13,6 +13,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = \ + backend_startup.o \ cmdtag.o \ dest.o \ fastpath.o \ diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c new file mode 100644 index 0000000000..0b9f899cd8 --- /dev/null +++ b/src/backend/tcop/backend_startup.c @@ -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 + +#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); +} diff --git a/src/backend/tcop/meson.build b/src/backend/tcop/meson.build index 6104f746f9..19a97bbf55 100644 --- a/src/backend/tcop/meson.build +++ b/src/backend/tcop/meson.build @@ -1,6 +1,7 @@ # Copyright (c) 2022-2024, PostgreSQL Global Development Group backend_sources += files( + 'backend_startup.c', 'cmdtag.c', 'dest.c', 'fastpath.c', diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 333f81c2c5..8ce990e800 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -52,6 +52,8 @@ extern PGDLLIMPORT int postmaster_alive_fds[2]; extern PGDLLIMPORT const char *progname; +extern bool LoadedSSL; + extern void PostmasterMain(int argc, char *argv[]) pg_attribute_noreturn(); extern void ClosePostmasterPorts(bool am_syslogger); extern void InitProcessGlobals(void); @@ -60,7 +62,7 @@ extern int MaxLivePostmasterChildren(void); 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 extern Size ShmemBackendArraySize(void); diff --git a/src/include/tcop/backend_startup.h b/src/include/tcop/backend_startup.h new file mode 100644 index 0000000000..d29eaed1e9 --- /dev/null +++ b/src/include/tcop/backend_startup.h @@ -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 */