*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.166 2008/08/01 09:09:49 mha Exp $
+ * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.167 2008/08/01 11:41:12 mha Exp $
*
*-------------------------------------------------------------------------
*/
#include "libpq/pqformat.h"
#include "storage/ipc.h"
-
+/*----------------------------------------------------------------
+ * Global authentication functions
+ *----------------------------------------------------------------
+ */
static void sendAuthRequest(Port *port, AuthRequest areq);
static void auth_failed(Port *port, int status);
static char *recv_password_packet(Port *port);
static int recv_and_check_password_packet(Port *port);
-static int authident(hbaPort *port);
-char *pg_krb_server_keyfile;
-char *pg_krb_srvnam;
-bool pg_krb_caseins_users;
-char *pg_krb_server_hostname = NULL;
-char *pg_krb_realm = NULL;
+/*----------------------------------------------------------------
+ * Ident authentication
+ *----------------------------------------------------------------
+ */
/* Max size of username ident server can return */
#define IDENT_USERNAME_MAX 512
/* Standard TCP port number for Ident service. Assigned by IANA */
#define IDENT_PORT 113
+static int authident(hbaPort *port);
+
+
+/*----------------------------------------------------------------
+ * PAM authentication
+ *----------------------------------------------------------------
+ */
#ifdef USE_PAM
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
* pam_passwd_conv_proc */
#endif /* USE_PAM */
+
+/*----------------------------------------------------------------
+ * LDAP authentication
+ *----------------------------------------------------------------
+ */
#ifdef USE_LDAP
#ifndef WIN32
/* We use a deprecated function to keep the codepath the same as win32. */
#endif
static int CheckLDAPAuth(Port *port);
-#endif
+#endif /* USE_LDAP */
+
+
+/*----------------------------------------------------------------
+ * Kerberos and GSSAPI GUCs
+ *----------------------------------------------------------------
+ */
+char *pg_krb_server_keyfile;
+char *pg_krb_srvnam;
+bool pg_krb_caseins_users;
+char *pg_krb_server_hostname = NULL;
+char *pg_krb_realm = NULL;
-#ifdef KRB5
/*----------------------------------------------------------------
* MIT Kerberos authentication system - protocol version 5
*----------------------------------------------------------------
*/
+static int pg_krb5_recvauth(Port *port);
+
+#ifdef KRB5
#include <krb5.h>
/* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */
#if !defined(__COM_ERR_H) && !defined(__COM_ERR_H__)
#include <com_err.h>
#endif
-
/*
* Various krb5 state which is not connection specfic, and a flag to
* indicate whether we have initialised it yet.
static krb5_context pg_krb5_context;
static krb5_keytab pg_krb5_keytab;
static krb5_principal pg_krb5_server;
+#endif /* KRB5 */
-static int
-pg_krb5_init(void)
-{
- krb5_error_code retval;
- char *khostname;
+/*----------------------------------------------------------------
+ * GSSAPI Authentication
+ *----------------------------------------------------------------
+ */
+static int pg_GSS_recvauth(Port *port);
- if (pg_krb5_initialised)
- return STATUS_OK;
+#ifdef ENABLE_GSS
+#if defined(HAVE_GSSAPI_H)
+#include <gssapi.h>
+#else
+#include <gssapi/gssapi.h>
+#endif
+#endif /* ENABLE_GSS */
- 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;
- }
+/*----------------------------------------------------------------
+ * SSPI Authentication
+ *----------------------------------------------------------------
+ */
+static int pg_SSPI_recvauth(Port *port);
- /*
- * If no hostname was specified, pg_krb_server_hostname is already NULL.
- * If it's set to blank, force it to NULL.
- */
- khostname = pg_krb_server_hostname;
- if (khostname && khostname[0] == '\0')
- khostname = NULL;
+#ifdef ENABLE_SSPI
+typedef SECURITY_STATUS
+ (WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
+ PCtxtHandle, void **);
+#endif
- 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;
-}
+
+/*----------------------------------------------------------------
+ * Global authentication functions
+ *----------------------------------------------------------------
+ */
/*
- * 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.
+ * Tell the user the authentication failed, but not (much about) why.
*
- * We have our own keytab file because postgres is unlikely to run as root,
- * and so cannot read the default keytab.
+ * There is a tradeoff here between security concerns and making life
+ * unnecessarily difficult for legitimate users. We would not, for example,
+ * want to report the password we were expecting to receive...
+ * But it seems useful to report the username and authorization method
+ * in use, and these are items that must be presumed known to an attacker
+ * anyway.
+ * Note that many sorts of failure report additional information in the
+ * postmaster log, which we hope is only readable by good guys.
*/
-static int
-pg_krb5_recvauth(Port *port)
+static void
+auth_failed(Port *port, int status)
{
- 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;
+ const char *errstr;
- ret = pg_krb5_init();
- if (ret != STATUS_OK)
- return ret;
+ /*
+ * If we failed due to EOF from client, just quit; there's no point in
+ * trying to send a message to the client, and not much point in logging
+ * the failure in the postmaster log. (Logging the failure might be
+ * desirable, were it not for the fact that libpq closes the connection
+ * unceremoniously if challenged for a password when it hasn't got one to
+ * send. We'll get a useless log entry for every psql connection under
+ * password auth, even if it's perfectly successful, if we log STATUS_EOF
+ * events.)
+ */
+ if (status == STATUS_EOF)
+ proc_exit(0);
- 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)
+ switch (port->auth_method)
{
- ereport(LOG,
- (errmsg("Kerberos recvauth returned error %d",
- retval)));
- com_err("postgres", retval, "from krb5_recvauth");
- return STATUS_ERROR;
+ case uaReject:
+ errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
+ break;
+ case uaKrb5:
+ errstr = gettext_noop("Kerberos 5 authentication failed for user \"%s\"");
+ break;
+ case uaGSS:
+ errstr = gettext_noop("GSSAPI authentication failed for user \"%s\"");
+ break;
+ case uaSSPI:
+ errstr = gettext_noop("SSPI authentication failed for user \"%s\"");
+ break;
+ case uaTrust:
+ errstr = gettext_noop("\"trust\" authentication failed for user \"%s\"");
+ break;
+ case uaIdent:
+ errstr = gettext_noop("Ident authentication failed for user \"%s\"");
+ break;
+ case uaMD5:
+ case uaCrypt:
+ case uaPassword:
+ errstr = gettext_noop("password authentication failed for user \"%s\"");
+ break;
+#ifdef USE_PAM
+ case uaPAM:
+ errstr = gettext_noop("PAM authentication failed for user \"%s\"");
+ break;
+#endif /* USE_PAM */
+#ifdef USE_LDAP
+ case uaLDAP:
+ errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
+ break;
+#endif /* USE_LDAP */
+ default:
+ errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
+ break;
}
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg(errstr, port->user_name)));
+ /* doesn't return */
+}
+
+
+/*
+ * Client authentication starts here. If there is an error, this
+ * function does not return and the backend process is terminated.
+ */
+void
+ClientAuthentication(Port *port)
+{
+ int status = 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.
+ * Get the authentication method to use for this frontend/database
+ * combination. Note: a failure return indicates a problem with the hba
+ * config file, not with the request. hba.c should have dropped an error
+ * message into the postmaster logfile if it failed.
*/
-#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;
- }
+ if (hba_getauthmethod(port) != STATUS_OK)
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("missing or erroneous pg_hba.conf file"),
+ errhint("See server log for details.")));
- cp = strchr(kusername, '@');
- if (cp)
+ switch (port->auth_method)
{
- *cp = '\0';
- cp++;
-
- if (pg_krb_realm != NULL && strlen(pg_krb_realm))
- {
- /* Match realm against configured */
- if (pg_krb_caseins_users)
- ret = pg_strcasecmp(pg_krb_realm, cp);
- else
- ret = strcmp(pg_krb_realm, cp);
+ case uaReject:
- if (ret)
+ /*
+ * 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.
+ */
{
- elog(DEBUG2,
- "krb5 realm (%s) and configured realm (%s) don't match",
- cp, pg_krb_realm);
+ char hostinfo[NI_MAXHOST];
- krb5_free_ticket(pg_krb5_context, ticket);
- krb5_auth_con_free(pg_krb5_context, auth_context);
- return STATUS_ERROR;
+ 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;
}
- }
- }
- else if (pg_krb_realm && strlen(pg_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;
- }
+ case uaKrb5:
+ sendAuthRequest(port, AUTH_REQ_KRB5);
+ status = pg_krb5_recvauth(port);
+ break;
- if (pg_krb_caseins_users)
- ret = pg_strncasecmp(port->user_name, kusername, SM_DATABASE_USER);
- else
- ret = strncmp(port->user_name, kusername, SM_DATABASE_USER);
- if (ret)
- {
- ereport(LOG,
- (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")",
- port->user_name, kusername)));
- ret = STATUS_ERROR;
- }
- else
- ret = STATUS_OK;
+ case uaGSS:
+ sendAuthRequest(port, AUTH_REQ_GSS);
+ status = pg_GSS_recvauth(port);
+ break;
- krb5_free_ticket(pg_krb5_context, ticket);
- krb5_auth_con_free(pg_krb5_context, auth_context);
- free(kusername);
+ case uaSSPI:
+ sendAuthRequest(port, AUTH_REQ_SSPI);
+ status = pg_SSPI_recvauth(port);
+ break;
- return ret;
-}
-#else
+ case uaIdent:
-static int
-pg_krb5_recvauth(Port *port)
-{
- ereport(LOG,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("Kerberos 5 not implemented on this server")));
- return STATUS_ERROR;
-}
-#endif /* KRB5 */
+ /*
+ * 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)
-/*----------------------------------------------------------------
- * GSSAPI authentication system
- *----------------------------------------------------------------
- */
+ /*
+ * Receive credentials on next message receipt, BSD/OS,
+ * NetBSD. We need to set this before the client sends the
+ * next packet.
+ */
+ int on = 1;
-#ifdef ENABLE_GSS
+ 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
-#if defined(HAVE_GSSAPI_H)
-#include <gssapi.h>
-#else
-#include <gssapi/gssapi.h>
+ sendAuthRequest(port, AUTH_REQ_SCM_CREDS);
+ }
#endif
+ status = authident(port);
+ break;
-#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;
+ case uaMD5:
+ sendAuthRequest(port, AUTH_REQ_MD5);
+ status = recv_and_check_password_packet(port);
+ break;
+
+ case uaCrypt:
+ sendAuthRequest(port, AUTH_REQ_CRYPT);
+ status = recv_and_check_password_packet(port);
+ break;
+
+ case uaPassword:
+ sendAuthRequest(port, AUTH_REQ_PASSWORD);
+ status = recv_and_check_password_packet(port);
+ break;
+
+#ifdef USE_PAM
+ case uaPAM:
+ pam_port_cludge = port;
+ status = CheckPAMAuth(port, port->user_name, "");
+ break;
+#endif /* USE_PAM */
+
+#ifdef USE_LDAP
+ case uaLDAP:
+ status = CheckLDAPAuth(port);
+ break;
#endif
+ 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
-pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
+sendAuthRequest(Port *port, AuthRequest areq)
{
- gss_buffer_desc gmsg;
- OM_uint32 lmaj_s,
- lmin_s,
- msg_ctx;
- char msg_major[128],
- msg_minor[128];
+ StringInfoData buf;
- /* 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);
+ pq_beginmessage(&buf, 'R');
+ pq_sendint(&buf, (int32) areq, sizeof(int32));
- if (msg_ctx)
+ /* Add the salt for encrypted passwords. */
+ if (areq == AUTH_REQ_MD5)
+ pq_sendbytes(&buf, port->md5Salt, 4);
+ else if (areq == AUTH_REQ_CRYPT)
+ pq_sendbytes(&buf, port->cryptSalt, 2);
- /*
- * More than one message available. XXX: Should we loop and read all
- * messages? (same below)
- */
- ereport(WARNING,
- (errmsg_internal("incomplete GSS error report")));
+#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
- /* 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);
+ /*
+ * 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);
- if (msg_ctx)
- ereport(WARNING,
- (errmsg_internal("incomplete GSS minor error report")));
+ pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
+ }
+ }
+#endif
+
+ pq_endmessage(&buf);
/*
- * errmsg_internal, since translation of the first part must be done
- * before calling this function anyway.
+ * Flush message so client will see it, except for AUTH_REQ_OK, which need
+ * not be sent until we are ready for queries.
*/
- ereport(severity,
- (errmsg_internal("%s", errmsg),
- errdetail("%s: %s", msg_major, msg_minor)));
+ if (areq != AUTH_REQ_OK)
+ pq_flush();
}
-static int
-pg_GSS_recvauth(Port *port)
+/*
+ * Collect password response packet from frontend.
+ *
+ * Returns NULL if couldn't get password, else palloc'd string.
+ */
+static char *
+recv_password_packet(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)
+ if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
{
- /*
- * 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);
+ /* Expect 'p' message type */
+ int mtype;
- 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);
+ 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 */
+ }
- /*
- * 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;
+ initStringInfo(&buf);
+ if (pq_getmessage(&buf, 1000)) /* receive password */
+ {
+ /* EOF - pq_getmessage already logged a suitable message */
+ pfree(buf.data);
+ return NULL;
+ }
/*
- * Initialize sequence with an empty context
+ * 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'.
*/
- port->gss->ctx = GSS_C_NO_CONTEXT;
+ 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")));
/*
- * 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').
+ * 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.
*/
- 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;
- }
+ return buf.data;
+}
- /* 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);
+/*----------------------------------------------------------------
+ * MD5 and crypt authentication
+ *----------------------------------------------------------------
+ */
- 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);
+/*
+ * 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;
- /* gbuf no longer used */
- pfree(buf.data);
+ passwd = recv_password_packet(port);
- 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 (passwd == NULL)
+ return STATUS_EOF; /* client wouldn't send password */
- if (port->gss->outbuf.length != 0)
- {
- /*
- * Negotiation generated data to be sent to the client.
- */
- OM_uint32 lmin_s;
+ result = md5_crypt_verify(port, port->user_name, passwd);
- elog(DEBUG4, "sending GSS response token of length %u",
- (unsigned int) port->gss->outbuf.length);
+ pfree(passwd);
- sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+ return result;
+}
- gss_release_buffer(&lmin_s, &port->gss->outbuf);
- }
- if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
- {
- OM_uint32 lmin_s;
+/*----------------------------------------------------------------
+ * MIT Kerberos authentication system - protocol version 5
+ *----------------------------------------------------------------
+ */
+#ifdef KRB5
- 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);
- }
+static int
+pg_krb5_init(void)
+{
+ krb5_error_code retval;
+ char *khostname;
- if (maj_stat == GSS_S_CONTINUE_NEEDED)
- elog(DEBUG4, "GSS continue needed");
+ if (pg_krb5_initialised)
+ return STATUS_OK;
- } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+ 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;
+ }
- if (port->gss->cred != GSS_C_NO_CREDENTIAL)
+ retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
+ if (retval)
{
- /*
- * Release service principal credentials
- */
- gss_release_cred(&min_stat, &port->gss->cred);
+ 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;
}
/*
- * 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.
+ * If no hostname was specified, pg_krb_server_hostname is already NULL.
+ * If it's set to blank, force it to NULL.
*/
- 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);
+ khostname = pg_krb_server_hostname;
+ if (khostname && khostname[0] == '\0')
+ khostname = NULL;
- /*
- * Split the username at the realm separator
- */
- if (strchr(gbuf.value, '@'))
+ retval = krb5_sname_to_principal(pg_krb5_context,
+ khostname,
+ pg_krb_srvnam,
+ KRB5_NT_SRV_HST,
+ &pg_krb5_server);
+ if (retval)
{
- char *cp = strchr(gbuf.value, '@');
+ 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;
+ }
- *cp = '\0';
- cp++;
+ 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();
+ 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)
+ {
+ *cp = '\0';
+ cp++;
if (pg_krb_realm != NULL && strlen(pg_krb_realm))
{
- /*
- * Match the realm part of the name first
- */
+ /* Match realm against configured */
if (pg_krb_caseins_users)
ret = pg_strcasecmp(pg_krb_realm, cp);
else
if (ret)
{
- /* GSS realm does not match */
elog(DEBUG2,
- "GSSAPI realm (%s) and configured realm (%s) don't match",
+ "krb5 realm (%s) and configured realm (%s) don't match",
cp, pg_krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
return STATUS_ERROR;
}
}
else if (pg_krb_realm && strlen(pg_krb_realm))
{
elog(DEBUG2,
- "GSSAPI did not return realm but realm matching was requested");
+ "krb5 did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
return STATUS_ERROR;
}
if (pg_krb_caseins_users)
- ret = pg_strcasecmp(port->user_name, gbuf.value);
+ ret = pg_strncasecmp(port->user_name, kusername, SM_DATABASE_USER);
else
- ret = strcmp(port->user_name, gbuf.value);
-
+ ret = strncmp(port->user_name, kusername, SM_DATABASE_USER);
if (ret)
{
- /* GSS name and PGUSER are not equivalent */
- elog(DEBUG2,
- "provided username (%s) and GSSAPI username (%s) don't match",
- port->user_name, (char *) gbuf.value);
-
- gss_release_buffer(&lmin_s, &gbuf);
- return STATUS_ERROR;
+ ereport(LOG,
+ (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")",
+ port->user_name, kusername)));
+ ret = STATUS_ERROR;
}
+ else
+ ret = STATUS_OK;
- gss_release_buffer(&lmin_s, &gbuf);
+ krb5_free_ticket(pg_krb5_context, ticket);
+ krb5_auth_con_free(pg_krb5_context, auth_context);
+ free(kusername);
- return STATUS_OK;
+ return ret;
}
-
-#else /* no ENABLE_GSS */
+#else
static int
-pg_GSS_recvauth(Port *port)
+pg_krb5_recvauth(Port *port)
{
ereport(LOG,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("GSSAPI not implemented on this server")));
+ errmsg("Kerberos 5 not implemented on this server")));
return STATUS_ERROR;
}
+#endif /* KRB5 */
-#endif /* ENABLE_GSS */
/*----------------------------------------------------------------
- * SSPI authentication system
+ * GSSAPI authentication system
*----------------------------------------------------------------
*/
+#ifdef ENABLE_GSS
-#ifdef ENABLE_SSPI
+#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
-typedef SECURITY_STATUS
- (WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
- PCtxtHandle, void **);
static void
-pg_SSPI_error(int severity, char *errmsg, SECURITY_STATUS r)
+pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
{
- char sysmsg[256];
+ gss_buffer_desc gmsg;
+ OM_uint32 lmaj_s,
+ lmin_s,
+ msg_ctx;
+ char msg_major[128],
+ msg_minor[128];
- 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)));
+ /* 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_SSPI_recvauth(Port *port)
+pg_GSS_recvauth(Port *port)
{
+ OM_uint32 maj_stat,
+ min_stat,
+ lmin_s,
+ gflags;
int mtype;
+ int ret;
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;
+ gss_buffer_desc gbuf;
/*
- * SSPI auth is not supported for protocol versions before 3, because it
- * relies on the overall message length word to determine the SSPI payload
+ * 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 SSPI support, because protocol
+ * (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("SSPI is not supported in protocol version 2")));
+ 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);
+ }
+ }
/*
- * Acquire a handle to the server credentials.
+ * 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.
*/
- r = AcquireCredentialsHandle(NULL,
- "negotiate",
+ 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, '@');
+
+ *cp = '\0';
+ cp++;
+
+ if (pg_krb_realm != NULL && strlen(pg_krb_realm))
+ {
+ /*
+ * Match the realm part of the name first
+ */
+ if (pg_krb_caseins_users)
+ ret = pg_strcasecmp(pg_krb_realm, cp);
+ else
+ ret = strcmp(pg_krb_realm, cp);
+
+ if (ret)
+ {
+ /* GSS realm does not match */
+ elog(DEBUG2,
+ "GSSAPI realm (%s) and configured realm (%s) don't match",
+ cp, pg_krb_realm);
+ gss_release_buffer(&lmin_s, &gbuf);
+ return STATUS_ERROR;
+ }
+ }
+ }
+ else if (pg_krb_realm && strlen(pg_krb_realm))
+ {
+ elog(DEBUG2,
+ "GSSAPI did not return realm but realm matching was requested");
+
+ gss_release_buffer(&lmin_s, &gbuf);
+ return STATUS_ERROR;
+ }
+
+ if (pg_krb_caseins_users)
+ ret = pg_strcasecmp(port->user_name, gbuf.value);
+ else
+ ret = strcmp(port->user_name, gbuf.value);
+
+ if (ret)
+ {
+ /* GSS name and PGUSER are not equivalent */
+ elog(DEBUG2,
+ "provided username (%s) and GSSAPI username (%s) don't match",
+ port->user_name, (char *) gbuf.value);
+
+ gss_release_buffer(&lmin_s, &gbuf);
+ return STATUS_ERROR;
+ }
+
+ gss_release_buffer(&lmin_s, &gbuf);
+
+ return STATUS_OK;
+}
+
+#else /* no ENABLE_GSS */
+
+static int
+pg_GSS_recvauth(Port *port)
+{
+ ereport(LOG,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("GSSAPI not implemented on this server")));
+ return STATUS_ERROR;
+}
+
+#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,
} 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 (pg_krb_realm && strlen(pg_krb_realm))
- {
- if (pg_strcasecmp(pg_krb_realm, domainname))
- {
- elog(DEBUG2,
- "SSPI domain (%s) and configured domain (%s) don't match",
- domainname, pg_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 (pg_strcasecmp(port->user_name, accountname))
- {
- /* GSS name and PGUSER are not equivalent */
- elog(DEBUG2,
- "provided username (%s) and SSPI username (%s) don't match",
- port->user_name, accountname);
-
- return STATUS_ERROR;
- }
-
- return STATUS_OK;
-}
-
-#else /* no ENABLE_SSPI */
-
-static int
-pg_SSPI_recvauth(Port *port)
-{
- ereport(LOG,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("SSPI not implemented on this server")));
- return STATUS_ERROR;
-}
-
-#endif /* ENABLE_SSPI */
-
-
-/*
- * Tell the user the authentication failed, but not (much about) why.
- *
- * There is a tradeoff here between security concerns and making life
- * unnecessarily difficult for legitimate users. We would not, for example,
- * want to report the password we were expecting to receive...
- * But it seems useful to report the username and authorization method
- * in use, and these are items that must be presumed known to an attacker
- * anyway.
- * Note that many sorts of failure report additional information in the
- * postmaster log, which we hope is only readable by good guys.
- */
-static void
-auth_failed(Port *port, int status)
-{
- const char *errstr;
-
- /*
- * If we failed due to EOF from client, just quit; there's no point in
- * trying to send a message to the client, and not much point in logging
- * the failure in the postmaster log. (Logging the failure might be
- * desirable, were it not for the fact that libpq closes the connection
- * unceremoniously if challenged for a password when it hasn't got one to
- * send. We'll get a useless log entry for every psql connection under
- * password auth, even if it's perfectly successful, if we log STATUS_EOF
- * events.)
- */
- if (status == STATUS_EOF)
- proc_exit(0);
-
- switch (port->auth_method)
- {
- case uaReject:
- errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
- break;
- case uaKrb5:
- errstr = gettext_noop("Kerberos 5 authentication failed for user \"%s\"");
- break;
- case uaGSS:
- errstr = gettext_noop("GSSAPI authentication failed for user \"%s\"");
- break;
- case uaSSPI:
- errstr = gettext_noop("SSPI authentication failed for user \"%s\"");
- break;
- case uaTrust:
- errstr = gettext_noop("\"trust\" authentication failed for user \"%s\"");
- break;
- case uaIdent:
- errstr = gettext_noop("Ident authentication failed for user \"%s\"");
- break;
- case uaMD5:
- case uaCrypt:
- case uaPassword:
- errstr = gettext_noop("password authentication failed for user \"%s\"");
- break;
-#ifdef USE_PAM
- case uaPAM:
- errstr = gettext_noop("PAM authentication failed for user \"%s\"");
- break;
-#endif /* USE_PAM */
-#ifdef USE_LDAP
- case uaLDAP:
- errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
- break;
-#endif /* USE_LDAP */
- default:
- errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
- break;
- }
-
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
- errmsg(errstr, port->user_name)));
- /* doesn't return */
-}
-
-
-/*
- * Client authentication starts here. If there is an error, this
- * function does not return and the backend process is terminated.
- */
-void
-ClientAuthentication(Port *port)
-{
- int status = STATUS_ERROR;
-
- /*
- * Get the authentication method to use for this frontend/database
- * combination. Note: a failure return indicates a problem with the hba
- * config file, not with the request. hba.c should have dropped an error
- * message into the postmaster logfile if it failed.
- */
- if (hba_getauthmethod(port) != STATUS_OK)
- ereport(FATAL,
- (errcode(ERRCODE_CONFIG_FILE_ERROR),
- errmsg("missing or erroneous pg_hba.conf file"),
- errhint("See server log for details.")));
-
- switch (port->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:
- sendAuthRequest(port, AUTH_REQ_KRB5);
- status = pg_krb5_recvauth(port);
- break;
-
- case uaGSS:
- sendAuthRequest(port, AUTH_REQ_GSS);
- status = pg_GSS_recvauth(port);
- break;
-
- case uaSSPI:
- sendAuthRequest(port, AUTH_REQ_SSPI);
- status = pg_SSPI_recvauth(port);
- 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:
- sendAuthRequest(port, AUTH_REQ_MD5);
- status = recv_and_check_password_packet(port);
- break;
+ /*
+ * Release service principal credentials
+ */
+ FreeCredentialsHandle(&sspicred);
- case uaCrypt:
- sendAuthRequest(port, AUTH_REQ_CRYPT);
- status = recv_and_check_password_packet(port);
- break;
- case uaPassword:
- sendAuthRequest(port, AUTH_REQ_PASSWORD);
- status = recv_and_check_password_packet(port);
- break;
+ /*
+ * 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.
+ */
-#ifdef USE_PAM
- case uaPAM:
- pam_port_cludge = port;
- status = CheckPAMAuth(port, port->user_name, "");
- break;
-#endif /* USE_PAM */
+ secur32 = LoadLibrary("SECUR32.DLL");
+ if (secur32 == NULL)
+ ereport(ERROR,
+ (errmsg_internal("could not load secur32.dll: %d",
+ (int) GetLastError())));
-#ifdef USE_LDAP
- case uaLDAP:
- status = CheckLDAPAuth(port);
- break;
-#endif
+ _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())));
+ }
- case uaTrust:
- status = STATUS_OK;
- break;
+ 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);
}
- if (status == STATUS_OK)
- sendAuthRequest(port, AUTH_REQ_OK);
- else
- auth_failed(port, status);
-}
+ FreeLibrary(secur32);
+ /*
+ * No longer need the security context, everything from here on uses the
+ * token instead.
+ */
+ DeleteSecurityContext(sspictx);
+ free(sspictx);
-/*
- * Send an authentication request packet to the frontend.
- */
-static void
-sendAuthRequest(Port *port, AuthRequest areq)
-{
- StringInfoData buf;
+ if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+ ereport(ERROR,
+ (errmsg_internal("could not get token user size: error code %d",
+ (int) GetLastError())));
- pq_beginmessage(&buf, 'R');
- pq_sendint(&buf, (int32) areq, sizeof(int32));
+ tokenuser = malloc(retlen);
+ if (tokenuser == NULL)
+ ereport(ERROR,
+ (errmsg("out of memory")));
- /* Add the salt for encrypted passwords. */
- if (areq == AUTH_REQ_MD5)
- pq_sendbytes(&buf, port->md5Salt, 4);
- else if (areq == AUTH_REQ_CRYPT)
- pq_sendbytes(&buf, port->cryptSalt, 2);
+ if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+ ereport(ERROR,
+ (errmsg_internal("could not get user token: error code %d",
+ (int) GetLastError())));
-#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+ 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);
/*
- * Add the authentication data for the next step of the GSSAPI or SSPI
- * negotiation.
+ * Compare realm/domain if requested. In SSPI, always compare case
+ * insensitive.
*/
- else if (areq == AUTH_REQ_GSS_CONT)
+ if (pg_krb_realm && strlen(pg_krb_realm))
{
- if (port->gss->outbuf.length > 0)
+ if (pg_strcasecmp(pg_krb_realm, domainname))
{
- elog(DEBUG4, "sending GSS token of length %u",
- (unsigned int) port->gss->outbuf.length);
+ elog(DEBUG2,
+ "SSPI domain (%s) and configured domain (%s) don't match",
+ domainname, pg_krb_realm);
- pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
+ return STATUS_ERROR;
}
}
-#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.
+ * We have the username (without domain/realm) in accountname, compare to
+ * the supplied value. In SSPI, always compare case insensitive.
*/
- if (areq != AUTH_REQ_OK)
- pq_flush();
+ if (pg_strcasecmp(port->user_name, accountname))
+ {
+ /* GSS name and PGUSER are not equivalent */
+ elog(DEBUG2,
+ "provided username (%s) and SSPI username (%s) don't match",
+ port->user_name, accountname);
+
+ return STATUS_ERROR;
+ }
+
+ return STATUS_OK;
+}
+
+#else /* no ENABLE_SSPI */
+
+static int
+pg_SSPI_recvauth(Port *port)
+{
+ ereport(LOG,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SSPI not implemented on this server")));
+ return STATUS_ERROR;
}
+#endif /* ENABLE_SSPI */
+
+
+
/*----------------------------------------------------------------
* Ident authentication system
*----------------------------------------------------------------
* PAM authentication system
*----------------------------------------------------------------
*/
-
#ifdef USE_PAM
/*
#endif /* USE_PAM */
+
+/*----------------------------------------------------------------
+ * LDAP authentication system
+ *----------------------------------------------------------------
+ */
#ifdef USE_LDAP
static int
}
#endif /* USE_LDAP */
-/*
- * 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;
-}
-
-
-/*
- * 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;
-}