Code: Select all
; ------------------------------------------------------------
; Program name: ots_iisapi_filter.pb
;
; Purpose: 64 bit Internet Information Server Filter API Dll
; ------------------------------------------------------------
EnableExplicit
; set the constants needed to start
#ProgramName = "ots_iisapi_filter"
#ProgramTitle = "Orion TEK Solutions - 64 bit Unicode Internet Information Server Filter API Dll"
#ProgramVersion = "1.0.01c." + #PB_Editor_BuildCount
; add the program include files we need
XIncludeFile "httpFilt_unicode.pbi"
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
#systemSlash = "\"
CompilerElse
#systemSlash = "/"
CompilerEndIf
; ************************************************************************************
; Procedure to write logging data to Mark Russinovich's Debug View
; https://docs.microsoft.com/en-us/sysinternals/downloads/debugview
; ************************************************************************************
Procedure.i WriteToLog(*logText)
Protected logText.s = PeekS(*logText)
OutputDebugString_(logText)
ProcedureReturn #True
EndProcedure
; ************************************************************************************
; initialization procedure
; ************************************************************************************
ProcedureDLL.i AttachProcess(instance.i)
Protected logText.s = #ProgramName + ", Ver: " + #ProgramVersion + " AttachProcess() > completed successfully"
WriteToLog(@logText)
ProcedureReturn #True
EndProcedure
; ************************************************************************************
; DetachProcess
; ************************************************************************************
ProcedureDLL.i DetachProcess(instance.i)
EndProcedure
; ************************************************************************************
; AttachThread
; ************************************************************************************
ProcedureDLL.i AttachThread(instance.i)
EndProcedure
; ************************************************************************************
; DetachThread
; ************************************************************************************
ProcedureDLL.i DetachThread(instance.i)
EndProcedure
; ************************************************************************************
; GetFilterVersion is called when the dll is loaded
; ************************************************************************************
ProcedureDLL.i GetFilterVersion(*pVer.HTTP_FILTER_VERSION)
Protected logText.s
Protected descriptionSize.l
Protected notificationMask.l
Protected description.s = "ISAPI Filter written in PureBasic, Ver: " + #ProgramVersion
Protected netSource.s = #ProgramName + ", Ver: " + #ProgramVersion + " GetFilterVersion()"
notificationMask = #SF_NOTIFY_PREPROC_HEADERS | #SF_NOTIFY_URL_MAP | #SF_NOTIFY_SEND_RESPONSE
descriptionSize = StringByteLength(description, #PB_UTF8)
; make the length of the description the same number of characters as #SF_MAX_FILTER_DESC_LEN
PokeL(@*pVer\dwFilterVersion, #HTTP_FILTER_REVISION)
PokeS(@*pVer\lpszFilterDesc, description, descriptionSize, #PB_UTF8)
PokeL(@*pVer\dwFlags, notificationMask)
logText = netSource + ", ISAPI Server Version: 0x" + RSet(Hex(*pVer\dwServerFilterVersion, #PB_Long), 8, "0")
logText + ", ISAPI Client Version: 0x" + RSet(Hex(*pVer\dwFilterVersion, #PB_Long), 8, "0")
logText + ", notifications requested: 0x" + RSet(Hex(notificationMask, #PB_Long), 8, "0")
WriteToLog(@logText)
; return true, returning false will cause the dll to be unloaded
ProcedureReturn #True
EndProcedure
; ************************************************************************************
; This is the main function that is called for each client request pfc contains all needed data
; ************************************************************************************
ProcedureDLL.i HttpFilterProc(*pfc.HTTP_FILTER_CONTEXT, notificationType.l, *pvNotification)
; *pvNotification: Pointer to the notification-specific Structure that contains more information about the current context of the request.
Protected pszURL.s
Protected *referer
Protected logText.s
Protected referer.s
Protected *lpszName
Protected *referURL
Protected foundOne.i
Protected loggedIn.i
Protected lpdwSize.i
Protected lpszName.s
Protected referURL.s
Protected *lpvBuffer
Protected headerVar.s
Protected sessionID.s
Protected cookieData.s
Protected dwReserved.l
Protected expireDtTm.i
Protected lenPhysMap.l
Protected lenReferer.i
Protected requestURI.s
Protected lenReferURL.i
Protected requestPage.s
Protected dbExpireDate.s
Protected keepChecking.i
Protected variableSize.i
Protected filterContext.i
Protected pageExtension.s
Protected processReturn.i
Protected sessionString.s
Protected sessionLocator.i
Protected physMapLocation.s
Protected lenSessionString.i
Protected sessionTerminator.i
Protected initialSize.i = 1024
Protected netSource.s = #ProgramName + ", Ver: " + #ProgramVersion + " HttpFilterProc()"
Protected *pURLMap.HTTP_FILTER_URL_MAP
Protected *pRawData.HTTP_FILTER_RAW_DATA
Protected *pProcHead.HTTP_FILTER_PREPROC_HEADERS
; filterContext: = 0 is the flag to signal later calls to this filter that it needs to send the login page
; filterContext: = 1 is the flag to signal later calls to this filter that it needs to add a referer header
; filterContext: = 2 is not yet used
; filterContext: = 3 is not yet used, etc.
logText = netSource + " , " + Str(notificationType)
; default return
processReturn = #SF_STATUS_REQ_NEXT_NOTIFICATION
; for server variables and header data, see https://docs.microsoft.com/en-us/iis/web-dev-reference/server-variables
; now let's see what notification type we've received
Select notificationType
Case #SF_NOTIFY_PREPROC_HEADERS
; use distinct process number for each notification type
logText + " #SF_NOTIFY_PREPROC_HEADERS"
*lpvBuffer = AllocateMemory(initialSize)
*lpszName = AllocateMemory(64)
lpszName = "HEADER_Cookie"
lpdwSize = initialSize
headerVar = Space(initialSize)
variableSize = StringByteLength(lpszName, #PB_UTF8)
PokeS(*lpszName, lpszName, variableSize, #PB_UTF8)
If *pfc\GetServerVariable(*pfc.HTTP_FILTER_CONTEXT, *lpszName, *lpvBuffer, @lpdwSize)
headerVar = PeekS(*lpvBuffer, lpdwSize, #PB_UTF8)
headerVar = Trim(headerVar)
Else
headerVar = ""
EndIf
cookieData = headerVar
logText + ", " + lpszName + " = '" + headerVar + "'"
lpszName = "REQUEST_URI"
lpdwSize = initialSize
headerVar = Space(initialSize)
variableSize = StringByteLength(lpszName, #PB_UTF8)
PokeS(*lpszName, lpszName, variableSize, #PB_UTF8)
If *pfc\GetServerVariable(*pfc.HTTP_FILTER_CONTEXT, *lpszName, *lpvBuffer, @lpdwSize)
headerVar = PeekS(*lpvBuffer, lpdwSize, #PB_UTF8)
headerVar = Trim(headerVar)
headerVar = StringField(headerVar, 1, "?")
headerVar = Trim(headerVar)
Else
headerVar = ""
EndIf
requestURI = LCase(headerVar)
logText + ", " + lpszName + " = '" + headerVar + "'"
lpszName = "QUERY_STRING"
lpdwSize = initialSize
headerVar = Space(initialSize)
variableSize = StringByteLength(lpszName, #PB_UTF8)
PokeS(*lpszName, lpszName, variableSize, #PB_UTF8)
If *pfc\GetServerVariable(*pfc.HTTP_FILTER_CONTEXT, *lpszName, *lpvBuffer, @lpdwSize)
headerVar = PeekS(*lpvBuffer, lpdwSize, #PB_UTF8)
headerVar = Trim(headerVar)
Else
headerVar = ""
EndIf
logText + ", " + lpszName + " = '" + headerVar + "'"
requestPage = GetFilePart(requestURI)
pageExtension = GetExtensionPart(requestURI)
loggedIn = #False
keepChecking = #True
; release the memory we have allocated
FreeMemory(*lpvBuffer)
FreeMemory(*lpszName)
; if this uri isn't an htm or html file, it's not a web page, so there's no reason to force it to a login
logText + ", pageExtension = " + pageExtension
If Right(pageExtension, 3) <> "htm" And Right(pageExtension, 4) <> "html"
keepChecking = #False
logText + ", keepChecking = " + Str(keepChecking)
EndIf
; if this is a page from user_portal, we don't need to process it any further, this is where users can create their account
If keepChecking
If Left(requestURI, 13) = "/user_portal/"
keepChecking = #False
logText + ", requestURI = " + requestURI + ", keepChecking = " + Str(keepChecking)
EndIf
EndIf
; if this is a page from testing, we don't need to process it any further
If keepChecking
If Left(requestURI, 9) = "/testing/"
keepChecking = #False
logText + ", requestURI = " + requestURI + ", keepChecking = " + Str(keepChecking)
EndIf
EndIf
; if it is a web page, and it doesn't reside in the web portal, we need to see if the user is logged in by having a valid session id. We get the session ID from the cookie
; add your own code here for cookie checking!!!!
; add your own code here for cookie checking!!!!
; add your own code here for cookie checking!!!!
; add your own code here for cookie checking!!!!
; add your own code here for cookie checking!!!!
; if this session is not logged in, this is a web page, and the source is not the user_portal...
; we must force the session To login by setting the first two bits of the filter context variable To 1
If keepChecking
filterContext = 1
; then allocate memory inside iisapi to hold a string, it gets deallocated (freed) by IIS
If *pfc\AllocMem(*pfc.HTTP_FILTER_CONTEXT, 2048, dwReserved)
PokeI(@*pfc\pFilterContext, filterContext)
logText + ", memory allocated, new pFilterContext = " + Str(filterContext)
Else
logText = "In #SF_NOTIFY_PREPROC_HEADERS unable to allocate memory"
WriteToLog(@logText)
filterContext = 0
; no need to continue further processing, since we cannot allocate memory
processReturn = #SF_STATUS_REQ_HANDLED_NOTIFICATION
EndIf
EndIf
; notification for when map url is fired
Case #SF_NOTIFY_URL_MAP
; use distinct process number for each notification type
filterContext = PeekI(@*pfc\pFilterContext)
logText + " #SF_NOTIFY_URL_MAP, current pFilterContext = " + Str(filterContext)
*pURLMap = *pvNotification
pszURL = PeekS(@*pURLMap\pszURL, -1, #PB_UTF8)
lenPhysMap = PeekL(@*pURLMap\cbPathBuff)
physMapLocation = PeekS(@*pURLMap\pszPhysicalPath, -1, #PB_UTF8)
; if we have previously set the context bit 0 to 1, map the url to a login page, the mapping is the physical hard drive!
If filterContext = 1
logText + ", current pszURL = '" + pszURL + "'"
physMapLocation = "d:\your_web_root\login.htm"
lenPhysMap = StringByteLength(physMapLocation, #PB_UTF8)
; poke in a remapped url, to force logging in
PokeS(@*pURLMap\pszPhysicalPath, physMapLocation, lenPhysMap, #PB_UTF8)
PokeL(@*pURLMap\cbPathBuff, lenPhysMap); length does not matter, IIS reads until it hits a null!
; update the filter context, and save the url for later when we will make it the referer
PokeI(@*pfc\pFilterContext, filterContext)
PokeS(@*pfc\pFilterContext + 8, pszURL, -1, #PB_UTF8)
logText + ", mapped url to '" + physMapLocation + "', new pFilterContext = " + Str(filterContext)
; else, we can send #SF_STATUS_REQ_HANDLED_NOTIFICATION, which tell IIS to stop any further filter notifications for this uri
Else
processReturn = #SF_STATUS_REQ_HANDLED_NOTIFICATION
EndIf
; notification for when send response is fired
Case #SF_NOTIFY_SEND_RESPONSE
; use distinct process number for each notification type
filterContext = PeekI(@*pfc\pFilterContext)
logText + " #SF_NOTIFY_SEND_RESPONSE, current pFilterContext = " + Str(filterContext)
; if we have previously set the context bit 1 to 1, map the url to a login page
If filterContext = 1
*pProcHead = *pvNotification
referURL = PeekS(@*pfc\pFilterContext + 8, -1, #PB_UTF8)
lenReferURL = StringByteLength(referURL, #PB_UTF8)
*referURL = AllocateMemory(lenReferURL + 1)
PokeS(*referURL, referURL, lenReferURL, #PB_UTF8)
referer = "Referer:"
lenReferer = StringByteLength(referer, #PB_UTF8)
*referer = AllocateMemory(lenReferer + 1)
PokeS(*referer, referer, lenReferer, #PB_UTF8)
filterContext = 0
; see https://docs.microsoft.com/en-us/previous-versions/dd435718(v=msdn.10)
If *pProcHead\AddHeader(*pfc.HTTP_FILTER_CONTEXT, *referer, *referURL)
logText + ", added header '" + referer + referURL + "' successfully, new pFilterContext = " + Str(filterContext)
Else
logText = "In #SF_NOTIFY_SEND_RESPONSE *pProcHead\AddHeader() returned 0"
EndIf
PokeI(@*pfc\pFilterContext, filterContext)
FreeMemory(*referURL)
FreeMemory(*referer)
EndIf
; no more processing is needed for this request
processReturn = #SF_STATUS_REQ_HANDLED_NOTIFICATION
; we should never hit this code!!!
Default
logText + ", Unlogged notification type encountered: " + Str(notificationType)
EndSelect
logText + ", procedure terminating"
WriteToLog(@logText)
; return whatever the next notification should be
ProcedureReturn processReturn
EndProcedure
; ************************************************************************************
; TerminateFilter is called before unloading the dll.
; ************************************************************************************
ProcedureDLL.i TerminateFilter(dwFlags.l)
Protected logText.s = #ProgramName + ", Ver: " + #ProgramVersion + " TerminateFilter > process terminating"
WriteToLog(@logText)
ProcedureReturn #True
EndProcedure
; IDE Options = PureBasic 5.73 LTS (Windows - x64)
; ExecutableFormat = Shared dll
; CursorPosition = 176
; FirstLine = 173
; Folding = --
; EnableThread
; UseIcon = ..\OTS\ots.ico
; Executable = ots_iisapi_filter.dll
; DisableDebugger
; HideErrorLog
; CurrentDirectory = \
; CompileSourceDirectory
; EnableCompileCount = 2
; EnableBuildCount = 2
; EnableExeConstant
; IncludeVersionInfo