diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ
diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-amd64.exe
Binary files differ

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ
diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-amd64.exe
Binary files differ
diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go
new file mode 100755
index 0000000..d3f29ed
--- /dev/null
+++ b/LdapUsrEnum.go
@@ -0,0 +1,411 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"crypto/tls"
+	"strings"
+	"strconv"
+	"math"
+	"time"
+
+	"github.com/akamensky/argparse"
+	"github.com/go-ldap/ldap/v3"
+)
+
+func main() {
+
+	parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d <ip> -w <domain.com> --username=\"<username>\" --password=\"<password>\"")
+	dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""})
+	domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""})
+	username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""})
+	password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""})
+	var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"})
+	//target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""})
+	err := parser.Parse(os.Args)
+	if err != nil {
+		fmt.Print(parser.Usage(err))
+		os.Exit(1)
+	}
+
+	if len(*dc) == 0 || len(*domain) == 0{
+		fmt.Print(parser.Usage(err))
+		fmt.Println("[-] Provide DC & domain minimum")
+		os.Exit(1)
+	}
+
+	fmt.Printf("[i] DC/AD: %v\n", *dc)
+	fmt.Printf("[i] domain: %v\n", *domain)
+	fmt.Println("[!] trying plaintext auth")
+	l, err := ldap.DialURL("ldap://" + *dc)
+	if err != nil {
+		fmt.Println(err)
+		fmt.Println("[!] trying TLS auth")
+		l.Close()
+		ldapURL := "ldaps://"+ *dc
+		l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+		if err2 != nil {
+	        fmt.Println(err2)
+	        fmt.Println("[-] could not connect")
+			os.Exit(1)
+		}
+		defer l.Close()
+	}
+	defer l.Close()
+
+	if len(*username) == 0{
+		*username = "read-only-admin"
+	}
+	dcParts := strings.Split(*domain, ".")
+	dcFmt := "CN="+*username
+	var baseDN string
+	for _, element := range dcParts {
+		baseDN += ",DC="+element
+	}
+	dcFmt += baseDN
+	baseDN = baseDN[1:]
+	fmt.Println("[+] using: "+dcFmt)
+
+	if len(*password) == 0{
+		fmt.Println("[!] trying to connect with unauth client")
+		err = l.UnauthenticatedBind(dcFmt)
+		if err != nil {
+		    fmt.Println("[-] unauth session failed - requires credentials")
+			os.Exit(1)
+		}
+	}else{
+		fmt.Println("[!] trying to connect with supplied password")
+		err = l.Bind(*username+"@"+*domain, *password)  // FFS this stumped me for SO LONG! >.<
+		if err != nil {
+		    fmt.Println(err)
+		    fmt.Println("[-] auth session failed - check credentials")
+			os.Exit(1)
+		}
+	}
+
+
+	// SEARCH FOR USERNAME'S "sAMAccountName" (basic test)
+	// Filters must start and finish with ()!
+	fmt.Print("[+] query to get account username... ")
+	var mainUser string = ""
+	filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username))
+	searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{})
+	result, err := l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result.Entries) == 0{
+		fmt.Println("[-] user can't query LDAP")
+	    os.Exit(1)
+	}
+	for _, entry := range result.Entries {
+		mainUser = entry.GetAttributeValue("sAMAccountName") 
+	}
+	fmt.Println( mainUser )
+	//result.PrettyPrint(2)
+
+	// SEARCH FOR ALL UERNAME'S 
+	fmt.Print("[+] ALL usernames... ")
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+	//result.PrettyPrint(2)
+
+	var foundUsers []string
+	var foundUCreated []string
+	var foundUChanged []string
+	var foundULogon []string
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") )
+		foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") )
+		foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") )
+		foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") )
+	}
+
+	// SEARCH FOR ALL LOCKED OUT ACCOUNTS
+	fmt.Print("[+] locked accounts... ")
+	filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var lockedUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		lockedUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL DISABLED ACCOUNTS
+	fmt.Print("[+] disabled accounts... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println(len(result.Entries))
+
+	var disabledUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		disabledUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL PASSWORD NEVER EXPIRE
+	fmt.Print("[+] non-expire passwords... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var neverExpireUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		neverExpireUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+   	var groups = map[string][]string{}
+
+	// SEARCH FOR ALL groups NOT "Default users" (rid 513)
+	fmt.Print("[+] groups... ")
+	filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))")
+	//filter = fmt.Sprintf("(&(CN=\"Administrator\"))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	//result.PrettyPrint(2)
+
+	// for each group
+	fmt.Println( len(result.Entries))
+	fmt.Print("[+] Matching users to groups.. this could take a while!\n")
+	for _, entry := range result.Entries {
+
+		// Search for all users of that group
+		filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" ))
+		searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+		result2, err := l.Search(searchReq2)
+		if err != nil {
+		        fmt.Println("[-] failed to query LDAP: %w", err)
+		        os.Exit(1)
+		}
+		if len(result2.Entries) != 0 {
+			for _, entry2 := range result2.Entries {			
+				groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName"))			
+			}
+		}
+		//os.Exit(1)
+	}
+
+	// All users of Default Group (RID 513)
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))")
+	searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result2, err := l.Search(searchReq2)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result2.Entries) != 0 {
+		for _, entry2 := range result2.Entries {			
+			groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName"))			
+		}
+	}
+
+
+	wantedUsers := []string{}
+	for x, _ := range groups { // for each group
+		addGroup := true
+		for i := range groups[x] {
+			if groups[x][i] == mainUser { // if user in group
+				addGroup = false // dont add these users to list
+			}
+		}
+		if addGroup == true{
+			for i := range groups[x] { // add the people to a list of accounts want to crack
+				_, found := Find(wantedUsers, groups[x][i])
+    			if !found {
+					wantedUsers = append(wantedUsers, groups[x][i] )
+				
+				}
+			}
+		}
+	}
+
+	if *dispGroup == true{ // display which users belong to which 
+		for x, _ := range groups {
+			fmt.Printf("[+] group: %v \n", x)
+			for i := range groups[x] {
+		        fmt.Println("[+]	", groups[x][i])
+		    }
+		}
+	}
+
+	// SEARCH FOR PASSWORD POLICY
+	fmt.Println("[+] password policy ")
+	filter = fmt.Sprintf("(objectClass=domainDNS)")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println("--- pwd pol ---")
+	var foundGPO []string
+	for _, entry := range result.Entries {
+		foundGPO = []string{
+			entry.GetAttributeValue("minPwdLength"),
+			entry.GetAttributeValue("minPwdAge"),
+			convertPwdAge(entry.GetAttributeValue("maxPwdAge")),
+			entry.GetAttributeValue("pwdHistoryLength"),
+			entry.GetAttributeValue("lockoutThreshold"),
+			convertLockout(entry.GetAttributeValue("lockoutDuration")),
+			convertLockout(entry.GetAttributeValue("lockOutObservationWindow")),
+		}
+	}
+	fmt.Printf("minPwdLength: %v \n",foundGPO[0])
+	fmt.Printf("minPwdAge: %v \n",foundGPO[1])
+	fmt.Printf("maxPwdAge: %v \n",foundGPO[2])
+	fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3])
+	fmt.Printf("lockoutThreshold: %v \n",foundGPO[4])
+	fmt.Printf("lockoutDuration: %v \n",foundGPO[5])
+	fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6])
+
+	// display results
+	fmt.Println("--- results ---")
+	for i := 0; i < len(foundUsers); i++ {
+		fmt.Printf("%-15v", foundUsers[i])
+		if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")}
+		if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")}
+		if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")}
+		fmt.Print("\n")
+		lasLog := foundULogon[i]
+		if lasLog != "0" {
+			fmt.Printf("		%v ", convertLDAPDate(lasLog) )
+			fmt.Printf(" (%v)\n", ldapDiff(lasLog) )
+		}else{
+			fmt.Println("		 Never Logged In")
+		}
+	}
+	fmt.Println("---  to try (ALL) ---")
+	toTry := ""
+	for i := 0; i < len(foundUsers); i++ {
+		if lockedUsers[foundUsers[i]] == 0{ // account not locked
+			if disabledUsers[foundUsers[i]] == 0{ // account not disabled
+				toTry += ", "+foundUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+	fmt.Println("---  to try (Diff Group) ---")
+	toTry = ""
+	for i := 0; i < len(wantedUsers); i++ {
+		if lockedUsers[wantedUsers[i]] == 0{ // account not locked
+			if disabledUsers[wantedUsers[i]] == 0{ // account not disabled
+				toTry += ", "+wantedUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+
+}
+
+func ldapDiff(timestamp string)string{
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t2 := time.Unix(date, 0)
+	t1 := time.Now()
+
+	diff := t1.Sub(t2)
+	t := time.Time{}.Add(diff)
+	// fmt.Println(diff)  // DEBUG
+	formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second())
+	return formatted
+}
+func RoundTime(input float64) int {
+	var result float64
+	if input < 0 {
+	     result = math.Ceil(input - 0.5)
+	} else {
+	     result = math.Floor(input + 0.5)
+	}
+	// only interested in integer, ignore fractional
+	i, _ := math.Modf(result)
+
+	return int(i)
+}
+func convertLDAPDate(timestamp string)string{
+	//baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t := time.Unix(date, 0)
+	formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d",
+        t.Year(), t.Month(), t.Day(),
+        t.Hour(), t.Minute(), t.Second())
+    return formatted
+}
+func convertPwdAge(pwdage string) string {
+	f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64)
+	age := ((f / (60 * 10000000)) / 60) / 24
+	flr := math.Floor(age)
+	s := strconv.Itoa(int(flr))
+
+	return s
+}
+func convertLockout(lockout string) string {
+	i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1))
+	age := i / (60 * 10000000)
+	s := strconv.Itoa(age)
+
+	return s
+}
+func after(value string, a string) string {
+    // Get substring after a string.
+    pos := strings.LastIndex(value, a)
+    if pos == -1 {
+        return ""
+    }
+    adjustedPos := pos + len(a)
+    if adjustedPos >= len(value) {
+        return ""
+    }
+    return value[adjustedPos:len(value)]
+}
+
+func Find(slice []string, val string) (int, bool) {
+    for i, item := range slice {
+        if item == val {
+            return i, true
+        }
+    }
+    return -1, false
+}

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ
diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-amd64.exe
Binary files differ
diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go
new file mode 100755
index 0000000..d3f29ed
--- /dev/null
+++ b/LdapUsrEnum.go
@@ -0,0 +1,411 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"crypto/tls"
+	"strings"
+	"strconv"
+	"math"
+	"time"
+
+	"github.com/akamensky/argparse"
+	"github.com/go-ldap/ldap/v3"
+)
+
+func main() {
+
+	parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d <ip> -w <domain.com> --username=\"<username>\" --password=\"<password>\"")
+	dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""})
+	domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""})
+	username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""})
+	password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""})
+	var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"})
+	//target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""})
+	err := parser.Parse(os.Args)
+	if err != nil {
+		fmt.Print(parser.Usage(err))
+		os.Exit(1)
+	}
+
+	if len(*dc) == 0 || len(*domain) == 0{
+		fmt.Print(parser.Usage(err))
+		fmt.Println("[-] Provide DC & domain minimum")
+		os.Exit(1)
+	}
+
+	fmt.Printf("[i] DC/AD: %v\n", *dc)
+	fmt.Printf("[i] domain: %v\n", *domain)
+	fmt.Println("[!] trying plaintext auth")
+	l, err := ldap.DialURL("ldap://" + *dc)
+	if err != nil {
+		fmt.Println(err)
+		fmt.Println("[!] trying TLS auth")
+		l.Close()
+		ldapURL := "ldaps://"+ *dc
+		l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+		if err2 != nil {
+	        fmt.Println(err2)
+	        fmt.Println("[-] could not connect")
+			os.Exit(1)
+		}
+		defer l.Close()
+	}
+	defer l.Close()
+
+	if len(*username) == 0{
+		*username = "read-only-admin"
+	}
+	dcParts := strings.Split(*domain, ".")
+	dcFmt := "CN="+*username
+	var baseDN string
+	for _, element := range dcParts {
+		baseDN += ",DC="+element
+	}
+	dcFmt += baseDN
+	baseDN = baseDN[1:]
+	fmt.Println("[+] using: "+dcFmt)
+
+	if len(*password) == 0{
+		fmt.Println("[!] trying to connect with unauth client")
+		err = l.UnauthenticatedBind(dcFmt)
+		if err != nil {
+		    fmt.Println("[-] unauth session failed - requires credentials")
+			os.Exit(1)
+		}
+	}else{
+		fmt.Println("[!] trying to connect with supplied password")
+		err = l.Bind(*username+"@"+*domain, *password)  // FFS this stumped me for SO LONG! >.<
+		if err != nil {
+		    fmt.Println(err)
+		    fmt.Println("[-] auth session failed - check credentials")
+			os.Exit(1)
+		}
+	}
+
+
+	// SEARCH FOR USERNAME'S "sAMAccountName" (basic test)
+	// Filters must start and finish with ()!
+	fmt.Print("[+] query to get account username... ")
+	var mainUser string = ""
+	filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username))
+	searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{})
+	result, err := l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result.Entries) == 0{
+		fmt.Println("[-] user can't query LDAP")
+	    os.Exit(1)
+	}
+	for _, entry := range result.Entries {
+		mainUser = entry.GetAttributeValue("sAMAccountName") 
+	}
+	fmt.Println( mainUser )
+	//result.PrettyPrint(2)
+
+	// SEARCH FOR ALL UERNAME'S 
+	fmt.Print("[+] ALL usernames... ")
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+	//result.PrettyPrint(2)
+
+	var foundUsers []string
+	var foundUCreated []string
+	var foundUChanged []string
+	var foundULogon []string
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") )
+		foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") )
+		foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") )
+		foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") )
+	}
+
+	// SEARCH FOR ALL LOCKED OUT ACCOUNTS
+	fmt.Print("[+] locked accounts... ")
+	filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var lockedUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		lockedUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL DISABLED ACCOUNTS
+	fmt.Print("[+] disabled accounts... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println(len(result.Entries))
+
+	var disabledUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		disabledUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL PASSWORD NEVER EXPIRE
+	fmt.Print("[+] non-expire passwords... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var neverExpireUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		neverExpireUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+   	var groups = map[string][]string{}
+
+	// SEARCH FOR ALL groups NOT "Default users" (rid 513)
+	fmt.Print("[+] groups... ")
+	filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))")
+	//filter = fmt.Sprintf("(&(CN=\"Administrator\"))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	//result.PrettyPrint(2)
+
+	// for each group
+	fmt.Println( len(result.Entries))
+	fmt.Print("[+] Matching users to groups.. this could take a while!\n")
+	for _, entry := range result.Entries {
+
+		// Search for all users of that group
+		filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" ))
+		searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+		result2, err := l.Search(searchReq2)
+		if err != nil {
+		        fmt.Println("[-] failed to query LDAP: %w", err)
+		        os.Exit(1)
+		}
+		if len(result2.Entries) != 0 {
+			for _, entry2 := range result2.Entries {			
+				groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName"))			
+			}
+		}
+		//os.Exit(1)
+	}
+
+	// All users of Default Group (RID 513)
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))")
+	searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result2, err := l.Search(searchReq2)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result2.Entries) != 0 {
+		for _, entry2 := range result2.Entries {			
+			groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName"))			
+		}
+	}
+
+
+	wantedUsers := []string{}
+	for x, _ := range groups { // for each group
+		addGroup := true
+		for i := range groups[x] {
+			if groups[x][i] == mainUser { // if user in group
+				addGroup = false // dont add these users to list
+			}
+		}
+		if addGroup == true{
+			for i := range groups[x] { // add the people to a list of accounts want to crack
+				_, found := Find(wantedUsers, groups[x][i])
+    			if !found {
+					wantedUsers = append(wantedUsers, groups[x][i] )
+				
+				}
+			}
+		}
+	}
+
+	if *dispGroup == true{ // display which users belong to which 
+		for x, _ := range groups {
+			fmt.Printf("[+] group: %v \n", x)
+			for i := range groups[x] {
+		        fmt.Println("[+]	", groups[x][i])
+		    }
+		}
+	}
+
+	// SEARCH FOR PASSWORD POLICY
+	fmt.Println("[+] password policy ")
+	filter = fmt.Sprintf("(objectClass=domainDNS)")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println("--- pwd pol ---")
+	var foundGPO []string
+	for _, entry := range result.Entries {
+		foundGPO = []string{
+			entry.GetAttributeValue("minPwdLength"),
+			entry.GetAttributeValue("minPwdAge"),
+			convertPwdAge(entry.GetAttributeValue("maxPwdAge")),
+			entry.GetAttributeValue("pwdHistoryLength"),
+			entry.GetAttributeValue("lockoutThreshold"),
+			convertLockout(entry.GetAttributeValue("lockoutDuration")),
+			convertLockout(entry.GetAttributeValue("lockOutObservationWindow")),
+		}
+	}
+	fmt.Printf("minPwdLength: %v \n",foundGPO[0])
+	fmt.Printf("minPwdAge: %v \n",foundGPO[1])
+	fmt.Printf("maxPwdAge: %v \n",foundGPO[2])
+	fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3])
+	fmt.Printf("lockoutThreshold: %v \n",foundGPO[4])
+	fmt.Printf("lockoutDuration: %v \n",foundGPO[5])
+	fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6])
+
+	// display results
+	fmt.Println("--- results ---")
+	for i := 0; i < len(foundUsers); i++ {
+		fmt.Printf("%-15v", foundUsers[i])
+		if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")}
+		if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")}
+		if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")}
+		fmt.Print("\n")
+		lasLog := foundULogon[i]
+		if lasLog != "0" {
+			fmt.Printf("		%v ", convertLDAPDate(lasLog) )
+			fmt.Printf(" (%v)\n", ldapDiff(lasLog) )
+		}else{
+			fmt.Println("		 Never Logged In")
+		}
+	}
+	fmt.Println("---  to try (ALL) ---")
+	toTry := ""
+	for i := 0; i < len(foundUsers); i++ {
+		if lockedUsers[foundUsers[i]] == 0{ // account not locked
+			if disabledUsers[foundUsers[i]] == 0{ // account not disabled
+				toTry += ", "+foundUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+	fmt.Println("---  to try (Diff Group) ---")
+	toTry = ""
+	for i := 0; i < len(wantedUsers); i++ {
+		if lockedUsers[wantedUsers[i]] == 0{ // account not locked
+			if disabledUsers[wantedUsers[i]] == 0{ // account not disabled
+				toTry += ", "+wantedUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+
+}
+
+func ldapDiff(timestamp string)string{
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t2 := time.Unix(date, 0)
+	t1 := time.Now()
+
+	diff := t1.Sub(t2)
+	t := time.Time{}.Add(diff)
+	// fmt.Println(diff)  // DEBUG
+	formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second())
+	return formatted
+}
+func RoundTime(input float64) int {
+	var result float64
+	if input < 0 {
+	     result = math.Ceil(input - 0.5)
+	} else {
+	     result = math.Floor(input + 0.5)
+	}
+	// only interested in integer, ignore fractional
+	i, _ := math.Modf(result)
+
+	return int(i)
+}
+func convertLDAPDate(timestamp string)string{
+	//baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t := time.Unix(date, 0)
+	formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d",
+        t.Year(), t.Month(), t.Day(),
+        t.Hour(), t.Minute(), t.Second())
+    return formatted
+}
+func convertPwdAge(pwdage string) string {
+	f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64)
+	age := ((f / (60 * 10000000)) / 60) / 24
+	flr := math.Floor(age)
+	s := strconv.Itoa(int(flr))
+
+	return s
+}
+func convertLockout(lockout string) string {
+	i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1))
+	age := i / (60 * 10000000)
+	s := strconv.Itoa(age)
+
+	return s
+}
+func after(value string, a string) string {
+    // Get substring after a string.
+    pos := strings.LastIndex(value, a)
+    if pos == -1 {
+        return ""
+    }
+    adjustedPos := pos + len(a)
+    if adjustedPos >= len(value) {
+        return ""
+    }
+    return value[adjustedPos:len(value)]
+}
+
+func Find(slice []string, val string) (int, bool) {
+    for i, item := range slice {
+        if item == val {
+            return i, true
+        }
+    }
+    return -1, false
+}
diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png
new file mode 100755
index 0000000..7f11d70
--- /dev/null
+++ b/LdapUsrEnum_Screenshot.png
Binary files differ

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ
diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-amd64.exe
Binary files differ
diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go
new file mode 100755
index 0000000..d3f29ed
--- /dev/null
+++ b/LdapUsrEnum.go
@@ -0,0 +1,411 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"crypto/tls"
+	"strings"
+	"strconv"
+	"math"
+	"time"
+
+	"github.com/akamensky/argparse"
+	"github.com/go-ldap/ldap/v3"
+)
+
+func main() {
+
+	parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d <ip> -w <domain.com> --username=\"<username>\" --password=\"<password>\"")
+	dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""})
+	domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""})
+	username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""})
+	password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""})
+	var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"})
+	//target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""})
+	err := parser.Parse(os.Args)
+	if err != nil {
+		fmt.Print(parser.Usage(err))
+		os.Exit(1)
+	}
+
+	if len(*dc) == 0 || len(*domain) == 0{
+		fmt.Print(parser.Usage(err))
+		fmt.Println("[-] Provide DC & domain minimum")
+		os.Exit(1)
+	}
+
+	fmt.Printf("[i] DC/AD: %v\n", *dc)
+	fmt.Printf("[i] domain: %v\n", *domain)
+	fmt.Println("[!] trying plaintext auth")
+	l, err := ldap.DialURL("ldap://" + *dc)
+	if err != nil {
+		fmt.Println(err)
+		fmt.Println("[!] trying TLS auth")
+		l.Close()
+		ldapURL := "ldaps://"+ *dc
+		l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+		if err2 != nil {
+	        fmt.Println(err2)
+	        fmt.Println("[-] could not connect")
+			os.Exit(1)
+		}
+		defer l.Close()
+	}
+	defer l.Close()
+
+	if len(*username) == 0{
+		*username = "read-only-admin"
+	}
+	dcParts := strings.Split(*domain, ".")
+	dcFmt := "CN="+*username
+	var baseDN string
+	for _, element := range dcParts {
+		baseDN += ",DC="+element
+	}
+	dcFmt += baseDN
+	baseDN = baseDN[1:]
+	fmt.Println("[+] using: "+dcFmt)
+
+	if len(*password) == 0{
+		fmt.Println("[!] trying to connect with unauth client")
+		err = l.UnauthenticatedBind(dcFmt)
+		if err != nil {
+		    fmt.Println("[-] unauth session failed - requires credentials")
+			os.Exit(1)
+		}
+	}else{
+		fmt.Println("[!] trying to connect with supplied password")
+		err = l.Bind(*username+"@"+*domain, *password)  // FFS this stumped me for SO LONG! >.<
+		if err != nil {
+		    fmt.Println(err)
+		    fmt.Println("[-] auth session failed - check credentials")
+			os.Exit(1)
+		}
+	}
+
+
+	// SEARCH FOR USERNAME'S "sAMAccountName" (basic test)
+	// Filters must start and finish with ()!
+	fmt.Print("[+] query to get account username... ")
+	var mainUser string = ""
+	filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username))
+	searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{})
+	result, err := l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result.Entries) == 0{
+		fmt.Println("[-] user can't query LDAP")
+	    os.Exit(1)
+	}
+	for _, entry := range result.Entries {
+		mainUser = entry.GetAttributeValue("sAMAccountName") 
+	}
+	fmt.Println( mainUser )
+	//result.PrettyPrint(2)
+
+	// SEARCH FOR ALL UERNAME'S 
+	fmt.Print("[+] ALL usernames... ")
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+	//result.PrettyPrint(2)
+
+	var foundUsers []string
+	var foundUCreated []string
+	var foundUChanged []string
+	var foundULogon []string
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") )
+		foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") )
+		foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") )
+		foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") )
+	}
+
+	// SEARCH FOR ALL LOCKED OUT ACCOUNTS
+	fmt.Print("[+] locked accounts... ")
+	filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var lockedUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		lockedUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL DISABLED ACCOUNTS
+	fmt.Print("[+] disabled accounts... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println(len(result.Entries))
+
+	var disabledUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		disabledUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL PASSWORD NEVER EXPIRE
+	fmt.Print("[+] non-expire passwords... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var neverExpireUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		neverExpireUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+   	var groups = map[string][]string{}
+
+	// SEARCH FOR ALL groups NOT "Default users" (rid 513)
+	fmt.Print("[+] groups... ")
+	filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))")
+	//filter = fmt.Sprintf("(&(CN=\"Administrator\"))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	//result.PrettyPrint(2)
+
+	// for each group
+	fmt.Println( len(result.Entries))
+	fmt.Print("[+] Matching users to groups.. this could take a while!\n")
+	for _, entry := range result.Entries {
+
+		// Search for all users of that group
+		filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" ))
+		searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+		result2, err := l.Search(searchReq2)
+		if err != nil {
+		        fmt.Println("[-] failed to query LDAP: %w", err)
+		        os.Exit(1)
+		}
+		if len(result2.Entries) != 0 {
+			for _, entry2 := range result2.Entries {			
+				groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName"))			
+			}
+		}
+		//os.Exit(1)
+	}
+
+	// All users of Default Group (RID 513)
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))")
+	searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result2, err := l.Search(searchReq2)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result2.Entries) != 0 {
+		for _, entry2 := range result2.Entries {			
+			groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName"))			
+		}
+	}
+
+
+	wantedUsers := []string{}
+	for x, _ := range groups { // for each group
+		addGroup := true
+		for i := range groups[x] {
+			if groups[x][i] == mainUser { // if user in group
+				addGroup = false // dont add these users to list
+			}
+		}
+		if addGroup == true{
+			for i := range groups[x] { // add the people to a list of accounts want to crack
+				_, found := Find(wantedUsers, groups[x][i])
+    			if !found {
+					wantedUsers = append(wantedUsers, groups[x][i] )
+				
+				}
+			}
+		}
+	}
+
+	if *dispGroup == true{ // display which users belong to which 
+		for x, _ := range groups {
+			fmt.Printf("[+] group: %v \n", x)
+			for i := range groups[x] {
+		        fmt.Println("[+]	", groups[x][i])
+		    }
+		}
+	}
+
+	// SEARCH FOR PASSWORD POLICY
+	fmt.Println("[+] password policy ")
+	filter = fmt.Sprintf("(objectClass=domainDNS)")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println("--- pwd pol ---")
+	var foundGPO []string
+	for _, entry := range result.Entries {
+		foundGPO = []string{
+			entry.GetAttributeValue("minPwdLength"),
+			entry.GetAttributeValue("minPwdAge"),
+			convertPwdAge(entry.GetAttributeValue("maxPwdAge")),
+			entry.GetAttributeValue("pwdHistoryLength"),
+			entry.GetAttributeValue("lockoutThreshold"),
+			convertLockout(entry.GetAttributeValue("lockoutDuration")),
+			convertLockout(entry.GetAttributeValue("lockOutObservationWindow")),
+		}
+	}
+	fmt.Printf("minPwdLength: %v \n",foundGPO[0])
+	fmt.Printf("minPwdAge: %v \n",foundGPO[1])
+	fmt.Printf("maxPwdAge: %v \n",foundGPO[2])
+	fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3])
+	fmt.Printf("lockoutThreshold: %v \n",foundGPO[4])
+	fmt.Printf("lockoutDuration: %v \n",foundGPO[5])
+	fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6])
+
+	// display results
+	fmt.Println("--- results ---")
+	for i := 0; i < len(foundUsers); i++ {
+		fmt.Printf("%-15v", foundUsers[i])
+		if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")}
+		if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")}
+		if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")}
+		fmt.Print("\n")
+		lasLog := foundULogon[i]
+		if lasLog != "0" {
+			fmt.Printf("		%v ", convertLDAPDate(lasLog) )
+			fmt.Printf(" (%v)\n", ldapDiff(lasLog) )
+		}else{
+			fmt.Println("		 Never Logged In")
+		}
+	}
+	fmt.Println("---  to try (ALL) ---")
+	toTry := ""
+	for i := 0; i < len(foundUsers); i++ {
+		if lockedUsers[foundUsers[i]] == 0{ // account not locked
+			if disabledUsers[foundUsers[i]] == 0{ // account not disabled
+				toTry += ", "+foundUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+	fmt.Println("---  to try (Diff Group) ---")
+	toTry = ""
+	for i := 0; i < len(wantedUsers); i++ {
+		if lockedUsers[wantedUsers[i]] == 0{ // account not locked
+			if disabledUsers[wantedUsers[i]] == 0{ // account not disabled
+				toTry += ", "+wantedUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+
+}
+
+func ldapDiff(timestamp string)string{
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t2 := time.Unix(date, 0)
+	t1 := time.Now()
+
+	diff := t1.Sub(t2)
+	t := time.Time{}.Add(diff)
+	// fmt.Println(diff)  // DEBUG
+	formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second())
+	return formatted
+}
+func RoundTime(input float64) int {
+	var result float64
+	if input < 0 {
+	     result = math.Ceil(input - 0.5)
+	} else {
+	     result = math.Floor(input + 0.5)
+	}
+	// only interested in integer, ignore fractional
+	i, _ := math.Modf(result)
+
+	return int(i)
+}
+func convertLDAPDate(timestamp string)string{
+	//baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t := time.Unix(date, 0)
+	formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d",
+        t.Year(), t.Month(), t.Day(),
+        t.Hour(), t.Minute(), t.Second())
+    return formatted
+}
+func convertPwdAge(pwdage string) string {
+	f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64)
+	age := ((f / (60 * 10000000)) / 60) / 24
+	flr := math.Floor(age)
+	s := strconv.Itoa(int(flr))
+
+	return s
+}
+func convertLockout(lockout string) string {
+	i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1))
+	age := i / (60 * 10000000)
+	s := strconv.Itoa(age)
+
+	return s
+}
+func after(value string, a string) string {
+    // Get substring after a string.
+    pos := strings.LastIndex(value, a)
+    if pos == -1 {
+        return ""
+    }
+    adjustedPos := pos + len(a)
+    if adjustedPos >= len(value) {
+        return ""
+    }
+    return value[adjustedPos:len(value)]
+}
+
+func Find(slice []string, val string) (int, bool) {
+    for i, item := range slice {
+        if item == val {
+            return i, true
+        }
+    }
+    return -1, false
+}
diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png
new file mode 100755
index 0000000..7f11d70
--- /dev/null
+++ b/LdapUsrEnum_Screenshot.png
Binary files differ
diff --git a/go.mod b/go.mod
new file mode 100755
index 0000000..3f09e41
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module main
+
+go 1.16
+
+require (
+	github.com/akamensky/argparse v1.3.1
+	github.com/go-ldap/ldap/v3 v3.3.0 // indirect
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
+	gopkg.in/ldap.v2 v2.5.1 // indirect
+)

