/*------------------------------------------------------------------------- * * 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 #include #include #include #ifdef HAVE_SYS_SELECT_H #include #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 (per RFC 1413) */ #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); /*---------------------------------------------------------------- * Peer authentication *---------------------------------------------------------------- */ static int auth_peer(hbaPort *port); /*---------------------------------------------------------------- * PAM authentication *---------------------------------------------------------------- */ #ifdef USE_PAM #ifdef HAVE_PAM_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_APPL_H #include #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 */ static bool pam_no_password; /* For detecting no-password-given */ #endif /* USE_PAM */ /*---------------------------------------------------------------- * BSD authentication *---------------------------------------------------------------- */ #ifdef USE_BSD_AUTH #include 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 #else #include /* 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: status = auth_peer(port); 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 @ 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; } /*---------------------------------------------------------------- * Peer authentication system *---------------------------------------------------------------- */ /* * 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. */ static int auth_peer(hbaPort *port) { uid_t uid; gid_t gid; #ifndef WIN32 struct passwd *pw; char *peer_user; int ret; #endif 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; } #ifndef WIN32 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; } /* Make a copy of static getpw*() result area. */ peer_user = pstrdup(pw->pw_name); ret = check_usermap(port->hba->usermap, port->user_name, peer_user, false); pfree(peer_user); return ret; #else /* should have failed with ENOSYS above */ Assert(false); return STATUS_ERROR; #endif } /*---------------------------------------------------------------- * 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, * either here or at higher levels. */ pam_no_password = true; 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; pam_no_password = false; /* * 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) { /* If pam_passwd_conv_proc saw EOF, don't log anything */ if (!pam_no_password) ereport(LOG, (errmsg("pam_authenticate failed: %s", pam_strerror(pamh, retval)))); pam_passwd = NULL; /* Unset pam_passwd */ return pam_no_password ? STATUS_EOF : STATUS_ERROR; } retval = pam_acct_mgmt(pamh, 0); if (retval != PAM_SUCCESS) { /* If pam_passwd_conv_proc saw EOF, don't log anything */ if (!pam_no_password) ereport(LOG, (errmsg("pam_acct_mgmt failed: %s", pam_strerror(pamh, retval)))); pam_passwd = NULL; /* Unset pam_passwd */ return pam_no_password ? STATUS_EOF : 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.. */ 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) */ }