Attached is the patch to support SSPI authentication in libpq. With this patch, I can authenticate windows clients against a linux server using kerberos *without* reqiring setting up MIT kerberos on the windows side. Protocol has not changed at all.
For now, it's implemented as mutually exclusive with the MIT version. This can of course be changed, but I have yet to see a good way how to expose the difference to the app. By setting it up the way it is, some code is shared between GSSAPI and SSPI, which is also why the SSPI functions are just different versions of the pg_GSS_startup/continue functions. If we want to support both, those will of course have to be changed. I have not set up autoconf for it yet, pending the outcome of the discussion about what should be default. What in envision doing there is the same as what I've done for MSVC so far - enable SSPI unless --enable-gss is specified, in which case SSPI is disabled and GSSAPI is enabled instead. I'll be working on the server side as well ;-), but getting libpq is the most important part of it. Comments? If there are none, I'll apply this version soon. //Magnus
Index: src/include/pg_config.h.win32 =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/pg_config.h.win32,v retrieving revision 1.42 diff -c -r1.42 pg_config.h.win32 *** src/include/pg_config.h.win32 16 Apr 2007 18:39:19 -0000 1.42 --- src/include/pg_config.h.win32 18 Jul 2007 10:03:43 -0000 *************** *** 582,588 **** /* Define to the name of the default PostgreSQL service principal in Kerberos. (--with-krb-srvnam=NAME) */ ! #define PG_KRB_SRVNAM "postgres" /* A string containing the version number, platform, and C compiler */ #define PG_VERSION_STR "Uninitialized version string (win32)" --- 582,588 ---- /* Define to the name of the default PostgreSQL service principal in Kerberos. (--with-krb-srvnam=NAME) */ ! #define PG_KRB_SRVNAM "POSTGRES" /* A string containing the version number, platform, and C compiler */ #define PG_VERSION_STR "Uninitialized version string (win32)" Index: src/interfaces/libpq/fe-auth.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-auth.c,v retrieving revision 1.127 diff -c -r1.127 fe-auth.c *** src/interfaces/libpq/fe-auth.c 12 Jul 2007 14:43:21 -0000 1.127 --- src/interfaces/libpq/fe-auth.c 18 Jul 2007 10:07:56 -0000 *************** *** 329,339 **** /* * GSSAPI authentication system. */ - #if defined(HAVE_GSSAPI_H) - #include <gssapi.h> - #else - #include <gssapi/gssapi.h> - #endif #if defined(WIN32) && !defined(WIN32_ONLY_COMPILER) /* --- 329,334 ---- *************** *** 378,384 **** * GSSAPI errors contains two parts. Put as much as possible of * both parts into the string. */ ! void pg_GSS_error(char *mprefix, char *msg, int msglen, OM_uint32 maj_stat, OM_uint32 min_stat) { --- 373,379 ---- * GSSAPI errors contains two parts. Put as much as possible of * both parts into the string. */ ! static void pg_GSS_error(char *mprefix, char *msg, int msglen, OM_uint32 maj_stat, OM_uint32 min_stat) { *************** *** 504,510 **** return pg_GSS_continue(PQerrormsg, conn); } ! #endif /* * Respond to AUTH_REQ_SCM_CREDS challenge. --- 499,677 ---- return pg_GSS_continue(PQerrormsg, conn); } ! #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_GSS_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 (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); ! } ! ! 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; ! } ! ! /* Cleanup is handled by the code in freePGconn() */ ! return STATUS_OK; ! } ! ! /* ! * Send initial SSPI authentication token ! */ ! static int ! pg_GSS_startup(char *PQerrormsg, PGconn *conn) ! { ! SECURITY_STATUS r; ! TimeStamp expire; ! ! /* ! * 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; ! } ! ! /* ! * XXX: Currently hardcoded to use kerberos authentication for interop with Unix. ! * Probably want to support others as well later. ! */ ! r = AcquireCredentialsHandle(NULL, "kerberos", SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, conn->sspicred, &expire); ! r = SEC_E_OK; ! if (r != SEC_E_OK) ! { ! pg_SSPI_error("acquire credentials failed", PQerrormsg, PQERRORMSG_LENGTH, r); ! return STATUS_ERROR; ! } ! ! conn->sspictx = NULL; ! ! /* ! * 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. ! */ ! 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); ! ! return pg_GSS_continue(PQerrormsg, conn); ! } ! #endif /* ENABLE_SSPI */ /* * Respond to AUTH_REQ_SCM_CREDS challenge. *************** *** 671,677 **** return STATUS_ERROR; #endif ! #ifdef ENABLE_GSS case AUTH_REQ_GSS: pglock_thread(); if (pg_GSS_startup(PQerrormsg, conn) != STATUS_OK) --- 838,844 ---- return STATUS_ERROR; #endif ! #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) case AUTH_REQ_GSS: pglock_thread(); if (pg_GSS_startup(PQerrormsg, conn) != STATUS_OK) Index: src/interfaces/libpq/fe-connect.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v retrieving revision 1.349 diff -c -r1.349 fe-connect.c *** src/interfaces/libpq/fe-connect.c 11 Jul 2007 08:27:33 -0000 1.349 --- src/interfaces/libpq/fe-connect.c 18 Jul 2007 09:52:11 -0000 *************** *** 181,187 **** {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ ! #if defined(KRB5) || defined(ENABLE_GSS) /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, "Kerberos-service-name", "", 20}, --- 181,187 ---- {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ ! #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}, *************** *** 412,418 **** conn->sslmode = strdup("require"); } #endif ! #if defined(KRB5) || defined(ENABLE_GSS) tmp = conninfo_getval(connOptions, "krbsrvname"); conn->krbsrvname = tmp ? strdup(tmp) : NULL; #endif --- 412,418 ---- conn->sslmode = strdup("require"); } #endif ! #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) tmp = conninfo_getval(connOptions, "krbsrvname"); conn->krbsrvname = tmp ? strdup(tmp) : NULL; #endif *************** *** 1661,1673 **** return PGRES_POLLING_READING; } } ! #ifdef ENABLE_GSS /* * 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 --- 1661,1675 ---- return PGRES_POLLING_READING; } } ! #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) /* * AUTH_REQ_GSS provides no input data * Just set the request flags */ + #ifdef ENABLE_GSS if (areq == AUTH_REQ_GSS) conn->gflags = GSS_C_MUTUAL_FLAG; + #endif /* * Read GSSAPI data packets *************** *** 1676,1682 **** { /* Continue GSSAPI authentication */ int llen = msgLength - 4; ! /* * We can be called repeatedly for the same buffer. * Avoid re-allocating the buffer in this case - --- 1678,1684 ---- { /* 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,2008 **** free(conn->pgpass); if (conn->sslmode) free(conn->sslmode); ! #if defined(KRB5) || defined(ENABLE_GSS) if (conn->krbsrvname) free(conn->krbsrvname); #endif --- 2004,2010 ---- free(conn->pgpass); if (conn->sslmode) free(conn->sslmode); ! #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) if (conn->krbsrvname) free(conn->krbsrvname); #endif *************** *** 2031,2036 **** --- 2033,2058 ---- 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) { Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.123 diff -c -r1.123 libpq-int.h *** src/interfaces/libpq/libpq-int.h 12 Jul 2007 14:36:52 -0000 1.123 --- src/interfaces/libpq/libpq-int.h 18 Jul 2007 09:52:16 -0000 *************** *** 52,57 **** --- 52,71 ---- #endif #endif + #ifdef ENABLE_SSPI + #define SECURITY_WIN32 + #include <security.h> + #undef SECURITY_WIN32 + + /* + * Define a fake structure compatible with GSSAPI on Unix. + */ + typedef struct { + void *value; + int length; + } gss_buffer_desc; + #endif + #ifdef USE_SSL #include <openssl/ssl.h> #include <openssl/err.h> *************** *** 276,282 **** char *pguser; /* Postgres username and password, if any */ char *pgpass; char *sslmode; /* SSL mode (require,prefer,allow,disable) */ ! #if defined(KRB5) || defined(ENABLE_GSS) char *krbsrvname; /* Kerberos service name */ #endif --- 290,296 ---- char *pguser; /* Postgres username and password, if any */ char *pgpass; char *sslmode; /* SSL mode (require,prefer,allow,disable) */ ! #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI) char *krbsrvname; /* Kerberos service name */ #endif *************** *** 366,371 **** --- 380,393 ---- gss_buffer_desc goutbuf; /* GSS output token */ #endif + #ifdef ENABLE_SSPI + CredHandle *sspicred; + CtxtHandle *sspictx; + gss_buffer_desc ginbuf; + char *sspitarget; + #endif + + /* Buffer for current error message */ PQExpBufferData errorMessage; /* expansible string */ *************** *** 415,426 **** #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); --- 437,442 ---- Index: src/tools/msvc/Mkvcbuild.pm =================================================================== RCS file: /projects/cvsroot/pgsql/src/tools/msvc/Mkvcbuild.pm,v retrieving revision 1.14 diff -c -r1.14 Mkvcbuild.pm *** src/tools/msvc/Mkvcbuild.pm 7 Jul 2007 07:43:20 -0000 1.14 --- src/tools/msvc/Mkvcbuild.pm 16 Jul 2007 10:57:21 -0000 *************** *** 120,125 **** --- 120,126 ---- $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'); Index: src/tools/msvc/Solution.pm =================================================================== RCS file: /projects/cvsroot/pgsql/src/tools/msvc/Solution.pm,v retrieving revision 1.29 diff -c -r1.29 Solution.pm *** src/tools/msvc/Solution.pm 12 Jul 2007 14:43:21 -0000 1.29 --- src/tools/msvc/Solution.pm 18 Jul 2007 10:03:50 -0000 *************** *** 124,139 **** 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"; } ! 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); --- 124,145 ---- 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"; } ! if ($self->{options}->{gssapi}) ! { ! print O "#defnie ENABLE_GSS 1\n"; ! } ! else ! { ! 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"; ! } print O "#define VAL_CONFIGURE \"" . $self->GetFakeConfigure() . "\"\n"; print O "#endif /* IGNORE_CONFIGURED_SETTINGS */\n"; close(O); *************** *** 328,339 **** $proj->AddIncludeDir($self->{options}->{nls} . '\include'); $proj->AddLibrary($self->{options}->{nls} . '\lib\libintl.lib'); } ! if ($self->{options}->{krb5}) { $proj->AddIncludeDir($self->{options}->{krb5} . '\inc\krb5'); $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\krb5_32.lib'); $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\comerr32.lib'); ! $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\gssapi32.lib'); } if ($self->{options}->{xml}) { --- 334,347 ---- $proj->AddIncludeDir($self->{options}->{nls} . '\include'); $proj->AddLibrary($self->{options}->{nls} . '\lib\libintl.lib'); } ! if ($self->{options}->{krb5} || $self->{options}->{gssapi}) { $proj->AddIncludeDir($self->{options}->{krb5} . '\inc\krb5'); $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\krb5_32.lib'); $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\comerr32.lib'); ! if ($self->{options}->{gssapi}) { ! $proj->AddLibrary($self->{options}->{krb5} . '\lib\i386\gssapi32.lib'); ! } } if ($self->{options}->{xml}) {
---------------------------(end of broadcast)--------------------------- TIP 2: Don't 'kill -9' the postmaster