summaryrefslogtreecommitdiffstats
path: root/pamldapd.go
diff options
context:
space:
mode:
Diffstat (limited to 'pamldapd.go')
-rw-r--r--pamldapd.go194
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
+}