diff --git a/LdapUsrEnum-linux-386 b/LdapUsrEnum-linux-386
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-386
Binary files differ
diff --git a/LdapUsrEnum-linux-amd64 b/LdapUsrEnum-linux-amd64
new file mode 100755
index 0000000..19fc46a
--- /dev/null
+++ b/LdapUsrEnum-linux-amd64
Binary files differ
diff --git a/LdapUsrEnum-windows-386.exe b/LdapUsrEnum-windows-386.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-386.exe
Binary files differ
diff --git a/LdapUsrEnum-windows-amd64.exe b/LdapUsrEnum-windows-amd64.exe
new file mode 100755
index 0000000..dd8e1ca
--- /dev/null
+++ b/LdapUsrEnum-windows-amd64.exe
Binary files differ
diff --git a/LdapUsrEnum.go b/LdapUsrEnum.go
new file mode 100755
index 0000000..d3f29ed
--- /dev/null
+++ b/LdapUsrEnum.go
@@ -0,0 +1,411 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"crypto/tls"
+	"strings"
+	"strconv"
+	"math"
+	"time"
+
+	"github.com/akamensky/argparse"
+	"github.com/go-ldap/ldap/v3"
+)
+
+func main() {
+
+	parser := argparse.NewParser("print", "AD/LDAP Account Brute-forcer ("+runtime.GOOS+") \n ex: ./progName -d <ip> -w <domain.com> --username=\"<username>\" --password=\"<password>\"")
+	dc := parser.String("d", "dc", &argparse.Options{Required: true, Help: "DC to connect to, use IP or full hostname ex. --dc=\"dc.test.local\""})
+	domain := parser.String("w", "domain", &argparse.Options{Required: true, Help: "domain ex. --domain=\"test.local\""})
+	username := parser.String("u", "username", &argparse.Options{Required: false, Help: "username to connect with ex. --username=\"testuser\""})
+	password := parser.String("p", "password", &argparse.Options{Required: false, Help: "password to connect with ex. --password=\"testpass!\""})
+	var dispGroup *bool = parser.Flag("g", "groups", &argparse.Options{Required: false, Help: "display user/group relationships. -g"})
+	//target := parser.String("t", "target", &argparse.Options{Required: true, Help: "username to bruteforce -target=\"testuser2\""})
+	err := parser.Parse(os.Args)
+	if err != nil {
+		fmt.Print(parser.Usage(err))
+		os.Exit(1)
+	}
+
+	if len(*dc) == 0 || len(*domain) == 0{
+		fmt.Print(parser.Usage(err))
+		fmt.Println("[-] Provide DC & domain minimum")
+		os.Exit(1)
+	}
+
+	fmt.Printf("[i] DC/AD: %v\n", *dc)
+	fmt.Printf("[i] domain: %v\n", *domain)
+	fmt.Println("[!] trying plaintext auth")
+	l, err := ldap.DialURL("ldap://" + *dc)
+	if err != nil {
+		fmt.Println(err)
+		fmt.Println("[!] trying TLS auth")
+		l.Close()
+		ldapURL := "ldaps://"+ *dc
+		l, err2 := ldap.DialURL(ldapURL, ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
+		if err2 != nil {
+	        fmt.Println(err2)
+	        fmt.Println("[-] could not connect")
+			os.Exit(1)
+		}
+		defer l.Close()
+	}
+	defer l.Close()
+
+	if len(*username) == 0{
+		*username = "read-only-admin"
+	}
+	dcParts := strings.Split(*domain, ".")
+	dcFmt := "CN="+*username
+	var baseDN string
+	for _, element := range dcParts {
+		baseDN += ",DC="+element
+	}
+	dcFmt += baseDN
+	baseDN = baseDN[1:]
+	fmt.Println("[+] using: "+dcFmt)
+
+	if len(*password) == 0{
+		fmt.Println("[!] trying to connect with unauth client")
+		err = l.UnauthenticatedBind(dcFmt)
+		if err != nil {
+		    fmt.Println("[-] unauth session failed - requires credentials")
+			os.Exit(1)
+		}
+	}else{
+		fmt.Println("[!] trying to connect with supplied password")
+		err = l.Bind(*username+"@"+*domain, *password)  // FFS this stumped me for SO LONG! >.<
+		if err != nil {
+		    fmt.Println(err)
+		    fmt.Println("[-] auth session failed - check credentials")
+			os.Exit(1)
+		}
+	}
+
+
+	// SEARCH FOR USERNAME'S "sAMAccountName" (basic test)
+	// Filters must start and finish with ()!
+	fmt.Print("[+] query to get account username... ")
+	var mainUser string = ""
+	filter := fmt.Sprintf("(CN=%s)", ldap.EscapeFilter(*username))
+	searchReq := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName","distinguishedName", "primaryGroupID"}, []ldap.Control{})
+	result, err := l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result.Entries) == 0{
+		fmt.Println("[-] user can't query LDAP")
+	    os.Exit(1)
+	}
+	for _, entry := range result.Entries {
+		mainUser = entry.GetAttributeValue("sAMAccountName") 
+	}
+	fmt.Println( mainUser )
+	//result.PrettyPrint(2)
+
+	// SEARCH FOR ALL UERNAME'S 
+	fmt.Print("[+] ALL usernames... ")
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName", "whenCreated", "whenChanged", "lastLogon",}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+	//result.PrettyPrint(2)
+
+	var foundUsers []string
+	var foundUCreated []string
+	var foundUChanged []string
+	var foundULogon []string
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		foundUsers = append(foundUsers, entry.GetAttributeValue("sAMAccountName") )
+		foundUCreated = append(foundUCreated, entry.GetAttributeValue("whenCreated") )
+		foundUChanged = append(foundUChanged, entry.GetAttributeValue("whenChanged") )
+		foundULogon = append(foundULogon, entry.GetAttributeValue("lastLogon") )
+	}
+
+	// SEARCH FOR ALL LOCKED OUT ACCOUNTS
+	fmt.Print("[+] locked accounts... ")
+	filter = fmt.Sprintf("(&(sAMAccountType=805306368)(lockoutTime>=1))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var lockedUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		lockedUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		//fmt.Printf("%s: %v\n", entry.GetAttributeValues("memberOf"), entry.GetAttributeValue("sAMAccountName"))
+		lockedUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL DISABLED ACCOUNTS
+	fmt.Print("[+] disabled accounts... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=2))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println(len(result.Entries))
+
+	var disabledUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		disabledUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		disabledUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+	// SEARCH FOR ALL PASSWORD NEVER EXPIRE
+	fmt.Print("[+] non-expire passwords... ")
+	filter = fmt.Sprintf("(&(samAccountType=805306368)(|(UserAccountControl:1.2.840.113556.1.4.803:=65536)(msDS-UserDontExpirePassword=TRUE)))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println( len(result.Entries))
+
+	var neverExpireUsers = map[string]int{}
+	for i := 0; i < len(foundUsers); i++ {
+		neverExpireUsers[foundUsers[i]] = 0
+	}
+	for _, entry := range result.Entries {
+		neverExpireUsers[entry.GetAttributeValue("sAMAccountName")] = 1
+	}
+
+   	var groups = map[string][]string{}
+
+	// SEARCH FOR ALL groups NOT "Default users" (rid 513)
+	fmt.Print("[+] groups... ")
+	filter = fmt.Sprintf("(&(objectCategory=group)(objectClass=group))")
+	//filter = fmt.Sprintf("(&(CN=\"Administrator\"))")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"objectCategory", "sAMAccountName", "distinguishedName"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	//result.PrettyPrint(2)
+
+	// for each group
+	fmt.Println( len(result.Entries))
+	fmt.Print("[+] Matching users to groups.. this could take a while!\n")
+	for _, entry := range result.Entries {
+
+		// Search for all users of that group
+		filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(SamAccountName=*)(memberOf:1.2.840.113556.1.4.1941:=%v))", strings.Trim(entry.GetAttributeValue("distinguishedName"), "\t \n" ))
+		searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+		result2, err := l.Search(searchReq2)
+		if err != nil {
+		        fmt.Println("[-] failed to query LDAP: %w", err)
+		        os.Exit(1)
+		}
+		if len(result2.Entries) != 0 {
+			for _, entry2 := range result2.Entries {			
+				groups[entry.GetAttributeValue("sAMAccountName")] = append(groups[entry.GetAttributeValue("sAMAccountName")], entry2.GetAttributeValue("sAMAccountName"))			
+			}
+		}
+		//os.Exit(1)
+	}
+
+	// All users of Default Group (RID 513)
+	filter = fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513))")
+	searchReq2 := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"sAMAccountName"}, []ldap.Control{})
+	result2, err := l.Search(searchReq2)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	if len(result2.Entries) != 0 {
+		for _, entry2 := range result2.Entries {			
+			groups["Default Group (RID 513)"] = append(groups["Default Group (RID 513)"], entry2.GetAttributeValue("sAMAccountName"))			
+		}
+	}
+
+
+	wantedUsers := []string{}
+	for x, _ := range groups { // for each group
+		addGroup := true
+		for i := range groups[x] {
+			if groups[x][i] == mainUser { // if user in group
+				addGroup = false // dont add these users to list
+			}
+		}
+		if addGroup == true{
+			for i := range groups[x] { // add the people to a list of accounts want to crack
+				_, found := Find(wantedUsers, groups[x][i])
+    			if !found {
+					wantedUsers = append(wantedUsers, groups[x][i] )
+				
+				}
+			}
+		}
+	}
+
+	if *dispGroup == true{ // display which users belong to which 
+		for x, _ := range groups {
+			fmt.Printf("[+] group: %v \n", x)
+			for i := range groups[x] {
+		        fmt.Println("[+]	", groups[x][i])
+		    }
+		}
+	}
+
+	// SEARCH FOR PASSWORD POLICY
+	fmt.Println("[+] password policy ")
+	filter = fmt.Sprintf("(objectClass=domainDNS)")
+	searchReq = ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"minPwdLength","minPwdAge","maxPwdAge","pwdHistoryLength","lockoutThreshold","lockoutDuration","lockOutObservationWindow"}, []ldap.Control{})
+	result, err = l.Search(searchReq)
+	if err != nil {
+	        fmt.Println("[-] failed to query LDAP: %w", err)
+	        os.Exit(1)
+	}
+	fmt.Println("--- pwd pol ---")
+	var foundGPO []string
+	for _, entry := range result.Entries {
+		foundGPO = []string{
+			entry.GetAttributeValue("minPwdLength"),
+			entry.GetAttributeValue("minPwdAge"),
+			convertPwdAge(entry.GetAttributeValue("maxPwdAge")),
+			entry.GetAttributeValue("pwdHistoryLength"),
+			entry.GetAttributeValue("lockoutThreshold"),
+			convertLockout(entry.GetAttributeValue("lockoutDuration")),
+			convertLockout(entry.GetAttributeValue("lockOutObservationWindow")),
+		}
+	}
+	fmt.Printf("minPwdLength: %v \n",foundGPO[0])
+	fmt.Printf("minPwdAge: %v \n",foundGPO[1])
+	fmt.Printf("maxPwdAge: %v \n",foundGPO[2])
+	fmt.Printf("pwdHistoryLength: %v \n",foundGPO[3])
+	fmt.Printf("lockoutThreshold: %v \n",foundGPO[4])
+	fmt.Printf("lockoutDuration: %v \n",foundGPO[5])
+	fmt.Printf("lockOutObservationWindow: %v \n",foundGPO[6])
+
+	// display results
+	fmt.Println("--- results ---")
+	for i := 0; i < len(foundUsers); i++ {
+		fmt.Printf("%-15v", foundUsers[i])
+		if lockedUsers[foundUsers[i]] == 1{ fmt.Print("Locked ")}
+		if disabledUsers[foundUsers[i]] == 1{ fmt.Print("Disabled ")}
+		if neverExpireUsers[foundUsers[i]] == 1{ fmt.Print("PassNevExp ")}
+		fmt.Print("\n")
+		lasLog := foundULogon[i]
+		if lasLog != "0" {
+			fmt.Printf("		%v ", convertLDAPDate(lasLog) )
+			fmt.Printf(" (%v)\n", ldapDiff(lasLog) )
+		}else{
+			fmt.Println("		 Never Logged In")
+		}
+	}
+	fmt.Println("---  to try (ALL) ---")
+	toTry := ""
+	for i := 0; i < len(foundUsers); i++ {
+		if lockedUsers[foundUsers[i]] == 0{ // account not locked
+			if disabledUsers[foundUsers[i]] == 0{ // account not disabled
+				toTry += ", "+foundUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+	fmt.Println("---  to try (Diff Group) ---")
+	toTry = ""
+	for i := 0; i < len(wantedUsers); i++ {
+		if lockedUsers[wantedUsers[i]] == 0{ // account not locked
+			if disabledUsers[wantedUsers[i]] == 0{ // account not disabled
+				toTry += ", "+wantedUsers[i]
+			}
+		}
+	}
+	toTry = toTry[2:] // remove first ","
+	fmt.Println(toTry)
+
+
+}
+
+func ldapDiff(timestamp string)string{
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t2 := time.Unix(date, 0)
+	t1 := time.Now()
+
+	diff := t1.Sub(t2)
+	t := time.Time{}.Add(diff)
+	// fmt.Println(diff)  // DEBUG
+	formatted := fmt.Sprintf("%d day(s) %02d:%02d:%02d",RoundTime(diff.Seconds()/86400),t.Hour(), t.Minute(), t.Second())
+	return formatted
+}
+func RoundTime(input float64) int {
+	var result float64
+	if input < 0 {
+	     result = math.Ceil(input - 0.5)
+	} else {
+	     result = math.Floor(input + 0.5)
+	}
+	// only interested in integer, ignore fractional
+	i, _ := math.Modf(result)
+
+	return int(i)
+}
+func convertLDAPDate(timestamp string)string{
+	//baseTime := time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
+	i, _ := strconv.ParseInt(timestamp, 10, 64)
+	date := ( i / 10000000 ) - 11644473600;
+	t := time.Unix(date, 0)
+	formatted := fmt.Sprintf(" %d-%02d-%02d %02d:%02d:%02d",
+        t.Year(), t.Month(), t.Day(),
+        t.Hour(), t.Minute(), t.Second())
+    return formatted
+}
+func convertPwdAge(pwdage string) string {
+	f, _ := strconv.ParseFloat((strings.Replace(pwdage, "-", "", -1)), 64)
+	age := ((f / (60 * 10000000)) / 60) / 24
+	flr := math.Floor(age)
+	s := strconv.Itoa(int(flr))
+
+	return s
+}
+func convertLockout(lockout string) string {
+	i, _ := strconv.Atoi(strings.Replace(lockout, "-", "", -1))
+	age := i / (60 * 10000000)
+	s := strconv.Itoa(age)
+
+	return s
+}
+func after(value string, a string) string {
+    // Get substring after a string.
+    pos := strings.LastIndex(value, a)
+    if pos == -1 {
+        return ""
+    }
+    adjustedPos := pos + len(a)
+    if adjustedPos >= len(value) {
+        return ""
+    }
+    return value[adjustedPos:len(value)]
+}
+
+func Find(slice []string, val string) (int, bool) {
+    for i, item := range slice {
+        if item == val {
+            return i, true
+        }
+    }
+    return -1, false
+}
diff --git a/LdapUsrEnum_Screenshot.png b/LdapUsrEnum_Screenshot.png
new file mode 100755
index 0000000..7f11d70
--- /dev/null
+++ b/LdapUsrEnum_Screenshot.png
Binary files differ
diff --git a/go.mod b/go.mod
new file mode 100755
index 0000000..3f09e41
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module main
+
+go 1.16
+
+require (
+	github.com/akamensky/argparse v1.3.1
+	github.com/go-ldap/ldap/v3 v3.3.0 // indirect
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
+	gopkg.in/ldap.v2 v2.5.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100755
index 0000000..e5f7c9a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,19 @@
+github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
+github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
+github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g=
+github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
+github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
+github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
+github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
+github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
+golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
+gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
+gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=