From f70866fb2353dba162fc296f644e7ce77af6d79f Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Mon, 23 Jul 2007 10:16:54 +0000 Subject: [PATCH] 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. --- doc/src/sgml/client-auth.sgml | 40 +++- doc/src/sgml/libpq.sgml | 22 ++- src/backend/libpq/auth.c | 262 ++++++++++++++++++++++++- src/backend/libpq/hba.c | 4 +- src/backend/libpq/pg_hba.conf.sample | 2 +- src/backend/libpq/pqcomm.c | 10 +- src/backend/postmaster/postmaster.c | 19 +- src/include/libpq/hba.h | 5 +- src/include/libpq/libpq-be.h | 26 ++- src/include/libpq/pqcomm.h | 3 +- src/interfaces/libpq/fe-auth.c | 277 +++++++++++++++++++++++++-- src/interfaces/libpq/fe-connect.c | 51 +++-- src/interfaces/libpq/libpq-int.h | 40 +++- src/tools/msvc/Mkvcbuild.pm | 5 +- src/tools/msvc/Solution.pm | 20 +- 15 files changed, 708 insertions(+), 78 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index e3fa1c8b27d..dd3bd8be4d4 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1,4 +1,4 @@ - + Client Authentication @@ -358,6 +358,17 @@ hostnossl database user + + sspi + + + Use SSPI to authenticate the user. This is only + available on Windows. See for details. + + + + krb5 @@ -677,6 +688,33 @@ local db1,db2,@demodbs all md5 + + SSPI authentication + + + SSPI + + + + SSPI is a Windows + technology for secure authentication with single sign-on. + PostgreSQL will use SSPI in + negotiate mode, which will use + Kerberos when possible and automatically + fall back to NTLM in other cases. + SSPI authentication only works when both + server and client are running Windows. + + + + When using Kerberos authentication, + SSPI works the same way + GSSAPI does. See + for details. + + + + Kerberos authentication diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index e1ee97ce182..117bc5f3509 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,4 +1,4 @@ - + <application>libpq</application> - C Library @@ -290,6 +290,17 @@ PGconn *PQconnectdb(const char *conninfo); + + gsslib + + + GSS library to use for GSSAPI authentication. Only used on Windows. + Set to gssapi to force libpq to use the GSSAPI + library for authentication instead of the default SSPI. + + + + service @@ -4220,6 +4231,15 @@ authenticating with Kerberos 5 or GSSAPI. + + PGGSSLIB + +PGGSSLIB sets the GSS library to use for GSSAPI +authentication. + + + + PGCONNECT_TIMEOUT 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) { @@ -1742,6 +1743,7 @@ ConnCreate(int serverFd) errmsg("out of memory"))); 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"); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index b68aa5899b5..603d8635238 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.46 2007/07/10 13:14:21 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.47 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -23,7 +23,8 @@ typedef enum UserAuth uaPassword, uaCrypt, uaMD5, - uaGSS + uaGSS, + uaSSPI #ifdef USE_PAM ,uaPAM #endif /* USE_PAM */ diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 891ab9614c0..319e5e86100 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -11,7 +11,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/libpq/libpq-be.h,v 1.61 2007/07/12 14:43:21 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/libpq-be.h,v 1.62 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -45,6 +45,22 @@ #endif #endif /* ENABLE_GSS */ +#ifdef ENABLE_SSPI +#define SECURITY_WIN32 +#include +#undef SECURITY_WIN32 + +#ifndef ENABLE_GSS +/* + * Define a fake structure compatible with GSSAPI on Unix. + */ +typedef struct { + void *value; + int length; +} gss_buffer_desc; +#endif +#endif /* ENABLE_SSPI */ + #include "libpq/hba.h" #include "libpq/pqcomm.h" #include "utils/timestamp.h" @@ -59,13 +75,15 @@ typedef enum CAC_state /* * GSSAPI specific state information */ -#ifdef ENABLE_GSS +#if defined(ENABLE_GSS) | defined(ENABLE_SSPI) typedef struct { + gss_buffer_desc outbuf; /* GSSAPI output token buffer */ +#ifdef ENABLE_GSS gss_cred_id_t cred; /* GSSAPI connection cred's */ gss_ctx_id_t ctx; /* GSSAPI connection context */ gss_name_t name; /* GSSAPI client name */ - gss_buffer_desc outbuf; /* GSSAPI output token buffer */ +#endif } pg_gssinfo; #endif @@ -128,7 +146,7 @@ typedef struct Port int keepalives_interval; int keepalives_count; -#ifdef ENABLE_GSS +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* * If GSSAPI is supported, store GSSAPI information. * Oterwise, store a NULL pointer to make sure offsets diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 8e66aaa9685..ec7427b6de0 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -9,7 +9,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/libpq/pqcomm.h,v 1.105 2007/07/10 13:14:21 mha Exp $ + * $PostgreSQL: pgsql/src/include/libpq/pqcomm.h,v 1.106 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -158,6 +158,7 @@ extern bool Db_user_namespace; #define AUTH_REQ_SCM_CREDS 6 /* transfer SCM credentials */ #define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ #define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ +#define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ typedef uint32 AuthRequest; diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 4c3207f63a6..e08dae8125d 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -10,7 +10,7 @@ * exceed INITIAL_EXPBUFFER_SIZE (currently 256 bytes). * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-auth.c,v 1.127 2007/07/12 14:43:21 mha Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-auth.c,v 1.128 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -329,11 +329,6 @@ pg_krb5_sendauth(char *PQerrormsg, int sock, const char *hostname, const char *s /* * GSSAPI authentication system. */ -#if defined(HAVE_GSSAPI_H) -#include -#else -#include -#endif #if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) /* @@ -378,7 +373,7 @@ pg_GSS_error_int(char *mprefix, char *msg, int msglen, * GSSAPI errors contains two parts. Put as much as possible of * both parts into the string. */ -void +static void pg_GSS_error(char *mprefix, char *msg, int msglen, OM_uint32 maj_stat, OM_uint32 min_stat) { @@ -407,7 +402,7 @@ pg_GSS_continue(char *PQerrormsg, PGconn *conn) &conn->gctx, conn->gtarg_nam, GSS_C_NO_OID, - conn->gflags, + GSS_C_MUTUAL_FLAG, 0, GSS_C_NO_CHANNEL_BINDINGS, (conn->gctx==GSS_C_NO_CONTEXT)?GSS_C_NO_BUFFER:&conn->ginbuf, @@ -504,7 +499,192 @@ pg_GSS_startup(char *PQerrormsg, PGconn *conn) return pg_GSS_continue(PQerrormsg, conn); } -#endif +#endif /* ENABLE_GSS */ + + +#ifdef ENABLE_SSPI +/* + * SSPI authentication system (Windows only) + */ + +static void +pg_SSPI_error(char *mprefix, char *msg, int msglen, SECURITY_STATUS r) +{ + char sysmsg[256]; + + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0, sysmsg, sizeof(sysmsg), NULL) == 0) + snprintf(msg, msglen, "%s: sspi error %x", mprefix, r); + else + snprintf(msg, msglen, "%s: %s (%x)", mprefix, sysmsg, r); +} + +/* + * Continue SSPI authentication with next token as needed. + */ +static int +pg_SSPI_continue(char *PQerrormsg, PGconn *conn) +{ + SECURITY_STATUS r; + CtxtHandle newContext; + ULONG contextAttr; + SecBufferDesc inbuf; + SecBufferDesc outbuf; + SecBuffer OutBuffers[1]; + SecBuffer InBuffers[1]; + + if (conn->sspictx != NULL) + { + /* + * On runs other than the first we have some data to send. Put this + * data in a SecBuffer type structure. + */ + inbuf.ulVersion = SECBUFFER_VERSION; + inbuf.cBuffers = 1; + inbuf.pBuffers = InBuffers; + InBuffers[0].pvBuffer = conn->ginbuf.value; + InBuffers[0].cbBuffer = conn->ginbuf.length; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + } + + OutBuffers[0].pvBuffer = NULL; + OutBuffers[0].BufferType = SECBUFFER_TOKEN; + OutBuffers[0].cbBuffer = 0; + outbuf.cBuffers = 1; + outbuf.pBuffers = OutBuffers; + outbuf.ulVersion = SECBUFFER_VERSION; + + r = InitializeSecurityContext(conn->sspicred, + conn->sspictx, + conn->sspitarget, + ISC_REQ_ALLOCATE_MEMORY, + 0, + SECURITY_NETWORK_DREP, + (conn->sspictx == NULL)?NULL:&inbuf, + 0, + &newContext, + &outbuf, + &contextAttr, + NULL); + + if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED) + { + pg_SSPI_error(libpq_gettext("SSPI continuation error"), + PQerrormsg, PQERRORMSG_LENGTH, r); + + return STATUS_ERROR; + } + + if (conn->sspictx == NULL) + { + /* On first run, transfer retreived context handle */ + conn->sspictx = malloc(sizeof(CtxtHandle)); + if (conn->sspictx == NULL) + { + strncpy(PQerrormsg, libpq_gettext("out of memory\n"), PQERRORMSG_LENGTH); + return STATUS_ERROR; + } + memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle)); + } + else + { + /* + * On subsequent runs when we had data to send, free buffers that contained + * this data. + */ + free(conn->ginbuf.value); + conn->ginbuf.value = NULL; + conn->ginbuf.length = 0; + } + + /* + * If SSPI returned any data to be sent to the server (as it normally would), + * send this data as a password packet. + */ + if (outbuf.cBuffers > 0) + { + if (outbuf.cBuffers != 1) + { + /* + * This should never happen, at least not for Kerberos authentication. Keep check + * in case it shows up with other authentication methods later. + */ + strncpy(PQerrormsg, "SSPI returned invalid number of output buffers\n", PQERRORMSG_LENGTH); + return STATUS_ERROR; + } + + if (pqPacketSend(conn, 'p', + outbuf.pBuffers[0].pvBuffer, outbuf.pBuffers[0].cbBuffer)) + { + FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); + return STATUS_ERROR; + } + FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); + } + + /* Cleanup is handled by the code in freePGconn() */ + return STATUS_OK; +} + +/* + * Send initial SSPI authentication token. + * If use_negotiate is 0, use kerberos authentication package which is + * compatible with Unix. If use_negotiate is 1, use the negotiate package + * which supports both kerberos and NTLM, but is not compatible with Unix. + */ +static int +pg_SSPI_startup(char *PQerrormsg, PGconn *conn, int use_negotiate) +{ + SECURITY_STATUS r; + TimeStamp expire; + + conn->sspictx = NULL; + + /* + * Retreive credentials handle + */ + conn->sspicred = malloc(sizeof(CredHandle)); + if (conn->sspicred == NULL) + { + strncpy(PQerrormsg, libpq_gettext("out of memory\n"), PQERRORMSG_LENGTH); + return STATUS_ERROR; + } + + r = AcquireCredentialsHandle(NULL, use_negotiate?"negotiate":"kerberos", SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, conn->sspicred, &expire); + if (r != SEC_E_OK) + { + pg_SSPI_error("acquire credentials failed", PQerrormsg, PQERRORMSG_LENGTH, r); + free(conn->sspicred); + conn->sspicred = NULL; + return STATUS_ERROR; + } + + /* + * Compute target principal name. SSPI has a different format from GSSAPI, but + * not more complex. We can skip the @REALM part, because Windows will fill that + * in for us automatically. + */ + if (conn->pghost == NULL) + { + strncpy(PQerrormsg, libpq_gettext("hostname must be specified\n"), PQERRORMSG_LENGTH); + return STATUS_ERROR; + } + conn->sspitarget = malloc(strlen(conn->krbsrvname)+strlen(conn->pghost)+2); + if (!conn->sspitarget) + { + strncpy(PQerrormsg, libpq_gettext("out of memory\n"), PQERRORMSG_LENGTH); + return STATUS_ERROR; + } + sprintf(conn->sspitarget, "%s/%s", conn->krbsrvname, conn->pghost); + + /* + * Indicate that we're in SSPI authentication mode to make sure that + * pg_SSPI_continue is called next time in the negotiation. + */ + conn->usesspi = 1; + + return pg_SSPI_continue(PQerrormsg, conn); +} +#endif /* ENABLE_SSPI */ /* * Respond to AUTH_REQ_SCM_CREDS challenge. @@ -671,27 +851,60 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn, const char *hostname, return STATUS_ERROR; #endif -#ifdef ENABLE_GSS +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) case AUTH_REQ_GSS: - pglock_thread(); - if (pg_GSS_startup(PQerrormsg, conn) != STATUS_OK) { - /* PQerrormsg already filled in. */ + int r; + pglock_thread(); + /* + * If we have both GSS and SSPI support compiled in, use SSPI + * support by default. This is overridable by a connection string parameter. + * Note that when using SSPI we still leave the negotiate parameter off, + * since we want SSPI to use the GSSAPI kerberos protocol. For actual + * SSPI negotiate protocol, we use AUTH_REQ_SSPI. + */ +#if defined(ENABLE_GSS) && defined(ENABLE_SSPI) + if (conn->gsslib && (pg_strcasecmp(conn->gsslib, "gssapi") == 0)) + r = pg_GSS_startup(PQerrormsg, conn); + else + r = pg_SSPI_startup(PQerrormsg, conn, 0); +#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI) + r = pg_GSS_startup(PQerrormsg, conn); +#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI) + r = pg_SSPI_startup(PQerrormsg, conn, 0); +#endif + if (r != STATUS_OK) + { + /* PQerrormsg already filled in. */ + pgunlock_thread(); + return STATUS_ERROR; + } pgunlock_thread(); - return STATUS_ERROR; } - pgunlock_thread(); break; case AUTH_REQ_GSS_CONT: - pglock_thread(); - if (pg_GSS_continue(PQerrormsg, conn) != STATUS_OK) { - /* PQerrormsg already filled in. */ + int r; + pglock_thread(); +#if defined(ENABLE_GSS) && defined(ENABLE_SSPI) + if (conn->usesspi) + r = pg_SSPI_continue(PQerrormsg, conn); + else + r = pg_GSS_continue(PQerrormsg, conn); +#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI) + r = pg_GSS_continue(PQerrormsg, conn); +#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI) + r = pg_SSPI_continue(PQerrormsg, conn); +#endif + if (r != STATUS_OK) + { + /* PQerrormsg already filled in. */ + pgunlock_thread(); + return STATUS_ERROR; + } pgunlock_thread(); - return STATUS_ERROR; } - pgunlock_thread(); break; #else @@ -702,6 +915,30 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn, const char *hostname, return STATUS_ERROR; #endif +#ifdef ENABLE_SSPI + case AUTH_REQ_SSPI: + /* + * SSPI has it's own startup message so libpq can decide which + * method to use. Indicate to pg_SSPI_startup that we want + * SSPI negotiation instead of Kerberos. + */ + pglock_thread(); + if (pg_SSPI_startup(PQerrormsg, conn, 1) != STATUS_OK) + { + /* PQerrormsg already filled in. */ + pgunlock_thread(); + return STATUS_ERROR; + } + pgunlock_thread(); + break; +#else + case AUTH_REQ_SSPI: + snpritnf(PQerrormsg, PQERRORMSG_LENGTH, + libpq_gettext("SSPI authentication not supported\n")); + return STATUS_ERROR; +#endif + + case AUTH_REQ_MD5: case AUTH_REQ_CRYPT: case AUTH_REQ_PASSWORD: diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index aebce1caf6e..f3eafb1eeb4 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.349 2007/07/11 08:27:33 mha Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.350 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -181,12 +181,18 @@ static const PQconninfoOption PQconninfoOptions[] = { {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ -#if defined(KRB5) || defined(ENABLE_GSS) +#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, "Kerberos-service-name", "", 20}, #endif +#if defined(ENABLE_GSS) && defined(ENABLE_SSPI) + /* GSSAPI and SSPI both enabled, give a way to override which is used by default */ + {"gsslib", "PGGSSLIB", NULL, NULL, + "GSS-library", "", 7}, /* sizeof("gssapi") = 7 */ +#endif + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -412,10 +418,14 @@ connectOptions1(PGconn *conn, const char *conninfo) conn->sslmode = strdup("require"); } #endif -#if defined(KRB5) || defined(ENABLE_GSS) +#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) tmp = conninfo_getval(connOptions, "krbsrvname"); conn->krbsrvname = tmp ? strdup(tmp) : NULL; #endif +#if defined(ENABLE_GSS) && defined(ENABLE_SSPI) + tmp = conninfo_getval(connOptions, "gsslib"); + conn->gsslib = tmp ? strdup(tmp) : NULL; +#endif /* * Free the option info - all is in conn now @@ -1661,22 +1671,13 @@ keep_going: /* We will come back to here until there is return PGRES_POLLING_READING; } } -#ifdef ENABLE_GSS +#if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* - * AUTH_REQ_GSS provides no input data - * Just set the request flags - */ - if (areq == AUTH_REQ_GSS) - conn->gflags = GSS_C_MUTUAL_FLAG; - - /* - * Read GSSAPI data packets + * Continue GSSAPI/SSPI authentication */ if (areq == AUTH_REQ_GSS_CONT) { - /* Continue GSSAPI authentication */ int llen = msgLength - 4; - /* * We can be called repeatedly for the same buffer. * Avoid re-allocating the buffer in this case - @@ -2002,7 +2003,7 @@ freePGconn(PGconn *conn) free(conn->pgpass); if (conn->sslmode) free(conn->sslmode); -#if defined(KRB5) || defined(ENABLE_GSS) +#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif @@ -2030,6 +2031,26 @@ freePGconn(PGconn *conn) if (conn->goutbuf.length) gss_release_buffer(&min_s, &conn->goutbuf); } +#endif +#ifdef ENABLE_SSPI + { + if (conn->ginbuf.length) + free(conn->ginbuf.value); + + if (conn->sspitarget) + free(conn->sspitarget); + + if (conn->sspicred) + { + FreeCredentialsHandle(conn->sspicred); + free(conn->sspicred); + } + if (conn->sspictx) + { + DeleteSecurityContext(conn->sspictx); + free(conn->sspictx); + } + } #endif pstatus = conn->pstatus; while (pstatus != NULL) diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index b497e1d68d1..cc3fa0106d2 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.123 2007/07/12 14:36:52 mha Exp $ + * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.124 2007/07/23 10:16:54 mha Exp $ * *------------------------------------------------------------------------- */ @@ -52,6 +52,22 @@ #endif #endif +#ifdef ENABLE_SSPI +#define SECURITY_WIN32 +#include +#undef SECURITY_WIN32 + +#ifndef ENABLE_GSS +/* + * Define a fake structure compatible with GSSAPI on Unix. + */ +typedef struct { + void *value; + int length; +} gss_buffer_desc; +#endif +#endif /* ENABLE_SSPI */ + #ifdef USE_SSL #include #include @@ -276,7 +292,7 @@ struct pg_conn char *pguser; /* Postgres username and password, if any */ char *pgpass; char *sslmode; /* SSL mode (require,prefer,allow,disable) */ -#if defined(KRB5) || defined(ENABLE_GSS) +#if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) char *krbsrvname; /* Kerberos service name */ #endif @@ -361,11 +377,23 @@ struct pg_conn #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ gss_name_t gtarg_nam; /* GSS target name */ - OM_uint32 gflags; /* GSS service request flags */ gss_buffer_desc ginbuf; /* GSS input token */ gss_buffer_desc goutbuf; /* GSS output token */ #endif +#ifdef ENABLE_SSPI +#ifndef ENABLE_GSS + gss_buffer_desc ginbuf; /* GSS input token */ +#else + char *gsslib; /* What GSS librart to use ("gssapi" or "sspi") */ +#endif + CredHandle *sspicred; /* SSPI credentials handle */ + CtxtHandle *sspictx; /* SSPI context */ + char *sspitarget;/* SSPI target name */ + int usesspi; /* Indicate if SSPI is in use on the connection */ +#endif + + /* Buffer for current error message */ PQExpBufferData errorMessage; /* expansible string */ @@ -415,12 +443,6 @@ extern pgthreadlock_t pg_g_threadlock; #define pgunlock_thread() ((void) 0) #endif -/* === in fe-auth.c === */ -#ifdef ENABLE_GSS -extern void pg_GSS_error(char *mprefix, char *msg, int msglen, - OM_uint32 maj_stat, OM_uint32 min_stat); -#endif - /* === in fe-exec.c === */ extern void pqSetResultError(PGresult *res, const char *msg); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 1bc19ed70fe..a1eb2af64b4 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -3,7 +3,7 @@ package Mkvcbuild; # # Package that generates build files for msvc build # -# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.14 2007/07/07 07:43:20 mha Exp $ +# $PostgreSQL: pgsql/src/tools/msvc/Mkvcbuild.pm,v 1.15 2007/07/23 10:16:54 mha Exp $ # use Carp; use Win32; @@ -66,7 +66,7 @@ sub mkvcbuild $postgres->AddFiles('src\backend\bootstrap','bootscanner.l','bootparse.y'); $postgres->AddFiles('src\backend\utils\misc','guc-file.l'); $postgres->AddDefine('BUILDING_DLL'); - $postgres->AddLibrary('wsock32.lib ws2_32.lib'); + $postgres->AddLibrary('wsock32.lib ws2_32.lib secur32.lib'); $postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $postgres->FullExportDLL('postgres.lib'); @@ -120,6 +120,7 @@ sub mkvcbuild $libpq->AddDefine('FRONTEND'); $libpq->AddIncludeDir('src\port'); $libpq->AddLibrary('wsock32.lib'); + $libpq->AddLibrary('secur32.lib'); $libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap}); $libpq->UseDef('src\interfaces\libpq\libpqdll.def'); $libpq->ReplaceFile('src\interfaces\libpq\libpqrc.c','src\interfaces\libpq\libpq.rc'); diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 3a23a15431d..3a5d4df655b 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -3,7 +3,7 @@ package Solution; # # Package that encapsulates a Visual C++ solution file generation # -# $PostgreSQL: pgsql/src/tools/msvc/Solution.pm,v 1.29 2007/07/12 14:43:21 mha Exp $ +# $PostgreSQL: pgsql/src/tools/msvc/Solution.pm,v 1.30 2007/07/23 10:16:54 mha Exp $ # use Carp; use strict; @@ -124,16 +124,16 @@ s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x) #x\n#define __STRINGIFY2(z) __STRINGIFY print O "#define HAVE_KRB5_ERROR_TEXT_DATA 1\n"; print O "#define HAVE_KRB5_TICKET_ENC_PART2 1\n"; print O "#define HAVE_KRB5_FREE_UNPARSED_NAME 1\n"; - print O "#define PG_KRB_SRVNAM \"postgres\"\n"; - print O "#define ENABLE_GSS\n"; + print O "#define ENABLE_GSS 1\n"; + } + print O "#define ENABLE_SSPI 1\n"; + if (my $port = $self->{options}->{"--with-pgport"}) + { + print O "#undef DEF_PGPORT\n"; + print O "#undef DEF_PGPORT_STR\n"; + print O "#define DEF_PGPORT $port\n"; + print O "#define DEF_PGPORT_STR \"$port\"\n"; } - if (my $port = $self->{options}->{"--with-pgport"}) - { - print O "#undef DEF_PGPORT\n"; - print O "#undef DEF_PGPORT_STR\n"; - print O "#define DEF_PGPORT $port\n"; - print O "#define DEF_PGPORT_STR \"$port\"\n"; - } print O "#define VAL_CONFIGURE \"" . $self->GetFakeConfigure() . "\"\n"; print O "#endif /* IGNORE_CONFIGURED_SETTINGS */\n"; close(O);