| /*------------------------------------------------------------------------- |
| * |
| * auth.c |
| * Routines to handle network authentication |
| * |
| * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.203 2010/07/06 19:18:56 momjian Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| #if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED) |
| #include <sys/uio.h> |
| #include <sys/ucred.h> |
| #endif |
| #ifdef HAVE_UCRED_H |
| #include <ucred.h> |
| #endif |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <unistd.h> |
| #include <stddef.h> |
| |
| #include "cdb/cdbvars.h" |
| #include "libpq/auth.h" |
| #include "libpq/crypt.h" |
| #include "libpq/ip.h" |
| #include "libpq/libpq.h" |
| #include "libpq/pqformat.h" |
| #include "libpq/md5.h" |
| #include "miscadmin.h" |
| #include "pgtime.h" |
| #include "postmaster/postmaster.h" |
| #include "utils/builtins.h" |
| #include "utils/cloudrest.h" |
| #include "utils/datetime.h" |
| #include "utils/guc.h" |
| #include "utils/timestamp.h" |
| /*#include "replication/walsender.h"*/ |
| #include "storage/ipc.h" |
| |
| extern bool gp_reject_internal_tcp_conn; |
| |
| #if defined(_AIX) |
| int getpeereid(int, uid_t *__restrict__, gid_t *__restrict__); |
| #endif |
| |
| /*---------------------------------------------------------------- |
| * Global authentication functions |
| *---------------------------------------------------------------- |
| */ |
| static void sendAuthRequest(Port *port, AuthRequest areq); |
| static void auth_failed(Port *port, int status); |
| static char *recv_password_packet(Port *port); |
| static int recv_and_check_password_packet(Port *port); |
| |
| |
| /*---------------------------------------------------------------- |
| * Ident authentication |
| *---------------------------------------------------------------- |
| */ |
| /* Max size of username ident server can return */ |
| #define IDENT_USERNAME_MAX 512 |
| |
| /* Standard TCP port number for Ident service. Assigned by IANA */ |
| #define IDENT_PORT 113 |
| |
| static int authident(hbaPort *port); |
| static bool ident_unix(int sock, char *ident_user); |
| |
| |
| /*---------------------------------------------------------------- |
| * 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, char *user, char *password); |
| |
| #if defined(pg_on_solaris) || (_AIX) |
| static int pam_passwd_conv_proc(int num_msg, struct pam_message **msg, |
| struct pam_response **resp, void *appdata_ptr); |
| #else |
| static int pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, |
| struct pam_response ** resp, void *appdata_ptr); |
| #endif |
| |
| static struct pam_conv pam_passw_conv = { |
| &pam_passwd_conv_proc, |
| NULL |
| }; |
| |
| static 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 */ |
| |
| |
| /*---------------------------------------------------------------- |
| * 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); |
| #endif /* USE_LDAP */ |
| |
| /*---------------------------------------------------------------- |
| * Cert authentication |
| *---------------------------------------------------------------- |
| */ |
| #ifdef USE_SSL |
| static int CheckCertAuth(Port *port); |
| #endif |
| |
| |
| /*---------------------------------------------------------------- |
| * Kerberos and GSSAPI GUCs |
| *---------------------------------------------------------------- |
| */ |
| char *pg_krb_server_keyfile; |
| char *pg_krb_srvnam; |
| bool pg_krb_caseins_users; |
| |
| |
| /*---------------------------------------------------------------- |
| * MIT Kerberos authentication system - protocol version 5 |
| *---------------------------------------------------------------- |
| */ |
| #ifdef KRB5 |
| static int pg_krb5_recvauth(Port *port); |
| |
| #include <krb5.h> |
| /* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */ |
| #if !defined(__COM_ERR_H) && !defined(__COM_ERR_H__) |
| #include <com_err.h> |
| #endif |
| /* |
| * Various krb5 state which is not connection specfic, and a flag to |
| * indicate whether we have initialised it yet. |
| */ |
| static int pg_krb5_initialised; |
| static krb5_context pg_krb5_context; |
| static krb5_keytab pg_krb5_keytab; |
| static krb5_principal pg_krb5_server; |
| #endif /* KRB5 */ |
| |
| |
| /*---------------------------------------------------------------- |
| * GSSAPI Authentication |
| *---------------------------------------------------------------- |
| */ |
| #ifdef ENABLE_GSS |
| #if defined(HAVE_GSSAPI_H) |
| #include <gssapi.h> |
| #else |
| #include <gssapi/gssapi.h> |
| #endif |
| |
| 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); |
| #endif |
| |
| /*---------------------------------------------------------------- |
| * RADIUS Authentication |
| *---------------------------------------------------------------- |
| */ |
| #ifdef USE_SSL |
| #include <openssl/rand.h> |
| #endif |
| static int CheckRADIUSAuth(Port *port); |
| |
| /*---------------------------------------------------------------- |
| * Cloud Authentication |
| *---------------------------------------------------------------- |
| */ |
| static int CheckCloudAuth(Port *port); |
| |
| /* |
| * 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 |
| |
| |
| /*---------------------------------------------------------------- |
| * Global authentication functions |
| *---------------------------------------------------------------- |
| */ |
| |
| |
| /* |
| * Tell the user the authentication failed, but not (much about) why. |
| * |
| * There is a tradeoff here between security concerns and making life |
| * unnecessarily difficult for legitimate users. We would not, for example, |
| * want to report the password we were expecting to receive... |
| * But it seems useful to report the username and authorization method |
| * in use, and these are items that must be presumed known to an attacker |
| * anyway. |
| * Note that many sorts of failure report additional information in the |
| * postmaster log, which we hope is only readable by good guys. |
| */ |
| static void |
| auth_failed(Port *port, int status) |
| { |
| const char *errstr; |
| 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); |
| |
| /* internal communication failure */ |
| if (!port->hba) |
| { |
| errstr = gettext_noop("authentication failed for user \"%s\": " |
| "invalid authentication method"); |
| } |
| else |
| { |
| switch (port->hba->auth_method) |
| { |
| case uaReject: |
| case uaImplicitReject: |
| errstr = gettext_noop("authentication failed for user \"%s\": host rejected"); |
| break; |
| case uaKrb5: |
| errstr = gettext_noop("Kerberos 5 authentication failed for user \"%s\""); |
| break; |
| case uaTrust: |
| errstr = gettext_noop("\"trust\" authentication failed for user \"%s\""); |
| break; |
| case uaIdent: |
| errstr = gettext_noop("Ident authentication failed for user \"%s\""); |
| break; |
| case uaPassword: |
| case uaMD5: |
| 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 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; |
| case uaCloud: |
| errstr = gettext_noop("Cloud authentication failed for user \"%s\""); |
| break; |
| default: |
| errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method"); |
| break; |
| } |
| } |
| |
| ereport(FATAL, |
| (errcode(errcode_return), |
| errmsg(errstr, port->user_name))); |
| /* doesn't return */ |
| } |
| |
| /* |
| * Special client authentication for QD to QE connections. This is run at the |
| * QE. This is non-trivial because a QE some times runs at the master (i.e., an |
| * entry-DB for things like master only tables). |
| */ |
| static void |
| internal_client_authentication(Port *port) |
| { |
| if (AmIMaster()) |
| { |
| /* |
| * The entry-DB (or QE at the master) case. |
| * |
| * The goal here is to block network connection from out of |
| * master to master db with magic bit packet. |
| * So, only when it comes from the same host, the connection |
| * is authenticated, if this connection is TCP/UDP. We |
| * don't assume the connection is via unix domain socket, |
| * but if it comes, just authenticate it. We'll need to |
| * verify user on UDS case, but for now we don't do too much |
| * for the goal described above. |
| */ |
| if(port->raddr.addr.ss_family == AF_INET |
| #ifdef HAVE_IPV6 |
| || port->raddr.addr.ss_family == AF_INET6 |
| #endif /* HAVE_IPV6 */ |
| ) |
| { |
| if (check_same_host_or_net(&port->raddr, ipCmpSameHost) && |
| !gp_reject_internal_tcp_conn) |
| { |
| elog(DEBUG1, "received same host internal TCP connection"); |
| FakeClientAuthentication(port); |
| } |
| else |
| { |
| /* Security violation? */ |
| elog(LOG, "rejecting TCP connection to master using internal" |
| "connection protocol"); |
| auth_failed(port, STATUS_ERROR); |
| } |
| return; |
| } |
| else if (port->raddr.addr.ss_family == AF_UNIX) |
| { |
| /* |
| * Internal connection via a domain socket -- use ident |
| */ |
| char *local_name; |
| char remote_name[IDENT_USERNAME_MAX + 1]; |
| struct passwd *pw; |
| |
| pw = getpwuid(geteuid()); |
| local_name = pw->pw_name; |
| |
| remote_name[0] = '\0'; |
| |
| if (!ident_unix(port->sock, remote_name) || |
| !strlen(remote_name) || |
| strcmp(local_name, remote_name) != 0) |
| { |
| auth_failed(port, STATUS_ERROR); |
| } |
| else |
| { |
| FakeClientAuthentication(port); |
| } |
| return; |
| } |
| else |
| { |
| /* Security violation? */ |
| elog(LOG, "rejecting TCP connection to master using internal" |
| "connection protocol"); |
| auth_failed(port, STATUS_ERROR); |
| } |
| } |
| else |
| { |
| /* We're on an actual segment host */ |
| FakeClientAuthentication(port); |
| } |
| } |
| |
| static bool |
| is_internal_gpdb_conn(Port *port) |
| { |
| /* |
| * This is an internal connection if major version is three and we've set |
| * the upper bits to 7. |
| */ |
| if (PG_PROTOCOL_MAJOR(port->proto) == 3 && port->proto >> 28 == 7) |
| return true; |
| else |
| return false; |
| } |
| |
| /* |
| * 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; |
| |
| /* |
| * If this is a QD to QE connection, we might be able to short circuit |
| * client authentication. |
| */ |
| if (is_internal_gpdb_conn(port)) |
| { |
| internal_client_authentication(port); |
| return; |
| } |
| |
| /* |
| * Get the authentication method to use for this frontend/database |
| * combination. Note: a failure return indicates a problem with the hba |
| * config file, not with the request. hba.c should have dropped an error |
| * message into the postmaster logfile if it failed. |
| */ |
| if (hba_getauthmethod(port) != STATUS_OK) |
| ereport(FATAL, |
| (errcode(ERRCODE_CONFIG_FILE_ERROR), |
| errmsg("missing or erroneous pg_hba.conf file"), |
| errhint("See server log for details."))); |
| |
| /* |
| * Enable immediate response to SIGTERM/SIGINT/timeout interrupts. (We |
| * don't want this during hba_getauthmethod() because it might have to do |
| * database access, eg for role membership checks.) |
| */ |
| ImmediateInterruptOK = true; |
| /* And don't forget to detect one that already arrived */ |
| 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) |
| { |
| /* |
| * When we parse pg_hba.conf, we have already made sure that we have |
| * been able to load a certificate store. Thus, if a certificate is |
| * present on the client, it has been verified against our root |
| * certificate store, and the connection would have been aborted |
| * already if it didn't verify ok. |
| */ |
| #ifdef USE_SSL |
| if (!port->peer) |
| { |
| ereport(FATAL, |
| (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
| errmsg("connection requires a valid client certificate"))); |
| } |
| #else |
| |
| /* |
| * hba.c makes sure hba->clientcert can't be set unless OpenSSL is |
| * present. |
| */ |
| Assert(false); |
| #endif |
| } |
| |
| /* |
| * Now proceed to do the actual authentication check |
| */ |
| switch (port->hba->auth_method) |
| { |
| case uaReject: |
| |
| /* |
| * 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 0 |
| 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 ? _("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 |
| #endif |
| { |
| #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 ? _("SSL on") : _("SSL off")), |
| errSendAlert(false))); |
| #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), |
| errSendAlert(false))); |
| #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); |
| |
| #if 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 ? _("SSL on") : _("SSL off")))); |
| #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))); |
| #endif |
| } |
| else |
| #endif |
| { |
| #ifdef USE_SSL |
| ereport(FATAL, |
| (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
| errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s", |
| hostinfo, port->user_name, |
| port->database_name, |
| port->ssl ? _("SSL on") : _("SSL off")))); |
| #else |
| ereport(FATAL, |
| (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
| errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"", |
| hostinfo, port->user_name, |
| port->database_name))); |
| #endif |
| } |
| break; |
| } |
| |
| case uaKrb5: |
| #ifdef KRB5 |
| sendAuthRequest(port, AUTH_REQ_KRB5); |
| status = pg_krb5_recvauth(port); |
| #else |
| Assert(false); |
| #endif |
| break; |
| |
| case uaGSS: |
| #ifdef ENABLE_GSS |
| sendAuthRequest(port, AUTH_REQ_GSS); |
| status = pg_GSS_recvauth(port); |
| #else |
| Assert(false); |
| #endif |
| break; |
| |
| case uaSSPI: |
| #ifdef ENABLE_SSPI |
| sendAuthRequest(port, AUTH_REQ_SSPI); |
| status = pg_SSPI_recvauth(port); |
| #else |
| Assert(false); |
| #endif |
| break; |
| |
| case uaIdent: |
| |
| /* |
| * If we are doing ident on unix-domain sockets, use SCM_CREDS |
| * only if it is defined and SO_PEERCRED isn't. |
| */ |
| #if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && \ |
| (defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || \ |
| (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))) |
| if (port->raddr.addr.ss_family == AF_UNIX) |
| { |
| #if defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED) |
| |
| /* |
| * Receive credentials on next message receipt, BSD/OS, |
| * NetBSD. We need to set this before the client sends the |
| * next packet. |
| */ |
| int on = 1; |
| |
| if (setsockopt(port->sock, 0, LOCAL_CREDS, &on, sizeof(on)) < 0) |
| ereport(FATAL, |
| (errcode_for_socket_access(), |
| errmsg("could not enable credential reception: %m"))); |
| #endif |
| |
| sendAuthRequest(port, AUTH_REQ_SCM_CREDS); |
| } |
| #endif |
| status = authident(port); |
| break; |
| |
| case uaMD5: |
| if (Db_user_namespace) |
| ereport(FATAL, |
| (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
| errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"))); |
| sendAuthRequest(port, AUTH_REQ_MD5); |
| status = recv_and_check_password_packet(port); |
| break; |
| |
| case uaPassword: |
| sendAuthRequest(port, AUTH_REQ_PASSWORD); |
| status = recv_and_check_password_packet(port); |
| break; |
| |
| case uaPAM: |
| #ifdef USE_PAM |
| status = CheckPAMAuth(port, port->user_name, ""); |
| #else |
| Assert(false); |
| #endif /* USE_PAM */ |
| break; |
| |
| case uaLDAP: |
| #ifdef USE_LDAP |
| status = CheckLDAPAuth(port); |
| #else |
| Assert(false); |
| #endif |
| break; |
| |
| case uaCert: |
| #ifdef USE_SSL |
| status = CheckCertAuth(port); |
| #else |
| Assert(false); |
| #endif |
| break; |
| case uaRADIUS: |
| status = CheckRADIUSAuth(port); |
| break; |
| case uaCloud: |
| status = CheckCloudAuth(port); |
| break; |
| case uaTrust: |
| status = STATUS_OK; |
| break; |
| } |
| |
| if (status == STATUS_OK) |
| if (!CheckAuthTimeConstraints(port->user_name)) |
| ereport(FATAL, |
| (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), |
| errmsg("authentication failed for user \"%s\": login not permitted at this time", port->user_name))); |
| |
| if (status == STATUS_OK) |
| sendAuthRequest(port, AUTH_REQ_OK); |
| else |
| auth_failed(port, status); |
| |
| /* Done with authentication, so we should turn off immediate interrupts */ |
| ImmediateInterruptOK = false; |
| } |
| |
| void |
| FakeClientAuthentication(Port *port) |
| { |
| sendAuthRequest(port, AUTH_REQ_OK); |
| } |
| |
| /* |
| * Send an authentication request packet to the frontend. |
| */ |
| static void |
| sendAuthRequest(Port *port, AuthRequest areq) |
| { |
| StringInfoData buf; |
| |
| pq_beginmessage(&buf, 'R'); |
| pq_sendint(&buf, (int32) areq, sizeof(int32)); |
| |
| /* Add the salt for encrypted passwords. */ |
| if (areq == AUTH_REQ_MD5) |
| pq_sendbytes(&buf, port->md5Salt, 4); |
| |
| #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) |
| |
| /* |
| * Add the authentication data for the next step of the GSSAPI or SSPI |
| * negotiation. |
| */ |
| else if (areq == AUTH_REQ_GSS_CONT) |
| { |
| if (port->gss->outbuf.length > 0) |
| { |
| elog(DEBUG4, "sending GSS token of length %u", |
| (unsigned int) port->gss->outbuf.length); |
| |
| pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length); |
| } |
| } |
| #endif |
| |
| pq_endmessage(&buf); |
| |
| /* |
| * Flush message so client will see it, except for AUTH_REQ_OK, which need |
| * not be sent until we are ready for queries. |
| */ |
| if (areq != AUTH_REQ_OK) |
| pq_flush(); |
| } |
| |
| /* |
| * Collect password response packet from frontend. |
| * |
| * Returns NULL if couldn't get password, else palloc'd string. |
| */ |
| static char * |
| recv_password_packet(Port *port) |
| { |
| StringInfoData buf; |
| |
| if (PG_PROTOCOL_MAJOR(port->proto) >= 3) |
| { |
| /* Expect 'p' message type */ |
| int mtype; |
| |
| mtype = pq_getbyte(); |
| if (mtype != 'p') |
| { |
| /* |
| * If the client just disconnects without offering a password, |
| * don't make a log entry. This is legal per protocol spec and in |
| * fact commonly done by psql, so complaining just clutters the |
| * log. |
| */ |
| if (mtype != EOF) |
| ereport(COMMERROR, |
| (errcode(ERRCODE_PROTOCOL_VIOLATION), |
| errmsg("expected password response, got message type %d", |
| mtype))); |
| return NULL; /* EOF or bad message type */ |
| } |
| } |
| else |
| { |
| /* For pre-3.0 clients, avoid log entry if they just disconnect */ |
| if (pq_peekbyte() == EOF) |
| return NULL; /* EOF */ |
| } |
| |
| initStringInfo(&buf); |
| if (pq_getmessage(&buf, 1000)) /* receive password */ |
| { |
| /* EOF - pq_getmessage already logged a suitable message */ |
| pfree(buf.data); |
| return NULL; |
| } |
| |
| /* |
| * Apply sanity check: password packet length should agree with length of |
| * contained string. Note it is safe to use strlen here because |
| * StringInfo is guaranteed to have an appended '\0'. |
| */ |
| if (strlen(buf.data) + 1 != buf.len) |
| ereport(COMMERROR, |
| (errcode(ERRCODE_PROTOCOL_VIOLATION), |
| errmsg("invalid password packet size"))); |
| |
| /* Do not echo password to logs, for security. */ |
| ereport(DEBUG5, |
| (errmsg("received password packet"))); |
| |
| /* |
| * Return the received string. Note we do not attempt to do any |
| * character-set conversion on it; since we don't yet know the client's |
| * encoding, there wouldn't be much point. |
| */ |
| return buf.data; |
| } |
| |
| |
| /*---------------------------------------------------------------- |
| * hashed password (MD5, SHA-256) authentication |
| *---------------------------------------------------------------- |
| */ |
| |
| /* |
| * Called when we have sent an authorization request for a password. |
| * Get the response and check it. |
| */ |
| static int |
| recv_and_check_password_packet(Port *port) |
| { |
| char *passwd; |
| int result; |
| |
| passwd = recv_password_packet(port); |
| |
| if (passwd == NULL) |
| return STATUS_EOF; /* client wouldn't send password */ |
| |
| result = hashed_passwd_verify(port, port->user_name, passwd); |
| |
| pfree(passwd); |
| |
| return result; |
| } |
| |
| |
| /*---------------------------------------------------------------- |
| * MIT Kerberos authentication system - protocol version 5 |
| *---------------------------------------------------------------- |
| */ |
| #ifdef KRB5 |
| |
| static int |
| pg_krb5_init(Port *port) |
| { |
| krb5_error_code retval; |
| char *khostname; |
| |
| if (pg_krb5_initialised) |
| return STATUS_OK; |
| |
| retval = krb5_init_context(&pg_krb5_context); |
| if (retval) |
| { |
| ereport(LOG, |
| (errmsg("Kerberos initialization returned error %d", |
| retval))); |
| com_err("postgres", retval, "while initializing krb5"); |
| return STATUS_ERROR; |
| } |
| |
| retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab); |
| if (retval) |
| { |
| ereport(LOG, |
| (errmsg("Kerberos keytab resolving returned error %d", |
| retval))); |
| com_err("postgres", retval, "while resolving keytab file \"%s\"", |
| pg_krb_server_keyfile); |
| krb5_free_context(pg_krb5_context); |
| return STATUS_ERROR; |
| } |
| |
| /* |
| * If no hostname was specified, pg_krb_server_hostname is already NULL. |
| * If it's set to blank, force it to NULL. |
| */ |
| khostname = port->hba->krb_server_hostname; |
| if (khostname && khostname[0] == '\0') |
| khostname = NULL; |
| |
| retval = krb5_sname_to_principal(pg_krb5_context, |
| khostname, |
| pg_krb_srvnam, |
| KRB5_NT_SRV_HST, |
| &pg_krb5_server); |
| if (retval) |
| { |
| ereport(LOG, |
| (errmsg("Kerberos sname_to_principal(\"%s\", \"%s\") returned error %d", |
| khostname ? khostname : "server hostname", pg_krb_srvnam, retval))); |
| com_err("postgres", retval, |
| "while getting server principal for server \"%s\" for service \"%s\"", |
| khostname ? khostname : "server hostname", pg_krb_srvnam); |
| krb5_kt_close(pg_krb5_context, pg_krb5_keytab); |
| krb5_free_context(pg_krb5_context); |
| return STATUS_ERROR; |
| } |
| |
| pg_krb5_initialised = 1; |
| return STATUS_OK; |
| } |
| |
| |
| /* |
| * pg_krb5_recvauth -- server routine to receive authentication information |
| * from the client |
| * |
| * We still need to compare the username obtained from the client's setup |
| * packet to the authenticated name. |
| * |
| * We have our own keytab file because postgres is unlikely to run as root, |
| * and so cannot read the default keytab. |
| */ |
| static int |
| pg_krb5_recvauth(Port *port) |
| { |
| krb5_error_code retval; |
| int ret; |
| krb5_auth_context auth_context = NULL; |
| krb5_ticket *ticket; |
| char *kusername; |
| char *cp; |
| |
| /* GPDB: Because we are still using the old flatfile method of getting roles, safest to check here if the role exists */ |
| /* Not sure if this is necessary, PG took this test out when getting rid of the pg_authid flatfile */ |
| if (get_role_line(port->user_name) == NULL) |
| return STATUS_ERROR; |
| |
| ret = pg_krb5_init(port); |
| if (ret != STATUS_OK) |
| return ret; |
| |
| retval = krb5_recvauth(pg_krb5_context, &auth_context, |
| (krb5_pointer) & port->sock, pg_krb_srvnam, |
| pg_krb5_server, 0, pg_krb5_keytab, &ticket); |
| if (retval) |
| { |
| ereport(LOG, |
| (errmsg("Kerberos recvauth returned error %d", |
| retval))); |
| com_err("postgres", retval, "from krb5_recvauth"); |
| return STATUS_ERROR; |
| } |
| |
| /* |
| * The "client" structure comes out of the ticket and is therefore |
| * authenticated. Use it to check the username obtained from the |
| * postmaster startup packet. |
| */ |
| #if defined(HAVE_KRB5_TICKET_ENC_PART2) |
| retval = krb5_unparse_name(pg_krb5_context, |
| ticket->enc_part2->client, &kusername); |
| #elif defined(HAVE_KRB5_TICKET_CLIENT) |
| retval = krb5_unparse_name(pg_krb5_context, |
| ticket->client, &kusername); |
| #else |
| #error "bogus configuration" |
| #endif |
| if (retval) |
| { |
| ereport(LOG, |
| (errmsg("Kerberos unparse_name returned error %d", |
| retval))); |
| com_err("postgres", retval, "while unparsing client name"); |
| krb5_free_ticket(pg_krb5_context, ticket); |
| krb5_auth_con_free(pg_krb5_context, auth_context); |
| return STATUS_ERROR; |
| } |
| |
| cp = strchr(kusername, '@'); |
| if (cp) |
| { |
| /* |
| * If we are not going to include the realm in the username that is |
| * passed to the ident map, destructively modify it here to remove the |
| * realm. Then advance past the separator to check the realm. |
| */ |
| if (!port->hba->include_realm) |
| *cp = '\0'; |
| cp++; |
| |
| if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) |
| { |
| /* Match realm against configured */ |
| if (pg_krb_caseins_users) |
| ret = pg_strcasecmp(port->hba->krb_realm, cp); |
| else |
| ret = strcmp(port->hba->krb_realm, cp); |
| |
| if (ret) |
| { |
| elog(DEBUG2, |
| "krb5 realm (%s) and configured realm (%s) don't match", |
| cp, port->hba->krb_realm); |
| |
| krb5_free_ticket(pg_krb5_context, ticket); |
| krb5_auth_con_free(pg_krb5_context, auth_context); |
| return STATUS_ERROR; |
| } |
| } |
| } |
| else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) |
| { |
| elog(DEBUG2, |
| "krb5 did not return realm but realm matching was requested"); |
| |
| krb5_free_ticket(pg_krb5_context, ticket); |
| krb5_auth_con_free(pg_krb5_context, auth_context); |
| return STATUS_ERROR; |
| } |
| |
| ret = check_usermap(port->hba->usermap, port->user_name, kusername, |
| pg_krb_caseins_users); |
| |
| krb5_free_ticket(pg_krb5_context, ticket); |
| krb5_auth_con_free(pg_krb5_context, auth_context); |
| free(kusername); |
| |
| return ret; |
| } |
| #endif /* KRB5 */ |
| |
| |
| /*---------------------------------------------------------------- |
| * GSSAPI authentication system |
| *---------------------------------------------------------------- |
| */ |
| #ifdef ENABLE_GSS |
| |
| #if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) |
| /* |
| * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW |
| * that contain the OIDs required. Redefine here, values copied |
| * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c |
| */ |
| static const gss_OID_desc GSS_C_NT_USER_NAME_desc = |
| {10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; |
| static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; |
| #endif |
| |
| |
| static void |
| pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat) |
| { |
| gss_buffer_desc gmsg; |
| OM_uint32 lmaj_s, |
| lmin_s, |
| msg_ctx; |
| char msg_major[128], |
| msg_minor[128]; |
| |
| /* Fetch major status message */ |
| msg_ctx = 0; |
| lmaj_s = gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, |
| GSS_C_NO_OID, &msg_ctx, &gmsg); |
| strlcpy(msg_major, gmsg.value, sizeof(msg_major)); |
| gss_release_buffer(&lmin_s, &gmsg); |
| |
| if (msg_ctx) |
| |
| /* |
| * More than one message available. XXX: Should we loop and read all |
| * messages? (same below) |
| */ |
| ereport(WARNING, |
| (errmsg_internal("incomplete GSS error report"))); |
| |
| /* Fetch mechanism minor status message */ |
| msg_ctx = 0; |
| lmaj_s = gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, |
| GSS_C_NO_OID, &msg_ctx, &gmsg); |
| strlcpy(msg_minor, gmsg.value, sizeof(msg_minor)); |
| gss_release_buffer(&lmin_s, &gmsg); |
| |
| if (msg_ctx) |
| ereport(WARNING, |
| (errmsg_internal("incomplete GSS minor error report"))); |
| |
| /* |
| * errmsg_internal, since translation of the first part must be done |
| * before calling this function anyway. |
| */ |
| ereport(severity, |
| (errmsg_internal("%s", errmsg), |
| errdetail("%s: %s", msg_major, msg_minor))); |
| } |
| |
| static int |
| pg_GSS_recvauth(Port *port) |
| { |
| OM_uint32 maj_stat, |
| min_stat, |
| lmin_s, |
| gflags; |
| int mtype; |
| int ret; |
| StringInfoData buf; |
| gss_buffer_desc gbuf; |
| |
| /* |
| * GSS auth is not supported for protocol versions before 3, because it |
| * relies on the overall message length word to determine the GSS payload |
| * size in AuthenticationGSSContinue and PasswordMessage messages. (This |
| * is, in fact, a design error in our GSS support, because protocol |
| * messages are supposed to be parsable without relying on the length |
| * word; but it's not worth changing it now.) |
| */ |
| if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) |
| ereport(FATAL, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("GSSAPI is not supported in protocol version 2"))); |
| |
| if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) |
| { |
| /* |
| * Set default Kerberos keytab file for the Krb5 mechanism. |
| * |
| * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv() |
| * not always available. |
| */ |
| if (getenv("KRB5_KTNAME") == NULL) |
| { |
| size_t kt_len = strlen(pg_krb_server_keyfile) + 14; |
| char *kt_path = malloc(kt_len); |
| |
| if (!kt_path) |
| { |
| ereport(LOG, |
| (errcode(ERRCODE_OUT_OF_MEMORY), |
| errmsg("out of memory"))); |
| return STATUS_ERROR; |
| } |
| snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", pg_krb_server_keyfile); |
| putenv(kt_path); |
| } |
| } |
| |
| /* |
| * We accept any service principal that's present in our keytab. This |
| * increases interoperability between kerberos implementations that see |
| * for example case sensitivity differently, while not really opening up |
| * any vector of attack. |
| */ |
| port->gss->cred = GSS_C_NO_CREDENTIAL; |
| |
| /* |
| * Initialize sequence with an empty context |
| */ |
| port->gss->ctx = GSS_C_NO_CONTEXT; |
| |
| /* |
| * Loop through GSSAPI message exchange. This exchange can consist of |
| * multiple messags sent in both directions. First message is always from |
| * the client. All messages from client to server are password packets |
| * (type 'p'). |
| */ |
| do |
| { |
| mtype = pq_getbyte(); |
| if (mtype != 'p') |
| { |
| /* Only log error if client didn't disconnect. */ |
| if (mtype != EOF) |
| ereport(COMMERROR, |
| (errcode(ERRCODE_PROTOCOL_VIOLATION), |
| errmsg("expected GSS response, got message type %d", |
| mtype))); |
| return STATUS_ERROR; |
| } |
| |
| /* Get the actual GSS token */ |
| initStringInfo(&buf); |
| if (pq_getmessage(&buf, 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); |
| |
| if (port->gss->outbuf.length != 0) |
| { |
| /* |
| * Negotiation generated data to be sent to the client. |
| */ |
| OM_uint32 lmin_s; |
| |
| elog(DEBUG4, "sending GSS response token of length %u", |
| (unsigned int) port->gss->outbuf.length); |
| |
| sendAuthRequest(port, AUTH_REQ_GSS_CONT); |
| |
| gss_release_buffer(&lmin_s, &port->gss->outbuf); |
| } |
| |
| if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) |
| { |
| OM_uint32 lmin_s; |
| |
| gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); |
| pg_GSS_error(ERROR, |
| gettext_noop("accepting GSS security context failed"), |
| maj_stat, min_stat); |
| } |
| |
| if (maj_stat == GSS_S_CONTINUE_NEEDED) |
| elog(DEBUG4, "GSS continue needed"); |
| |
| } while (maj_stat == GSS_S_CONTINUE_NEEDED); |
| |
| if (port->gss->cred != GSS_C_NO_CREDENTIAL) |
| { |
| /* |
| * Release service principal credentials |
| */ |
| gss_release_cred(&min_stat, &port->gss->cred); |
| } |
| |
| /* |
| * GSS_S_COMPLETE indicates that authentication is now complete. |
| * |
| * Get the name of the user that authenticated, and compare it to the pg |
| * username that was specified for the connection. |
| */ |
| maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); |
| if (maj_stat != GSS_S_COMPLETE) |
| pg_GSS_error(ERROR, |
| gettext_noop("retrieving GSS user name failed"), |
| maj_stat, min_stat); |
| |
| /* |
| * Split the username at the realm separator |
| */ |
| if (strchr(gbuf.value, '@')) |
| { |
| char *cp = strchr(gbuf.value, '@'); |
| |
| /* |
| * If we are not going to include the realm in the username that is |
| * passed to the ident map, destructively modify it here to remove the |
| * realm. Then advance past the separator to check the realm. |
| */ |
| if (!port->hba->include_realm) |
| *cp = '\0'; |
| cp++; |
| |
| if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm)) |
| { |
| /* |
| * Match the realm part of the name first |
| */ |
| if (pg_krb_caseins_users) |
| ret = pg_strcasecmp(port->hba->krb_realm, cp); |
| else |
| ret = strcmp(port->hba->krb_realm, cp); |
| |
| if (ret) |
| { |
| /* GSS realm does not match */ |
| elog(DEBUG2, |
| "GSSAPI realm (%s) and configured realm (%s) don't match", |
| cp, port->hba->krb_realm); |
| gss_release_buffer(&lmin_s, &gbuf); |
| return STATUS_ERROR; |
| } |
| } |
| } |
| else if (port->hba->krb_realm && strlen(port->hba->krb_realm)) |
| { |
| elog(DEBUG2, |
| "GSSAPI did not return realm but realm matching was requested"); |
| |
| gss_release_buffer(&lmin_s, &gbuf); |
| return STATUS_ERROR; |
| } |
| |
| ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value, |
| pg_krb_caseins_users); |
| |
| gss_release_buffer(&lmin_s, &gbuf); |
| |
| return ret; |
| } |
| #endif /* ENABLE_GSS */ |
| |
| |
| /*---------------------------------------------------------------- |
| * SSPI authentication system |
| *---------------------------------------------------------------- |
| */ |
| #ifdef ENABLE_SSPI |
| static void |
| pg_SSPI_error(int severity, const char *errmsg, SECURITY_STATUS r) |
| { |
| char sysmsg[256]; |
| |
| if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0, |
| sysmsg, sizeof(sysmsg), NULL) == 0) |
| ereport(severity, |
| (errmsg_internal("%s", errmsg), |
| errdetail("SSPI error %x", (unsigned int) r))); |
| else |
| ereport(severity, |
| (errmsg_internal("%s", errmsg), |
| errdetail("%s (%x)", sysmsg, (unsigned int) r))); |
| } |
| |
| static int |
| pg_SSPI_recvauth(Port *port) |
| { |
| int mtype; |
| StringInfoData buf; |
| SECURITY_STATUS r; |
| CredHandle sspicred; |
| CtxtHandle *sspictx = NULL, |
| newctx; |
| TimeStamp expiry; |
| ULONG contextattr; |
| SecBufferDesc inbuf; |
| SecBufferDesc outbuf; |
| SecBuffer OutBuffers[1]; |
| SecBuffer InBuffers[1]; |
| HANDLE token; |
| TOKEN_USER *tokenuser; |
| DWORD retlen; |
| char accountname[MAXPGPATH]; |
| char domainname[MAXPGPATH]; |
| DWORD accountnamesize = sizeof(accountname); |
| DWORD domainnamesize = sizeof(domainname); |
| SID_NAME_USE accountnameuse; |
| HMODULE secur32; |
| QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken; |
| |
| /* |
| * SSPI auth is not supported for protocol versions before 3, because it |
| * relies on the overall message length word to determine the SSPI payload |
| * size in AuthenticationGSSContinue and PasswordMessage messages. (This |
| * is, in fact, a design error in our SSPI support, because protocol |
| * messages are supposed to be parsable without relying on the length |
| * word; but it's not worth changing it now.) |
| */ |
| if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) |
| ereport(FATAL, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("SSPI is not supported in protocol version 2"))); |
| |
| /* |
| * Acquire a handle to the server credentials. |
| */ |
| r = AcquireCredentialsHandle(NULL, |
| "negotiate", |
| SECPKG_CRED_INBOUND, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| &sspicred, |
| &expiry); |
| if (r != SEC_E_OK) |
| pg_SSPI_error(ERROR, _("could not acquire SSPI credentials"), r); |
| |
| /* |
| * Loop through SSPI message exchange. This exchange can consist of |
| * multiple messags sent in both directions. First message is always from |
| * the client. All messages from client to server are password packets |
| * (type 'p'). |
| */ |
| do |
| { |
| mtype = pq_getbyte(); |
| if (mtype != 'p') |
| { |
| /* Only log error if client didn't disconnect. */ |
| if (mtype != EOF) |
| ereport(COMMERROR, |
| (errcode(ERRCODE_PROTOCOL_VIOLATION), |
| errmsg("expected SSPI response, got message type %d", |
| mtype))); |
| return STATUS_ERROR; |
| } |
| |
| /* Get the actual SSPI token */ |
| initStringInfo(&buf); |
| if (pq_getmessage(&buf, 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); |
| |
| 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); |
| } |
| |
| if (sspictx == NULL) |
| { |
| sspictx = malloc(sizeof(CtxtHandle)); |
| if (sspictx == NULL) |
| ereport(ERROR, |
| (errmsg("out of memory"))); |
| |
| memcpy(sspictx, &newctx, sizeof(CtxtHandle)); |
| } |
| |
| if (r == SEC_I_CONTINUE_NEEDED) |
| elog(DEBUG4, "SSPI continue needed"); |
| |
| } while (r == SEC_I_CONTINUE_NEEDED); |
| |
| |
| /* |
| * Release service principal credentials |
| */ |
| FreeCredentialsHandle(&sspicred); |
| |
| |
| /* |
| * SEC_E_OK indicates that authentication is now complete. |
| * |
| * Get the name of the user that authenticated, and compare it to the pg |
| * username that was specified for the connection. |
| * |
| * MingW is missing the export for QuerySecurityContextToken in the |
| * secur32 library, so we have to load it dynamically. |
| */ |
| |
| secur32 = LoadLibrary("SECUR32.DLL"); |
| if (secur32 == NULL) |
| ereport(ERROR, |
| (errmsg_internal("could not load secur32.dll: %d", |
| (int) GetLastError()))); |
| |
| _QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN) |
| GetProcAddress(secur32, "QuerySecurityContextToken"); |
| if (_QuerySecurityContextToken == NULL) |
| { |
| FreeLibrary(secur32); |
| ereport(ERROR, |
| (errmsg_internal("could not locate QuerySecurityContextToken in secur32.dll: %d", |
| (int) GetLastError()))); |
| } |
| |
| r = (_QuerySecurityContextToken) (sspictx, &token); |
| if (r != SEC_E_OK) |
| { |
| FreeLibrary(secur32); |
| pg_SSPI_error(ERROR, |
| _("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 user size: error code %d", |
| (int) GetLastError()))); |
| |
| tokenuser = malloc(retlen); |
| if (tokenuser == NULL) |
| ereport(ERROR, |
| (errmsg("out of memory"))); |
| |
| if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen)) |
| ereport(ERROR, |
| (errmsg_internal("could not get user token: error code %d", |
| (int) GetLastError()))); |
| |
| if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize, |
| domainname, &domainnamesize, &accountnameuse)) |
| ereport(ERROR, |
| (errmsg_internal("could not lookup acconut sid: error code %d", |
| (int) GetLastError()))); |
| |
| free(tokenuser); |
| |
| /* |
| * Compare realm/domain if requested. In SSPI, always compare case |
| * insensitive. |
| */ |
| if (port->hba->krb_realm && strlen(port->hba->krb_realm)) |
| { |
| if (pg_strcasecmp(port->hba->krb_realm, domainname)) |
| { |
| elog(DEBUG2, |
| "SSPI domain (%s) and configured domain (%s) don't match", |
| domainname, port->hba->krb_realm); |
| |
| return STATUS_ERROR; |
| } |
| } |
| |
| /* |
| * We have the username (without domain/realm) in accountname, compare to |
| * the supplied value. In SSPI, always compare case insensitive. |
| * |
| * If set to include realm, append it in <username>@<realm> format. |
| */ |
| if (port->hba->include_realm) |
| { |
| char *namebuf; |
| int retval; |
| |
| namebuf = palloc(strlen(accountname) + strlen(domainname) + 2); |
| sprintf(namebuf, "%s@%s", accountname, domainname); |
| retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true); |
| pfree(namebuf); |
| return retval; |
| } |
| else |
| return check_usermap(port->hba->usermap, port->user_name, accountname, true); |
| } |
| #endif /* ENABLE_SSPI */ |
| |
| |
| |
| /*---------------------------------------------------------------- |
| * Ident authentication system |
| *---------------------------------------------------------------- |
| */ |
| |
| /* |
| * Parse the string "*ident_response" as a response from a query to an Ident |
| * server. If it's a normal response indicating a user name, return true |
| * and store the user name at *ident_user. If it's anything else, |
| * return false. |
| */ |
| static bool |
| interpret_ident_response(const char *ident_response, |
| char *ident_user) |
| { |
| const char *cursor = ident_response; /* Cursor into *ident_response */ |
| |
| /* |
| * Ident's response, in the telnet tradition, should end in crlf (\r\n). |
| */ |
| if (strlen(ident_response) < 2) |
| return false; |
| else if (ident_response[strlen(ident_response) - 2] != '\r') |
| return false; |
| else |
| { |
| while (*cursor != ':' && *cursor != '\r') |
| cursor++; /* skip port field */ |
| |
| if (*cursor != ':') |
| return false; |
| else |
| { |
| /* We're positioned to colon before response type field */ |
| char response_type[80]; |
| int i; /* Index into *response_type */ |
| |
| cursor++; /* Go over colon */ |
| while (pg_isblank(*cursor)) |
| cursor++; /* skip blanks */ |
| i = 0; |
| while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) && |
| i < (int) (sizeof(response_type) - 1)) |
| response_type[i++] = *cursor++; |
| response_type[i] = '\0'; |
| while (pg_isblank(*cursor)) |
| cursor++; /* skip blanks */ |
| if (strcmp(response_type, "USERID") != 0) |
| return false; |
| else |
| { |
| /* |
| * It's a USERID response. Good. "cursor" should be pointing |
| * to the colon that precedes the operating system type. |
| */ |
| if (*cursor != ':') |
| return false; |
| else |
| { |
| cursor++; /* Go over colon */ |
| /* Skip over operating system field. */ |
| while (*cursor != ':' && *cursor != '\r') |
| cursor++; |
| if (*cursor != ':') |
| return false; |
| else |
| { |
| int i; /* Index into *ident_user */ |
| |
| cursor++; /* Go over colon */ |
| while (pg_isblank(*cursor)) |
| cursor++; /* skip blanks */ |
| /* Rest of line is user name. Copy it over. */ |
| i = 0; |
| while (*cursor != '\r' && i < IDENT_USERNAME_MAX) |
| ident_user[i++] = *cursor++; |
| ident_user[i] = '\0'; |
| return true; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /* |
| * Talk to the ident server on host "remote_ip_addr" and find out who |
| * owns the tcp connection from his port "remote_port" to port |
| * "local_port_addr" on host "local_ip_addr". Return the user name the |
| * ident server gives as "*ident_user". |
| * |
| * IP addresses and port numbers are in network byte order. |
| * |
| * But iff we're unable to get the information from ident, return false. |
| */ |
| static bool |
| ident_inet(const SockAddr remote_addr, |
| const SockAddr local_addr, |
| char *ident_user) |
| { |
| pgsocket sock_fd, /* File descriptor for socket on which we talk |
| * to Ident */ |
| rc; /* Return code from a locally called function */ |
| bool ident_return; |
| char remote_addr_s[NI_MAXHOST]; |
| char remote_port[NI_MAXSERV]; |
| char local_addr_s[NI_MAXHOST]; |
| char local_port[NI_MAXSERV]; |
| char ident_port[NI_MAXSERV]; |
| char ident_query[80]; |
| char ident_response[80 + IDENT_USERNAME_MAX]; |
| struct addrinfo *ident_serv = NULL, |
| *la = NULL, |
| hints; |
| |
| /* |
| * Might look a little weird to first convert it to text and then back to |
| * sockaddr, but it's protocol independent. |
| */ |
| pg_getnameinfo_all(&remote_addr.addr, remote_addr.salen, |
| remote_addr_s, sizeof(remote_addr_s), |
| remote_port, sizeof(remote_port), |
| NI_NUMERICHOST | NI_NUMERICSERV); |
| pg_getnameinfo_all(&local_addr.addr, local_addr.salen, |
| local_addr_s, sizeof(local_addr_s), |
| local_port, sizeof(local_port), |
| NI_NUMERICHOST | NI_NUMERICSERV); |
| |
| snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT); |
| hints.ai_flags = AI_NUMERICHOST; |
| hints.ai_family = remote_addr.addr.ss_family; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_protocol = 0; |
| hints.ai_addrlen = 0; |
| hints.ai_canonname = NULL; |
| hints.ai_addr = NULL; |
| hints.ai_next = NULL; |
| rc = pg_getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv); |
| if (rc || !ident_serv) |
| { |
| if (ident_serv) |
| pg_freeaddrinfo_all(hints.ai_family, ident_serv); |
| return false; /* we don't expect this to happen */ |
| } |
| |
| hints.ai_flags = AI_NUMERICHOST; |
| hints.ai_family = local_addr.addr.ss_family; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_protocol = 0; |
| hints.ai_addrlen = 0; |
| hints.ai_canonname = NULL; |
| hints.ai_addr = NULL; |
| hints.ai_next = NULL; |
| rc = pg_getaddrinfo_all(local_addr_s, NULL, &hints, &la); |
| if (rc || !la) |
| { |
| if (la) |
| pg_freeaddrinfo_all(hints.ai_family, la); |
| return false; /* we don't expect this to happen */ |
| } |
| |
| sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype, |
| ident_serv->ai_protocol); |
| if (sock_fd < 0) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not create socket for Ident connection: %m"))); |
| ident_return = false; |
| goto ident_inet_done; |
| } |
| |
| /* |
| * Bind to the address which the client originally contacted, otherwise |
| * the ident server won't be able to match up the right connection. This |
| * is necessary if the PostgreSQL server is running on an IP alias. |
| */ |
| rc = bind(sock_fd, la->ai_addr, la->ai_addrlen); |
| if (rc != 0) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not bind to local address \"%s\": %m", |
| local_addr_s))); |
| ident_return = false; |
| goto ident_inet_done; |
| } |
| |
| rc = connect(sock_fd, ident_serv->ai_addr, |
| ident_serv->ai_addrlen); |
| if (rc != 0) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not connect to Ident server at address \"%s\", port %s: %m", |
| remote_addr_s, ident_port))); |
| ident_return = false; |
| goto ident_inet_done; |
| } |
| |
| /* The query we send to the Ident server */ |
| snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n", |
| remote_port, local_port); |
| |
| /* loop in case send is interrupted */ |
| do |
| { |
| rc = send(sock_fd, ident_query, strlen(ident_query), 0); |
| } while (rc < 0 && errno == EINTR); |
| |
| if (rc < 0) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not send query to Ident server at address \"%s\", port %s: %m", |
| remote_addr_s, ident_port))); |
| ident_return = false; |
| goto ident_inet_done; |
| } |
| |
| do |
| { |
| rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0); |
| } while (rc < 0 && errno == EINTR); |
| |
| if (rc < 0) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not receive response from Ident server at address \"%s\", port %s: %m", |
| remote_addr_s, ident_port))); |
| ident_return = false; |
| goto ident_inet_done; |
| } |
| |
| ident_response[rc] = '\0'; |
| ident_return = interpret_ident_response(ident_response, ident_user); |
| if (!ident_return) |
| ereport(LOG, |
| (errmsg("invalidly formatted response from Ident server: \"%s\"", |
| ident_response))); |
| |
| ident_inet_done: |
| if (sock_fd >= 0) |
| closesocket(sock_fd); |
| pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv); |
| pg_freeaddrinfo_all(local_addr.addr.ss_family, la); |
| return ident_return; |
| } |
| |
| /* |
| * Ask kernel about the credentials of the connecting process and |
| * determine the symbolic name of the corresponding user. |
| * |
| * Returns either true and the username put into "ident_user", |
| * or false if we were unable to determine the username. |
| */ |
| #ifdef HAVE_UNIX_SOCKETS |
| |
| static bool |
| ident_unix(int sock, char *ident_user) |
| { |
| #if defined(HAVE_GETPEEREID) |
| /* OpenBSD style: */ |
| uid_t uid; |
| gid_t gid; |
| struct passwd *pass; |
| |
| errno = 0; |
| if (getpeereid(sock, &uid, &gid) != 0) |
| { |
| /* We didn't get a valid credentials struct. */ |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not get peer credentials: %m"))); |
| return false; |
| } |
| |
| pass = getpwuid(uid); |
| |
| if (pass == NULL) |
| { |
| ereport(LOG, |
| (errmsg("local user with ID %d does not exist", |
| (int) uid))); |
| return false; |
| } |
| |
| strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1); |
| |
| return true; |
| #elif defined(SO_PEERCRED) |
| /* Linux style: use getsockopt(SO_PEERCRED) */ |
| struct ucred peercred; |
| socklen_t so_len = sizeof(peercred); |
| struct passwd *pass; |
| |
| errno = 0; |
| if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 || |
| so_len != sizeof(peercred)) |
| { |
| /* We didn't get a valid credentials struct. */ |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not get peer credentials: %m"))); |
| return false; |
| } |
| |
| pass = getpwuid(peercred.uid); |
| |
| if (pass == NULL) |
| { |
| ereport(LOG, |
| (errmsg("local user with ID %d does not exist", |
| (int) peercred.uid))); |
| return false; |
| } |
| |
| strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1); |
| |
| return true; |
| #elif defined(HAVE_GETPEERUCRED) |
| /* Solaris > 10 */ |
| uid_t uid; |
| struct passwd *pass; |
| ucred_t *ucred; |
| |
| ucred = NULL; /* must be initialized to NULL */ |
| if (getpeerucred(sock, &ucred) == -1) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not get peer credentials: %m"))); |
| return false; |
| } |
| |
| if ((uid = ucred_geteuid(ucred)) == -1) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not get effective UID from peer credentials: %m"))); |
| return false; |
| } |
| |
| ucred_free(ucred); |
| |
| pass = getpwuid(uid); |
| if (pass == NULL) |
| { |
| ereport(LOG, |
| (errmsg("local user with ID %d does not exist", |
| (int) uid))); |
| return false; |
| } |
| |
| strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1); |
| |
| return true; |
| #elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS)) |
| struct msghdr msg; |
| |
| /* Credentials structure */ |
| #if defined(HAVE_STRUCT_CMSGCRED) |
| typedef struct cmsgcred Cred; |
| |
| #define cruid cmcred_uid |
| #elif defined(HAVE_STRUCT_FCRED) |
| typedef struct fcred Cred; |
| |
| #define cruid fc_uid |
| #elif defined(HAVE_STRUCT_SOCKCRED) |
| typedef struct sockcred Cred; |
| |
| #define cruid sc_uid |
| #endif |
| Cred *cred; |
| |
| /* Compute size without padding */ |
| char cmsgmem[ALIGN(sizeof(struct cmsghdr)) + ALIGN(sizeof(Cred))]; /* for NetBSD */ |
| |
| /* Point to start of first structure */ |
| struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem; |
| |
| struct iovec iov; |
| char buf; |
| struct passwd *pw; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = (char *) cmsg; |
| msg.msg_controllen = sizeof(cmsgmem); |
| memset(cmsg, 0, sizeof(cmsgmem)); |
| |
| /* |
| * The one character which is received here is not meaningful; its |
| * purposes is only to make sure that recvmsg() blocks long enough for the |
| * other side to send its credentials. |
| */ |
| iov.iov_base = &buf; |
| iov.iov_len = 1; |
| |
| if (recvmsg(sock, &msg, 0) < 0 || |
| cmsg->cmsg_len < sizeof(cmsgmem) || |
| cmsg->cmsg_type != SCM_CREDS) |
| { |
| ereport(LOG, |
| (errcode_for_socket_access(), |
| errmsg("could not get peer credentials: %m"))); |
| return false; |
| } |
| |
| cred = (Cred *) CMSG_DATA(cmsg); |
| |
| pw = getpwuid(cred->cruid); |
| |
| if (pw == NULL) |
| { |
| ereport(LOG, |
| (errmsg("local user with ID %d does not exist", |
| (int) cred->cruid))); |
| return false; |
| } |
| |
| strlcpy(ident_user, pw->pw_name, IDENT_USERNAME_MAX + 1); |
| |
| return true; |
| #else |
| ereport(LOG, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("Ident authentication is not supported on local connections on this platform"))); |
| |
| return false; |
| #endif |
| } |
| #endif /* HAVE_UNIX_SOCKETS */ |
| |
| |
| /* |
| * Determine the username of the initiator of the connection described |
| * by "port". Then look in the usermap file under the usermap |
| * port->hba->usermap and see if that user is equivalent to Postgres user |
| * port->user. |
| * |
| * Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info). |
| */ |
| static int |
| authident(hbaPort *port) |
| { |
| char ident_user[IDENT_USERNAME_MAX + 1]; |
| |
| /* GPDB: Because we are still using the old flatfile method of getting roles, safest to check here if the role exists */ |
| if (get_role_line(port->user_name) == NULL) |
| return STATUS_ERROR; |
| |
| switch (port->raddr.addr.ss_family) |
| { |
| case AF_INET: |
| #ifdef HAVE_IPV6 |
| case AF_INET6: |
| #endif |
| if (!ident_inet(port->raddr, port->laddr, ident_user)) |
| return STATUS_ERROR; |
| break; |
| |
| #ifdef HAVE_UNIX_SOCKETS |
| case AF_UNIX: |
| if (!ident_unix(port->sock, ident_user)) |
| return STATUS_ERROR; |
| break; |
| #endif |
| |
| default: |
| return STATUS_ERROR; |
| } |
| |
| return check_usermap(port->hba->usermap, port->user_name, ident_user, false); |
| } |
| |
| |
| /*---------------------------------------------------------------- |
| * PAM authentication system |
| *---------------------------------------------------------------- |
| */ |
| #ifdef USE_PAM |
| |
| /* |
| * PAM conversation function |
| */ |
| |
| |
| #if defined(pg_on_solaris) || defined(_AIX) |
| static int |
| pam_passwd_conv_proc(int num_msg, struct pam_message **msg, |
| struct pam_response **resp, void *appdata_ptr) |
| #else |
| static int |
| pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg, |
| struct pam_response ** resp, void *appdata_ptr) |
| #endif |
| { |
| 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); |
| 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 (strlen(passwd) == 0) |
| { |
| ereport(LOG, |
| (errmsg("empty password returned by client"))); |
| 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, char *user, 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 = (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; |
| } |
| |
| 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 */ |
| |
| |
| |
| /*---------------------------------------------------------------- |
| * LDAP authentication system |
| *---------------------------------------------------------------- |
| */ |
| #ifdef USE_LDAP |
| |
| /* |
| * Initialize a connection to the LDAP server, including setting up |
| * TLS if requested. |
| */ |
| static int |
| InitializeLDAPConnection(Port *port, LDAP **ldap) |
| { |
| int ldapversion = LDAP_VERSION3; |
| int r; |
| |
| if (strncmp(port->hba->ldapserver, "ldaps://", 8) == 0 || |
| strncmp(port->hba->ldapserver, "ldap://", 7) == 0) |
| { |
| if ((r = ldap_initialize(ldap, port->hba->ldapserver)) != LDAP_SUCCESS) |
| { |
| ereport(LOG, (errmsg("could not initialize LDAP: code: %d, msg: %s", r, ldap_err2string(r)))); |
| *ldap = NULL; |
| } |
| } |
| else |
| { |
| *ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport); |
| } |
| |
| if (!*ldap) |
| { |
| #ifndef WIN32 |
| ereport(LOG, |
| (errmsg("could not initialize LDAP: error code %d", |
| errno))); |
| #else |
| ereport(LOG, |
| (errmsg("could not initialize LDAP: error code %d", |
| (int) LdapGetLastError()))); |
| #endif |
| return STATUS_ERROR; |
| } |
| |
| if ((r = ldap_set_option(*ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS) |
| { |
| ldap_unbind(*ldap); |
| ereport(LOG, |
| (errmsg("could not set LDAP protocol version: error code %d", r))); |
| 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 |
| */ |
| ldap_unbind(*ldap); |
| ereport(LOG, |
| (errmsg("could not load wldap32.dll"))); |
| return STATUS_ERROR; |
| } |
| _ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA"); |
| if (_ldap_start_tls_sA == NULL) |
| { |
| ldap_unbind(*ldap); |
| ereport(LOG, |
| (errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"), |
| errdetail("LDAP over SSL is not supported on this platform."))); |
| 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 |
| { |
| ldap_unbind(*ldap); |
| ereport(LOG, |
| (errmsg("could not start LDAP TLS session: error code '%d', error message: %s, server: %s, port: %d", r, |
| ldap_err2string(r), port->hba->ldapserver, port->hba->ldapport))); |
| return STATUS_ERROR; |
| } |
| } |
| |
| return STATUS_OK; |
| } |
| |
| /* |
| * Perform LDAP authentication |
| */ |
| static int |
| CheckLDAPAuth(Port *port) |
| { |
| char *passwd; |
| LDAP *ldap; |
| int r; |
| char *fulluser; |
| |
| if (!port->hba->ldapserver || port->hba->ldapserver[0] == '\0') |
| { |
| ereport(LOG, |
| (errmsg("LDAP server not specified"))); |
| return STATUS_ERROR; |
| } |
| |
| if (port->hba->ldapport == 0) |
| port->hba->ldapport = LDAP_PORT; |
| |
| sendAuthRequest(port, AUTH_REQ_PASSWORD); |
| |
| passwd = recv_password_packet(port); |
| if (passwd == NULL) |
| return STATUS_EOF; /* client wouldn't send password */ |
| |
| if (strlen(passwd) == 0) |
| { |
| ereport(LOG, |
| (errmsg("empty password returned by client"))); |
| return STATUS_ERROR; |
| } |
| |
| if (InitializeLDAPConnection(port, &ldap) == STATUS_ERROR) |
| /* Error message already sent */ |
| 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[2]; |
| char *dn; |
| char *c; |
| |
| /* |
| * 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"))); |
| 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\": error code %d", |
| port->hba->ldapbinddn, port->hba->ldapserver, r))); |
| return STATUS_ERROR; |
| } |
| |
| /* Fetch just one attribute, else *all* attributes are returned */ |
| attributes[0] = port->hba->ldapsearchattribute ? port->hba->ldapsearchattribute : "uid"; |
| attributes[1] = NULL; |
| |
| filter = palloc(strlen(attributes[0]) + strlen(port->user_name) + 4); |
| sprintf(filter, "(%s=%s)", |
| attributes[0], |
| port->user_name); |
| |
| r = ldap_search_s(ldap, |
| port->hba->ldapbasedn, |
| LDAP_SCOPE_SUBTREE, |
| filter, |
| attributes, |
| 0, |
| &search_message); |
| |
| if (r != LDAP_SUCCESS) |
| { |
| ereport(LOG, |
| (errmsg("could not search LDAP for filter \"%s\" on server \"%s\": error code %d", |
| filter, port->hba->ldapserver, r))); |
| pfree(filter); |
| return STATUS_ERROR; |
| } |
| |
| if (ldap_count_entries(ldap, search_message) != 1) |
| { |
| if (ldap_count_entries(ldap, search_message) == 0) |
| ereport(LOG, |
| (errmsg("LDAP search failed for filter \"%s\" on server \"%s\": no such user", |
| filter, port->hba->ldapserver))); |
| else |
| ereport(LOG, |
| (errmsg("LDAP search failed for filter \"%s\" on server \"%s\": user is not unique (%ld matches)", |
| filter, port->hba->ldapserver, |
| (long) ldap_count_entries(ldap, search_message)))); |
| |
| 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, port->hba->ldapserver, ldap_err2string(error)))); |
| 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) |
| { |
| int error; |
| |
| (void) ldap_get_option(ldap, LDAP_OPT_ERROR_NUMBER, &error); |
| ereport(LOG, |
| (errmsg("could not unbind after searching for user \"%s\" on server \"%s\": %s", |
| fulluser, port->hba->ldapserver, ldap_err2string(error)))); |
| 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(fulluser); |
| |
| /* Error message already sent */ |
| return STATUS_ERROR; |
| } |
| } |
| else |
| { |
| fulluser = palloc((port->hba->ldapprefix ? strlen(port->hba->ldapprefix) : 0) + |
| strlen(port->user_name) + |
| (port->hba->ldapsuffix ? strlen(port->hba->ldapsuffix) : 0) + |
| 1); |
| |
| sprintf(fulluser, "%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); |
| ldap_unbind(ldap); |
| |
| if (r != LDAP_SUCCESS) |
| { |
| ereport(LOG, |
| (errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d", |
| fulluser, port->hba->ldapserver, r))); |
| pfree(fulluser); |
| return STATUS_ERROR; |
| } |
| |
| pfree(fulluser); |
| |
| return STATUS_OK; |
| } |
| #endif /* USE_LDAP */ |
| |
| |
| /*---------------------------------------------------------------- |
| * SSL client certificate authentication |
| *---------------------------------------------------------------- |
| */ |
| #ifdef USE_SSL |
| static int |
| CheckCertAuth(Port *port) |
| { |
| 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 */ |
| return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false); |
| } |
| #endif |
| |
| /* |
| * Called when we have sent an authorization request for a password. |
| * Get the response and check it. |
| */ |
| static int |
| CheckCloudAuth(Port *port) |
| { |
| char *passwd; |
| int result; |
| |
| pg_cloud_auth = true; |
| |
| elog( |
| DEBUG3, "in CheckCloudAuth, port->hba->cloudserver=%s, pg_cloud_clustername=%s", port->hba->cloudserver, pg_cloud_clustername); |
| if (!port->hba->cloudserver || port->hba->cloudserver[0] == '\0') |
| { |
| ereport(LOG, |
| (errmsg("cloud server not specified"))); |
| return STATUS_ERROR; |
| } |
| |
| sendAuthRequest(port, AUTH_REQ_PASSWORD); |
| |
| passwd = recv_password_packet(port); |
| if (passwd == NULL) |
| return STATUS_EOF; /* client wouldn't send password */ |
| |
| if (strlen(passwd) == 0) |
| { |
| ereport(LOG, |
| (errmsg("empty password returned by client"))); |
| return STATUS_ERROR; |
| } |
| |
| init_cloud_curl(); |
| |
| char *errormsg; |
| result = check_authentication_from_cloud(port->user_name, passwd, NULL, |
| AUTHENTICATION_CHECK, "", &errormsg); |
| if (result) |
| { |
| ereport(LOG, |
| (errmsg("%s", errormsg))); |
| if (errormsg) { |
| pfree(errormsg); |
| errormsg = NULL; |
| } |
| } |
| |
| pfree(passwd); |
| |
| return result; |
| |
| } |
| |
| |
| /*---------------------------------------------------------------- |
| * RADIUS authentication |
| *---------------------------------------------------------------- |
| */ |
| |
| /* |
| * RADIUS authentication is described in RFC2865 (and several |
| * others). |
| */ |
| |
| #define RADIUS_VECTOR_LENGTH 16 |
| #define RADIUS_HEADER_LENGTH 20 |
| |
| typedef struct |
| { |
| uint8 attribute; |
| uint8 length; |
| uint8 data[1]; |
| } radius_attribute; |
| |
| typedef struct |
| { |
| uint8 code; |
| uint8 id; |
| uint16 length; |
| uint8 vector[RADIUS_VECTOR_LENGTH]; |
| } radius_packet; |
| |
| /* RADIUS packet types */ |
| #define RADIUS_ACCESS_REQUEST 1 |
| #define RADIUS_ACCESS_ACCEPT 2 |
| #define RADIUS_ACCESS_REJECT 3 |
| |
| /* RAIDUS 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 |
| |
| /* Maximum size of a RADIUS packet we will create or accept */ |
| #define RADIUS_BUFFER_SIZE 1024 |
| |
| /* 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 %i with length %i 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; |
| char *identifier = "postgresql"; |
| char radius_buffer[RADIUS_BUFFER_SIZE]; |
| char receive_buffer[RADIUS_BUFFER_SIZE]; |
| radius_packet *packet = (radius_packet *) radius_buffer; |
| radius_packet *receivepacket = (radius_packet *) receive_buffer; |
| int32 service = htonl(RADIUS_AUTHENTICATE_ONLY); |
| uint8 *cryptvector; |
| uint8 encryptedpassword[RADIUS_VECTOR_LENGTH]; |
| 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; |
| char portstr[128]; |
| socklen_t addrsize; |
| fd_set fdset; |
| struct timeval timeout; |
| int i, |
| r; |
| |
| /* Make sure struct alignment is correct */ |
| Assert(offsetof(radius_packet, vector) == 4); |
| |
| /* Verify parameters */ |
| if (!port->hba->radiusserver || port->hba->radiusserver[0] == '\0') |
| { |
| ereport(LOG, |
| (errmsg("RADIUS server not specified"))); |
| return STATUS_ERROR; |
| } |
| |
| if (!port->hba->radiussecret || port->hba->radiussecret[0] == '\0') |
| { |
| ereport(LOG, |
| (errmsg("RADIUS secret not specified"))); |
| return STATUS_ERROR; |
| } |
| |
| if (port->hba->radiusport == 0) |
| port->hba->radiusport = 1812; |
| |
| MemSet(&hint, 0, sizeof(hint)); |
| hint.ai_socktype = SOCK_DGRAM; |
| hint.ai_family = AF_UNSPEC; |
| snprintf(portstr, sizeof(portstr), "%d", port->hba->radiusport); |
| |
| r = pg_getaddrinfo_all(port->hba->radiusserver, portstr, &hint, &serveraddrs); |
| if (r || !serveraddrs) |
| { |
| ereport(LOG, |
| (errmsg("could not translate RADIUS server name \"%s\" to address: %s", |
| port->hba->radiusserver, gai_strerror(r)))); |
| if (serveraddrs) |
| pg_freeaddrinfo_all(hint.ai_family, serveraddrs); |
| return STATUS_ERROR; |
| } |
| /* XXX: add support for multiple returned addresses? */ |
| |
| if (port->hba->radiusidentifier && port->hba->radiusidentifier[0]) |
| identifier = port->hba->radiusidentifier; |
| |
| /* Send regular password request to client, and get the response */ |
| sendAuthRequest(port, AUTH_REQ_PASSWORD); |
| |
| passwd = recv_password_packet(port); |
| if (passwd == NULL) |
| return STATUS_EOF; /* client wouldn't send password */ |
| |
| if (strlen(passwd) == 0) |
| { |
| ereport(LOG, |
| (errmsg("empty password returned by client"))); |
| return STATUS_ERROR; |
| } |
| |
| if (strlen(passwd) > RADIUS_VECTOR_LENGTH) |
| { |
| ereport(LOG, |
| (errmsg("RADIUS authentication does not support passwords longer than 16 characters"))); |
| return STATUS_ERROR; |
| } |
| |
| /* Construct RADIUS packet */ |
| packet->code = RADIUS_ACCESS_REQUEST; |
| packet->length = RADIUS_HEADER_LENGTH; |
| #ifdef USE_SSL |
| if (RAND_bytes(packet->vector, RADIUS_VECTOR_LENGTH) != 1) |
| { |
| ereport(LOG, |
| (errmsg("could not generate random encryption vector"))); |
| return STATUS_ERROR; |
| } |
| #else |
| for (i = 0; i < RADIUS_VECTOR_LENGTH; i++) |
| /* Use a lower strengh random number of OpenSSL is not available */ |
| packet->vector[i] = random() % 255; |
| #endif |
| packet->id = packet->vector[0]; |
| radius_add_attribute(packet, RADIUS_SERVICE_TYPE, (unsigned char *) &service, sizeof(service)); |
| radius_add_attribute(packet, RADIUS_USER_NAME, (unsigned char *) port->user_name, strlen(port->user_name)); |
| radius_add_attribute(packet, RADIUS_NAS_IDENTIFIER, (unsigned char *) identifier, strlen(identifier)); |
| |
| /* |
| * RADIUS password attributes are calculated as: e[0] = p[0] XOR |
| * MD5(secret + vector) |
| */ |
| cryptvector = palloc(RADIUS_VECTOR_LENGTH + strlen(port->hba->radiussecret)); |
| memcpy(cryptvector, port->hba->radiussecret, strlen(port->hba->radiussecret)); |
| memcpy(cryptvector + strlen(port->hba->radiussecret), packet->vector, RADIUS_VECTOR_LENGTH); |
| if (!pg_md5_binary(cryptvector, RADIUS_VECTOR_LENGTH + strlen(port->hba->radiussecret), encryptedpassword)) |
| { |
| ereport(LOG, |
| (errmsg("could not perform MD5 encryption of password"))); |
| pfree(cryptvector); |
| return STATUS_ERROR; |
| } |
| pfree(cryptvector); |
| for (i = 0; i < RADIUS_VECTOR_LENGTH; i++) |
| { |
| if (i < strlen(passwd)) |
| encryptedpassword[i] = passwd[i] ^ encryptedpassword[i]; |
| else |
| encryptedpassword[i] = '\0' ^ encryptedpassword[i]; |
| } |
| radius_add_attribute(packet, RADIUS_PASSWORD, encryptedpassword, RADIUS_VECTOR_LENGTH); |
| |
| /* Length need to be in network order on the wire */ |
| packetlength = packet->length; |
| packet->length = htons(packet->length); |
| |
| sock = socket(serveraddrs[0].ai_family, SOCK_DGRAM, 0); |
| if (sock < 0) |
| { |
| 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); |
| |
| /* Wait for a response */ |
| timeout.tv_sec = RADIUS_TIMEOUT; |
| timeout.tv_usec = 0; |
| FD_ZERO(&fdset); |
| FD_SET(sock, &fdset); |
| |
| while (true) |
| { |
| 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"))); |
| closesocket(sock); |
| return STATUS_ERROR; |
| } |
| |
| /* else we actually have a packet ready to read */ |
| break; |
| } |
| |
| /* Read the response packet */ |
| 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; |
| } |
| |
| closesocket(sock); |
| |
| #ifdef HAVE_IPV6 |
| if (remoteaddr.sin6_port != htons(port->hba->radiusport)) |
| #else |
| if (remoteaddr.sin_port != htons(port->hba->radiusport)) |
| #endif |
| { |
| #ifdef HAVE_IPV6 |
| ereport(LOG, |
| (errmsg("RADIUS response was sent from incorrect port: %i", |
| ntohs(remoteaddr.sin6_port)))); |
| #else |
| ereport(LOG, |
| (errmsg("RADIUS response was sent from incorrect port: %i", |
| ntohs(remoteaddr.sin_port)))); |
| #endif |
| return STATUS_ERROR; |
| } |
| |
| if (packetlength < RADIUS_HEADER_LENGTH) |
| { |
| ereport(LOG, |
| (errmsg("RADIUS response too short: %i", packetlength))); |
| return STATUS_ERROR; |
| } |
| |
| if (packetlength != ntohs(receivepacket->length)) |
| { |
| ereport(LOG, |
| (errmsg("RADIUS response has corrupt length: %i (actual length %i)", |
| ntohs(receivepacket->length), packetlength))); |
| return STATUS_ERROR; |
| } |
| |
| if (packet->id != receivepacket->id) |
| { |
| ereport(LOG, |
| (errmsg("RADIUS response is to a different request: %i (should be %i)", |
| receivepacket->id, packet->id))); |
| return STATUS_ERROR; |
| } |
| |
| /* |
| * Verify the response authenticator, which is calculated as |
| * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret) |
| */ |
| cryptvector = palloc(packetlength + strlen(port->hba->radiussecret)); |
| |
| 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, port->hba->radiussecret, strlen(port->hba->radiussecret)); |
| |
| if (!pg_md5_binary(cryptvector, |
| packetlength + strlen(port->hba->radiussecret), |
| encryptedpassword)) |
| { |
| ereport(LOG, |
| (errmsg("could not perform MD5 encryption of received packet"))); |
| pfree(cryptvector); |
| return STATUS_ERROR; |
| } |
| pfree(cryptvector); |
| |
| if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0) |
| { |
| ereport(LOG, |
| (errmsg("RADIUS response has incorrect MD5 signature"))); |
| return STATUS_ERROR; |
| } |
| |
| if (receivepacket->code == RADIUS_ACCESS_ACCEPT) |
| return STATUS_OK; |
| else if (receivepacket->code == RADIUS_ACCESS_REJECT) |
| return STATUS_ERROR; |
| else |
| { |
| ereport(LOG, |
| (errmsg("RADIUS response has invalid code (%i) for user \"%s\"", |
| receivepacket->code, port->user_name))); |
| return STATUS_ERROR; |
| } |
| } |
| |
| /*---------------------------------------------------------------- |
| * Time-based authentication |
| *---------------------------------------------------------------- |
| */ |
| /* |
| * interval_overlap -- Return true iff intersection of a, b is nonempty |
| */ |
| bool |
| interval_overlap(const authInterval *a, const authInterval *b) |
| { |
| return point_cmp(&a->start, &b->end) <= 0 && |
| point_cmp(&a->end, &b->start) >= 0; |
| } |
| |
| /* |
| * interval_contains -- Return true iff interval contains point |
| */ |
| bool |
| interval_contains(const authInterval *interval, const authPoint *point) |
| { |
| return point_cmp(point, &interval->start) >= 0 && |
| point_cmp(point, &interval->end) <= 0; |
| } |
| |
| /* Comparator for authPoint struct */ |
| int |
| point_cmp(const authPoint *a, const authPoint *b) |
| { |
| if (a->day > b->day) |
| return 1; |
| else if (a->day == b->day) |
| if (a->time > b->time) |
| return 1; |
| else if (a->time == b->time) |
| return 0; |
| else |
| return -1; |
| else |
| return -1; |
| } |
| |
| /* convert timestamptz to authPoint through use of timestamp2tm and timestamptz_time */ |
| void |
| timestamptz_to_point(TimestampTz in, authPoint *out) |
| { |
| /* from timestamptz_to_char */ |
| struct pg_tm tm; |
| fsec_t fsec; |
| char *tzn; |
| int tzp, thisdate; |
| if (timestamp2tm(in, &tzp, &tm, &fsec, &tzn, NULL) != 0) |
| ereport(FATAL, |
| (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
| errmsg("current timestamp out of range"))); |
| |
| thisdate = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday); |
| out->day = (thisdate + 1) % 7; |
| out->time = DatumGetTimeADT(DirectFunctionCall1(timestamptz_time, |
| TimestampTzGetDatum(in))); |
| } |
| |
| /* |
| * CheckAuthTimeConstraints - check pg_auth_time_constraint for login restrictions |
| * |
| * Invokes check_auth_time_constraints_internal against the current timestamp |
| */ |
| bool |
| CheckAuthTimeConstraints(char *rolname) |
| { |
| if (gp_auth_time_override_str != NULL && gp_auth_time_override_str[0] != '\0') |
| { |
| TimestampTz timestamptz = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in, |
| CStringGetDatum(gp_auth_time_override_str), |
| InvalidOid, |
| Int32GetDatum(0))); |
| return check_auth_time_constraints_internal(rolname, timestamptz); |
| } |
| return check_auth_time_constraints_internal(rolname, GetCurrentTimestamp()); |
| } |
| |
| /* |
| * check_auth_time_constraints_internal - helper to CheckAuthTimeConstraints |
| * |
| * Called out as separate function to facilitate unit testing, where the provided |
| * timestamp is likely to be hardcoded for deterministic test runs |
| * |
| * Returns false iff it finds an interval that contains timestamp from among the |
| * entries of pg_auth_time_constraint that pertain to rolname |
| */ |
| bool |
| check_auth_time_constraints_internal(char *rolname, TimestampTz timestamp) |
| { |
| List *role_intervals = get_role_intervals(rolname); |
| |
| if (role_intervals == NIL) |
| return true; |
| |
| List *row; |
| ListCell *line, *cell; |
| char *temp; |
| authInterval given; |
| authPoint now; |
| |
| timestamptz_to_point(timestamp, &now); |
| |
| foreach (line, role_intervals) |
| { |
| row = lfirst(line); |
| /* |
| * Each row of role_intervals is a List of 5 records |
| * <rolname> <startday> <starttime> <endday> <endtime> |
| */ |
| cell = list_head(row); |
| cell = lnext(cell); /* skip first entry in record, which is rolname */ |
| temp = lfirst(cell); |
| given.start.day = pg_atoi(temp, sizeof(int16), 0); |
| |
| cell = lnext(cell); |
| temp = lfirst(cell); |
| given.start.time = DatumGetTimeADT(DirectFunctionCall1(time_in, |
| CStringGetDatum(temp))); |
| |
| cell = lnext(cell); |
| temp = lfirst(cell); |
| given.end.day = pg_atoi(temp, sizeof(int16), 0); |
| |
| cell = lnext(cell); |
| temp = lfirst(cell); |
| given.end.time = DatumGetTimeADT(DirectFunctionCall1(time_in, |
| CStringGetDatum(temp))); |
| |
| if (interval_contains(&given, &now)) |
| { |
| list_free(role_intervals); |
| return false; |
| } |
| } |
| |
| list_free(role_intervals); |
| return true; |
| } |