Active Directory Password Filter

Windows specific forum
Amundo
Enthusiast
Enthusiast
Posts: 200
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Active Directory Password Filter

Post by Amundo »

Greetings,

Due to a security review at my employer, we had to beef up password complexity. Minimum 8 chars, at least two numeric, and at least upper and lower, as well as special char (special chars available on standard (?) US keyboard).

Found an article on DevX (http://www.devx.com/security/Article/21522/0) using C++ and RegEx to write your own filter. RegEx was overkill, so I decided to try using PB and here is the result.

You'll notice that I address the three strings passed by AD, using different addressing techniques - they all seem to work!!! Figuring out the "UNICODE_STRING" structure, and how to work with it, took the longest amount of time.

Two registry entries control filtering and logging. Using the registry entry to enable/disable filtering is much faster than removing the DLL and rebooting your Domain Controller!!!

The Datasection at the end contains disallowed strings - some of them are obviously to filter swearwords - just add to the list however many you like.

Thanks to several PB users for their code, specifically, Luis for his Logging code, and pcfreak for his wildcard matching stuff.

Code: Select all

; Active Directory Password Filter
; April 2010

EnableExplicit

Global AccountName$, FullName$, Password$, c$, i
Global CntLowerCase, CntUpperCase, CntNumeric, CntSpecial
Global *p.String, PwdLen, NoFilter = 0, NoLog = 0

#WildCardsStringMatchEscapeCharacter = '/'

Declare.l EvalWildCardsStringMatch(string.s,wildcards.s,flags.l)
Declare.l WildCardsStringMatch(string.s,wildcards.s,flag.l)

Structure UNICODE_STRING
  Length.w
  MaximumLength.w
  Buffer.s
EndStructure

#MinPwdLen = 8

#OffsetOfLength=OffsetOf(UNICODE_STRING\Length)
#OffsetOfMaximumLength=OffsetOf(UNICODE_STRING\MaximumLength)
#OffsetOfBuffer=OffsetOf(UNICODE_STRING\Buffer)

Define SizeOfChar=SizeOf(Character)
#L2F_ENABLE=1
IncludeFile "C:\Program Files\PureBasic\INCLUDES\PBLogger.pbi"

ProcedureDLL AttachProcess(Instance)
  L2F_SET_LOGFILE("PwdDLL.log")
  
  NoFilter = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  NoLog = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  
  If Not NoLog
    L2F("AttachProcess "+Hex(Instance))
  EndIf
  
  Global NewList pattern.s()
  
  Restore Patterns
  Protected NextPattern$
  Read.s NextPattern$
  While NextPattern$ <> "__END__"
    AddElement(pattern())
    PrintN("Read pattern: "+NextPattern$)
    pattern() = NextPattern$
    Read.s NextPattern$
  Wend
  
  Global PatternCnt = ListSize(pattern()) - 1
  
  ProcedureReturn #True
EndProcedure

ProcedureDLL PasswordFilter(*AccountName.UNICODE_STRING, *FullName.UNICODE_STRING, *Password.UNICODE_STRING, SetOperation)
  NoFilter = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  NoLog = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  
  Protected *p.String
  Protected rc = #True          ; true by default
  
  If Not NoLog
    L2F("Entering PasswordFilter")
  EndIf
  
  If NoFilter
    If Not NoLog
      L2F("NoFilter selected")
    EndIf
    Goto ExitPasswordFilter
  EndIf
  
  Protected  PasswordLength.w = PeekW(*Password+#OffsetOfLength)
  Protected  PasswordMaximumLength.w = PeekW(*Password+#OffsetOfMaximumLength)
  Password$ = PeekS(@*Password\Buffer, PasswordLength/2, #PB_Unicode)
  If Not NoLog
    L2F("Password$: "+Password$)
  EndIf
  
  If (PasswordLength/2) < #MinPwdLen
    rc = #False
    If Not NoLog
      L2F("Invalid: PasswordLength < #MinPwdLen, leaving PasswordFilter" + #CRLF$)
    EndIf
    Goto ExitPasswordFilter
  EndIf
  
  Protected  AccountNameLength.w = PeekW(@*AccountName\Length)
  Protected  AccountNameMaximumLength.w = PeekW(@*AccountName\MaximumLength)
  AccountName$ = PeekS(@*AccountName\Buffer, AccountNameLength/2, #PB_Unicode)
  If Not NoLog
    L2F("AccountName$: "+AccountName$)
  EndIf
  
  Protected  FullNameLength.w = PeekW(@*FullName\Length)
  Protected  FullNameMaximumLength.w = PeekW(@*FullName\MaximumLength)
  FullName$ = PeekS(@*FullName\Buffer, FullNameLength/2, #PB_Unicode)
  If Not NoLog
    L2F("FullName$: "+FullName$)
  EndIf
  
  CntLowerCase = 0
  CntUpperCase = 0
  CntNumeric = 0
  CntSpecial = 0
  
  *p = @Password$
  PwdLen=Len(Password$)
  
  Define c.s
  For i = 0 To (PwdLen*2)-1 Step 2
    c = PeekS(*p+i, 1, #PB_Unicode)
    PrintN("c: " + c + ", chr(" + Str(Asc(c)) + ")")
    Debug ("c: " + c + ", chr(" + Str(Asc(c)) + ")")
    If c >= "a" And c <="z"
      CntLowerCase + 1
    EndIf
    If c >= "A" And c <="Z"
      CntUpperCase + 1
    EndIf
    If c >= "0" And c <="9"
      CntNumeric + 1
    EndIf
    
    ; Special chars:
    ;  $21 to $2e = !"#$%&'()*+,-./ (15 chars)
    ;  $3a to $40 = :;<=>?@         (7 chars)
    ;  $5b to $60 = [\]^_`          (6 chars)
    ;  $7b to $7e = {|}~            (4 chars)
    ;                       Total = 32 chars
    If c >= "!" And c <="/"
      CntSpecial + 1
    EndIf
    If c >= ":" And c <="@"
      CntSpecial + 1
    EndIf
    If c >= "[" And c <="`"
      CntSpecial + 1
    EndIf
    If c >= "{" And c <="~"
      CntSpecial + 1
    EndIf
  Next i
  
  L2F("Total lowercase: " + Str(CntLowerCase))
  L2F("Total uppercase: " + Str(CntUpperCase))
  L2F("Total digits: " + Str(CntNumeric))
  L2F("Total special: " + Str(CntSpecial))
  
  ; Check if password is valid before checking forbidden strings
  If (CntNumeric) < 2
    If Not NoLog
      L2F ("Invalid password, not enough numeric chars")
    EndIf
    rc = #False
    Goto ExitPasswordFilter
  EndIf
  If Not (CntLowerCase And CntUpperCase And CntNumeric And CntSpecial)
    If Not NoLog
      L2F ("Invalid password, did not meet mix requirements")
    EndIf
    rc = #False
    Goto ExitPasswordFilter
  EndIf
  
  Define Match
  ; remove '*' and '?', replace with '.' (just for patternmatching)
  Define s$ = Password$
  ReplaceString(s$, "*", ".", #PB_String_InPlace)
  ReplaceString(s$, "?", ".", #PB_String_InPlace)
  FirstElement(pattern())
  
  FirstElement(pattern())
  MeasureHiResIntervalStart()
  For i = 0 To PatternCnt
    Match = EvalWildCardsStringMatch(s$, pattern(), 0)
    If Match
      If Not NoLog
        L2F ("Method2: Invalid password, contains '"+pattern()+"'")
      EndIf
      rc = #False
      Goto ExitPasswordFilter
    EndIf
    NextElement(pattern())
  Next i
  If Not NoLog
    L2F("Method2: " + StrF(MeasureHiResIntervalStop()) + "s ")
  EndIf
  
  ; Zero memory before returning
  FillMemory(@AccountName$,Len(AccountName$))
  FillMemory(@FullName$,Len(FullName$))
  FillMemory(@Password$,Len(Password$))
  FillMemory(@s$,Len(s$))
  
  If Not NoLog
    L2F("Leaving PasswordFilter" + #CRLF$)
  EndIf
  
  ExitPasswordFilter:
  ProcedureReturn rc
EndProcedure

ProcedureDLL InitializeChangeNotify()
  NoFilter = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  NoLog = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  If Not NoLog
    L2F("InitializeChangeNotify called")
  EndIf
  ProcedureReturn #True
EndProcedure

ProcedureDLL PasswordChangeNotify(*UserName.UNICODE_STRING, RelativeId, *NewPassword.UNICODE_STRING)
  NoFilter = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  NoLog = Val(RegGetValue("HKEY_LOCAL_MACHINE\SOFTWARE\FTC Active Directory Password Filter","Filter","."))
  If Not NoLog
    L2F("PasswordChangeNotify called")
  EndIf
  ProcedureReturn #True
EndProcedure

ProcedureDLL DetachProcess(Instance)
  If Not NoLog
    L2F("DetachProcess "+Hex(Instance))
  EndIf
  ProcedureReturn #True
EndProcedure

ProcedureDLL AttachThread(Instance)
  If Not NoLog
    L2F("AttachThread "+Hex(Instance))
  EndIf
  ProcedureReturn #True
EndProcedure

ProcedureDLL DetachThread(Instance)
  If Not NoLog
    L2F("DetachThread "+Hex(Instance))
  EndIf
  ProcedureReturn #True
EndProcedure

Procedure.l EvalWildCardsStringMatch(string.s,wildcards.s,flags.l)
  Protected *char.CHARACTER=@wildcards
  Protected lastChar.c=0
  Protected Part$=""
  Protected Result$=""
  Protected Negate=#False
  While *char\c<>0
    Select *char\c
    Case '|'
      Select lastChar
      Case #WildCardsStringMatchEscapeCharacter
        Part$+Chr(*char\c)
      Default
        Result$+Str(WildCardsStringMatch(string,Part$,flags) ! Negate)+"|"
        Part$=""
        Negate=#False
      EndSelect
      lastChar=0
    Case '&'
      Select lastChar
      Case #WildCardsStringMatchEscapeCharacter
        Part$+Chr(*char\c)
      Default
        Result$+Str(WildCardsStringMatch(string,Part$,flags) ! Negate)+"&"
        Part$=""
        Negate=#False
      EndSelect
      lastChar=0
    Case '~'
      Select lastChar
      Case #WildCardsStringMatchEscapeCharacter
        Part$+Chr(*char\c)
      Default
        If Part$="" And Negate=#False
          Negate=#True
        Else
          Part$+Chr(*char\c)
        EndIf
      EndSelect
      lastChar=0
    Case #WildCardsStringMatchEscapeCharacter
      Select lastChar
      Case #WildCardsStringMatchEscapeCharacter
        Part$+Chr(*char\c)
        lastChar=0
      Default
        lastChar=#WildCardsStringMatchEscapeCharacter
      EndSelect
    Default
      If lastChar=0
        Part$+Chr(*char\c)
      Else
        Part$+Chr(lastChar)+Chr(*char\c)
        lastChar=0
      EndIf
    EndSelect
    *char+1+#PB_Compiler_Unicode
  Wend
  Result$+Str(WildCardsStringMatch(string,Part$,flags) ! Negate)
  
  If Len(Result$)>1
    DataSection
      !@@wcsm_eval_ORs:
      Data.l 0
      !@@wcsm_eval_ANDs:
      Data.l 0
    EndDataSection
    !MOV esi, dword [p.v_Result$]
    CompilerIf #PB_Compiler_Unicode
      !MOVZX ebx, word [esi]
    CompilerElse
      !MOVZX ebx, byte [esi]
    CompilerEndIf
    !CMP ebx, 0
    !JZ @@wcsm_eval_wend
    !@@wcsm_eval_while:
    !CMP ebx, '|'
    !JNE @@wcsm_eval_case2
    !@@wcsm_eval_case1:;case '|'
    !MOV ecx, dword [@@wcsm_eval_ORs]
    !JECXZ @@wcsm_eval_subwend1
    !@@wcsm_eval_subwhile1:
    !POP eax
    !POP edx
    !OR eax, edx
    !PUSH eax
    !DEC ecx
    !JNZ @@wcsm_eval_subwhile1
    !@@wcsm_eval_subwend1:
    !INC ecx
    !MOV dword [@@wcsm_eval_ORs], ecx
    !JMP @@wcsm_eval_endselect
    !@@wcsm_eval_case2:;case '&'
    !CMP ebx, '&'
    !JNE @@wcsm_eval_default
    !INC dword [@@wcsm_eval_ANDs]
    !JMP @@wcsm_eval_endselect
    !@@wcsm_eval_default:;default
    !SUB ebx, 30h
    !PUSH ebx
    !MOV ecx, dword [@@wcsm_eval_ANDs]
    !JECXZ @@wcsm_eval_subwend2
    !@@wcsm_eval_subwhile2:
    !POP eax
    !POP edx
    !AND eax, edx
    !PUSH eax
    !DEC ecx
    !JNZ @@wcsm_eval_subwhile2
    !@@wcsm_eval_subwend2:
    !MOV dword [@@wcsm_eval_ANDs], ecx
    !@@wcsm_eval_endselect:
    CompilerIf #PB_Compiler_Unicode
      !INC esi
      !INC esi
      !MOVZX ebx, word [esi]
    CompilerElse
      !INC esi
      !MOVZX ebx, byte [esi]
    CompilerEndIf
    !CMP ebx, 0
    !JNZ @@wcsm_eval_while
    !@@wcsm_eval_wend:
    !MOV ecx, dword [@@wcsm_eval_ORs]
    !JECXZ @@wcsm_eval_subwend3
    !@@wcsm_eval_subwhile3:
    !POP eax
    !POP edx
    !OR eax, edx
    !PUSH eax
    !DEC ecx
    !JNZ @@wcsm_eval_subwhile3
    !@@wcsm_eval_subwend3:
    !MOV dword [@@wcsm_eval_ORs], ecx
    !POP eax
    ProcedureReturn
  Else
    ProcedureReturn Val(Result$)
  EndIf
EndProcedure

Procedure.l WildCardsStringMatch(string.s,wildcards.s,flag.l)
  If flag=0
    string=LCase(string)
    wildcards=LCase(wildcards)
  EndIf
  If wildcards=""
    ProcedureReturn #True
  EndIf
  If Left(wildcards,1)<>"*" And Left(wildcards,1)<>"?" And Left(wildcards,1)<>"#" And Right(wildcards,1)<>"*" And Right(wildcards,1)<>"?" And Right(wildcards,1)<>"#" : wildcards="*"+wildcards+"*" : EndIf
  Protected *Wide1.CHARACTER
  Protected *Wide2.CHARACTER
  Protected *pos.CHARACTER=@string
  Protected *char.CHARACTER=@wildcards
  Protected *sPos.CHARACTER
  CompilerIf #PB_Compiler_Unicode
    Macro UnicodeNumberWildCardCompare(var)
      ((var>='0' And var<='9') Or (var>=$FF10 And var<=$FF19) Or (var>=$00BC And var<=$00BE) Or (var>=2153 And var<=$2182) Or (var>=$2070 And var<=$2079) Or (var>=$2080 And var<=$2089) Or (var>=$2460 And var<=$249B) Or (var>=$2776 And var<=$2793) Or (var>=$3220 And var<=$3229) Or (var>=$3280 And var<=$3289))
    EndMacro
  CompilerElse
    Macro UnicodeNumberWildCardCompare(var)
      ((var>='0' And var<='9') Or var=$B9 Or var=$B2 Or var=$B3 Or (var>=$BC And var<=$BE))
    EndMacro
  CompilerEndIf
  Repeat
    Select *char\c
    Case '*'
      *Wide1=*pos
      *Wide2=*char+1+#PB_Compiler_Unicode
      If *Wide2\c=0 Or (*Wide1\c=0 And RemoveString(wildcards,"*")="")
        ProcedureReturn #True
      EndIf
      If *Wide1\c=0
        ProcedureReturn #False
      EndIf
      *sPos=*char+1+#PB_Compiler_Unicode
      While *sPos\c='*'
        *char=*sPos
        *sPos+1+#PB_Compiler_Unicode
      Wend
      If *sPos\c=*pos\c Or *sPos\c='?' Or (*sPos\c='#' And UnicodeNumberWildCardCompare(*pos\c))
        *Wide2=*sPos
        While *Wide2\c<>'*' And *Wide2\c<>0
          If *Wide1\c=*Wide2\c Or *Wide2\c='?' Or (*Wide2\c='#' And UnicodeNumberWildCardCompare(*Wide1\c))
            *Wide1+1+#PB_Compiler_Unicode
            *Wide2+1+#PB_Compiler_Unicode
          Else
            If *sPos\c=*Wide1\c
              *Wide2=*char+1+#PB_Compiler_Unicode
            Else
              *Wide1+1+#PB_Compiler_Unicode
              *Wide2=*sPos
            EndIf
          EndIf
          If *Wide1\c=0
            While *Wide2\c='*'
              *Wide2+1+#PB_Compiler_Unicode
            Wend
            If *Wide2\c=0
              ProcedureReturn #True
            Else
              ProcedureReturn #False
            EndIf
          EndIf
        Wend
        If *Wide2\c='*'
          *pos=*Wide1
          *sPos=*Wide2
        Else
          If *Wide1\c=*Wide2\c And *Wide2\c=0
            ProcedureReturn #True
          Else
            If *Wide2\c=0 And *sPos\c<>'*'
              If *Wide1\c=0
                ProcedureReturn #False
              Else
                *pos+1+#PB_Compiler_Unicode
                *sPos=*char
              EndIf
            Else
              *pos=*Wide1
            EndIf
          EndIf
        EndIf
        *char=*sPos
      Else
        *pos+1+#PB_Compiler_Unicode
      EndIf
    Case '?'
      If *pos\c<>0
        *pos+1+#PB_Compiler_Unicode
        *char+1+#PB_Compiler_Unicode
        If *pos\c<>0 And *char\c=0
          ProcedureReturn #False
        EndIf
      Else
        ProcedureReturn #False
      EndIf
    Case '#'
      If UnicodeNumberWildCardCompare(*pos\c)
        *pos+1+#PB_Compiler_Unicode
        *char+1+#PB_Compiler_Unicode
        If *pos\c<>0 And *char\c=0
          ProcedureReturn #False
        EndIf
      Else
        ProcedureReturn #False
      EndIf
    Default
      If *pos\c=*char\c
        *pos+1+#PB_Compiler_Unicode
        *char+1+#PB_Compiler_Unicode
      Else
        ProcedureReturn #False
      EndIf
    EndSelect
  Until *char\c=0
  ProcedureReturn #True
EndProcedure

DataSection
  Patterns:
  Data.s _
    "*00*" _
    ,"*11*" _
    ,"*123*" _
    ,"*22*" _
    ,"*234*" _
    ,"*33*" _
    ,"*345*" _
    ,"*44*" _
    ,"*456*" _
    ,"*55*" _
    ,"*567*" _
    ,"*66*" _
    ,"*678*" _
    ,"*77*" _
    ,"*789*" _
    ,"*88*" _
    ,"*890*" _
    ,"*99*" _
    ,"*arse*" _
    ,"*asdfgh*" _
    ,"*aug*" _
    ,"*cu?t*" _
    ,"*dec*" _
    ,"*feb*" _
    ,"*fri*" _
    ,"*frm*" _
    ,"*fu?k*" _
    ,"*jan*" _
    ,"*jul*" _
    ,"*jun*" _
    ,"*mar*" _
    ,"*may*" _
    ,"*mon*" _
    ,"*nov*" _
    ,"*qwerty*" _
    ,"*sat*" _
    ,"*sep*" _
    ,"*sh?t*" _
    ,"*sun*" _
    ,"*thu*" _
    ,"*tue*" _
    ,"*wed*" _
    ,"__END__"
EndDataSection
Compile as a DLL, copy to your Domain Controller(s), edit the registry, reboot, and you're good to go!

I have yet to write a GUI to control the two registry entries - one to turn on/off logging, and the other to enable/disable the filter itself. I may update this post with it (if I ever get around to writing it!).

One immediate piece of (testing) code I need to write, is something that will bombard the DC with password changes, and stress test everything. I thought of using the Conficker worm, but thought better of it :-)

Hope someone finds this useful.

Almost forgot: uses registry functions from Droopy's Lib, and compiled with 4.41.
Win10, PB6.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
JCDinPGH
New User
New User
Posts: 4
Joined: Mon Feb 06, 2012 2:38 am

Re: Active Directory Password Filter

Post by JCDinPGH »

Sorry to dig up an old topic but I'm in a bind. We need to implement a custom password filter and have a very short deadline. I am definitely not a programmer but am hoping someone else has created a custom DLL using the code posted or something similar? I tried compiling the code but got a syntax error in line 12. What I'm hoping for are the exact steps to create a DLL (completely new to PB). I'm pretty sure I can do the system stuff to make a windows system start using the DLL. I know there are pay solutions but because of our deadline and incredible lack of budget, a free solution is our only hope at this point.
Amundo
Enthusiast
Enthusiast
Posts: 200
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Re: Active Directory Password Filter

Post by Amundo »

Hi JCDinPGH,

I got your PM. You're most welcome to use my code.

Can't understand why you're getting an error in line 12 - it's a forward declaration for a procedure - which version of PB were you using?
Win10, PB6.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
JCDinPGH
New User
New User
Posts: 4
Joined: Mon Feb 06, 2012 2:38 am

Re: Active Directory Password Filter

Post by JCDinPGH »

Thanks or replying. I'm using version 10.0 to try and compile the code. It's been since I've done any programming but I changed the DECLARE statements to #DECLARE and the compiler seemed to get past that line but then when it got to the FOR/NEXT loops it keeps saying "expecting NEXT" although they look fine to me.
Thanks for any help you can supply.
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Active Directory Password Filter

Post by idle »

umm purebasic is only at version 4.61
Windows 11, Manjaro, Raspberry Pi OS
Image
MachineCode
Addict
Addict
Posts: 1482
Joined: Tue Feb 22, 2011 1:16 pm

Re: Active Directory Password Filter

Post by MachineCode »

JCDinPGH wrote:DataSection
Patterns:
Data.s _
"*00*" _
,"*11*" _
,"*123*" _
EndDataSection
Um, line continuation chars? And PureBasic v10.00 mentioned? WTF is going on here? :shock:
Microsoft Visual Basic only lasted 7 short years: 1991 to 1998.
PureBasic: Born in 1998 and still going strong to this very day!
Lost
User
User
Posts: 63
Joined: Fri Dec 19, 2008 12:24 am
Location: Tasmania

Re: Active Directory Password Filter

Post by Lost »

Don't you guys have version 10? I'm running the latest 10.02 and it's awesome. Compile for ARM, WinRT (support for WinWTF will be added shortly when Windows 12 is released) and IOS18.

Get with the program guys, you're slowing us down...

:wink:
JCDinPGH
New User
New User
Posts: 4
Joined: Mon Feb 06, 2012 2:38 am

Re: Active Directory Password Filter

Post by JCDinPGH »

OMG. I am such a moron. I have been looking at geting back into programming and have both Pure Basic and Power Basic and was comparing the two, and was trying to compile the Pure Basic code in Power Basic. Duh! Feel free to flame away!
JCDinPGH
New User
New User
Posts: 4
Joined: Mon Feb 06, 2012 2:38 am

Re: Active Directory Password Filter

Post by JCDinPGH »

It looks like the code references "IncludeFile "C:\Program Files\PureBasic\INCLUDES\PBLogger.pbi". Any idea where I can get that?
Amundo
Enthusiast
Enthusiast
Posts: 200
Joined: Thu Feb 16, 2006 1:41 am
Location: New Zealand

Re: Active Directory Password Filter

Post by Amundo »

It is luis' "File Logger" available here:

http://www.purebasic.fr/english/viewtopic.php?p=279380

I saved it as "PBLogger.pbi"

P.S. Make sure you turn off the logging once you're happy it's working - if the auditors ever find out the passwords get logged to a file....well, I did warn you!
Win10, PB6.x, okayish CPU, onboard video card, fuzzy monitor (or is that my eyesight?)
"When the facts change, I change my mind" - John Maynard Keynes
Post Reply