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 */