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

Reply via email to