/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1993 **/ /**********************************************************************/ /* security.c This module manages security for the W3 Service. FILE HISTORY: KeithMo 07-Mar-1993 Created. */ #include "w3p.hxx" #include DWORD DeniedFlagTable[] = { 0, SF_DENIED_BY_CONFIG, SF_DENIED_RESOURCE, SF_DENIED_FILTER, SF_DENIED_APPLICATION }; DWORD DeniedFlagsToSubStatus( DWORD fFlags ) /*++ Routine Description: Map a set of denied flags to a substatus for use in custom error lookup. Arguments: fFlags - The flags to be mapped. Return Value: The substatus if we can map it, or 0 otherwise. --*/ { int i; fFlags &= ~SF_DENIED_LOGON; for (i = 0; i < sizeof(DeniedFlagTable)/sizeof(DWORD);i++) { if (DeniedFlagTable[i] == fFlags) { return i+1; } } return 0; } // // Public functions. // BOOL HTTP_REQ_BASE::SendAuthNeededResp( BOOL * pfFinished ) /*++ Routine Description: Sends an access denied HTTP server response with the accompanying authentication schemes the server supports Parameters: pfFinished - If set to TRUE, indicates no further processing is needed for this request Return Value: TRUE if successful, FALSE on error --*/ { CHAR * pszTail; DWORD cbRespBufUsed; DWORD cbRespBufLeft; DWORD cbNeeded; LPCSTR pszAccessDeniedMsg; CHAR * pszMsgBody; BYTE cMsgBuffer[128] ={ '\0' }; BUFFER bufMsg(cMsgBuffer, sizeof(cMsgBuffer)); DWORD dwSubStatus; DWORD dwMsgSize; DWORD dwMsgSizeNeeded; BOOL bHaveCustom; STR strAuthHdrs; *pfFinished = FALSE; if ( QueryRespBuf()->QuerySize() < MIN_BUFFER_SIZE_FOR_HEADERS ) { if ( !QueryRespBuf()->Resize( MIN_BUFFER_SIZE_FOR_HEADERS ) ) { return FALSE; } } if ( !HTTP_REQ_BASE::BuildStatusLine( QueryRespBuf(), !IsProxyRequest() ? HT_DENIED : HT_PROXY_AUTH_REQ, NO_ERROR )) { return FALSE; } // // "Server: Microsoft/xxx // pszTail = (char*) QueryRespBuf()->QueryPtr() + QueryRespBufCB(); APPEND_VER_STR( pszTail ); // // "Date: " - Time the response was sent. // // build Date: uses Date/Time cache pszTail += g_pDateTimeCache->GetFormattedCurrentDateTime( pszTail ); // // See if we have a custom error message defined for this. // dwSubStatus = DeniedFlagsToSubStatus(_Filter.QueryDeniedFlags()); if (CheckCustomError(&bufMsg, !IsProxyRequest() ? HT_DENIED : HT_PROXY_AUTH_REQ, dwSubStatus, pfFinished, &dwMsgSize, FALSE)) { DBG_ASSERT(!*pfFinished); pszAccessDeniedMsg = (CHAR *)bufMsg.QueryPtr(); dwMsgSizeNeeded = strlen(pszAccessDeniedMsg); bHaveCustom = TRUE; } else { pszAccessDeniedMsg = QueryW3Instance()->QueryAccessDeniedMsg(); if ( memcmp( _strMethod.QueryStr(), "HEAD", 4 )) { dwMsgSize = strlen(pszAccessDeniedMsg); } else { dwMsgSize = 0; } dwMsgSizeNeeded = dwMsgSize; bHaveCustom = FALSE; } // // If this is not the first call, then return the current authentication // data blob otherwise return the forms of authentication the server // accepts // if ( IsAuthenticating() ) { if ( !strAuthHdrs.Copy( IsProxyRequest() ? "Proxy-Authenticate" : "WWW-Authenticate" ) || !strAuthHdrs.Append( ": " ) || !strAuthHdrs.Append( _strAuthInfo ) || !strAuthHdrs.Append( "\r\n" ) ) { return FALSE; } } else { if ( !AppendAuthenticationHdrs( &strAuthHdrs, pfFinished )) { return FALSE; } if ( *pfFinished ) { return TRUE; } } // // Make sure there's enough size for any ISAPI denial headers plus any // admin specified access denied message // cbRespBufUsed = QueryRespBufCB(); cbRespBufLeft = QueryRespBuf()->QuerySize() - cbRespBufUsed; cbNeeded = dwMsgSizeNeeded + _strDenialHdrs.QueryCB() + 250 + strAuthHdrs.QueryCB(); if ( cbNeeded > cbRespBufLeft ) { if ( !QueryRespBuf()->Resize( cbNeeded + cbRespBufUsed )) { return FALSE; } } pszTail = QueryRespBufPtr() + cbRespBufUsed; memcpy( pszTail, strAuthHdrs.QueryStr(), strAuthHdrs.QueryCB() ); pszTail += strAuthHdrs.QueryCB(); if ( IsKeepConnSet() ) { if (!IsOneOne()) { if ( !IsProxyRequest() ) { APPEND_STRING( pszTail, "Connection: keep-alive\r\n" ); } else { APPEND_STRING( pszTail, "Proxy-Connection: keep-alive\r\n" ); } } } else { if (IsOneOne()) { if ( !IsProxyRequest() ) { APPEND_STRING( pszTail, "Connection: close\r\n" ); } else { APPEND_STRING( pszTail, "Proxy-Connection: close\r\n" ); } } } // // Add any additional headers supplied by the filters plus the header // termination // APPEND_NUMERIC_HEADER( pszTail, "Content-Length: ", dwMsgSize, "\r\n" ); if (!_strDenialHdrs.IsEmpty()) { DWORD cb = _strDenialHdrs.QueryCCH(); CHAR *pszDenialHdr = _strDenialHdrs.QueryStr(); // // We must always have CR-LF at the end // DBG_ASSERT( cb >= 2 && pszDenialHdr[cb - 2] == '\r' && pszDenialHdr[cb - 1] == '\n' ); APPEND_STR_HEADER( pszTail, "", _strDenialHdrs, "" ); } if (!bHaveCustom) { APPEND_STRING( pszTail, "Content-Type: text/html\r\n\r\n" ); } if ( memcmp( _strMethod.QueryStr(), "HEAD", 4 )) { APPEND_PSZ_HEADER( pszTail, "", pszAccessDeniedMsg, "" ); } else { if (bHaveCustom) { DWORD dwBytesToCopy; // Copy in only the content-type header, which is everything // except for the message itself. dwBytesToCopy = dwMsgSizeNeeded - dwMsgSize; memcpy(pszTail, pszAccessDeniedMsg, dwBytesToCopy); pszTail += dwBytesToCopy; *pszTail++ = '\0'; } } DBG_ASSERT( QueryRespBuf()->QuerySize() > QueryRespBufCB() ); IF_DEBUG( PARSING ) { DBGPRINTF(( DBG_CONTEXT, "[SendAuthNeededResp] Sending headers: %s", QueryRespBufPtr() )); } // // Add IO_FLAG_AND_RECV if we're doing a multi-leg authentication exchange and the // connection is to be kept open for the duration of the exchange // return SendHeader( QueryRespBufPtr(), (DWORD) -1, (IO_FLAG_ASYNC | ( ( IsAuthenticating() && IsKeepConnSet() ) ? IO_FLAG_AND_RECV : 0)), pfFinished ); } BOOL HTTP_REQ_BASE::AppendAuthenticationHdrs( STR * pstrAuthenticationHdrs, BOOL * pfFinished ) /*++ Routine Description: This method adds the appropriate "WWW-Authenticate" strings to the passed server response string This routine assumes pRespBuf is large enough to hold the authentication headers Parameters: pStrAuthenticationHdrs - buffer pfFinished - Set to TRUE if no further processing is needed on this request Return Value: TRUE if successful, FALSE on error --*/ { CHAR * pchField; DWORD dwAuth; BOOL fDoBasic; const LPSTR * apszNTProviders = NULL; PW3_SERVER_INSTANCE pInstance = QueryW3Instance(); STACK_STR( strRealm, MAX_PATH); // make a local copy of the realm headers. // // If no realm was supplied in the registry, use the host name // if ( !_fBasicRealm ) { fDoBasic = TRUE; } else { fDoBasic = FALSE; } // // Notify any Access Denied filters the user has been denied access // if ( _Filter.IsNotificationNeeded( SF_NOTIFY_ACCESS_DENIED, IsSecurePort() )) { if ( !_Filter.NotifyAccessDenied( _strURL.QueryStr(), _strPhysicalPath.QueryStr(), pfFinished )) { return FALSE; } if ( *pfFinished ) { return TRUE; } } // // We may not have read the metadata for this URL yet if a filter // returned access denied during the initial read_raw notifications // if ( !QueryMetaData() ) { DBGPRINTF(( DBG_CONTEXT, "[AppendAuthenticationHeaders] Warning - Metadata not read yet for NT auth list!\n")); return TRUE; } // // Send the correct header depending on if we're a proxy // if ( !IsProxyRequest() ) { pchField = "WWW-Authenticate: "; } else { pchField = "Proxy-Authenticate: "; } dwAuth = QueryAuthentication(); apszNTProviders = QueryMetaData()->QueryNTProviders(); // // generate the realm information for this request // strRealm.Copy( QueryMetaData()->QueryRealm() ? QueryMetaData()->QueryRealm() : QueryHostAddr() ); // // Append the appropriate authentication headers // if ( dwAuth & INET_INFO_AUTH_NT_AUTH ) { DWORD i = 0; // // For each authentication package the server supports, add a // WWW-Authenticate header // while ( apszNTProviders[i] ) { if ( !pstrAuthenticationHdrs->Append( pchField ) || !pstrAuthenticationHdrs->Append( apszNTProviders[ i ] ) || !pstrAuthenticationHdrs->Append( "\r\n" ) ) { return FALSE; } i++; } } if ( fDoBasic && (dwAuth & INET_INFO_AUTH_CLEARTEXT) ) { if ( !pstrAuthenticationHdrs->Append( pchField ) || !pstrAuthenticationHdrs->Append( "Basic realm=\"" ) || !pstrAuthenticationHdrs->Append( strRealm ) || !pstrAuthenticationHdrs->Append( "\"\r\n" ) ) { return FALSE; } } return TRUE; } BOOL HTTP_REQ_BASE::ExtractClearNameAndPswd( CHAR * pch, STR * pstrUserName, STR * pstrPassword, BOOL fUUEncoded ) /*++ Routine Description: This method breaks a string in the form "username:password" and places the components into pstrUserName and pstrPassword. If fUUEncoded is TRUE, then the string is UUDecoded first. Parameters: pch - Pointer to : Return Value: TRUE if successful, FALSE on error --*/ { STACK_STR( strDecoded, MAX_PATH ); CHAR * pchtmp; pch = SkipWhite( pch ); if ( fUUEncoded ) { if ( !uudecode( pch, &strDecoded )) { return FALSE; } pch = strDecoded.QueryStrA(); } pchtmp = SkipTo( pch, TEXT(':') ); if ( *pchtmp == TEXT(':') ) { *pchtmp = TEXT('\0'); if ( !_strUserName.Copy( pch ) || !_strPassword.Copy( pchtmp + 1 )) { return FALSE; } } return TRUE; } HANDLE HTTP_REQ_BASE::QueryPrimaryToken( HANDLE * phDelete ) /*++ Routine Description: This method returns a non-impersonation user token handle that's usable with CreateProcessAsUser. Parameters: phDelete - If returned as non-null, the caller is responsible for calling CloseHandle on this value when done using the returned value. Return Value: The primary token handle if successful, FALSE otherwise --*/ { *phDelete = NULL; if ( !UseVrAccessToken() ) { return _tcpauth.QueryPrimaryToken(); } return _pMetaData->QueryVrPrimaryAccessToken(); }