diff options
Diffstat (limited to '')
-rw-r--r-- | pamldapd.go | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/pamldapd.go b/pamldapd.go new file mode 100644 index 0000000..af1d36a --- /dev/null +++ b/pamldapd.go @@ -0,0 +1,143 @@ +package main + +import ( + "errors" + "fmt" + "github.com/msteinert/pam" + "github.com/nmcclain/ldap" + "log" + "net" + "os" + "os/user" + "strings" +) + +type Backend struct { + ldap.Binder + ldap.Searcher + ldap.Closer + logger *log.Logger + BaseDN string + PAMServiceName string +} + +var logger = log.New(os.Stdout, "", log.LstdFlags) + +func main() { + logger.Println("start") + l := ldap.NewServer() + l.EnforceLDAP = true + var handler = Backend{ + PAMServiceName: "password-auth", + logger: logger, + BaseDN: "dc=example,dc=com", + } + 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()) + } +} + +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 + } + 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()) + var username string + if username, err = b.getUserNameFromBindDN(bindDN); err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err + } + 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 filterEntity == "posixaccount" || filterEntity == "" { + var entry *ldap.Entry + if entry, err = b.makeSearchEntry(bindDN, 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{make([]*ldap.Entry, 0), []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, nil +} + +func (b Backend) Close(bindDN string, conn net.Conn) (err error) { + b.logger.Printf("Close addr=%s bindDN=%s", conn.RemoteAddr().String(), bindDN) + return nil +} + +func PAMAuth(serviceName, userName, passwd string) error { + t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) { + switch s { + case pam.PromptEchoOff: + return passwd, nil + case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo: + return "", nil + } + return "", errors.New("Unrecognized PAM message style") + }) + + if err != nil { + return err + } + + if err = t.Authenticate(0); err != nil { + return err + } + + return nil +} + +func (b Backend) getUserNameFromBindDN(bindDN string) (username string, err error) { + if bindDN == "" { + return "", errors.New("bindDN not specified") + } + if !strings.HasSuffix(bindDN, ","+b.BaseDN) { + return "", errors.New("bindDN not matched") + } + rest := strings.TrimSuffix(bindDN, ","+b.BaseDN) + 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") + } + username = strings.TrimPrefix(rest, "uid=") + return username, nil +} + +func (b Backend) makeSearchEntry(dn string, username string) (entry *ldap.Entry, err error) { + attrs := []*ldap.EntryAttribute{} + var u *user.User + if u, err = user.Lookup(username); err != nil { + return entry, err + } + attrs = append(attrs, &ldap.EntryAttribute{"objectClass", []string{"posixAccount"}}) + attrs = append(attrs, &ldap.EntryAttribute{"cn", []string{username}}) + attrs = append(attrs, &ldap.EntryAttribute{"uid", []string{username}}) + attrs = append(attrs, &ldap.EntryAttribute{"uidNumber", []string{u.Uid}}) + attrs = append(attrs, &ldap.EntryAttribute{"givenName", []string{u.Name}}) + attrs = append(attrs, &ldap.EntryAttribute{"gidNumber", []string{u.Gid}}) + attrs = append(attrs, &ldap.EntryAttribute{"homeDirectory", []string{u.HomeDir}}) + + entry = &ldap.Entry{dn, attrs} + return entry, nil +} |