diff options
-rw-r--r-- | pamldapd.go | 194 |
1 files changed, 158 insertions, 36 deletions
diff --git a/pamldapd.go b/pamldapd.go index af1d36a..e836a1c 100644 --- a/pamldapd.go +++ b/pamldapd.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/msteinert/pam" + "github.com/nmcclain/asn1-ber" "github.com/nmcclain/ldap" "log" "net" @@ -16,9 +17,14 @@ type Backend struct { ldap.Binder ldap.Searcher ldap.Closer - logger *log.Logger - BaseDN string - PAMServiceName string + logger *log.Logger + Listen string + BaseDN string + PAMServiceName string + PeopleDN string + GroupsDN string + BindAdminDN string + BindAdminPassword string } var logger = log.New(os.Stdout, "", log.LstdFlags) @@ -27,51 +33,85 @@ func main() { logger.Println("start") l := ldap.NewServer() l.EnforceLDAP = true - var handler = Backend{ - PAMServiceName: "password-auth", - logger: logger, - BaseDN: "dc=example,dc=com", + var backend = Backend{ + PAMServiceName: "password-auth", + logger: logger, + Listen: "127.0.0.1:10389", + BaseDN: "dc=example,dc=com", + PeopleDN: "ou=people,dc=example,dc=com", + GroupsDN: "ou=groups,dc=example,dc=com", + BindAdminDN: "uid=user,dc=example,dc=com", + BindAdminPassword: "password", } - l.BindFunc("", handler) - l.SearchFunc("", handler) - l.CloseFunc("", handler) - if err := l.ListenAndServe("0.0.0.0:10893"); err != nil { - logger.Fatalf("LDAP serve failed: %s", err.Error()) + l.BindFunc("", backend) + l.SearchFunc("", backend) + l.CloseFunc("", backend) + if err := l.ListenAndServe(backend.Listen); err != nil { + backend.logger.Fatalf("LDAP serve failed: %s", err.Error()) } } func (b Backend) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) { b.logger.Printf("Bind attempt addr=%s bindDN=%s", conn.RemoteAddr().String(), bindDN) - var username string - if username, err = b.getUserNameFromBindDN(bindDN); err != nil { - return ldap.LDAPResultInvalidCredentials, err - } - if err := PAMAuth(b.PAMServiceName, username, bindSimplePw); err != nil { - return ldap.LDAPResultInvalidCredentials, err + if bindDN == b.BindAdminDN { + if bindSimplePw != b.BindAdminPassword { + return ldap.LDAPResultInvalidCredentials, errors.New("Password Incorrect") + } + return ldap.LDAPResultSuccess, nil + } else { + var username string + if username, err = b.getUserNameFromBindDN(bindDN); err != nil { + return ldap.LDAPResultInvalidCredentials, err + } + if err := PAMAuth(b.PAMServiceName, username, bindSimplePw); err != nil { + return ldap.LDAPResultInvalidCredentials, err + } + return ldap.LDAPResultSuccess, nil } - return ldap.LDAPResultSuccess, nil } func (b Backend) Search(bindDN string, req ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) { b.logger.Printf("Search bindDN=%s baseDN=%s filter=%s addr=%s", bindDN, req.BaseDN, req.Filter, conn.RemoteAddr().String()) + filterObjectClass, err := ldap.GetFilterObjectClass(req.Filter) + if err != nil { + b.logger.Printf("Search Error: error parsing ObjectClass: %s", req.Filter) + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing ObjectClass: %s", req.Filter) + } var username string - if username, err = b.getUserNameFromBindDN(bindDN); err != nil { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err + var user_entity_name string + if filterObjectClass == "posixaccount" || filterObjectClass == "" { + user_entity_name = "uid" + } else if filterObjectClass == "posixgroup" { + user_entity_name = "memberUid" + } else { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Filter does not contain objectclass=posixaccount nor objectclass=posixgroup") } - filterEntity, err := ldap.GetFilterObjectClass(req.Filter) - if err != nil { - b.logger.Printf("Search Error: error parsing filter: %s", req.Filter) - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error parsing filter: %s", req.Filter) + + if bindDN == b.BindAdminDN { + filterUid, err := GetFilterEntity(user_entity_name, req.Filter) + if err != nil { + b.logger.Printf("Search Error: error find condition uid: %s", req.Filter) + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: error find condition uid: %s", req.Filter) + } + username = filterUid + } else { + if username, err = b.getUserNameFromBindDN(bindDN); err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err + } } - if filterEntity == "posixaccount" || filterEntity == "" { - var entry *ldap.Entry - if entry, err = b.makeSearchEntry(bindDN, username); err != nil { + var entry *ldap.Entry + if filterObjectClass == "posixaccount" || filterObjectClass == "" { + if entry, err = b.makeSearchEntryAccount("cn="+username+","+b.PeopleDN, username); err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err + } + } else if filterObjectClass == "posixgroup" { + if entry, err = b.makeSearchEntryGroup(b.GroupsDN, username); err != nil { return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err } - return ldap.ServerSearchResult{[]*ldap.Entry{entry}, []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil } else { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("filter entity could be: posixaccount") + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Filter does not contain objectclass=posixaccount nor objectclass=posixgroup") } + return ldap.ServerSearchResult{[]*ldap.Entry{entry}, []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil return ldap.ServerSearchResult{make([]*ldap.Entry, 0), []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil } @@ -107,24 +147,27 @@ func (b Backend) getUserNameFromBindDN(bindDN string) (username string, err erro if bindDN == "" { return "", errors.New("bindDN not specified") } - if !strings.HasSuffix(bindDN, ","+b.BaseDN) { + if !strings.HasSuffix(bindDN, ","+b.PeopleDN) { return "", errors.New("bindDN not matched") } - rest := strings.TrimSuffix(bindDN, ","+b.BaseDN) + rest := strings.TrimSuffix(bindDN, ","+b.PeopleDN) if rest == "" { return "", errors.New("bindDN format error") } if strings.Contains(rest, ",") { return "", errors.New("bindDN has too much entities") } - if !strings.HasPrefix(rest, "uid=") { - return "", errors.New("bindDN contains no uid entry") + if strings.HasPrefix(rest, "uid=") { + username = strings.TrimPrefix(rest, "uid=") + } else if strings.HasPrefix(rest, "cn=") { + username = strings.TrimPrefix(rest, "cn=") + } else { + return "", errors.New("bindDN contains no cn/uid entry") } - username = strings.TrimPrefix(rest, "uid=") return username, nil } -func (b Backend) makeSearchEntry(dn string, username string) (entry *ldap.Entry, err error) { +func (b Backend) makeSearchEntryAccount(dn string, username string) (entry *ldap.Entry, err error) { attrs := []*ldap.EntryAttribute{} var u *user.User if u, err = user.Lookup(username); err != nil { @@ -141,3 +184,82 @@ func (b Backend) makeSearchEntry(dn string, username string) (entry *ldap.Entry, entry = &ldap.Entry{dn, attrs} return entry, nil } + +func (b Backend) makeSearchEntryGroup(basedn string, username string) (entry *ldap.Entry, err error) { + attrs := []*ldap.EntryAttribute{} + var ( + u *user.User + g *user.Group + ) + if u, err = user.Lookup(username); err != nil { + return entry, err + } + if g, err = user.LookupGroupId(u.Gid); err != nil { + return entry, err + } + + attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixGroup"}}) + attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{g.Name}}) + attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{u.Gid}}) + attrs = append(attrs, &ldap.EntryAttribute{"memberUid", []string{username}}) + + dn := "cn=" + g.Name + "," + basedn + entry = &ldap.Entry{dn, attrs} + return entry, nil +} + +func GetFilterEntity(entity string, filter string) (string, error) { + f, err := ldap.CompileFilter(filter) + if err != nil { + return "", err + } + return parseFilterEntity(entity, f) +} + +func parseFilterEntity(entity string, f *ber.Packet) (string, error) { + foundEntity := "" + switch ldap.FilterMap[f.Tag] { + case "Equality Match": + if len(f.Children) != 2 { + return "", errors.New("Equality match must have only two children") + } + attribute := strings.ToLower(f.Children[0].Value.(string)) + value := f.Children[1].Value.(string) + if attribute == entity { + foundEntity = strings.ToLower(value) + } + case "And": + for _, child := range f.Children { + subType, err := parseFilterEntity(entity, child) + if err != nil { + return "", err + } + if len(subType) > 0 { + foundEntity = subType + } + } + case "Or": + for _, child := range f.Children { + subType, err := parseFilterEntity(entity, child) + if err != nil { + return "", err + } + if len(subType) > 0 { + foundEntity = subType + } + } + case "Not": + if len(f.Children) != 1 { + return "", errors.New("Not filter must have only one child") + } + subType, err := parseFilterEntity(entity, f.Children[0]) + if err != nil { + return "", err + } + if len(subType) > 0 { + foundEntity = subType + } + + } + return strings.ToLower(foundEntity), nil +} |