mirror of
https://github.com/jakobadam/RDSFactor.git
synced 2025-06-02 02:28:18 +02:00
284 lines
9.8 KiB
VB.net
284 lines
9.8 KiB
VB.net
Imports System.DirectoryServices
|
|
|
|
Public Class RDSHandler
|
|
|
|
Private Shared userSessions As New Hashtable
|
|
Private Shared sessionTimestamps As New Hashtable
|
|
|
|
Private Shared userSidTokens As New Hashtable
|
|
Private Shared tokenTimestamps As New Hashtable
|
|
|
|
Private Shared userLaunchTimestamps As New Hashtable
|
|
|
|
Private mPacket As RADIUSPacket
|
|
Private mUsername As String
|
|
Private mPassword As String
|
|
|
|
' RDS specific values
|
|
Private mIsAppLaunchRequest As Boolean
|
|
Private mIsGatewayRequest As Boolean
|
|
Private mUseSMSFactor As Boolean
|
|
Private mUseEmailFactor As Boolean
|
|
|
|
Public Sub New(packet As RADIUSPacket)
|
|
mPacket = packet
|
|
|
|
mUsername = mPacket.UserName
|
|
mPassword = mPacket.UserPassword
|
|
|
|
CleanUsername()
|
|
|
|
For Each atts As RADIUSAttribute In mPacket.Attributes.GetAllAttributes(RadiusAttributeType.VendorSpecific)
|
|
Dim value As String = atts.GetVendorSpecific.VendorValue.ToString
|
|
|
|
Select Case UCase(value)
|
|
Case "LAUNCH"
|
|
mIsAppLaunchRequest = True
|
|
Case "TSGATEWAY"
|
|
mIsGatewayRequest = True
|
|
Case "SMS"
|
|
mUseSMSFactor = True
|
|
Case "EMAIL"
|
|
mUseEmailFactor = True
|
|
End Select
|
|
Next
|
|
End Sub
|
|
|
|
Private Sub CleanUsername()
|
|
' RD Gateway sends EXAMPLE\username
|
|
' RD Web sends example\username or - TODO - even example.com\username
|
|
If Not mUsername = Nothing Then
|
|
mUsername = mUsername.ToLower
|
|
End If
|
|
End Sub
|
|
|
|
Public Sub ProcessRequest()
|
|
If mIsAppLaunchRequest Then
|
|
ProcessAppLaunchRequest()
|
|
ElseIf mIsGatewayRequest Then
|
|
ProcessGatewayRequest()
|
|
Else
|
|
ProcessAccessRequest()
|
|
End If
|
|
|
|
End Sub
|
|
|
|
' Process the RDS specific App Launch request.
|
|
' These requests are sent when an app is clicked in RD Web.
|
|
'
|
|
' It's checked whether the session is still valid. In which case, a
|
|
' window is opened for the user, where we allow the user to connect
|
|
' through the gateway, an Accept-Access is returned and the RD Web
|
|
' launches the RDP client.
|
|
'
|
|
' NOTE: Requests contain the session GUID in the password attribute
|
|
' of the packet.
|
|
Public Sub ProcessAppLaunchRequest()
|
|
CICRadarR.AccessLog("ProcessAppLaunchRequest")
|
|
|
|
' When the packet is an AppLaunchRequest the password attribute contains the session id!
|
|
Dim packetSessionId = mPassword
|
|
Dim storedSessionId = userSessions(mUsername)
|
|
Dim sessionTimestamp = sessionTimestamps(mUsername)
|
|
|
|
If storedSessionId = Nothing Or sessionTimestamp = Nothing Then
|
|
CICRadarR.AccessLog("User has no session. MUST re-authenticate!")
|
|
mPacket.RejectAccessRequest()
|
|
Exit Sub
|
|
End If
|
|
|
|
If packetSessionId = storedSessionId Then
|
|
Dim minsSinceLastActivity = DateDiff(DateInterval.Minute, sessionTimestamp, Now)
|
|
If minsSinceLastActivity < CICRadarR.SessionTimeOut Then
|
|
CICRadarR.AccessLog("Opening window for: " & mUsername)
|
|
' Pro-long session
|
|
sessionTimestamps(storedSessionId) = Now
|
|
' Open launch window
|
|
userLaunchTimestamps(mUsername) = Now
|
|
mPacket.AcceptAccessRequest()
|
|
Exit Sub
|
|
Else
|
|
CICRadarR.AccessLog("Session timed out -- User MUST re-authenticate")
|
|
userSessions.Remove(mUsername)
|
|
sessionTimestamps.Remove(mUsername)
|
|
End If
|
|
Else
|
|
CICRadarR.AccessLog("Stored session id didn't match packet session id!")
|
|
End If
|
|
|
|
mPacket.RejectAccessRequest()
|
|
End Sub
|
|
|
|
' Process the request from the Network Policy Server in the RDS Gateway.
|
|
' These are sent when an RDP client tries to connect through the Gateway.
|
|
'
|
|
' Accept-Access is returned when the user has a
|
|
' * valid session; and a
|
|
' * valid app launch window
|
|
'
|
|
' The launch window is closed after this request.
|
|
'
|
|
' TODO: Fix race-condition RD Web vs. Gateway. Don't start RDP client in RD Web
|
|
' before ensuring App Launch request was successful
|
|
Public Sub ProcessGatewayRequest()
|
|
CICRadarR.AccessLog("Gateway Request for user: " & mUsername)
|
|
|
|
Dim sessionId = userSessions(mUsername)
|
|
Dim launchTimestamp = userLaunchTimestamps(mUsername)
|
|
Dim attributes As New RADIUSAttributes
|
|
|
|
If sessionId = Nothing Or launchTimestamp = Nothing Then
|
|
CICRadarR.AccessLog("User's has no launch window. User must re-authenticate")
|
|
mPacket.RejectAccessRequest()
|
|
Exit Sub
|
|
End If
|
|
|
|
Dim hasProxyState = mPacket.Attributes.AttributeExists(RadiusAttributeType.ProxyState)
|
|
If hasProxyState Then
|
|
Dim proxyState = mPacket.Attributes.GetFirstAttribute(RadiusAttributeType.ProxyState)
|
|
attributes.Add(proxyState)
|
|
End If
|
|
|
|
Dim secondsSinceLaunch = DateDiff(DateInterval.Second, launchTimestamp, Now)
|
|
If secondsSinceLaunch < CICRadarR.LaunchTimeOut Then
|
|
CICRadarR.AccessLog("Allowing access through gateway for user: " & mUsername & " -- closing window")
|
|
mPacket.AcceptAccessRequest(attributes)
|
|
Else
|
|
CICRadarR.AccessLog("Launch window has closed!")
|
|
End If
|
|
|
|
' close window
|
|
userLaunchTimestamps.Remove(mUsername)
|
|
End Sub
|
|
|
|
Public Sub ProcessAccessRequest()
|
|
Dim hasState = mPacket.Attributes.AttributeExists(RadiusAttributeType.State)
|
|
If hasState Then
|
|
' An Access-Request with a state is pr. definition a challange response.
|
|
ProcessChallengeResponse()
|
|
Exit Sub
|
|
End If
|
|
|
|
CICRadarR.AccessLog("ProcessAccessRequest")
|
|
Try
|
|
Dim ldapResult = Authenticate()
|
|
|
|
If CICRadarR.EnableOTP Then
|
|
TwoFactorChallenge()
|
|
Exit Sub
|
|
Else
|
|
Accept()
|
|
End If
|
|
Catch ex As Exception
|
|
CICRadarR.AccessLog("Authentication failed. Sending reject. Error: " & ex.Message)
|
|
mPacket.RejectAccessRequest()
|
|
End Try
|
|
End Sub
|
|
|
|
Private Sub Accept()
|
|
CICRadarR.AccessLog("Accept")
|
|
Dim sGUID As String = System.Guid.NewGuid.ToString()
|
|
userSessions(mUsername) = sGUID
|
|
sessionTimestamps(mUsername) = Now
|
|
|
|
Dim attributes As New RADIUSAttributes
|
|
Dim guidAttribute As New RADIUSAttribute(RadiusAttributeType.ReplyMessage, sGUID)
|
|
|
|
attributes.Add(guidAttribute)
|
|
mPacket.AcceptAccessRequest(attributes)
|
|
End Sub
|
|
|
|
Private Sub ProcessChallengeResponse()
|
|
CICRadarR.AccessLog("ProcessChallengeResponse")
|
|
|
|
' When the packet is an Challange-Response the password attr. contains the token
|
|
Dim challangeCode = mPassword
|
|
Dim state = mPacket.Attributes.GetFirstAttribute(RadiusAttributeType.State)
|
|
|
|
Dim sid = EncDec.Encrypt(mUsername & "_" & challangeCode, CICRadarR.encCode)
|
|
If sid = state.ToString Then
|
|
Accept()
|
|
Else
|
|
mPacket.RejectAccessRequest()
|
|
End If
|
|
End Sub
|
|
|
|
Private Sub TwoFactorChallenge()
|
|
Dim code = CICRadarR.GenerateCode
|
|
Dim sid = EncDec.Encrypt(mUsername & "_" & code, CICRadarR.encCode) 'generate unique code
|
|
CICRadarR.AccessLog("Access Challange Code: " & code)
|
|
|
|
userSidTokens(mUsername) = sid
|
|
tokenTimestamps(mUsername) = Now
|
|
|
|
If mUseSMSFactor Then
|
|
CICRadarR.AccessLog("TODO: Send SMS")
|
|
End If
|
|
|
|
If mUseEmailFactor Then
|
|
CICRadarR.AccessLog("TODO: Send Email")
|
|
End If
|
|
|
|
Dim attributes As New RADIUSAttributes
|
|
|
|
Dim attr As New RADIUSAttribute(RadiusAttributeType.ReplyMessage, "SMS Token")
|
|
Dim state As New RADIUSAttribute(RadiusAttributeType.State, sid)
|
|
|
|
attributes.Add(attr)
|
|
attributes.Add(state)
|
|
|
|
mPacket.SendAccessChallenge(attributes)
|
|
End Sub
|
|
|
|
Private Function Authenticate() As System.DirectoryServices.SearchResult
|
|
Dim password As String = mPacket.UserPassword
|
|
Dim ldapDomain As String = CICRadarR.LDAPDomain
|
|
|
|
CICRadarR.AccessLog("Authenticating: LDAPPAth: " & "LDAP://" & ldapDomain & ", Username: " & mUsername)
|
|
Dim dirEntry As New DirectoryEntry("LDAP://" & ldapDomain, mUsername, password)
|
|
|
|
Dim obj As Object = dirEntry.NativeObject
|
|
Dim search As New DirectorySearcher(dirEntry)
|
|
|
|
If InStr(mUsername, "@") > 0 Then
|
|
search.Filter = "(userPrincipalName=" + mUsername + ")"
|
|
Else
|
|
search.Filter = "(SAMAccountName=" + Split(mUsername, "\")(1) + ")"
|
|
End If
|
|
|
|
search.PropertiesToLoad.Add("distinguishedName")
|
|
If CICRadarR.EnableOTP = True Then
|
|
search.PropertiesToLoad.Add(CICRadarR.ADField)
|
|
search.PropertiesToLoad.Add(CICRadarR.ADMailField)
|
|
End If
|
|
|
|
Dim result = search.FindOne()
|
|
|
|
If IsDBNull(result) Then
|
|
CICRadarR.AccessLog("Failed to authenticate with Active Directory")
|
|
Throw New MissingUser
|
|
End If
|
|
|
|
Return result
|
|
End Function
|
|
|
|
Private Function LdapGetNumber(result As SearchResult) As String
|
|
Dim mobile = result.Properties(CICRadarR.ADField)(0)
|
|
mobile = Replace(mobile, "+", "")
|
|
If mobile.Trim.Length = 0 Then
|
|
CICRadarR.AccessLog("Unable to find correct phone number for user " & mUsername)
|
|
End If
|
|
Return mobile
|
|
End Function
|
|
|
|
Private Function LdapGetEmail(result As SearchResult) As String
|
|
Dim email = result.Properties(CICRadarR.ADMailField)(0)
|
|
|
|
If InStr(email, "@") = 0 Then
|
|
CICRadarR.AccessLog("Unable to find correct email for user " & mUsername)
|
|
End If
|
|
Return email
|
|
End Function
|
|
|
|
End Class
|
|
|