+ /*
+ * This is the first point where we have access to the hba record for
+ * the current connection, so perform any verifications based on the
+ * hba options field that should be done *before* the authentication
+ * here.
+ */
+ if (port->hba->clientcert)
+ {
+ /*
+ * When we parse pg_hba.conf, we have already made sure that we have
+ * been able to load a certificate store. Thus, if a certificate is
+ * present on the client, it has been verified against our root
+ * certificate store, and the connection would have been aborted
+ * already if it didn't verify ok.
+ */
+#ifdef USE_SSL
+ if (!port->peer)
+ {
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("connection requires a valid client certificate")));
+ }
+#else
+ /*
+ * hba.c makes sure hba->clientcert can't be set unless OpenSSL
+ * is present.
+ */
+ Assert(false);
+#endif
+ }
+
+ /*
+ * Now proceed to do the actual authentication check
+ */
+ switch (port->hba->auth_method)
+ {
+ case uaReject:
+
+ /*
+ * This could have come from an explicit "reject" entry in
+ * pg_hba.conf, but more likely it means there was no matching
+ * entry. Take pity on the poor user and issue a helpful error
+ * message. NOTE: this is not a security breach, because all the
+ * info reported here is known at the frontend and must be assumed
+ * known to bad guys. We're merely helping out the less clueful
+ * good guys.
+ */
+ {
+ char hostinfo[NI_MAXHOST];
+
+ pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
+ hostinfo, sizeof(hostinfo),
+ NULL, 0,
+ NI_NUMERICHOST);
+
+#ifdef USE_SSL
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
+ hostinfo, port->user_name, port->database_name,
+ port->ssl ? _("SSL on") : _("SSL off"))));
+#else
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
+ hostinfo, port->user_name, port->database_name)));
+#endif
+ break;
+ }
+
+ case uaKrb5:
+#ifdef KRB5
+ sendAuthRequest(port, AUTH_REQ_KRB5);
+ status = pg_krb5_recvauth(port);
+#else
+ Assert(false);
+#endif
+ break;
+
+ case uaGSS:
+#ifdef ENABLE_GSS
+ sendAuthRequest(port, AUTH_REQ_GSS);
+ status = pg_GSS_recvauth(port);
+#else
+ Assert(false);
+#endif
+ break;
+
+ case uaSSPI:
+#ifdef ENABLE_SSPI
+ sendAuthRequest(port, AUTH_REQ_SSPI);
+ status = pg_SSPI_recvauth(port);
+#else
+ Assert(false);
+#endif
+ break;
+
+ case uaIdent:
+
+ /*
+ * If we are doing ident on unix-domain sockets, use SCM_CREDS
+ * only if it is defined and SO_PEERCRED isn't.
+ */
+#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && \
+ (defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || \
+ (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS)))
+ if (port->raddr.addr.ss_family == AF_UNIX)
+ {
+#if defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
+
+ /*
+ * Receive credentials on next message receipt, BSD/OS,
+ * NetBSD. We need to set this before the client sends the
+ * next packet.
+ */
+ int on = 1;
+
+ if (setsockopt(port->sock, 0, LOCAL_CREDS, &on, sizeof(on)) < 0)
+ ereport(FATAL,
+ (errcode_for_socket_access(),
+ errmsg("could not enable credential reception: %m")));
+#endif
+
+ sendAuthRequest(port, AUTH_REQ_SCM_CREDS);
+ }
+#endif
+ status = authident(port);
+ break;
+
+ case uaMD5:
+ if (Db_user_namespace)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
+ sendAuthRequest(port, AUTH_REQ_MD5);
+ status = recv_and_check_password_packet(port);
+ break;
+
+ case uaPassword:
+ sendAuthRequest(port, AUTH_REQ_PASSWORD);
+ status = recv_and_check_password_packet(port);
+ break;
+
+ case uaPAM:
+#ifdef USE_PAM
+ pam_port_cludge = port;
+ status = CheckPAMAuth(port, port->user_name, "");
+#else
+ Assert(false);
+#endif /* USE_PAM */
+ break;
+
+ case uaLDAP:
+#ifdef USE_LDAP
+ status = CheckLDAPAuth(port);
+#else
+ Assert(false);
+#endif
+ break;
+
+ case uaCert:
+#ifdef USE_SSL
+ status = CheckCertAuth(port);
+#else
+ Assert(false);
+#endif
+ break;
+
+ case uaTrust:
+ status = STATUS_OK;
+ break;
+ }
+
+ if (status == STATUS_OK)
+ sendAuthRequest(port, AUTH_REQ_OK);
+ else
+ auth_failed(port, status);
+}
+
+
+/*
+ * Send an authentication request packet to the frontend.
+ */
+static void
+sendAuthRequest(Port *port, AuthRequest areq)
+{
+ StringInfoData buf;
+
+ pq_beginmessage(&buf, 'R');
+ pq_sendint(&buf, (int32) areq, sizeof(int32));
+
+ /* Add the salt for encrypted passwords. */
+ if (areq == AUTH_REQ_MD5)
+ pq_sendbytes(&buf, port->md5Salt, 4);
+
+#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+
+ /*
+ * Add the authentication data for the next step of the GSSAPI or SSPI
+ * negotiation.
+ */
+ else if (areq == AUTH_REQ_GSS_CONT)
+ {
+ if (port->gss->outbuf.length > 0)
+ {
+ elog(DEBUG4, "sending GSS token of length %u",
+ (unsigned int) port->gss->outbuf.length);
+
+ pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
+ }
+ }
+#endif
+
+ pq_endmessage(&buf);
+
+ /*
+ * Flush message so client will see it, except for AUTH_REQ_OK, which need
+ * not be sent until we are ready for queries.
+ */
+ if (areq != AUTH_REQ_OK)
+ pq_flush();
+}
+
+/*
+ * Collect password response packet from frontend.
+ *
+ * Returns NULL if couldn't get password, else palloc'd string.
+ */
+static char *
+recv_password_packet(Port *port)
+{
+ StringInfoData buf;
+
+ if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
+ {
+ /* Expect 'p' message type */
+ int mtype;
+
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /*
+ * If the client just disconnects without offering a password,
+ * don't make a log entry. This is legal per protocol spec and in
+ * fact commonly done by psql, so complaining just clutters the
+ * log.
+ */
+ if (mtype != EOF)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("expected password response, got message type %d",
+ mtype)));
+ return NULL; /* EOF or bad message type */
+ }
+ }
+ else
+ {
+ /* For pre-3.0 clients, avoid log entry if they just disconnect */
+ if (pq_peekbyte() == EOF)
+ return NULL; /* EOF */
+ }
+
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, 1000)) /* receive password */
+ {
+ /* EOF - pq_getmessage already logged a suitable message */
+ pfree(buf.data);
+ return NULL;
+ }
+
+ /*
+ * Apply sanity check: password packet length should agree with length of
+ * contained string. Note it is safe to use strlen here because
+ * StringInfo is guaranteed to have an appended '\0'.
+ */
+ if (strlen(buf.data) + 1 != buf.len)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("invalid password packet size")));
+
+ /* Do not echo password to logs, for security. */
+ ereport(DEBUG5,
+ (errmsg("received password packet")));
+
+ /*
+ * Return the received string. Note we do not attempt to do any
+ * character-set conversion on it; since we don't yet know the client's
+ * encoding, there wouldn't be much point.
+ */
+ return buf.data;
+}
+
+
+/*----------------------------------------------------------------
+ * MD5 and crypt authentication
+ *----------------------------------------------------------------
+ */
+
+/*
+ * Called when we have sent an authorization request for a password.
+ * Get the response and check it.
+ */
+static int
+recv_and_check_password_packet(Port *port)
+{
+ char *passwd;
+ int result;
+
+ passwd = recv_password_packet(port);
+
+ if (passwd == NULL)
+ return STATUS_EOF; /* client wouldn't send password */
+
+ result = md5_crypt_verify(port, port->user_name, passwd);
+
+ pfree(passwd);
+
+ return result;
+}
+
+
+/*----------------------------------------------------------------
+ * MIT Kerberos authentication system - protocol version 5
+ *----------------------------------------------------------------
+ */
+#ifdef KRB5
+
+static int
+pg_krb5_init(Port *port)
+{
+ krb5_error_code retval;
+ char *khostname;
+
+ if (pg_krb5_initialised)
+ return STATUS_OK;
+
+ retval = krb5_init_context(&pg_krb5_context);
+ if (retval)
+ {
+ ereport(LOG,
+ (errmsg("Kerberos initialization returned error %d",
+ retval)));
+ com_err("postgres", retval, "while initializing krb5");
+ return STATUS_ERROR;
+ }
+
+ retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
+ if (retval)
+ {
+ ereport(LOG,
+ (errmsg("Kerberos keytab resolving returned error %d",
+ retval)));
+ com_err("postgres", retval, "while resolving keytab file \"%s\"",
+ pg_krb_server_keyfile);
+ krb5_free_context(pg_krb5_context);
+ return STATUS_ERROR;
+ }
+
+ /*
+ * If no hostname was specified, pg_krb_server_hostname is already NULL.
+ * If it's set to blank, force it to NULL.
+ */
+ khostname = port->hba->krb_server_hostname;
+ if (khostname && khostname[0] == '\0')
+ khostname = NULL;
+
+ retval = krb5_sname_to_principal(pg_krb5_context,
+ khostname,
+ pg_krb_srvnam,
+ KRB5_NT_SRV_HST,
+ &pg_krb5_server);
+ if (retval)
+ {
+ ereport(LOG,
+ (errmsg("Kerberos sname_to_principal(\"%s\", \"%s\") returned error %d",
+ khostname ? khostname : "server hostname", pg_krb_srvnam, retval)));
+ com_err("postgres", retval,
+ "while getting server principal for server \"%s\" for service \"%s\"",
+ khostname ? khostname : "server hostname", pg_krb_srvnam);
+ krb5_kt_close(pg_krb5_context, pg_krb5_keytab);
+ krb5_free_context(pg_krb5_context);
+ return STATUS_ERROR;
+ }
+
+ pg_krb5_initialised = 1;
+ return STATUS_OK;
+}
+
+
+/*
+ * pg_krb5_recvauth -- server routine to receive authentication information
+ * from the client
+ *
+ * We still need to compare the username obtained from the client's setup
+ * packet to the authenticated name.
+ *
+ * We have our own keytab file because postgres is unlikely to run as root,
+ * and so cannot read the default keytab.
+ */
+static int
+pg_krb5_recvauth(Port *port)
+{
+ krb5_error_code retval;
+ int ret;
+ krb5_auth_context auth_context = NULL;
+ krb5_ticket *ticket;
+ char *kusername;
+ char *cp;
+
+ if (get_role_line(port->user_name) == NULL)
+ return STATUS_ERROR;
+
+ ret = pg_krb5_init(port);
+ if (ret != STATUS_OK)
+ return ret;
+
+ retval = krb5_recvauth(pg_krb5_context, &auth_context,
+ (krb5_pointer) & port->sock, pg_krb_srvnam,
+ pg_krb5_server, 0, pg_krb5_keytab, &ticket);
+ if (retval)
+ {
+ ereport(LOG,
+ (errmsg("Kerberos recvauth returned error %d",
+ retval)));
+ com_err("postgres", retval, "from krb5_recvauth");
+ return STATUS_ERROR;
+ }
+
+ /*
+ * The "client" structure comes out of the ticket and is therefore
+ * authenticated. Use it to check the username obtained from the
+ * postmaster startup packet.
+ */
+#if defined(HAVE_KRB5_TICKET_ENC_PART2)
+ retval = krb5_unparse_name(pg_krb5_context,
+ ticket->enc_part2->client, &kusername);
+#elif defined(HAVE_KRB5_TICKET_CLIENT)
+ retval = krb5_unparse_name(pg_krb5_context,
+ ticket->client, &kusername);
+#else
+#error "bogus configuration"
+#endif
+ if (retval)
+ {
+ ereport(LOG,
+ (errmsg("Kerberos unparse_name returned error %d",
+ retval)));
+ com_err("postgres", retval, "while unparsing client name");
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
+ return STATUS_ERROR;
+ }
+
+ cp = strchr(kusername, '@');
+ if (cp)
+ {
+ /*
+ * If we are not going to include the realm in the username that is passed
+ * to the ident map, destructively modify it here to remove the realm. Then
+ * advance past the separator to check the realm.
+ */
+ if (!port->hba->include_realm)
+ *cp = '\0';
+ cp++;
+
+ if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
+ {
+ /* Match realm against configured */
+ if (pg_krb_caseins_users)
+ ret = pg_strcasecmp(port->hba->krb_realm, cp);
+ else
+ ret = strcmp(port->hba->krb_realm, cp);
+
+ if (ret)
+ {
+ elog(DEBUG2,
+ "krb5 realm (%s) and configured realm (%s) don't match",
+ cp, port->hba->krb_realm);
+
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
+ return STATUS_ERROR;
+ }
+ }
+ }
+ else if (port->hba->krb_realm&& strlen(port->hba->krb_realm))
+ {
+ elog(DEBUG2,
+ "krb5 did not return realm but realm matching was requested");
+
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
+ return STATUS_ERROR;
+ }
+
+ ret = check_usermap(port->hba->usermap, port->user_name, kusername,
+ pg_krb_caseins_users);
+
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
+ free(kusername);
+
+ return ret;
+}
+#endif /* KRB5 */
+
+
+/*----------------------------------------------------------------
+ * GSSAPI authentication system
+ *----------------------------------------------------------------
+ */
+#ifdef ENABLE_GSS
+
+#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
+/*
+ * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
+ * that contain the OIDs required. Redefine here, values copied
+ * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
+ */
+static const gss_OID_desc GSS_C_NT_USER_NAME_desc =
+{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"};
+static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
+#endif
+
+
+static void
+pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ gss_buffer_desc gmsg;
+ OM_uint32 lmaj_s,
+ lmin_s,
+ msg_ctx;
+ char msg_major[128],
+ msg_minor[128];
+
+ /* Fetch major status message */
+ msg_ctx = 0;
+ lmaj_s = gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &msg_ctx, &gmsg);
+ strlcpy(msg_major, gmsg.value, sizeof(msg_major));
+ gss_release_buffer(&lmin_s, &gmsg);
+
+ if (msg_ctx)
+
+ /*
+ * More than one message available. XXX: Should we loop and read all
+ * messages? (same below)
+ */
+ ereport(WARNING,
+ (errmsg_internal("incomplete GSS error report")));
+
+ /* Fetch mechanism minor status message */
+ msg_ctx = 0;
+ lmaj_s = gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE,
+ GSS_C_NO_OID, &msg_ctx, &gmsg);
+ strlcpy(msg_minor, gmsg.value, sizeof(msg_minor));
+ gss_release_buffer(&lmin_s, &gmsg);
+
+ if (msg_ctx)
+ ereport(WARNING,
+ (errmsg_internal("incomplete GSS minor error report")));
+
+ /*
+ * errmsg_internal, since translation of the first part must be done
+ * before calling this function anyway.
+ */
+ ereport(severity,
+ (errmsg_internal("%s", errmsg),
+ errdetail("%s: %s", msg_major, msg_minor)));
+}
+
+static int
+pg_GSS_recvauth(Port *port)
+{
+ OM_uint32 maj_stat,
+ min_stat,
+ lmin_s,
+ gflags;
+ int mtype;
+ int ret;
+ StringInfoData buf;
+ gss_buffer_desc gbuf;
+
+ /*
+ * GSS auth is not supported for protocol versions before 3, because it
+ * relies on the overall message length word to determine the GSS payload
+ * size in AuthenticationGSSContinue and PasswordMessage messages.
+ * (This is, in fact, a design error in our GSS support, because protocol
+ * messages are supposed to be parsable without relying on the length
+ * word; but it's not worth changing it now.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("GSSAPI is not supported in protocol version 2")));
+
+ if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0)
+ {
+ /*
+ * Set default Kerberos keytab file for the Krb5 mechanism.
+ *
+ * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv()
+ * not always available.
+ */
+ if (getenv("KRB5_KTNAME") == NULL)
+ {
+ size_t kt_len = strlen(pg_krb_server_keyfile) + 14;
+ char *kt_path = malloc(kt_len);
+
+ if (!kt_path)
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ return STATUS_ERROR;
+ }
+ snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", pg_krb_server_keyfile);
+ putenv(kt_path);
+ }
+ }
+
+ /*
+ * We accept any service principal that's present in our keytab. This
+ * increases interoperability between kerberos implementations that see
+ * for example case sensitivity differently, while not really opening up
+ * any vector of attack.
+ */
+ port->gss->cred = GSS_C_NO_CREDENTIAL;
+
+ /*
+ * Initialize sequence with an empty context
+ */
+ port->gss->ctx = GSS_C_NO_CONTEXT;
+
+ /*
+ * Loop through GSSAPI message exchange. This exchange can consist of
+ * multiple messags sent in both directions. First message is always from
+ * the client. All messages from client to server are password packets
+ * (type 'p').
+ */
+ do
+ {
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /* Only log error if client didn't disconnect. */
+ if (mtype != EOF)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("expected GSS response, got message type %d",
+ mtype)));
+ return STATUS_ERROR;
+ }
+
+ /* Get the actual GSS token */
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, 2000))
+ {
+ /* EOF - pq_getmessage already logged error */
+ pfree(buf.data);
+ return STATUS_ERROR;
+ }
+
+ /* Map to GSSAPI style buffer */
+ gbuf.length = buf.len;
+ gbuf.value = buf.data;
+
+ elog(DEBUG4, "Processing received GSS token of length %u",
+ (unsigned int) gbuf.length);
+
+ maj_stat = gss_accept_sec_context(
+ &min_stat,
+ &port->gss->ctx,
+ port->gss->cred,
+ &gbuf,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &port->gss->name,
+ NULL,
+ &port->gss->outbuf,
+ &gflags,
+ NULL,
+ NULL);
+
+ /* gbuf no longer used */
+ pfree(buf.data);
+
+ elog(DEBUG5, "gss_accept_sec_context major: %d, "
+ "minor: %d, outlen: %u, outflags: %x",
+ maj_stat, min_stat,
+ (unsigned int) port->gss->outbuf.length, gflags);
+
+ if (port->gss->outbuf.length != 0)
+ {
+ /*
+ * Negotiation generated data to be sent to the client.
+ */
+ OM_uint32 lmin_s;
+
+ elog(DEBUG4, "sending GSS response token of length %u",
+ (unsigned int) port->gss->outbuf.length);
+
+ sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+
+ gss_release_buffer(&lmin_s, &port->gss->outbuf);
+ }
+
+ if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
+ {
+ OM_uint32 lmin_s;
+
+ gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
+ pg_GSS_error(ERROR,
+ gettext_noop("accepting GSS security context failed"),
+ maj_stat, min_stat);
+ }
+
+ if (maj_stat == GSS_S_CONTINUE_NEEDED)
+ elog(DEBUG4, "GSS continue needed");
+
+ } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+ if (port->gss->cred != GSS_C_NO_CREDENTIAL)
+ {
+ /*
+ * Release service principal credentials
+ */
+ gss_release_cred(&min_stat, &port->gss->cred);
+ }
+
+ /*
+ * GSS_S_COMPLETE indicates that authentication is now complete.
+ *
+ * Get the name of the user that authenticated, and compare it to the pg
+ * username that was specified for the connection.
+ */
+ maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
+ if (maj_stat != GSS_S_COMPLETE)
+ pg_GSS_error(ERROR,
+ gettext_noop("retrieving GSS user name failed"),
+ maj_stat, min_stat);
+
+ /*
+ * Split the username at the realm separator
+ */
+ if (strchr(gbuf.value, '@'))
+ {
+ char *cp = strchr(gbuf.value, '@');
+
+ /*
+ * If we are not going to include the realm in the username that is passed
+ * to the ident map, destructively modify it here to remove the realm. Then
+ * advance past the separator to check the realm.
+ */
+ if (!port->hba->include_realm)
+ *cp = '\0';
+ cp++;
+
+ if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
+ {
+ /*
+ * Match the realm part of the name first
+ */
+ if (pg_krb_caseins_users)
+ ret = pg_strcasecmp(port->hba->krb_realm, cp);
+ else
+ ret = strcmp(port->hba->krb_realm, cp);
+
+ if (ret)
+ {
+ /* GSS realm does not match */
+ elog(DEBUG2,
+ "GSSAPI realm (%s) and configured realm (%s) don't match",
+ cp, port->hba->krb_realm);
+ gss_release_buffer(&lmin_s, &gbuf);
+ return STATUS_ERROR;
+ }
+ }
+ }
+ else if (port->hba->krb_realm && strlen(port->hba->krb_realm))
+ {
+ elog(DEBUG2,
+ "GSSAPI did not return realm but realm matching was requested");
+
+ gss_release_buffer(&lmin_s, &gbuf);
+ return STATUS_ERROR;
+ }
+
+ ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
+ pg_krb_caseins_users);
+
+ gss_release_buffer(&lmin_s, &gbuf);
+
+ return STATUS_OK;
+}
+#endif /* ENABLE_GSS */
+
+
+/*----------------------------------------------------------------
+ * SSPI authentication system
+ *----------------------------------------------------------------
+ */
+#ifdef ENABLE_SSPI
+static void
+pg_SSPI_error(int severity, char *errmsg, SECURITY_STATUS r)
+{
+ char sysmsg[256];
+
+ if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0, sysmsg, sizeof(sysmsg), NULL) == 0)
+ ereport(severity,
+ (errmsg_internal("%s", errmsg),
+ errdetail("SSPI error %x", (unsigned int) r)));
+ else
+ ereport(severity,
+ (errmsg_internal("%s", errmsg),
+ errdetail("%s (%x)", sysmsg, (unsigned int) r)));
+}
+
+static int
+pg_SSPI_recvauth(Port *port)
+{
+ int mtype;
+ StringInfoData buf;
+ SECURITY_STATUS r;
+ CredHandle sspicred;
+ CtxtHandle *sspictx = NULL,
+ newctx;
+ TimeStamp expiry;
+ ULONG contextattr;
+ SecBufferDesc inbuf;
+ SecBufferDesc outbuf;
+ SecBuffer OutBuffers[1];
+ SecBuffer InBuffers[1];
+ HANDLE token;
+ TOKEN_USER *tokenuser;
+ DWORD retlen;
+ char accountname[MAXPGPATH];
+ char domainname[MAXPGPATH];
+ DWORD accountnamesize = sizeof(accountname);
+ DWORD domainnamesize = sizeof(domainname);
+ SID_NAME_USE accountnameuse;
+ HMODULE secur32;
+ QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
+
+ /*
+ * SSPI auth is not supported for protocol versions before 3, because it
+ * relies on the overall message length word to determine the SSPI payload
+ * size in AuthenticationGSSContinue and PasswordMessage messages.
+ * (This is, in fact, a design error in our SSPI support, because protocol
+ * messages are supposed to be parsable without relying on the length
+ * word; but it's not worth changing it now.)
+ */
+ if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SSPI is not supported in protocol version 2")));
+
+ /*
+ * Acquire a handle to the server credentials.
+ */
+ r = AcquireCredentialsHandle(NULL,
+ "negotiate",
+ SECPKG_CRED_INBOUND,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &sspicred,
+ &expiry);
+ if (r != SEC_E_OK)
+ pg_SSPI_error(ERROR,
+ gettext_noop("could not acquire SSPI credentials handle"), r);
+
+ /*
+ * Loop through SSPI message exchange. This exchange can consist of
+ * multiple messags sent in both directions. First message is always from
+ * the client. All messages from client to server are password packets
+ * (type 'p').
+ */
+ do
+ {
+ mtype = pq_getbyte();
+ if (mtype != 'p')
+ {
+ /* Only log error if client didn't disconnect. */
+ if (mtype != EOF)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("expected SSPI response, got message type %d",
+ mtype)));
+ return STATUS_ERROR;
+ }
+
+ /* Get the actual SSPI token */
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, 2000))
+ {
+ /* EOF - pq_getmessage already logged error */
+ pfree(buf.data);
+ return STATUS_ERROR;
+ }
+
+ /* Map to SSPI style buffer */
+ inbuf.ulVersion = SECBUFFER_VERSION;
+ inbuf.cBuffers = 1;
+ inbuf.pBuffers = InBuffers;
+ InBuffers[0].pvBuffer = buf.data;
+ InBuffers[0].cbBuffer = buf.len;
+ InBuffers[0].BufferType = SECBUFFER_TOKEN;
+
+ /* Prepare output buffer */
+ OutBuffers[0].pvBuffer = NULL;
+ OutBuffers[0].BufferType = SECBUFFER_TOKEN;
+ OutBuffers[0].cbBuffer = 0;
+ outbuf.cBuffers = 1;
+ outbuf.pBuffers = OutBuffers;
+ outbuf.ulVersion = SECBUFFER_VERSION;
+
+
+ elog(DEBUG4, "Processing received SSPI token of length %u",
+ (unsigned int) buf.len);
+
+ r = AcceptSecurityContext(&sspicred,
+ sspictx,
+ &inbuf,
+ ASC_REQ_ALLOCATE_MEMORY,
+ SECURITY_NETWORK_DREP,
+ &newctx,
+ &outbuf,
+ &contextattr,
+ NULL);
+
+ /* input buffer no longer used */
+ pfree(buf.data);
+
+ if (outbuf.cBuffers > 0 && outbuf.pBuffers[0].cbBuffer > 0)
+ {
+ /*
+ * Negotiation generated data to be sent to the client.
+ */
+ elog(DEBUG4, "sending SSPI response token of length %u",
+ (unsigned int) outbuf.pBuffers[0].cbBuffer);
+
+ port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
+ port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
+
+ sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+
+ FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
+ }
+
+ if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
+ {
+ if (sspictx != NULL)
+ {
+ DeleteSecurityContext(sspictx);
+ free(sspictx);
+ }
+ FreeCredentialsHandle(&sspicred);
+ pg_SSPI_error(ERROR,
+ gettext_noop("could not accept SSPI security context"), r);
+ }
+
+ if (sspictx == NULL)
+ {
+ sspictx = malloc(sizeof(CtxtHandle));
+ if (sspictx == NULL)
+ ereport(ERROR,
+ (errmsg("out of memory")));
+
+ memcpy(sspictx, &newctx, sizeof(CtxtHandle));
+ }
+
+ if (r == SEC_I_CONTINUE_NEEDED)
+ elog(DEBUG4, "SSPI continue needed");
+
+ } while (r == SEC_I_CONTINUE_NEEDED);
+
+
+ /*
+ * Release service principal credentials
+ */
+ FreeCredentialsHandle(&sspicred);
+
+
+ /*
+ * SEC_E_OK indicates that authentication is now complete.
+ *
+ * Get the name of the user that authenticated, and compare it to the pg
+ * username that was specified for the connection.
+ *
+ * MingW is missing the export for QuerySecurityContextToken in the
+ * secur32 library, so we have to load it dynamically.
+ */
+
+ secur32 = LoadLibrary("SECUR32.DLL");
+ if (secur32 == NULL)
+ ereport(ERROR,
+ (errmsg_internal("could not load secur32.dll: %d",
+ (int) GetLastError())));
+
+ _QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN)
+ GetProcAddress(secur32, "QuerySecurityContextToken");
+ if (_QuerySecurityContextToken == NULL)
+ {
+ FreeLibrary(secur32);
+ ereport(ERROR,
+ (errmsg_internal("could not locate QuerySecurityContextToken in secur32.dll: %d",
+ (int) GetLastError())));
+ }
+
+ r = (_QuerySecurityContextToken) (sspictx, &token);
+ if (r != SEC_E_OK)
+ {
+ FreeLibrary(secur32);
+ pg_SSPI_error(ERROR,
+ gettext_noop("could not get security token from context"), r);
+ }
+
+ FreeLibrary(secur32);
+
+ /*
+ * No longer need the security context, everything from here on uses the
+ * token instead.
+ */
+ DeleteSecurityContext(sspictx);
+ free(sspictx);
+
+ if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+ ereport(ERROR,
+ (errmsg_internal("could not get token user size: error code %d",
+ (int) GetLastError())));
+
+ tokenuser = malloc(retlen);
+ if (tokenuser == NULL)
+ ereport(ERROR,
+ (errmsg("out of memory")));
+
+ if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+ ereport(ERROR,
+ (errmsg_internal("could not get user token: error code %d",
+ (int) GetLastError())));
+
+ if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
+ domainname, &domainnamesize, &accountnameuse))
+ ereport(ERROR,
+ (errmsg_internal("could not lookup acconut sid: error code %d",
+ (int) GetLastError())));
+
+ free(tokenuser);
+
+ /*
+ * Compare realm/domain if requested. In SSPI, always compare case
+ * insensitive.
+ */
+ if (port->hba->krb_realm && strlen(port->hba->krb_realm))
+ {
+ if (pg_strcasecmp(port->hba->krb_realm, domainname))
+ {
+ elog(DEBUG2,
+ "SSPI domain (%s) and configured domain (%s) don't match",
+ domainname, port->hba->krb_realm);
+
+ return STATUS_ERROR;
+ }
+ }
+
+ /*
+ * We have the username (without domain/realm) in accountname, compare to
+ * the supplied value. In SSPI, always compare case insensitive.
+ *
+ * If set to include realm, append it in <username>@<realm> format.
+ */
+ if (port->hba->include_realm)
+ {
+ char *namebuf;
+ int retval;
+
+ namebuf = palloc(strlen(accountname) + strlen(domainname) + 2);
+ sprintf(namebuf, "%s@%s", accountname, domainname);
+ retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true);
+ pfree(namebuf);
+ return retval;
+ }
+ else
+ return check_usermap(port->hba->usermap, port->user_name, accountname, true);
+}
+#endif /* ENABLE_SSPI */
+
+
+
+/*----------------------------------------------------------------
+ * Ident authentication system
+ *----------------------------------------------------------------
+ */
+
+/*
+ * Parse the string "*ident_response" as a response from a query to an Ident
+ * server. If it's a normal response indicating a user name, return true
+ * and store the user name at *ident_user. If it's anything else,
+ * return false.
+ */
+static bool
+interpret_ident_response(const char *ident_response,
+ char *ident_user)
+{
+ const char *cursor = ident_response; /* Cursor into *ident_response */
+
+ /*
+ * Ident's response, in the telnet tradition, should end in crlf (\r\n).
+ */
+ if (strlen(ident_response) < 2)
+ return false;
+ else if (ident_response[strlen(ident_response) - 2] != '\r')
+ return false;
+ else
+ {
+ while (*cursor != ':' && *cursor != '\r')
+ cursor++; /* skip port field */
+
+ if (*cursor != ':')
+ return false;
+ else
+ {
+ /* We're positioned to colon before response type field */
+ char response_type[80];
+ int i; /* Index into *response_type */
+
+ cursor++; /* Go over colon */
+ while (pg_isblank(*cursor))
+ cursor++; /* skip blanks */
+ i = 0;
+ while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) &&
+ i < (int) (sizeof(response_type) - 1))
+ response_type[i++] = *cursor++;
+ response_type[i] = '\0';
+ while (pg_isblank(*cursor))
+ cursor++; /* skip blanks */
+ if (strcmp(response_type, "USERID") != 0)
+ return false;
+ else
+ {
+ /*
+ * It's a USERID response. Good. "cursor" should be pointing
+ * to the colon that precedes the operating system type.
+ */
+ if (*cursor != ':')
+ return false;
+ else
+ {
+ cursor++; /* Go over colon */
+ /* Skip over operating system field. */
+ while (*cursor != ':' && *cursor != '\r')
+ cursor++;
+ if (*cursor != ':')
+ return false;
+ else
+ {
+ int i; /* Index into *ident_user */
+
+ cursor++; /* Go over colon */
+ while (pg_isblank(*cursor))
+ cursor++; /* skip blanks */
+ /* Rest of line is user name. Copy it over. */
+ i = 0;
+ while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
+ ident_user[i++] = *cursor++;
+ ident_user[i] = '\0';
+ return true;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/*
+ * Talk to the ident server on host "remote_ip_addr" and find out who
+ * owns the tcp connection from his port "remote_port" to port
+ * "local_port_addr" on host "local_ip_addr". Return the user name the
+ * ident server gives as "*ident_user".
+ *
+ * IP addresses and port numbers are in network byte order.
+ *
+ * But iff we're unable to get the information from ident, return false.
+ */
+static bool
+ident_inet(const SockAddr remote_addr,
+ const SockAddr local_addr,
+ char *ident_user)
+{
+ int sock_fd, /* File descriptor for socket on which we talk
+ * to Ident */
+ rc; /* Return code from a locally called function */
+ bool ident_return;
+ char remote_addr_s[NI_MAXHOST];
+ char remote_port[NI_MAXSERV];
+ char local_addr_s[NI_MAXHOST];
+ char local_port[NI_MAXSERV];
+ char ident_port[NI_MAXSERV];
+ char ident_query[80];
+ char ident_response[80 + IDENT_USERNAME_MAX];
+ struct addrinfo *ident_serv = NULL,
+ *la = NULL,
+ hints;
+
+ /*
+ * Might look a little weird to first convert it to text and then back to
+ * sockaddr, but it's protocol independent.
+ */
+ pg_getnameinfo_all(&remote_addr.addr, remote_addr.salen,
+ remote_addr_s, sizeof(remote_addr_s),
+ remote_port, sizeof(remote_port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ pg_getnameinfo_all(&local_addr.addr, local_addr.salen,
+ local_addr_s, sizeof(local_addr_s),
+ local_port, sizeof(local_port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT);
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = remote_addr.addr.ss_family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ rc = pg_getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv);
+ if (rc || !ident_serv)
+ {
+ if (ident_serv)
+ pg_freeaddrinfo_all(hints.ai_family, ident_serv);
+ return false; /* we don't expect this to happen */
+ }
+
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = local_addr.addr.ss_family;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+ rc = pg_getaddrinfo_all(local_addr_s, NULL, &hints, &la);
+ if (rc || !la)
+ {
+ if (la)
+ pg_freeaddrinfo_all(hints.ai_family, la);
+ return false; /* we don't expect this to happen */
+ }
+
+ sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
+ ident_serv->ai_protocol);
+ if (sock_fd < 0)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not create socket for Ident connection: %m")));
+ ident_return = false;
+ goto ident_inet_done;
+ }
+
+ /*
+ * Bind to the address which the client originally contacted, otherwise
+ * the ident server won't be able to match up the right connection. This
+ * is necessary if the PostgreSQL server is running on an IP alias.
+ */
+ rc = bind(sock_fd, la->ai_addr, la->ai_addrlen);
+ if (rc != 0)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not bind to local address \"%s\": %m",
+ local_addr_s)));
+ ident_return = false;
+ goto ident_inet_done;
+ }
+
+ rc = connect(sock_fd, ident_serv->ai_addr,
+ ident_serv->ai_addrlen);
+ if (rc != 0)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
+ remote_addr_s, ident_port)));
+ ident_return = false;
+ goto ident_inet_done;
+ }
+
+ /* The query we send to the Ident server */
+ snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n",
+ remote_port, local_port);
+
+ /* loop in case send is interrupted */
+ do
+ {
+ rc = send(sock_fd, ident_query, strlen(ident_query), 0);
+ } while (rc < 0 && errno == EINTR);
+
+ if (rc < 0)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
+ remote_addr_s, ident_port)));
+ ident_return = false;
+ goto ident_inet_done;
+ }
+
+ do
+ {
+ rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
+ } while (rc < 0 && errno == EINTR);
+
+ if (rc < 0)
+ {
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
+ remote_addr_s, ident_port)));
+ ident_return = false;
+ goto ident_inet_done;
+ }
+
+ ident_response[rc] = '\0';
+ ident_return = interpret_ident_response(ident_response, ident_user);
+ if (!ident_return)
+ ereport(LOG,
+ (errmsg("invalidly formatted response from Ident server: \"%s\"",
+ ident_response)));
+
+ident_inet_done:
+ if (sock_fd >= 0)
+ closesocket(sock_fd);
+ pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
+ pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
+ return ident_return;
+}
+
+/*
+ * Ask kernel about the credentials of the connecting process and
+ * determine the symbolic name of the corresponding user.
+ *
+ * Returns either true and the username put into "ident_user",
+ * or false if we were unable to determine the username.
+ */
+#ifdef HAVE_UNIX_SOCKETS
+
+static bool
+ident_unix(int sock, char *ident_user)
+{
+#if defined(HAVE_GETPEEREID)
+ /* OpenBSD style: */
+ uid_t uid;
+ gid_t gid;
+ struct passwd *pass;
+
+ errno = 0;
+ if (getpeereid(sock, &uid, &gid) != 0)
+ {
+ /* We didn't get a valid credentials struct. */
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not get peer credentials: %m")));
+ return false;
+ }
+
+ pass = getpwuid(uid);
+
+ if (pass == NULL)
+ {
+ ereport(LOG,
+ (errmsg("local user with ID %d does not exist",
+ (int) uid)));
+ return false;
+ }
+
+ strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
+
+ return true;
+#elif defined(SO_PEERCRED)
+ /* Linux style: use getsockopt(SO_PEERCRED) */
+ struct ucred peercred;
+ ACCEPT_TYPE_ARG3 so_len = sizeof(peercred);
+ struct passwd *pass;
+
+ errno = 0;
+ if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 ||
+ so_len != sizeof(peercred))
+ {
+ /* We didn't get a valid credentials struct. */
+ ereport(LOG,
+ (errcode_for_socket_access(),
+ errmsg("could not get peer credentials: %m")));
+ return false;
+ }
+
+ pass = getpwuid(peercred.uid);
+
+ if (pass == NULL)
+ {
+ ereport(LOG,
+ (errmsg("local user with ID %d does not exist",
+ (int) peercred.uid)));
+ return false;
+ }
+
+ strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
+
+ return true;
+#elif defined(HAVE_GETPEERUCRED)
+ /* Solaris > 10 */
+ uid_t uid;
+ struct passwd *pass;
+ ucred_t *ucred;
+
+ ucred = NULL; /* must be initialized to NULL */
+ if (getpeerucred(sock, &ucred) == -1)