package main import ( "errors" "fmt" "github.com/go-ldap/ldap/v3" log "github.com/sirupsen/logrus" "os" "strings" ) const ( BaseDN = "dc=ad,dc=example,dc=com" URL = "ldaps://ldap.example.com" ORGFILTER = "&(objectCategory=user)(objectClass=user)(!(useraccountcontrol:1.2.840.113556.1.4.803:=2))" // exclude disabled accts ENABLEDORGFILTER = "&(objectCategory=user)(objectClass=user)" // include active and inactive accts GROUPORGFILTER = "&(objectCategory=user)(objectClass=person)" ATTRIBUTES = "samaccountname givenName sn mail title department l co" AUTH = "username" ) func doSearch(auth, filter string, at *[]string, debug bool) (string, error) { attr := *at nconn, err := ldap.DialURL(URL) if err != nil { log.Errorf("ldap connection error: %s\n",err) return "",err } defer nconn.Close() //throws connection close errors, segmentation fault nconn.Debug = false if debug { nconn.Debug = true } if len(auth) == 0 { log.Errorf("authentication required. Make sure you have PASSWORD properly setup for %s\n", AUTH) return "", errors.New("Authentication required. PASSWORD expected.") } err = nconn.Bind(AUTH, auth) if err != nil { log.Error(err) return "", err } nconn.Start() fmt.Printf("doSearch:\n\tfilter: %s\n\tattribs: %v\n",filter, attr) var pageSize uint32 = 256 preq := ldap.SearchRequest{ BaseDN: BaseDN, Scope: ldap.ScopeWholeSubtree, DerefAliases: ldap.NeverDerefAliases, Filter: filter, Attributes: attr, Controls: nil, } resp, err := nconn.SearchWithPaging(&preq, pageSize) if err != nil { log.Errorf("ldap search error in doSearch: %s\n", err) return "", err } resp.PrettyPrint(3) fmt.Printf("with paging.... done!\n") return "", nil } func formatFilter (filter string) string { filterElems := strings.Split(filter,",") var newfilter string for _, f := range filterElems { if len(f) == 0 { continue } newfilter += fmt.Sprintf("(%s)", f) } return newfilter } func getCreds() string { cred := os.Getenv("PASSWORD") ldapauth := "" if len(cred) > 0 { ldapauth = fmt.Sprintf("%s",cred) } return ldapauth } func getFilter(filter string, enable bool, isgroup bool) string { theFilter := filter orgFilter := ORGFILTER if isgroup { orgFilter = GROUPORGFILTER theFilter = fmt.Sprintf("(memberof=CN=%s,OU=Application,OU=Groups,DC=ad,DC=example,DC=com)", theFilter) } else { if theFilter == "-ALL-" { enable = true theFilter = "" } if enable { orgFilter = ENABLEDORGFILTER } theFilter = formatFilter(theFilter) } return fmt.Sprintf("(%s%s)",orgFilter,theFilter) } func getAttribs(attr []string, showgroup, group bool) *[]string { theAttr := ATTRIBUTES var attribs []string if group { theAttr = "member" } else { if len(attr) > 0 { theAttr = fmt.Sprintf("%s %s", theAttr, strings.Join(attr, " ")) } if showgroup { //theAttr += fmt.Sprint(" memberOf") //adding to default list throws 'out of range' error theAttr = "memberOf" } } attribs = strings.Split(theAttr, " ") return &attribs } func main() { var meta Meta var filter string var attr []string var enable, showgroup, isgroup, debug bool var args = os.Args[1:] flags := meta.FlagSet("do search") flags.Usage = func() {fmt.Println(Help())} flags.StringVar(&filter, "filter", "","comma separated list of attributes to search for; special value -ALL- prints out entire AD user list. Required") flags.BoolVar(&enable, "enable", false, "enable include both active and inactive accounts") flags.BoolVar(&showgroup, "showgroups", false, "display group membership in output") flags.BoolVar(&isgroup, "group", false, "search for group membership, instead") flags.Var((*ArrayFlags)(&attr), "attr", "additional attributes to include in output") flags.BoolVar(&debug, "debug", false, "enable debugging (verbose)") if debug { log.SetLevel(log.DebugLevel) } if err := flags.Parse(args); err != nil { log.Error(err) os.Exit(-1) } _, err := doSearch(getCreds(), getFilter(filter, enable, isgroup), getAttribs(attr, showgroup, isgroup), debug) if err != nil { log.Error(err) } }