]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Move code for backend startup to separate file
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 18 Mar 2024 09:38:10 +0000 (11:38 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 18 Mar 2024 09:38:10 +0000 (11:38 +0200)
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
src/backend/postmaster/postmaster.c
src/backend/tcop/Makefile
src/backend/tcop/backend_startup.c [new file with mode: 0644]
src/backend/tcop/meson.build
src/include/postmaster/postmaster.h
src/include/tcop/backend_startup.h [new file with mode: 0644]

index d159096a584a7b2f16d43c7971061822d9ab7b28..3b5f73524c0957f615030716c6c06f5870b0621e 100644 (file)
@@ -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"
index 9a82c3dafaf684c3572e9c85ae5962496f11d28b..7f3170a8f06fc2eb22c6e1326f78a4351ac4b4b0 100644 (file)
 #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"
 #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.
  */
index f662a7dd1cfd1022a1fcf039f762026405349468..9119667345aee2fcecb8529b09b47c8bbc401f50 100644 (file)
@@ -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 (file)
index 0000000..0b9f899
--- /dev/null
@@ -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);
+}
index 6104f746f9ef8313ed011bc2af4f8344e0477918..19a97bbf55ea7560ef64ab71d35d4c5356ee4db9 100644 (file)
@@ -1,6 +1,7 @@
 # Copyright (c) 2022-2024, PostgreSQL Global Development Group
 
 backend_sources += files(
+  'backend_startup.c',
   'cmdtag.c',
   'dest.c',
   'fastpath.c',
index 333f81c2c529e1d76b6c778fa963a956de4845d5..8ce990e8009e090233bc2e652666f6bc6a9631c9 100644 (file)
@@ -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 (file)
index 0000000..d29eaed
--- /dev/null
@@ -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 */