diff options
author | Magnus Hagander <magnus@hagander.net> | 2007-07-23 10:16:54 +0000 |
---|---|---|
committer | Magnus Hagander <magnus@hagander.net> | 2007-07-23 10:16:54 +0000 |
commit | f70866fb2353dba162fc296f644e7ce77af6d79f (patch) | |
tree | b061ad6406fcd27c42b2fb3c210925c7b11dfb33 /src/backend | |
parent | a0dab332a2e1961f45b38b23bd428859621e6f3c (diff) | |
download | postgresql-f70866fb2353dba162fc296f644e7ce77af6d79f.tar.gz postgresql-f70866fb2353dba162fc296f644e7ce77af6d79f.zip |
SSPI authentication on Windows. GSSAPI compatible client when doing Kerberos
against a Unix server, and Windows-specific server-side authentication
using SSPI "negotiate" method (Kerberos or NTLM).
Only builds properly with MSVC for now.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/libpq/auth.c | 262 | ||||
-rw-r--r-- | src/backend/libpq/hba.c | 4 | ||||
-rw-r--r-- | src/backend/libpq/pg_hba.conf.sample | 2 | ||||
-rw-r--r-- | src/backend/libpq/pqcomm.c | 10 | ||||
-rw-r--r-- | src/backend/postmaster/postmaster.c | 19 |
5 files changed, 284 insertions, 13 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 224115fde8e..c475c6429d6 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.153 2007/07/12 20:36:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.154 2007/07/23 10:16:53 mha Exp $ * *------------------------------------------------------------------------- */ @@ -464,10 +464,14 @@ pg_GSS_recvauth(Port *port) /* * 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) @@ -536,7 +540,7 @@ pg_GSS_recvauth(Port *port) return STATUS_OK; } -#else /* no ENABLE_GSS */ +#else /* no ENABLE_GSS */ static int pg_GSS_recvauth(Port *port) { @@ -547,6 +551,245 @@ pg_GSS_recvauth(Port *port) } #endif /* ENABLE_GSS */ +#ifdef ENABLE_SSPI +static void +pg_SSPI_error(int severity, char *errmsg, SECURITY_STATUS r) +{ + char sysmsg[256]; + + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0, sysmsg, sizeof(sysmsg), NULL) == 0) + ereport(severity, + (errmsg_internal("%s", errmsg), + errdetail("sspi error %x", r))); + else + ereport(severity, + (errmsg_internal("%s", errmsg), + errdetail("%s (%x)", sysmsg, 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; + + + /* + * Acquire a handle to the server credentials. + */ + r = AcquireCredentialsHandle(NULL, + "negotiate", + SECPKG_CRED_INBOUND, + NULL, + NULL, + NULL, + NULL, + &sspicred, + &expiry); + if (r != SEC_E_OK) + pg_SSPI_error(ERROR, + gettext_noop("could not acquire SSPI credentials handle"), r); + + /* + * Loop through SSPI message exchange. This exchange can consist + * of multiple messags sent in both directions. First message is always + * from the client. All messages from client to server are password + * packets (type 'p'). + */ + do + { + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* Only log error if client didn't disconnect. */ + if (mtype != EOF) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("expected SSPI response, got message type %d", + mtype))); + return STATUS_ERROR; + } + + /* Get the actual SSPI token */ + initStringInfo(&buf); + if (pq_getmessage(&buf, 2000)) + { + /* EOF - pq_getmessage already logged error */ + pfree(buf.data); + return STATUS_ERROR; + } + + /* Map to SSPI style buffer */ + inbuf.ulVersion = SECBUFFER_VERSION; + inbuf.cBuffers = 1; + inbuf.pBuffers = InBuffers; + InBuffers[0].pvBuffer = buf.data; + InBuffers[0].cbBuffer = buf.len; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + /* Prepare output buffer */ + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + outbuf.cBuffers = 1; + outbuf.pBuffers = OutBuffers; + outbuf.ulVersion = SECBUFFER_VERSION; + + + elog(DEBUG4, "Processing received SSPI token of length %u", + (unsigned int) buf.len); + + r = AcceptSecurityContext(&sspicred, + sspictx, + &inbuf, + ASC_REQ_ALLOCATE_MEMORY, + SECURITY_NETWORK_DREP, + &newctx, + &outbuf, + &contextattr, + NULL); + + /* input buffer no longer used */ + pfree(buf.data); + + if (outbuf.cBuffers > 0 && outbuf.pBuffers[0].cbBuffer > 0) + { + /* + * Negotiation generated data to be sent to the client. + */ + elog(DEBUG4, "sending SSPI response token of length %u", + (unsigned int) outbuf.pBuffers[0].cbBuffer); + + port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer; + port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer; + + sendAuthRequest(port, AUTH_REQ_GSS_CONT); + + FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); + } + + if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED) + { + if (sspictx != NULL) + { + DeleteSecurityContext(sspictx); + free(sspictx); + } + FreeCredentialsHandle(&sspicred); + pg_SSPI_error(ERROR, + gettext_noop("could not accept SSPI security context"), r); + } + + if (sspictx == NULL) + { + sspictx = malloc(sizeof(CtxtHandle)); + if (sspictx == NULL) + ereport(ERROR, + (errmsg("out of memory"))); + + memcpy(sspictx, &newctx, sizeof(CtxtHandle)); + } + + if (r == SEC_I_CONTINUE_NEEDED) + elog(DEBUG4, "SSPI continue needed"); + + } while (r == SEC_I_CONTINUE_NEEDED); + + + /* + * Release service principal credentials + */ + FreeCredentialsHandle(&sspicred); + + + /* + * SEC_E_OK indicates that authentication is now complete. + * + * Get the name of the user that authenticated, and compare it to the + * pg username that was specified for the connection. + */ + + r = QuerySecurityContextToken(sspictx, &token); + if (r != SEC_E_OK) + pg_SSPI_error(ERROR, + gettext_noop("could not get security token from context"), r); + + /* + * 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); + + /* + * We have the username (without domain/realm) in accountname, compare + * to the supplied value. In SSPI, always compare case insensitive. + */ + if (pg_strcasecmp(port->user_name, accountname)) + { + /* GSS name and PGUSER are not equivalent */ + elog(DEBUG2, + "provided username (%s) and SSPI username (%s) don't match", + port->user_name, accountname); + + return STATUS_ERROR; + } + + return STATUS_OK; +} +#else /* no ENABLE_SSPI */ +static int +pg_SSPI_recvauth(Port *port) +{ + ereport(LOG, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SSPI not implemented on this server."))); + return STATUS_ERROR; +} +#endif /* ENABLE_SSPI */ + /* * Tell the user the authentication failed, but not (much about) why. @@ -589,6 +832,9 @@ auth_failed(Port *port, int status) case uaGSS: errstr = gettext_noop("GSSAPI authentication failed for user \"%s\""); break; + case uaSSPI: + errstr = gettext_noop("SSPI authentication failed for user \"%s\""); + break; case uaTrust: errstr = gettext_noop("\"trust\" authentication failed for user \"%s\""); break; @@ -689,6 +935,11 @@ ClientAuthentication(Port *port) status = pg_GSS_recvauth(port); break; + case uaSSPI: + sendAuthRequest(port, AUTH_REQ_SSPI); + status = pg_SSPI_recvauth(port); + break; + case uaIdent: /* @@ -778,20 +1029,17 @@ sendAuthRequest(Port *port, AuthRequest areq) else if (areq == AUTH_REQ_CRYPT) pq_sendbytes(&buf, port->cryptSalt, 2); -#ifdef ENABLE_GSS +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Add the authentication data for the next step of - * the GSSAPI negotiation. */ + * the GSSAPI or SSPI negotiation. */ else if (areq == AUTH_REQ_GSS_CONT) { if (port->gss->outbuf.length > 0) { - OM_uint32 lmin_s; - 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); - gss_release_buffer(&lmin_s, &port->gss->outbuf); } } #endif diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 9f917b16661..c3cde8cb1bf 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.161 2007/07/10 13:14:20 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.162 2007/07/23 10:16:53 mha Exp $ * *------------------------------------------------------------------------- */ @@ -604,6 +604,8 @@ parse_hba_auth(ListCell **line_item, UserAuth *userauth_p, *userauth_p = uaKrb5; else if (strcmp(token, "gss") == 0) *userauth_p = uaGSS; + else if (strcmp(token, "sspi") == 0) + *userauth_p = uaSSPI; else if (strcmp(token, "reject") == 0) *userauth_p = uaReject; else if (strcmp(token, "md5") == 0) diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample index ebdf76904ae..1447a8c4bef 100644 --- a/src/backend/libpq/pg_hba.conf.sample +++ b/src/backend/libpq/pg_hba.conf.sample @@ -34,7 +34,7 @@ # the number of significant bits in the mask. Alternatively, you can write # an IP address and netmask in separate columns to specify the set of hosts. # -# METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", +# METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi", # "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords # in clear text; "md5" is preferred since it sends encrypted passwords. # diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index a40e6a6cb27..f5dccfda7ab 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -30,7 +30,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.193 2007/07/10 13:14:20 mha Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/pqcomm.c,v 1.194 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -173,15 +173,21 @@ pq_close(int code, Datum arg) { if (MyProcPort != NULL) { +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) #ifdef ENABLE_GSS OM_uint32 min_s; + /* Shutdown GSSAPI layer */ if (MyProcPort->gss->ctx) gss_delete_sec_context(&min_s, MyProcPort->gss->ctx, NULL); if (MyProcPort->gss->cred) gss_release_cred(&min_s, MyProcPort->gss->cred); -#endif +#endif /* ENABLE_GSS */ + /* GSS and SSPI share the port->gss struct */ + + free(MyProcPort->gss); +#endif /* ENABLE_GSS || ENABLE_SSPI */ /* Cleanly shut down SSL layer */ secure_close(MyProcPort); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 240f150d62a..7a1270b0149 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.533 2007/07/19 19:13:43 adunstan Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.534 2007/07/23 10:16:54 mha Exp $ * * NOTES * @@ -1733,7 +1733,8 @@ ConnCreate(int serverFd) /* * Allocate GSSAPI specific state struct */ -#ifdef ENABLE_GSS +#ifndef EXEC_BACKEND +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) port->gss = (pg_gssinfo *)calloc(1, sizeof(pg_gssinfo)); if (!port->gss) { @@ -1743,6 +1744,7 @@ ConnCreate(int serverFd) ExitPostmaster(1); } #endif +#endif return port; } @@ -3344,6 +3346,19 @@ SubPostmasterMain(int argc, char *argv[]) memset(&port, 0, sizeof(Port)); read_backend_variables(argv[2], &port); + /* + * Set up memory area for GSS information. Mirrors the code in + * ConnCreate for the non-exec case. + */ +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) + port.gss = (pg_gssinfo *)calloc(1, sizeof(pg_gssinfo)); + if (!port.gss) + ereport(FATAL, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); +#endif + + /* Check we got appropriate args */ if (argc < 3) elog(FATAL, "invalid subpostmaster invocation"); |