/* * Continue SSPI authentication with next token as needed. */ static int pg_SSPI_continue(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(conn, libpq_gettext("SSPI continuation error"), r); return STATUS_ERROR; } if (conn->sspictx == NULL) { /* On first run, transfer retreived context handle */ conn->sspictx = malloc(sizeof(CtxtHandle)); if (conn->sspictx == NULL) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); 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. */ printfPQExpBuffer(&conn->errorMessage, "SSPI returned invalid number of output buffers\n"); return STATUS_ERROR; } /* * If the negotiation is complete, there may be zero bytes to send. * The server is at this point not expecting any more data, so don't * send it. */ if (outbuf.pBuffers[0].cbBuffer > 0) { 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(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) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); 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(conn, libpq_gettext("could not acquire SSPI credentials"), 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 && conn->pghost[0] != '\0')) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("host name must be specified\n")); return STATUS_ERROR; } conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(conn->pghost) + 2); if (!conn->sspitarget) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); 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(conn); }
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, 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. * * 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, gettext_noop("could not get security token from 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 (pg_krb_realm && strlen(pg_krb_realm)) { if (pg_strcasecmp(pg_krb_realm, domainname)) { elog(DEBUG2, "SSPI domain (%s) and configured domain (%s) don't match", domainname, pg_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 (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; }