postgresql/src/backend/libpq/auth.c

3376 lines
90 KiB
C

/*-------------------------------------------------------------------------
*
* auth.c
* Routines to handle network authentication
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/libpq/auth.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include "commands/user.h"
#include "common/ip.h"
#include "common/md5.h"
#include "common/scram-common.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "port/pg_bswap.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
static void sendAuthRequest(Port *port, AuthRequest areq, const char *extradata,
int extralen);
static void auth_failed(Port *port, int status, char *logdetail);
static char *recv_password_packet(Port *port);
/*----------------------------------------------------------------
* Password-based authentication methods (password, md5, and scram-sha-256)
*----------------------------------------------------------------
*/
static int CheckPasswordAuth(Port *port, char **logdetail);
static int CheckPWChallengeAuth(Port *port, char **logdetail);
static int CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail);
static int CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail);
/*----------------------------------------------------------------
* 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 ident_inet(hbaPort *port);
#ifdef HAVE_UNIX_SOCKETS
static int auth_peer(hbaPort *port);
#endif
/*----------------------------------------------------------------
* PAM authentication
*----------------------------------------------------------------
*/
#ifdef USE_PAM
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#define PGSQL_PAM_SERVICE "postgresql" /* Service name passed to PAM */
static int CheckPAMAuth(Port *port, const char *user, const char *password);
static int pam_passwd_conv_proc(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr);
static struct pam_conv pam_passw_conv = {
&pam_passwd_conv_proc,
NULL
};
static const char *pam_passwd = NULL; /* Workaround for Solaris 2.6
* brokenness */
static Port *pam_port_cludge; /* Workaround for passing "Port *port" into
* pam_passwd_conv_proc */
#endif /* USE_PAM */
/*----------------------------------------------------------------
* BSD authentication
*----------------------------------------------------------------
*/
#ifdef USE_BSD_AUTH
#include <bsd_auth.h>
static int CheckBSDAuth(Port *port, char *user);
#endif /* USE_BSD_AUTH */
/*----------------------------------------------------------------
* LDAP authentication
*----------------------------------------------------------------
*/
#ifdef USE_LDAP
#ifndef WIN32
/* We use a deprecated function to keep the codepath the same as win32. */
#define LDAP_DEPRECATED 1
#include <ldap.h>
#else
#include <winldap.h>
/* Correct header from the Platform SDK */
typedef
ULONG (*__ldap_start_tls_sA) (IN PLDAP ExternalHandle,
OUT PULONG ServerReturnValue,
OUT LDAPMessage **result,
IN PLDAPControlA * ServerControls,
IN PLDAPControlA * ClientControls
);
#endif
static int CheckLDAPAuth(Port *port);
/* LDAP_OPT_DIAGNOSTIC_MESSAGE is the newer spelling */
#ifndef LDAP_OPT_DIAGNOSTIC_MESSAGE
#define LDAP_OPT_DIAGNOSTIC_MESSAGE LDAP_OPT_ERROR_STRING
#endif
#endif /* USE_LDAP */
/*----------------------------------------------------------------
* Cert authentication
*----------------------------------------------------------------
*/
#ifdef USE_SSL
static int CheckCertAuth(Port *port);
#endif
/*----------------------------------------------------------------
* Kerberos and GSSAPI GUCs
*----------------------------------------------------------------
*/
char *pg_krb_server_keyfile;
bool pg_krb_caseins_users;
/*----------------------------------------------------------------
* GSSAPI Authentication
*----------------------------------------------------------------
*/
#ifdef ENABLE_GSS
#include "libpq/be-gssapi-common.h"
static int pg_GSS_checkauth(Port *port);
static int pg_GSS_recvauth(Port *port);
#endif /* ENABLE_GSS */
/*----------------------------------------------------------------
* SSPI Authentication
*----------------------------------------------------------------
*/
#ifdef ENABLE_SSPI
typedef SECURITY_STATUS
(WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
PCtxtHandle, void **);
static int pg_SSPI_recvauth(Port *port);
static int pg_SSPI_make_upn(char *accountname,
size_t accountnamesize,
char *domainname,
size_t domainnamesize,
bool update_accountname);
#endif
/*----------------------------------------------------------------
* RADIUS Authentication
*----------------------------------------------------------------
*/
static int CheckRADIUSAuth(Port *port);
static int PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd);
/*
* Maximum accepted size of GSS and SSPI authentication tokens.
*
* Kerberos tickets are usually quite small, but the TGTs issued by Windows
* domain controllers include an authorization field known as the Privilege
* Attribute Certificate (PAC), which contains the user's Windows permissions
* (group memberships etc.). The PAC is copied into all tickets obtained on
* the basis of this TGT (even those issued by Unix realms which the Windows
* realm trusts), and can be several kB in size. The maximum token size
* accepted by Windows systems is determined by the MaxAuthToken Windows
* registry setting. Microsoft recommends that it is not set higher than
* 65535 bytes, so that seems like a reasonable limit for us as well.
*/
#define PG_MAX_AUTH_TOKEN_LENGTH 65535
/*
* Maximum accepted size of SASL messages.
*
* The messages that the server or libpq generate are much smaller than this,
* but have some headroom.
*/
#define PG_MAX_SASL_MESSAGE_LENGTH 1024
/*----------------------------------------------------------------
* Global authentication functions
*----------------------------------------------------------------
*/
/*
* This hook allows plugins to get control following client authentication,
* but before the user has been informed about the results. It could be used
* to record login events, insert a delay after failed authentication, etc.
*/
ClientAuthentication_hook_type ClientAuthentication_hook = NULL;
/*
* 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. In
* particular, if logdetail isn't NULL, we send that string to the log.
*/
static void
auth_failed(Port *port, int status, char *logdetail)
{
const char *errstr;
char *cdetail;
int errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
/*
* 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->hba->auth_method)
{
case uaReject:
case uaImplicitReject:
errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
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 uaPeer:
errstr = gettext_noop("Peer authentication failed for user \"%s\"");
break;
case uaPassword:
case uaMD5:
case uaSCRAM:
errstr = gettext_noop("password authentication failed for user \"%s\"");
/* We use it to indicate if a .pgpass password failed. */
errcode_return = ERRCODE_INVALID_PASSWORD;
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 uaPAM:
errstr = gettext_noop("PAM authentication failed for user \"%s\"");
break;
case uaBSD:
errstr = gettext_noop("BSD authentication failed for user \"%s\"");
break;
case uaLDAP:
errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
break;
case uaCert:
errstr = gettext_noop("certificate authentication failed for user \"%s\"");
break;
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
}
cdetail = psprintf(_("Connection matched pg_hba.conf line %d: \"%s\""),
port->hba->linenumber, port->hba->rawline);
if (logdetail)
logdetail = psprintf("%s\n%s", logdetail, cdetail);
else
logdetail = cdetail;
ereport(FATAL,
(errcode(errcode_return),
errmsg(errstr, port->user_name),
logdetail ? errdetail_log("%s", logdetail) : 0));
/* 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;
char *logdetail = NULL;
/*
* Get the authentication method to use for this frontend/database
* combination. Note: we do not parse the file at this point; this has
* already been done elsewhere. hba.c dropped an error message into the
* server logfile if parsing the hba config file failed.
*/
hba_getauthmethod(port);
CHECK_FOR_INTERRUPTS();
/*
* 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 != clientCertOff)
{
/* If we haven't loaded a root certificate store, fail */
if (!secure_loaded_verify_locations())
ereport(FATAL,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("client certificates can only be checked if a root certificate store is available")));
/*
* If we loaded a root certificate store, and if a certificate is
* present on the client, then it has been verified against our root
* certificate store, and the connection would have been aborted
* already if it didn't verify ok.
*/
if (!port->peer_cert_valid)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("connection requires a valid client certificate")));
}
#ifdef ENABLE_GSS
if (port->gss->enc && port->hba->auth_method != uaReject &&
port->hba->auth_method != uaImplicitReject &&
port->hba->auth_method != uaTrust &&
port->hba->auth_method != uaGSS)
{
ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("GSSAPI encryption can only be used with gss, trust, or reject authentication methods")));
}
#endif
/*
* Now proceed to do the actual authentication check
*/
switch (port->hba->auth_method)
{
case uaReject:
/*
* An explicit "reject" entry in pg_hba.conf. This report exposes
* the fact that there's an explicit reject entry, which is
* perhaps not so desirable from a security standpoint; but the
* message for an implicit reject could confuse the DBA a lot when
* the true situation is a match to an explicit reject. And we
* don't want to change the message for an implicit reject. As
* noted below, the additional information shown here doesn't
* expose anything not known to an attacker.
*/
{
char hostinfo[NI_MAXHOST];
pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
hostinfo, sizeof(hostinfo),
NULL, 0,
NI_NUMERICHOST);
if (am_walsender)
{
#ifdef USE_SSL
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\", %s",
hostinfo, port->user_name,
port->ssl_in_use ? _("SSL on") : _("SSL off"))));
#else
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("pg_hba.conf rejects replication connection for host \"%s\", user \"%s\"",
hostinfo, port->user_name)));
#endif
}
else
{
#ifdef USE_SSL
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\", %s",
hostinfo, port->user_name,
port->database_name,
port->ssl_in_use ? _("SSL on") : _("SSL off"))));
#else
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("pg_hba.conf rejects connection for host \"%s\", user \"%s\", database \"%s\"",
hostinfo, port->user_name,
port->database_name)));
#endif
}
break;
}
case uaImplicitReject:
/*
* No matching entry, so tell the user we fell through.
*
* NOTE: the extra info reported here is not a security breach,
* because all that info 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);
#define HOSTNAME_LOOKUP_DETAIL(port) \
(port->remote_hostname ? \
(port->remote_hostname_resolv == +1 ? \
errdetail_log("Client IP address resolved to \"%s\", forward lookup matches.", \
port->remote_hostname) : \
port->remote_hostname_resolv == 0 ? \
errdetail_log("Client IP address resolved to \"%s\", forward lookup not checked.", \
port->remote_hostname) : \
port->remote_hostname_resolv == -1 ? \
errdetail_log("Client IP address resolved to \"%s\", forward lookup does not match.", \
port->remote_hostname) : \
port->remote_hostname_resolv == -2 ? \
errdetail_log("Could not translate client host name \"%s\" to IP address: %s.", \
port->remote_hostname, \
gai_strerror(port->remote_hostname_errcode)) : \
0) \
: (port->remote_hostname_resolv == -2 ? \
errdetail_log("Could not resolve client IP address to a host name: %s.", \
gai_strerror(port->remote_hostname_errcode)) : \
0))
if (am_walsender)
{
#ifdef USE_SSL
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\", %s",
hostinfo, port->user_name,
port->ssl_in_use ? _("SSL on") : _("SSL off")),
HOSTNAME_LOOKUP_DETAIL(port)));
#else
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("no pg_hba.conf entry for replication connection from host \"%s\", user \"%s\"",
hostinfo, port->user_name),
HOSTNAME_LOOKUP_DETAIL(port)));
#endif
}
else
{
#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_in_use ? _("SSL on") : _("SSL off")),
HOSTNAME_LOOKUP_DETAIL(port)));
#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),
HOSTNAME_LOOKUP_DETAIL(port)));
#endif
}
break;
}
case uaGSS:
#ifdef ENABLE_GSS
port->gss->auth = true;
if (port->gss->enc)
status = pg_GSS_checkauth(port);
else
{
sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
status = pg_GSS_recvauth(port);
}
#else
Assert(false);
#endif
break;
case uaSSPI:
#ifdef ENABLE_SSPI
sendAuthRequest(port, AUTH_REQ_SSPI, NULL, 0);
status = pg_SSPI_recvauth(port);
#else
Assert(false);
#endif
break;
case uaPeer:
#ifdef HAVE_UNIX_SOCKETS
status = auth_peer(port);
#else
Assert(false);
#endif
break;
case uaIdent:
status = ident_inet(port);
break;
case uaMD5:
case uaSCRAM:
status = CheckPWChallengeAuth(port, &logdetail);
break;
case uaPassword:
status = CheckPasswordAuth(port, &logdetail);
break;
case uaPAM:
#ifdef USE_PAM
status = CheckPAMAuth(port, port->user_name, "");
#else
Assert(false);
#endif /* USE_PAM */
break;
case uaBSD:
#ifdef USE_BSD_AUTH
status = CheckBSDAuth(port, port->user_name);
#else
Assert(false);
#endif /* USE_BSD_AUTH */
break;
case uaLDAP:
#ifdef USE_LDAP
status = CheckLDAPAuth(port);
#else
Assert(false);
#endif
break;
case uaRADIUS:
status = CheckRADIUSAuth(port);
break;
case uaCert:
/* uaCert will be treated as if clientcert=verify-full (uaTrust) */
case uaTrust:
status = STATUS_OK;
break;
}
if ((status == STATUS_OK && port->hba->clientcert == clientCertFull)
|| port->hba->auth_method == uaCert)
{
/*
* Make sure we only check the certificate if we use the cert method
* or verify-full option.
*/
#ifdef USE_SSL
status = CheckCertAuth(port);
#else
Assert(false);
#endif
}
if (ClientAuthentication_hook)
(*ClientAuthentication_hook) (port, status);
if (status == STATUS_OK)
sendAuthRequest(port, AUTH_REQ_OK, NULL, 0);
else
auth_failed(port, status, logdetail);
}
/*
* Send an authentication request packet to the frontend.
*/
static void
sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extralen)
{
StringInfoData buf;
CHECK_FOR_INTERRUPTS();
pq_beginmessage(&buf, 'R');
pq_sendint32(&buf, (int32) areq);
if (extralen > 0)
pq_sendbytes(&buf, extradata, extralen);
pq_endmessage(&buf);
/*
* Flush message so client will see it, except for AUTH_REQ_OK and
* AUTH_REQ_SASL_FIN, which need not be sent until we are ready for
* queries.
*/
if (areq != AUTH_REQ_OK && areq != AUTH_REQ_SASL_FIN)
pq_flush();
CHECK_FOR_INTERRUPTS();
}
/*
* 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;
pq_startmsgread();
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(ERROR,
(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(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("invalid password packet size")));
/*
* Don't allow an empty password. Libpq treats an empty password the same
* as no password at all, and won't even try to authenticate. But other
* clients might, so allowing it would be confusing.
*
* Note that this only catches an empty password sent by the client in
* plaintext. There's also a check in CREATE/ALTER USER that prevents an
* empty string from being stored as a user's password in the first place.
* We rely on that for MD5 and SCRAM authentication, but we still need
* this check here, to prevent an empty password from being used with
* authentication methods that check the password against an external
* system, like PAM, LDAP and RADIUS.
*/
if (buf.len == 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PASSWORD),
errmsg("empty password returned by client")));
/* Do not echo password to logs, for security. */
elog(DEBUG5, "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;
}
/*----------------------------------------------------------------
* Password-based authentication mechanisms
*----------------------------------------------------------------
*/
/*
* Plaintext password authentication.
*/
static int
CheckPasswordAuth(Port *port, char **logdetail)
{
char *passwd;
int result;
char *shadow_pass;
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
shadow_pass = get_role_password(port->user_name, logdetail);
if (shadow_pass)
{
result = plain_crypt_verify(port->user_name, shadow_pass, passwd,
logdetail);
}
else
result = STATUS_ERROR;
if (shadow_pass)
pfree(shadow_pass);
pfree(passwd);
return result;
}
/*
* MD5 and SCRAM authentication.
*/
static int
CheckPWChallengeAuth(Port *port, char **logdetail)
{
int auth_result;
char *shadow_pass;
PasswordType pwtype;
Assert(port->hba->auth_method == uaSCRAM ||
port->hba->auth_method == uaMD5);
/* First look up the user's password. */
shadow_pass = get_role_password(port->user_name, logdetail);
/*
* If the user does not exist, or has no password or it's expired, we
* still go through the motions of authentication, to avoid revealing to
* the client that the user didn't exist. If 'md5' is allowed, we choose
* whether to use 'md5' or 'scram-sha-256' authentication based on current
* password_encryption setting. The idea is that most genuine users
* probably have a password of that type, and if we pretend that this user
* had a password of that type, too, it "blends in" best.
*/
if (!shadow_pass)
pwtype = Password_encryption;
else
pwtype = get_password_type(shadow_pass);
/*
* If 'md5' authentication is allowed, decide whether to perform 'md5' or
* 'scram-sha-256' authentication based on the type of password the user
* has. If it's an MD5 hash, we must do MD5 authentication, and if it's a
* SCRAM secret, we must do SCRAM authentication.
*
* If MD5 authentication is not allowed, always use SCRAM. If the user
* had an MD5 password, CheckSCRAMAuth() will fail.
*/
if (port->hba->auth_method == uaMD5 && pwtype == PASSWORD_TYPE_MD5)
auth_result = CheckMD5Auth(port, shadow_pass, logdetail);
else
auth_result = CheckSCRAMAuth(port, shadow_pass, logdetail);
if (shadow_pass)
pfree(shadow_pass);
/*
* If get_role_password() returned error, return error, even if the
* authentication succeeded.
*/
if (!shadow_pass)
{
Assert(auth_result != STATUS_OK);
return STATUS_ERROR;
}
return auth_result;
}
static int
CheckMD5Auth(Port *port, char *shadow_pass, char **logdetail)
{
char md5Salt[4]; /* Password salt */
char *passwd;
int result;
if (Db_user_namespace)
ereport(FATAL,
(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
/* include the salt to use for computing the response */
if (!pg_strong_random(md5Salt, 4))
{
ereport(LOG,
(errmsg("could not generate random MD5 salt")));
return STATUS_ERROR;
}
sendAuthRequest(port, AUTH_REQ_MD5, md5Salt, 4);
passwd = recv_password_packet(port);
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
if (shadow_pass)
result = md5_crypt_verify(port->user_name, shadow_pass, passwd,
md5Salt, 4, logdetail);
else
result = STATUS_ERROR;
pfree(passwd);
return result;
}
static int
CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
{
StringInfoData sasl_mechs;
int mtype;
StringInfoData buf;
void *scram_opaq = NULL;
char *output = NULL;
int outputlen = 0;
const char *input;
int inputlen;
int result;
bool initial;
/*
* SASL auth is not supported for protocol versions before 3, because it
* relies on the overall message length word to determine the SASL payload
* size in AuthenticationSASLContinue and PasswordMessage messages. (We
* used to have a hard rule that protocol messages must be parsable
* without relying on the length word, but we hardly care about older
* protocol version anymore.)
*/
if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
ereport(FATAL,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SASL authentication is not supported in protocol version 2")));
/*
* Send the SASL authentication request to user. It includes the list of
* authentication mechanisms that are supported.
*/
initStringInfo(&sasl_mechs);
pg_be_scram_get_mechanisms(port, &sasl_mechs);
/* Put another '\0' to mark that list is finished. */
appendStringInfoChar(&sasl_mechs, '\0');
sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
pfree(sasl_mechs.data);
/*
* Loop through SASL message exchange. This exchange can consist of
* multiple messages sent in both directions. First message is always
* from the client. All messages from client to server are password
* packets (type 'p').
*/
initial = true;
do
{
pq_startmsgread();
mtype = pq_getbyte();
if (mtype != 'p')
{
/* Only log error if client didn't disconnect. */
if (mtype != EOF)
{
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("expected SASL response, got message type %d",
mtype)));
}
else
return STATUS_EOF;
}
/* Get the actual SASL message */
initStringInfo(&buf);
if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
{
/* EOF - pq_getmessage already logged error */
pfree(buf.data);
return STATUS_ERROR;
}
elog(DEBUG4, "Processing received SASL response of length %d", buf.len);
/*
* The first SASLInitialResponse message is different from the others.
* It indicates which SASL mechanism the client selected, and contains
* an optional Initial Client Response payload. The subsequent
* SASLResponse messages contain just the SASL payload.
*/
if (initial)
{
const char *selected_mech;
selected_mech = pq_getmsgrawstring(&buf);
/*
* Initialize the status tracker for message exchanges.
*
* If the user doesn't exist, or doesn't have a valid password, or
* it's expired, we still go through the motions of SASL
* authentication, but tell the authentication method that the
* authentication is "doomed". That is, it's going to fail, no
* matter what.
*
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
scram_opaq = pg_be_scram_init(port, selected_mech, shadow_pass);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
input = NULL;
else
input = pq_getmsgbytes(&buf, inputlen);
initial = false;
}
else
{
inputlen = buf.len;
input = pq_getmsgbytes(&buf, buf.len);
}
pq_getmsgend(&buf);
/*
* The StringInfo guarantees that there's a \0 byte after the
* response.
*/
Assert(input == NULL || input[inputlen] == '\0');
/*
* we pass 'logdetail' as NULL when doing a mock authentication,
* because we should already have a better error message in that case
*/
result = pg_be_scram_exchange(scram_opaq, input, inputlen,
&output, &outputlen,
logdetail);
/* input buffer no longer used */
pfree(buf.data);
if (output)
{
/*
* Negotiation generated data to be sent to the client.
*/
elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
if (result == SASL_EXCHANGE_SUCCESS)
sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
else
sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
pfree(output);
}
} while (result == SASL_EXCHANGE_CONTINUE);
/* Oops, Something bad happened */
if (result != SASL_EXCHANGE_SUCCESS)
{
return STATUS_ERROR;
}
return STATUS_OK;
}
/*----------------------------------------------------------------
* GSSAPI authentication system
*----------------------------------------------------------------
*/
#ifdef ENABLE_GSS
static int
pg_GSS_recvauth(Port *port)
{
OM_uint32 maj_stat,
min_stat,
lmin_s,
gflags;
int mtype;
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 ||
snprintf(kt_path, kt_len, "KRB5_KTNAME=%s",
pg_krb_server_keyfile) != kt_len - 2 ||
putenv(kt_path) != 0)
{
ereport(LOG,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
return STATUS_ERROR;
}
}
}
/*
* 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 messages sent in both directions. First message is always from
* the client. All messages from client to server are password packets
* (type 'p').
*/
do
{
pq_startmsgread();
CHECK_FOR_INTERRUPTS();
mtype = pq_getbyte();
if (mtype != 'p')
{
/* Only log error if client didn't disconnect. */
if (mtype != EOF)
ereport(ERROR,
(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, PG_MAX_AUTH_TOKEN_LENGTH))
{
/* 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);
CHECK_FOR_INTERRUPTS();
if (port->gss->outbuf.length != 0)
{
/*
* Negotiation generated data to be sent to the client.
*/
elog(DEBUG4, "sending GSS response token of length %u",
(unsigned int) port->gss->outbuf.length);
sendAuthRequest(port, AUTH_REQ_GSS_CONT,
port->gss->outbuf.value, port->gss->outbuf.length);
gss_release_buffer(&lmin_s, &port->gss->outbuf);
}
if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
{
gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
pg_GSS_error(ERROR,
_("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);
}
return pg_GSS_checkauth(port);
}
/*
* Check whether the GSSAPI-authenticated user is allowed to connect as the
* claimed username.
*/
static int
pg_GSS_checkauth(Port *port)
{
int ret;
OM_uint32 maj_stat,
min_stat,
lmin_s;
gss_buffer_desc gbuf;
/*
* 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,
_("retrieving GSS user name failed"),
maj_stat, min_stat);
/*
* Copy the original name of the authenticated principal into our backend
* memory for display later.
*/
port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
/*
* 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 ret;
}
#endif /* ENABLE_GSS */
/*----------------------------------------------------------------
* SSPI authentication system
*----------------------------------------------------------------
*/
#ifdef ENABLE_SSPI
/*
* Generate an error for SSPI authentication. The caller should apply
* _() to errmsg to make it translatable.
*/
static void
pg_SSPI_error(int severity, const char *errmsg, SECURITY_STATUS r)
{
char sysmsg[256];
if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, r, 0,
sysmsg, sizeof(sysmsg), NULL) == 0)
ereport(severity,
(errmsg_internal("%s", errmsg),
errdetail_internal("SSPI error %x", (unsigned int) r)));
else
ereport(severity,
(errmsg_internal("%s", errmsg),
errdetail_internal("%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, _("could not acquire SSPI credentials"), r);
/*
* Loop through SSPI message exchange. This exchange can consist of
* multiple messages sent in both directions. First message is always from
* the client. All messages from client to server are password packets
* (type 'p').
*/
do
{
pq_startmsgread();
mtype = pq_getbyte();
if (mtype != 'p')
{
/* Only log error if client didn't disconnect. */
if (mtype != EOF)
ereport(ERROR,
(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, PG_MAX_AUTH_TOKEN_LENGTH))
{
/* 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,
port->gss->outbuf.value, port->gss->outbuf.length);
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,
_("could not accept SSPI security context"), r);
}
/*
* Overwrite the current context with the one we just received. If
* sspictx is NULL it was the first loop and we need to allocate a
* buffer for it. On subsequent runs, we can just overwrite the buffer
* contents since the size does not change.
*/
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: error code %lu",
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: error code %lu",
GetLastError())));
}
r = (_QuerySecurityContextToken) (sspictx, &token);
if (r != SEC_E_OK)
{
FreeLibrary(secur32);
pg_SSPI_error(ERROR,
_("could not get token from SSPI security 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 information buffer size: error code %lu",
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 token information: error code %lu",
GetLastError())));
CloseHandle(token);
if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
domainname, &domainnamesize, &accountnameuse))
ereport(ERROR,
(errmsg_internal("could not look up account SID: error code %lu",
GetLastError())));
free(tokenuser);
if (!port->hba->compat_realm)
{
int status = pg_SSPI_make_upn(accountname, sizeof(accountname),
domainname, sizeof(domainname),
port->hba->upn_username);
if (status != STATUS_OK)
/* Error already reported from pg_SSPI_make_upn */
return status;
}
/*
* 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) != 0)
{
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 = psprintf("%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);
}
/*
* Replaces the domainname with the Kerberos realm name,
* and optionally the accountname with the Kerberos user name.
*/
static int
pg_SSPI_make_upn(char *accountname,
size_t accountnamesize,
char *domainname,
size_t domainnamesize,
bool update_accountname)
{
char *samname;
char *upname = NULL;
char *p = NULL;
ULONG upnamesize = 0;
size_t upnamerealmsize;
BOOLEAN res;
/*
* Build SAM name (DOMAIN\user), then translate to UPN
* (user@kerberos.realm). The realm name is returned in lower case, but
* that is fine because in SSPI auth, string comparisons are always
* case-insensitive.
*/
samname = psprintf("%s\\%s", domainname, accountname);
res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
NULL, &upnamesize);
if ((!res && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|| upnamesize == 0)
{
pfree(samname);
ereport(LOG,
(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
errmsg("could not translate name")));
return STATUS_ERROR;
}
/* upnamesize includes the terminating NUL. */
upname = palloc(upnamesize);
res = TranslateName(samname, NameSamCompatible, NameUserPrincipal,
upname, &upnamesize);
pfree(samname);
if (res)
p = strchr(upname, '@');
if (!res || p == NULL)
{
pfree(upname);
ereport(LOG,
(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
errmsg("could not translate name")));
return STATUS_ERROR;
}
/* Length of realm name after the '@', including the NUL. */
upnamerealmsize = upnamesize - (p - upname + 1);
/* Replace domainname with realm name. */
if (upnamerealmsize > domainnamesize)
{
pfree(upname);
ereport(LOG,
(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
errmsg("realm name too long")));
return STATUS_ERROR;
}
/* Length is now safe. */
strcpy(domainname, p + 1);
/* Replace account name as well (in case UPN != SAM)? */
if (update_accountname)
{
if ((p - upname + 1) > accountnamesize)
{
pfree(upname);
ereport(LOG,
(errcode(ERRCODE_INVALID_ROLE_SPECIFICATION),
errmsg("translated account name too long")));
return STATUS_ERROR;
}
*p = 0;
strcpy(accountname, upname);
}
pfree(upname);
return STATUS_OK;
}
#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 "remote_addr" and find out who
* owns the tcp connection to "local_addr"
* If the username is successfully retrieved, check the usermap.
*
* XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
* latch was set would improve the responsiveness to timeouts/cancellations.
*/
static int
ident_inet(hbaPort *port)
{
const SockAddr remote_addr = port->raddr;
const SockAddr local_addr = port->laddr;
char ident_user[IDENT_USERNAME_MAX + 1];
pgsocket sock_fd = PGINVALID_SOCKET; /* for talking to Ident server */
int 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)
{
/* we don't expect this to happen */
ident_return = false;
goto ident_inet_done;
}
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)
{
/* we don't expect this to happen */
ident_return = false;
goto ident_inet_done;
}
sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
ident_serv->ai_protocol);
if (sock_fd == PGINVALID_SOCKET)
{
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
{
CHECK_FOR_INTERRUPTS();
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
{
CHECK_FOR_INTERRUPTS();
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 != PGINVALID_SOCKET)
closesocket(sock_fd);
if (ident_serv)
pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
if (la)
pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
if (ident_return)
/* Success! Check the usermap */
return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
return STATUS_ERROR;
}
/*
* Ask kernel about the credentials of the connecting process,
* determine the symbolic name of the corresponding user, and check
* if valid per the usermap.
*
* Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
*/
#ifdef HAVE_UNIX_SOCKETS
static int
auth_peer(hbaPort *port)
{
char ident_user[IDENT_USERNAME_MAX + 1];
uid_t uid;
gid_t gid;
struct passwd *pw;
if (getpeereid(port->sock, &uid, &gid) != 0)
{
/* Provide special error message if getpeereid is a stub */
if (errno == ENOSYS)
ereport(LOG,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("peer authentication is not supported on this platform")));
else
ereport(LOG,
(errcode_for_socket_access(),
errmsg("could not get peer credentials: %m")));
return STATUS_ERROR;
}
errno = 0; /* clear errno before call */
pw = getpwuid(uid);
if (!pw)
{
int save_errno = errno;
ereport(LOG,
(errmsg("could not look up local user ID %ld: %s",
(long) uid,
save_errno ? strerror(save_errno) : _("user does not exist"))));
return STATUS_ERROR;
}
strlcpy(ident_user, pw->pw_name, IDENT_USERNAME_MAX + 1);
return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
}
#endif /* HAVE_UNIX_SOCKETS */
/*----------------------------------------------------------------
* PAM authentication system
*----------------------------------------------------------------
*/
#ifdef USE_PAM
/*
* PAM conversation function
*/
static int
pam_passwd_conv_proc(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
const char *passwd;
struct pam_response *reply;
int i;
if (appdata_ptr)
passwd = (char *) appdata_ptr;
else
{
/*
* Workaround for Solaris 2.6 where the PAM library is broken and does
* not pass appdata_ptr to the conversation routine
*/
passwd = pam_passwd;
}
*resp = NULL; /* in case of error exit */
if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
return PAM_CONV_ERR;
/*
* Explicitly not using palloc here - PAM will free this memory in
* pam_end()
*/
if ((reply = calloc(num_msg, sizeof(struct pam_response))) == NULL)
{
ereport(LOG,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
return PAM_CONV_ERR;
}
for (i = 0; i < num_msg; i++)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_OFF:
if (strlen(passwd) == 0)
{
/*
* Password wasn't passed to PAM the first time around -
* let's go ask the client to send a password, which we
* then stuff into PAM.
*/
sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(pam_port_cludge);
if (passwd == NULL)
{
/*
* Client didn't want to send password. We
* intentionally do not log anything about this.
*/
goto fail;
}
}
if ((reply[i].resp = strdup(passwd)) == NULL)
goto fail;
reply[i].resp_retcode = PAM_SUCCESS;
break;
case PAM_ERROR_MSG:
ereport(LOG,
(errmsg("error from underlying PAM layer: %s",
msg[i]->msg)));
/* FALL THROUGH */
case PAM_TEXT_INFO:
/* we don't bother to log TEXT_INFO messages */
if ((reply[i].resp = strdup("")) == NULL)
goto fail;
reply[i].resp_retcode = PAM_SUCCESS;
break;
default:
elog(LOG, "unsupported PAM conversation %d/\"%s\"",
msg[i]->msg_style,
msg[i]->msg ? msg[i]->msg : "(none)");
goto fail;
}
}
*resp = reply;
return PAM_SUCCESS;
fail:
/* free up whatever we allocated */
for (i = 0; i < num_msg; i++)
{
if (reply[i].resp != NULL)
free(reply[i].resp);
}
free(reply);
return PAM_CONV_ERR;
}
/*
* Check authentication against PAM.
*/
static int
CheckPAMAuth(Port *port, const char *user, const char *password)
{
int retval;
pam_handle_t *pamh = NULL;
/*
* We can't entirely rely on PAM to pass through appdata --- it appears
* not to work on at least Solaris 2.6. So use these ugly static
* variables instead.
*/
pam_passwd = password;
pam_port_cludge = port;
/*
* Set the application data portion of the conversation struct. This is
* later used inside the PAM conversation to pass the password to the
* authentication module.
*/
pam_passw_conv.appdata_ptr = unconstify(char *, password); /* from password above,
* not allocated */
/* Optionally, one can set the service name in pg_hba.conf */
if (port->hba->pamservice && port->hba->pamservice[0] != '\0')
retval = pam_start(port->hba->pamservice, "pgsql@",
&pam_passw_conv, &pamh);
else
retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
&pam_passw_conv, &pamh);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("could not create PAM authenticator: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL; /* Unset pam_passwd */
return STATUS_ERROR;
}
retval = pam_set_item(pamh, PAM_USER, user);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("pam_set_item(PAM_USER) failed: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL; /* Unset pam_passwd */
return STATUS_ERROR;
}
if (port->hba->conntype != ctLocal)
{
char hostinfo[NI_MAXHOST];
int flags;
if (port->hba->pam_use_hostname)
flags = 0;
else
flags = NI_NUMERICHOST | NI_NUMERICSERV;
retval = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
hostinfo, sizeof(hostinfo), NULL, 0,
flags);
if (retval != 0)
{
ereport(WARNING,
(errmsg_internal("pg_getnameinfo_all() failed: %s",
gai_strerror(retval))));
return STATUS_ERROR;
}
retval = pam_set_item(pamh, PAM_RHOST, hostinfo);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("pam_set_item(PAM_RHOST) failed: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL;
return STATUS_ERROR;
}
}
retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("pam_set_item(PAM_CONV) failed: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL; /* Unset pam_passwd */
return STATUS_ERROR;
}
retval = pam_authenticate(pamh, 0);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("pam_authenticate failed: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL; /* Unset pam_passwd */
return STATUS_ERROR;
}
retval = pam_acct_mgmt(pamh, 0);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("pam_acct_mgmt failed: %s",
pam_strerror(pamh, retval))));
pam_passwd = NULL; /* Unset pam_passwd */
return STATUS_ERROR;
}
retval = pam_end(pamh, retval);
if (retval != PAM_SUCCESS)
{
ereport(LOG,
(errmsg("could not release PAM authenticator: %s",
pam_strerror(pamh, retval))));
}
pam_passwd = NULL; /* Unset pam_passwd */
return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
}
#endif /* USE_PAM */
/*----------------------------------------------------------------
* BSD authentication system
*----------------------------------------------------------------
*/
#ifdef USE_BSD_AUTH
static int
CheckBSDAuth(Port *port, char *user)
{
char *passwd;
int retval;
/* Send regular password request to client, and get the response */
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
return STATUS_EOF;
/*
* Ask the BSD auth system to verify password. Note that auth_userokay
* will overwrite the password string with zeroes, but it's just a
* temporary string so we don't care.
*/
retval = auth_userokay(user, NULL, "auth-postgresql", passwd);
pfree(passwd);
if (!retval)
return STATUS_ERROR;
return STATUS_OK;
}
#endif /* USE_BSD_AUTH */
/*----------------------------------------------------------------
* LDAP authentication system
*----------------------------------------------------------------
*/
#ifdef USE_LDAP
static int errdetail_for_ldap(LDAP *ldap);
/*
* Initialize a connection to the LDAP server, including setting up
* TLS if requested.
*/
static int
InitializeLDAPConnection(Port *port, LDAP **ldap)
{
const char *scheme;
int ldapversion = LDAP_VERSION3;
int r;
scheme = port->hba->ldapscheme;
if (scheme == NULL)
scheme = "ldap";
#ifdef WIN32
if (strcmp(scheme, "ldaps") == 0)
*ldap = ldap_sslinit(port->hba->ldapserver, port->hba->ldapport, 1);
else
*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
if (!*ldap)
{
ereport(LOG,
(errmsg("could not initialize LDAP: error code %d",
(int) LdapGetLastError())));
return STATUS_ERROR;
}
#else
#ifdef HAVE_LDAP_INITIALIZE
/*
* OpenLDAP provides a non-standard extension ldap_initialize() that takes
* a list of URIs, allowing us to request "ldaps" instead of "ldap". It
* also provides ldap_domain2hostlist() to find LDAP servers automatically
* using DNS SRV. They were introduced in the same version, so for now we
* don't have an extra configure check for the latter.
*/
{
StringInfoData uris;
char *hostlist = NULL;
char *p;
bool append_port;
/* We'll build a space-separated scheme://hostname:port list here */
initStringInfo(&uris);
/*
* If pg_hba.conf provided no hostnames, we can ask OpenLDAP to try to
* find some by extracting a domain name from the base DN and looking
* up DSN SRV records for _ldap._tcp.<domain>.
*/
if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
{
char *domain;
/* ou=blah,dc=foo,dc=bar -> foo.bar */
if (ldap_dn2domain(port->hba->ldapbasedn, &domain))
{
ereport(LOG,
(errmsg("could not extract domain name from ldapbasedn")));
return STATUS_ERROR;
}
/* Look up a list of LDAP server hosts and port numbers */
if (ldap_domain2hostlist(domain, &hostlist))
{
ereport(LOG,
(errmsg("LDAP authentication could not find DNS SRV records for \"%s\"",
domain),
(errhint("Set an LDAP server name explicitly."))));
ldap_memfree(domain);
return STATUS_ERROR;
}
ldap_memfree(domain);
/* We have a space-separated list of host:port entries */
p = hostlist;
append_port = false;
}
else
{
/* We have a space-separated list of hosts from pg_hba.conf */
p = port->hba->ldapserver;
append_port = true;
}
/* Convert the list of host[:port] entries to full URIs */
do
{
size_t size;
/* Find the span of the next entry */
size = strcspn(p, " ");
/* Append a space separator if this isn't the first URI */
if (uris.len > 0)
appendStringInfoChar(&uris, ' ');
/* Append scheme://host:port */
appendStringInfoString(&uris, scheme);
appendStringInfoString(&uris, "://");
appendBinaryStringInfo(&uris, p, size);
if (append_port)
appendStringInfo(&uris, ":%d", port->hba->ldapport);
/* Step over this entry and any number of trailing spaces */
p += size;
while (*p == ' ')
++p;
} while (*p);
/* Free memory from OpenLDAP if we looked up SRV records */
if (hostlist)
ldap_memfree(hostlist);
/* Finally, try to connect using the URI list */
r = ldap_initialize(ldap, uris.data);
pfree(uris.data);
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("could not initialize LDAP: %s",
ldap_err2string(r))));
return STATUS_ERROR;
}
}
#else
if (strcmp(scheme, "ldaps") == 0)
{
ereport(LOG,
(errmsg("ldaps not supported with this LDAP library")));
return STATUS_ERROR;
}
*ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
if (!*ldap)
{
ereport(LOG,
(errmsg("could not initialize LDAP: %m")));
return STATUS_ERROR;
}
#endif
#endif
if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("could not set LDAP protocol version: %s",
ldap_err2string(r)),
errdetail_for_ldap(*ldap)));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
if (port->hba->ldaptls)
{
#ifndef WIN32
if ((r = ldap_start_tls_s(*ldap, NULL, NULL)) != LDAP_SUCCESS)
#else
static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;
if (_ldap_start_tls_sA == NULL)
{
/*
* Need to load this function dynamically because it does not
* exist on Windows 2000, and causes a load error for the whole
* exe if referenced.
*/
HANDLE ldaphandle;
ldaphandle = LoadLibrary("WLDAP32.DLL");
if (ldaphandle == NULL)
{
/*
* should never happen since we import other files from
* wldap32, but check anyway
*/
ereport(LOG,
(errmsg("could not load wldap32.dll")));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
_ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
if (_ldap_start_tls_sA == NULL)
{
ereport(LOG,
(errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"),
errdetail("LDAP over SSL is not supported on this platform.")));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
/*
* Leak LDAP handle on purpose, because we need the library to
* stay open. This is ok because it will only ever be leaked once
* per process and is automatically cleaned up on process exit.
*/
}
if ((r = _ldap_start_tls_sA(*ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
#endif
{
ereport(LOG,
(errmsg("could not start LDAP TLS session: %s",
ldap_err2string(r)),
errdetail_for_ldap(*ldap)));
ldap_unbind(*ldap);
return STATUS_ERROR;
}
}
return STATUS_OK;
}
/* Placeholders recognized by FormatSearchFilter. For now just one. */
#define LPH_USERNAME "$username"
#define LPH_USERNAME_LEN (sizeof(LPH_USERNAME) - 1)
/* Not all LDAP implementations define this. */
#ifndef LDAP_NO_ATTRS
#define LDAP_NO_ATTRS "1.1"
#endif
/* Not all LDAP implementations define this. */
#ifndef LDAPS_PORT
#define LDAPS_PORT 636
#endif
/*
* Return a newly allocated C string copied from "pattern" with all
* occurrences of the placeholder "$username" replaced with "user_name".
*/
static char *
FormatSearchFilter(const char *pattern, const char *user_name)
{
StringInfoData output;
initStringInfo(&output);
while (*pattern != '\0')
{
if (strncmp(pattern, LPH_USERNAME, LPH_USERNAME_LEN) == 0)
{
appendStringInfoString(&output, user_name);
pattern += LPH_USERNAME_LEN;
}
else
appendStringInfoChar(&output, *pattern++);
}
return output.data;
}
/*
* Perform LDAP authentication
*/
static int
CheckLDAPAuth(Port *port)
{
char *passwd;
LDAP *ldap;
int r;
char *fulluser;
const char *server_name;
#ifdef HAVE_LDAP_INITIALIZE
/*
* For OpenLDAP, allow empty hostname if we have a basedn. We'll look for
* servers with DNS SRV records via OpenLDAP library facilities.
*/
if ((!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') &&
(!port->hba->ldapbasedn || port->hba->ldapbasedn[0] == '\0'))
{
ereport(LOG,
(errmsg("LDAP server not specified, and no ldapbasedn")));
return STATUS_ERROR;
}
#else
if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0')
{
ereport(LOG,
(errmsg("LDAP server not specified")));
return STATUS_ERROR;
}
#endif
/*
* If we're using SRV records, we don't have a server name so we'll just
* show an empty string in error messages.
*/
server_name = port->hba->ldapserver ? port->hba->ldapserver : "";
if (port->hba->ldapport == 0)
{
if (port->hba->ldapscheme != NULL &&
strcmp(port->hba->ldapscheme, "ldaps") == 0)
port->hba->ldapport = LDAPS_PORT;
else
port->hba->ldapport = LDAP_PORT;
}
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR)
{
/* Error message already sent */
pfree(passwd);
return STATUS_ERROR;
}
if (port->hba->ldapbasedn)
{
/*
* First perform an LDAP search to find the DN for the user we are
* trying to log in as.
*/
char *filter;
LDAPMessage *search_message;
LDAPMessage *entry;
char *attributes[] = {LDAP_NO_ATTRS, NULL};
char *dn;
char *c;
int count;
/*
* Disallow any characters that we would otherwise need to escape,
* since they aren't really reasonable in a username anyway. Allowing
* them would make it possible to inject any kind of custom filters in
* the LDAP filter.
*/
for (c = port->user_name; *c; c++)
{
if (*c == '*' ||
*c == '(' ||
*c == ')' ||
*c == '\\' ||
*c == '/')
{
ereport(LOG,
(errmsg("invalid character in user name for LDAP authentication")));
ldap_unbind(ldap);
pfree(passwd);
return STATUS_ERROR;
}
}
/*
* Bind with a pre-defined username/password (if available) for
* searching. If none is specified, this turns into an anonymous bind.
*/
r = ldap_simple_bind_s(ldap,
port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
port->hba->ldapbindpasswd ? port->hba->ldapbindpasswd : "");
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("could not perform initial LDAP bind for ldapbinddn \"%s\" on server \"%s\": %s",
port->hba->ldapbinddn ? port->hba->ldapbinddn : "",
server_name,
ldap_err2string(r)),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
return STATUS_ERROR;
}
/* Build a custom filter or a single attribute filter? */
if (port->hba->ldapsearchfilter)
filter = FormatSearchFilter(port->hba->ldapsearchfilter, port->user_name);
else if (port->hba->ldapsearchattribute)
filter = psprintf("(%s=%s)", port->hba->ldapsearchattribute, port->user_name);
else
filter = psprintf("(uid=%s)", port->user_name);
r = ldap_search_s(ldap,
port->hba->ldapbasedn,
port->hba->ldapscope,
filter,
attributes,
0,
&search_message);
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("could not search LDAP for filter \"%s\" on server \"%s\": %s",
filter, server_name, ldap_err2string(r)),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
return STATUS_ERROR;
}
count = ldap_count_entries(ldap, search_message);
if (count != 1)
{
if (count == 0)
ereport(LOG,
(errmsg("LDAP user \"%s\" does not exist", port->user_name),
errdetail("LDAP search for filter \"%s\" on server \"%s\" returned no entries.",
filter, server_name)));
else
ereport(LOG,
(errmsg("LDAP user \"%s\" is not unique", port->user_name),
errdetail_plural("LDAP search for filter \"%s\" on server \"%s\" returned %d entry.",
"LDAP search for filter \"%s\" on server \"%s\" returned %d entries.",
count,
filter, server_name, count)));
ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
return STATUS_ERROR;
}
entry = ldap_first_entry(ldap, search_message);
dn = ldap_get_dn(ldap, entry);
if (dn == NULL)
{
int error;
(void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error);
ereport(LOG,
(errmsg("could not get dn for the first entry matching \"%s\" on server \"%s\": %s",
filter, server_name,
ldap_err2string(error)),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
pfree(filter);
ldap_msgfree(search_message);
return STATUS_ERROR;
}
fulluser = pstrdup(dn);
pfree(filter);
ldap_memfree(dn);
ldap_msgfree(search_message);
/* Unbind and disconnect from the LDAP server */
r = ldap_unbind_s(ldap);
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("could not unbind after searching for user \"%s\" on server \"%s\"",
fulluser, server_name)));
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
/*
* Need to re-initialize the LDAP connection, so that we can bind to
* it with a different username.
*/
if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR)
{
pfree(passwd);
pfree(fulluser);
/* Error message already sent */
return STATUS_ERROR;
}
}
else
fulluser = psprintf("%s%s%s",
port->hba->ldapprefix ? port->hba->ldapprefix : "",
port->user_name,
port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
r = ldap_simple_bind_s(ldap, fulluser, passwd);
if (r != LDAP_SUCCESS)
{
ereport(LOG,
(errmsg("LDAP login failed for user \"%s\" on server \"%s\": %s",
fulluser, server_name, ldap_err2string(r)),
errdetail_for_ldap(ldap)));
ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_ERROR;
}
ldap_unbind(ldap);
pfree(passwd);
pfree(fulluser);
return STATUS_OK;
}
/*
* Add a detail error message text to the current error if one can be
* constructed from the LDAP 'diagnostic message'.
*/
static int
errdetail_for_ldap(LDAP *ldap)
{
char *message;
int rc;
rc = ldap_get_option(ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &message);
if (rc == LDAP_SUCCESS && message != NULL)
{
errdetail("LDAP diagnostics: %s", message);
ldap_memfree(message);
}
return 0;
}
#endif /* USE_LDAP */
/*----------------------------------------------------------------
* SSL client certificate authentication
*----------------------------------------------------------------
*/
#ifdef USE_SSL
static int
CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
Assert(port->ssl);
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
strlen(port->peer_cn) <= 0)
{
ereport(LOG,
(errmsg("certificate authentication failed for user \"%s\": client certificate contains no user name",
port->user_name)));
return STATUS_ERROR;
}
/* Just pass the certificate cn to the usermap check */
status_check_usermap = check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
if (status_check_usermap != STATUS_OK)
{
/*
* If clientcert=verify-full was specified and the authentication
* method is other than uaCert, log the reason for rejecting the
* authentication.
*/
if (port->hba->clientcert == clientCertFull && port->hba->auth_method != uaCert)
{
ereport(LOG,
(errmsg("certificate validation (clientcert=verify-full) failed for user \"%s\": CN mismatch",
port->user_name)));
}
}
return status_check_usermap;
}
#endif
/*----------------------------------------------------------------
* RADIUS authentication
*----------------------------------------------------------------
*/
/*
* RADIUS authentication is described in RFC2865 (and several others).
*/
#define RADIUS_VECTOR_LENGTH 16
#define RADIUS_HEADER_LENGTH 20
#define RADIUS_MAX_PASSWORD_LENGTH 128
/* Maximum size of a RADIUS packet we will create or accept */
#define RADIUS_BUFFER_SIZE 1024
typedef struct
{
uint8 attribute;
uint8 length;
uint8 data[FLEXIBLE_ARRAY_MEMBER];
} radius_attribute;
typedef struct
{
uint8 code;
uint8 id;
uint16 length;
uint8 vector[RADIUS_VECTOR_LENGTH];
/* this is a bit longer than strictly necessary: */
char pad[RADIUS_BUFFER_SIZE - RADIUS_VECTOR_LENGTH];
} radius_packet;
/* RADIUS packet types */
#define RADIUS_ACCESS_REQUEST 1
#define RADIUS_ACCESS_ACCEPT 2
#define RADIUS_ACCESS_REJECT 3
/* RADIUS attributes */
#define RADIUS_USER_NAME 1
#define RADIUS_PASSWORD 2
#define RADIUS_SERVICE_TYPE 6
#define RADIUS_NAS_IDENTIFIER 32
/* RADIUS service types */
#define RADIUS_AUTHENTICATE_ONLY 8
/* Seconds to wait - XXX: should be in a config variable! */
#define RADIUS_TIMEOUT 3
static void
radius_add_attribute(radius_packet *packet, uint8 type, const unsigned char *data, int len)
{
radius_attribute *attr;
if (packet->length + len > RADIUS_BUFFER_SIZE)
{
/*
* With remotely realistic data, this can never happen. But catch it
* just to make sure we don't overrun a buffer. We'll just skip adding
* the broken attribute, which will in the end cause authentication to
* fail.
*/
elog(WARNING,
"Adding attribute code %d with length %d to radius packet would create oversize packet, ignoring",
type, len);
return;
}
attr = (radius_attribute *) ((unsigned char *) packet + packet->length);
attr->attribute = type;
attr->length = len + 2; /* total size includes type and length */
memcpy(attr->data, data, len);
packet->length += attr->length;
}
static int
CheckRADIUSAuth(Port *port)
{
char *passwd;
ListCell *server,
*secrets,
*radiusports,
*identifiers;
/* Make sure struct alignment is correct */
Assert(offsetof(radius_packet, vector) == 4);
/* Verify parameters */
if (list_length(port->hba->radiusservers) < 1)
{
ereport(LOG,
(errmsg("RADIUS server not specified")));
return STATUS_ERROR;
}
if (list_length(port->hba->radiussecrets) < 1)
{
ereport(LOG,
(errmsg("RADIUS secret not specified")));
return STATUS_ERROR;
}
/* Send regular password request to client, and get the response */
sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
passwd = recv_password_packet(port);
if (passwd == NULL)
return STATUS_EOF; /* client wouldn't send password */
if (strlen(passwd) > RADIUS_MAX_PASSWORD_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH)));
pfree(passwd);
return STATUS_ERROR;
}
/*
* Loop over and try each server in order.
*/
secrets = list_head(port->hba->radiussecrets);
radiusports = list_head(port->hba->radiusports);
identifiers = list_head(port->hba->radiusidentifiers);
foreach(server, port->hba->radiusservers)
{
int ret = PerformRadiusTransaction(lfirst(server),
lfirst(secrets),
radiusports ? lfirst(radiusports) : NULL,
identifiers ? lfirst(identifiers) : NULL,
port->user_name,
passwd);
/*------
* STATUS_OK = Login OK
* STATUS_ERROR = Login not OK, but try next server
* STATUS_EOF = Login not OK, and don't try next server
*------
*/
if (ret == STATUS_OK)
{
pfree(passwd);
return STATUS_OK;
}
else if (ret == STATUS_EOF)
{
pfree(passwd);
return STATUS_ERROR;
}
/*
* secret, port and identifiers either have length 0 (use default),
* length 1 (use the same everywhere) or the same length as servers.
* So if the length is >1, we advance one step. In other cases, we
* don't and will then reuse the correct value.
*/
if (list_length(port->hba->radiussecrets) > 1)
secrets = lnext(port->hba->radiussecrets, secrets);
if (list_length(port->hba->radiusports) > 1)
radiusports = lnext(port->hba->radiusports, radiusports);
if (list_length(port->hba->radiusidentifiers) > 1)
identifiers = lnext(port->hba->radiusidentifiers, identifiers);
}
/* No servers left to try, so give up */
pfree(passwd);
return STATUS_ERROR;
}
static int
PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd)
{
radius_packet radius_send_pack;
radius_packet radius_recv_pack;
radius_packet *packet = &radius_send_pack;
radius_packet *receivepacket = &radius_recv_pack;
char *radius_buffer = (char *) &radius_send_pack;
char *receive_buffer = (char *) &radius_recv_pack;
int32 service = pg_hton32(RADIUS_AUTHENTICATE_ONLY);
uint8 *cryptvector;
int encryptedpasswordlen;
uint8 encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH];
uint8 *md5trailer;
int packetlength;
pgsocket sock;
#ifdef HAVE_IPV6
struct sockaddr_in6 localaddr;
struct sockaddr_in6 remoteaddr;
#else
struct sockaddr_in localaddr;
struct sockaddr_in remoteaddr;
#endif
struct addrinfo hint;
struct addrinfo *serveraddrs;
int port;
ACCEPT_TYPE_ARG3 addrsize;
fd_set fdset;
struct timeval endtime;
int i,
j,
r;
/* Assign default values */
if (portstr == NULL)
portstr = "1812";
if (identifier == NULL)
identifier = "postgresql";
MemSet(&hint, 0, sizeof(hint));
hint.ai_socktype = SOCK_DGRAM;
hint.ai_family = AF_UNSPEC;
port = atoi(portstr);
r = pg_getaddrinfo_all(server, portstr, &hint, &serveraddrs);
if (r || !serveraddrs)
{
ereport(LOG,
(errmsg("could not translate RADIUS server name \"%s\" to address: %s",
server, gai_strerror(r))));
if (serveraddrs)
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
/* XXX: add support for multiple returned addresses? */
/* Construct RADIUS packet */
packet->code = RADIUS_ACCESS_REQUEST;
packet->length = RADIUS_HEADER_LENGTH;
if (!pg_strong_random(packet->vector, RADIUS_VECTOR_LENGTH))
{
ereport(LOG,
(errmsg("could not generate random encryption vector")));
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
packet->id = packet->vector[0];
radius_add_attribute(packet, RADIUS_SERVICE_TYPE, (const unsigned char *) &service, sizeof(service));
radius_add_attribute(packet, RADIUS_USER_NAME, (const unsigned char *) user_name, strlen(user_name));
radius_add_attribute(packet, RADIUS_NAS_IDENTIFIER, (const unsigned char *) identifier, strlen(identifier));
/*
* RADIUS password attributes are calculated as: e[0] = p[0] XOR
* MD5(secret + Request Authenticator) for the first group of 16 octets,
* and then: e[i] = p[i] XOR MD5(secret + e[i-1]) for the following ones
* (if necessary)
*/
encryptedpasswordlen = ((strlen(passwd) + RADIUS_VECTOR_LENGTH - 1) / RADIUS_VECTOR_LENGTH) * RADIUS_VECTOR_LENGTH;
cryptvector = palloc(strlen(secret) + RADIUS_VECTOR_LENGTH);
memcpy(cryptvector, secret, strlen(secret));
/* for the first iteration, we use the Request Authenticator vector */
md5trailer = packet->vector;
for (i = 0; i < encryptedpasswordlen; i += RADIUS_VECTOR_LENGTH)
{
memcpy(cryptvector + strlen(secret), md5trailer, RADIUS_VECTOR_LENGTH);
/*
* .. and for subsequent iterations the result of the previous XOR
* (calculated below)
*/
md5trailer = encryptedpassword + i;
if (!pg_md5_binary(cryptvector, strlen(secret) + RADIUS_VECTOR_LENGTH, encryptedpassword + i))
{
ereport(LOG,
(errmsg("could not perform MD5 encryption of password")));
pfree(cryptvector);
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
for (j = i; j < i + RADIUS_VECTOR_LENGTH; j++)
{
if (j < strlen(passwd))
encryptedpassword[j] = passwd[j] ^ encryptedpassword[j];
else
encryptedpassword[j] = '\0' ^ encryptedpassword[j];
}
}
pfree(cryptvector);
radius_add_attribute(packet, RADIUS_PASSWORD, encryptedpassword, encryptedpasswordlen);
/* Length needs to be in network order on the wire */
packetlength = packet->length;
packet->length = pg_hton16(packet->length);
sock = socket(serveraddrs[0].ai_family, SOCK_DGRAM, 0);
if (sock == PGINVALID_SOCKET)
{
ereport(LOG,
(errmsg("could not create RADIUS socket: %m")));
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
memset(&localaddr, 0, sizeof(localaddr));
#ifdef HAVE_IPV6
localaddr.sin6_family = serveraddrs[0].ai_family;
localaddr.sin6_addr = in6addr_any;
if (localaddr.sin6_family == AF_INET6)
addrsize = sizeof(struct sockaddr_in6);
else
addrsize = sizeof(struct sockaddr_in);
#else
localaddr.sin_family = serveraddrs[0].ai_family;
localaddr.sin_addr.s_addr = INADDR_ANY;
addrsize = sizeof(struct sockaddr_in);
#endif
if (bind(sock, (struct sockaddr *) &localaddr, addrsize))
{
ereport(LOG,
(errmsg("could not bind local RADIUS socket: %m")));
closesocket(sock);
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
if (sendto(sock, radius_buffer, packetlength, 0,
serveraddrs[0].ai_addr, serveraddrs[0].ai_addrlen) < 0)
{
ereport(LOG,
(errmsg("could not send RADIUS packet: %m")));
closesocket(sock);
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
return STATUS_ERROR;
}
/* Don't need the server address anymore */
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
/*
* Figure out at what time we should time out. We can't just use a single
* call to select() with a timeout, since somebody can be sending invalid
* packets to our port thus causing us to retry in a loop and never time
* out.
*
* XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
* the latch was set would improve the responsiveness to
* timeouts/cancellations.
*/
gettimeofday(&endtime, NULL);
endtime.tv_sec += RADIUS_TIMEOUT;
while (true)
{
struct timeval timeout;
struct timeval now;
int64 timeoutval;
gettimeofday(&now, NULL);
timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
if (timeoutval <= 0)
{
ereport(LOG,
(errmsg("timeout waiting for RADIUS response from %s",
server)));
closesocket(sock);
return STATUS_ERROR;
}
timeout.tv_sec = timeoutval / 1000000;
timeout.tv_usec = timeoutval % 1000000;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
r = select(sock + 1, &fdset, NULL, NULL, &timeout);
if (r < 0)
{
if (errno == EINTR)
continue;
/* Anything else is an actual error */
ereport(LOG,
(errmsg("could not check status on RADIUS socket: %m")));
closesocket(sock);
return STATUS_ERROR;
}
if (r == 0)
{
ereport(LOG,
(errmsg("timeout waiting for RADIUS response from %s",
server)));
closesocket(sock);
return STATUS_ERROR;
}
/*
* Attempt to read the response packet, and verify the contents.
*
* Any packet that's not actually a RADIUS packet, or otherwise does
* not validate as an explicit reject, is just ignored and we retry
* for another packet (until we reach the timeout). This is to avoid
* the possibility to denial-of-service the login by flooding the
* server with invalid packets on the port that we're expecting the
* RADIUS response on.
*/
addrsize = sizeof(remoteaddr);
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
(struct sockaddr *) &remoteaddr, &addrsize);
if (packetlength < 0)
{
ereport(LOG,
(errmsg("could not read RADIUS response: %m")));
closesocket(sock);
return STATUS_ERROR;
}
#ifdef HAVE_IPV6
if (remoteaddr.sin6_port != pg_hton16(port))
#else
if (remoteaddr.sin_port != pg_hton16(port))
#endif
{
#ifdef HAVE_IPV6
ereport(LOG,
(errmsg("RADIUS response from %s was sent from incorrect port: %d",
server, pg_ntoh16(remoteaddr.sin6_port))));
#else
ereport(LOG,
(errmsg("RADIUS response from %s was sent from incorrect port: %d",
server, pg_ntoh16(remoteaddr.sin_port))));
#endif
continue;
}
if (packetlength < RADIUS_HEADER_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS response from %s too short: %d", server, packetlength)));
continue;
}
if (packetlength != pg_ntoh16(receivepacket->length))
{
ereport(LOG,
(errmsg("RADIUS response from %s has corrupt length: %d (actual length %d)",
server, pg_ntoh16(receivepacket->length), packetlength)));
continue;
}
if (packet->id != receivepacket->id)
{
ereport(LOG,
(errmsg("RADIUS response from %s is to a different request: %d (should be %d)",
server, receivepacket->id, packet->id)));
continue;
}
/*
* Verify the response authenticator, which is calculated as
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
*/
cryptvector = palloc(packetlength + strlen(secret));
memcpy(cryptvector, receivepacket, 4); /* code+id+length */
memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
* authenticator, from
* original packet */
if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no
* attributes at all */
memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
memcpy(cryptvector + packetlength, secret, strlen(secret));
if (!pg_md5_binary(cryptvector,
packetlength + strlen(secret),
encryptedpassword))
{
ereport(LOG,
(errmsg("could not perform MD5 encryption of received packet")));
pfree(cryptvector);
continue;
}
pfree(cryptvector);
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
{
ereport(LOG,
(errmsg("RADIUS response from %s has incorrect MD5 signature",
server)));
continue;
}
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
{
closesocket(sock);
return STATUS_OK;
}
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
{
closesocket(sock);
return STATUS_EOF;
}
else
{
ereport(LOG,
(errmsg("RADIUS response from %s has invalid code (%d) for user \"%s\"",
server, receivepacket->code, user_name)));
continue;
}
} /* while (true) */
}