|
package main |
|
|
|
import ( |
|
"crypto/tls" |
|
"encoding/json" |
|
"fmt" |
|
"github.com/spf13/pflag" |
|
"gopkg.in/ldap.v2" |
|
"os" |
|
"strings" |
|
) |
|
|
|
var ( |
|
searchBase string |
|
bindPassword string |
|
bindUser string |
|
ldapAddress string |
|
ldapTLS bool |
|
searchScope string |
|
searchQuery string |
|
pageSize = uint32(500) |
|
|
|
scopes = map[string]int{ |
|
"base": ldap.ScopeBaseObject, |
|
"one": ldap.ScopeSingleLevel, |
|
"sub": ldap.ScopeWholeSubtree, |
|
} |
|
) |
|
|
|
func connect() (conn *ldap.Conn, err error) { |
|
if ldapTLS { |
|
conn, err = ldap.DialTLS("tcp", ldapAddress, &tls.Config{ |
|
InsecureSkipVerify: true, |
|
}) |
|
} else { |
|
conn, err = ldap.Dial("tcp", ldapAddress) |
|
} |
|
if err != nil { |
|
return |
|
} |
|
if err = conn.Bind(bindUser, bindPassword); err != nil { |
|
defer conn.Close() |
|
return |
|
} |
|
return |
|
} |
|
|
|
func warn(msg string, args ...interface{}) { |
|
if !strings.HasSuffix(msg, "\n") { |
|
msg += "\n" |
|
} |
|
fmt.Fprintf(os.Stderr, msg, args...) |
|
} |
|
|
|
func die(msg string, args ...interface{}) { |
|
warn(msg, args...) |
|
os.Exit(1) |
|
} |
|
|
|
func transformAttribs(attribs []*ldap.EntryAttribute) map[string]interface{} { |
|
res := make(map[string]interface{}) |
|
for _, attrib := range attribs { |
|
switch len(attrib.Values) { |
|
case 0: |
|
res[attrib.Name] = nil |
|
case 1: |
|
res[attrib.Name] = attrib.Values[0] |
|
default: |
|
res[attrib.Name] = attrib.Values |
|
} |
|
} |
|
return res |
|
} |
|
|
|
func transform(entries []*ldap.Entry) map[string]map[string]interface{} { |
|
res := make(map[string]map[string]interface{}) |
|
for _, entry := range entries { |
|
res[entry.DN] = transformAttribs(entry.Attributes) |
|
} |
|
return res |
|
} |
|
|
|
func main() { |
|
pflag.StringVarP(&searchBase, "search-base", "b", "", "Use searchbase as the starting point for the search instead of the default") |
|
pflag.StringVarP(&bindPassword, "passwd", "w", "", "Use passwd as the password for simple authentication") |
|
pflag.StringVarP(&bindUser, "binddn", "D", "", "Use the Distinguished Name binddn to bind to the LDAP directory") |
|
pflag.StringVarP(&ldapAddress, "address", "a", "localhost:389", "Specify an alternate address on which the ldap server is running") |
|
pflag.StringVarP(&searchScope, "scope", "s", "one", "Specify the scope of the search to be one of base, one, sub, or children") |
|
pflag.BoolVarP(&ldapTLS, "tls", "Z", false, "Issue StartTLS (Transport Layer Security) extended operation") |
|
pflag.Uint32VarP(&pageSize, "page-size", "P", pageSize, "internally used page size for requests") |
|
pflag.Parse() |
|
if pflag.NArg() == 0 { |
|
fmt.Printf("Missing LDAP query") |
|
} |
|
searchQuery = strings.Join(pflag.Args(), " ") |
|
|
|
conn, err := connect() |
|
if err != nil { |
|
die("Failed to connect: %s", err) |
|
} |
|
defer conn.Close() |
|
|
|
scope, ok := scopes[searchScope] |
|
if !ok { |
|
die("Scope \"%s\" undefined", searchScope) |
|
} |
|
|
|
res, err := conn.SearchWithPaging(&ldap.SearchRequest{ |
|
BaseDN: searchBase, |
|
Scope: scope, |
|
Filter: searchQuery, |
|
}, pageSize) |
|
|
|
if err != nil { |
|
die("Failed to search: %s\n", err) |
|
} |
|
|
|
warn("# Found %d entries\n", len(res.Entries)) |
|
raw, err := json.MarshalIndent(transform(res.Entries), "", " ") |
|
if err != nil { |
|
die("Could not marshal to JSON: %s", err) |
|
} |
|
fmt.Println(string(raw)) |
|
} |