From d2379c77684ac9050e477e2b3ed95928cc83d334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 4 Jun 2024 00:03:33 +0200 Subject: fork --- Dockerfile_i386 | 11 -- Dockerfile_x86-64 | 11 -- Makefile | 18 --- README.adoc | 33 +----- go.mod | 9 ++ go.sum | 10 ++ main.go | 328 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pamldapd.go | 328 ------------------------------------------------------ 8 files changed, 350 insertions(+), 398 deletions(-) delete mode 100644 Dockerfile_i386 delete mode 100644 Dockerfile_x86-64 delete mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go delete mode 100644 src/pamldapd.go diff --git a/Dockerfile_i386 b/Dockerfile_i386 deleted file mode 100644 index 101135a..0000000 --- a/Dockerfile_i386 +++ /dev/null @@ -1,11 +0,0 @@ -FROM i386/centos:7 -ENV PATH $PATH:/usr/local/go/bin -RUN linux32 sh -c 'yum install -y gcc git pam-devel \ - && curl -o go1.10.3.linux-386.tar.gz https://dl.google.com/go/go1.10.3.linux-386.tar.gz \ - && tar -C /usr/local -xzf go1.10.3.linux-386.tar.gz \ - && go get github.com/msteinert/pam \ - && go get github.com/nmcclain/asn1-ber \ - && go get github.com/nmcclain/ldap' -COPY src /root/go/src -RUN linux32 sh -c 'cd /root/go/src \ - && env GOOS=linux GOARCH=386 go build -a -ldflags "-extldflags \"-Wl,--hash-style=both\"" -x pamldapd.go' diff --git a/Dockerfile_x86-64 b/Dockerfile_x86-64 deleted file mode 100644 index 0a1902d..0000000 --- a/Dockerfile_x86-64 +++ /dev/null @@ -1,11 +0,0 @@ -FROM centos:7 -ENV PATH $PATH:/usr/local/go/bin -RUN yum install -y gcc git pam-devel \ - && curl -o go1.10.3.linux-amd64.tar.gz https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz \ - && tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz \ - && go get github.com/msteinert/pam \ - && go get github.com/nmcclain/asn1-ber \ - && go get github.com/nmcclain/ldap -COPY src /root/go/src -RUN cd /root/go/src \ - && go build -a pamldapd.go diff --git a/Makefile b/Makefile deleted file mode 100644 index 0093c01..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -.DEFAULT_GOAL := default - -all: pamldapd-x86-64 pamldapd-i386 -default: pamldapd-x86-64 -pamldapd-x86-64: x86-64.build Dockerfile_x86-64 -pamldapd-i386: i386.build Dockerfile_i386 -%.build: src/pamldapd.go - @echo BUILD ARCH $(shell basename $@ .build) - docker build -t pamldapd-build-$(shell basename $@ .build)-tmp -f Dockerfile_$(shell basename $@ .build) . - docker run --name pamldapd-build-$(shell basename $@ .build)-tmp pamldapd-build-$(shell basename $@ .build)-tmp - docker wait pamldapd-build-$(shell basename $@ .build)-tmp - docker cp pamldapd-build-$(shell basename $@ .build)-tmp:/root/go/src/pamldapd pamldapd-$(shell basename $@ .build) - docker rm pamldapd-build-$(shell basename $@ .build)-tmp - -clean: x86-64.clean i386.clean -%.clean: - @echo CLEAN ARCH $(shell basename $@ .build) - docker rmi pamldapd-build-$(shell basename $@ .build)-tmp || true diff --git a/README.adoc b/README.adoc index 53ed9fa..ab692d8 100644 --- a/README.adoc +++ b/README.adoc @@ -2,22 +2,6 @@ ## Getting Started -### Requirements - -This guide is based on Amazon Linux - -. Check requirements is installed - - $ rpm -q git make docker - -. Check the Docker works without `sudo` - - $ docker ps - -. Check the free disk space (at least 2GB-3GB needed) - - $ df -h - ### Download and Build . Clone a repository @@ -25,18 +9,7 @@ This guide is based on Amazon Linux $ git clone https://github.com/eisin/pamldapd $ cd pamldapd -. Build using Docker - - build only x86-64: - $ make - - build only i386: - $ make i386 - - build binaries both x86-64 and i386: - $ make all - -. (Build without docker) +. Build $ yum install -y gcc golang pam-devel $ go get github.com/msteinert/pam @@ -68,11 +41,11 @@ While pamldapd uses PAM authentication, root privilege is required. Start using configuration file, puts messages to STDOUT - $ sudo pamldapd -c pamldapd.json + # pamldapd -c pamldapd.json Start using configuration file, puts messages to a log file - $ sudo pamldapd -c pamldapd.json -l /var/log/pamldapd.log + # pamldapd -c pamldapd.json -l /var/log/pamldapd.log ## Configuration diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd05de7 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module 4a.si/pamldapd + +go 1.22.3 + +require ( + github.com/msteinert/pam v1.2.0 + github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 + github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..338c0ec --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= +github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= +github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s= +github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA= +github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba h1:DO8NFYdcRv1dnyAINJIBm6Bw2XibtLvQniNFGzf2W8E= +github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba/go.mod h1:4S0XndRL8HNOaQBfdViJ2F/GPCgL524xlXRuXFH12/U= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9685b3e --- /dev/null +++ b/main.go @@ -0,0 +1,328 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "github.com/msteinert/pam" + "github.com/nmcclain/asn1-ber" + "github.com/nmcclain/ldap" + "log" + "net" + "os" + "os/user" + "strings" +) + +type Backend struct { + ldap.Binder + ldap.Searcher + ldap.Closer + logger *log.Logger + Listen string + PAMServiceName string + PeopleDN string + GroupsDN string + BindAdminDN string + BindAdminPassword string +} + +func main() { + var configfile = flag.String("c", "pamldapd.json", "Configuration file") + var logfile = flag.String("l", "", "Log file (STDOUT if blank)") + flag.Parse() + var backend = Backend{} + { + confighandle, err := os.Open(*configfile) + if err != nil { + fmt.Printf("Could not read: %s\n", err) + os.Exit(1) + } + decoder := json.NewDecoder(confighandle) + if err := decoder.Decode(&backend); err != nil { + fmt.Printf("Could not decode configuration configfile %s: %s\n", *configfile, err) + confighandle.Close() + os.Exit(1) + } + confighandle.Close() + } + if *logfile == "" { + backend.logger = log.New(os.Stdout, "", log.LstdFlags) + } else { + loghandle, err := os.OpenFile(*logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + fmt.Printf("Could not open log file: %s\n", err) + os.Exit(1) + } + defer loghandle.Close() + log.SetOutput(loghandle) + backend.logger = log.New(loghandle, "", log.LstdFlags) + } + + current_user, err := user.Current() + if err != nil { + fmt.Printf("Could not get current user: %s\n", err) + os.Exit(1) + } + if current_user.Uid != "0" { + backend.logger.Printf("WARNING: PAM authentication will fail because not running as root user") + } + + l := ldap.NewServer() + l.EnforceLDAP = true + l.BindFunc("", backend) + l.SearchFunc("", backend) + l.CloseFunc("", backend) + backend.logger.Printf("LDAP server listen: %s", backend.Listen) + if err := l.ListenAndServe(backend.Listen); err != nil { + backend.logger.Printf("LDAP server listen failed: %s", err.Error()) + os.Exit(1) + } +} + +func (b Backend) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) { + var logger_title = fmt.Sprintf("Bind addr=%s bindDN=%s", conn.RemoteAddr().String(), bindDN) + b.logger.Printf("%s begin", logger_title) + if bindDN == b.BindAdminDN { + if bindSimplePw != b.BindAdminPassword { + return ldap.LDAPResultInvalidCredentials, errors.New("Password Incorrect") + } + b.logger.Printf("%s success as administrator", logger_title) + 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 + } + b.logger.Printf("%s success as normal user", logger_title) + return ldap.LDAPResultSuccess, nil + } +} + +func (b Backend) Search(bindDN string, req ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) { + var logger_title = fmt.Sprintf("Search bindDN=%s baseDN=%s filter=%s addr=%s", bindDN, req.BaseDN, req.Filter, conn.RemoteAddr().String()) + b.logger.Printf("%s begin", logger_title) + filterObjectClass, err := ldap.GetFilterObjectClass(req.Filter) + if err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error parsing ObjectClass: %s", logger_title, req.Filter) + } + var username string + 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("%s error: Filter does not contain objectclass=posixaccount nor objectclass=posixgroup", logger_title) + } + + if bindDN == b.BindAdminDN { + filterUid, err := GetFilterEntity(user_entity_name, req.Filter) + if err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error find condition uid: %s", logger_title, req.Filter) + } + if binddn_username, err := b.getUserNameFromBaseDN(req.BaseDN); err == nil { + username = binddn_username + } else { + username = filterUid + } + } else { + if username, err = b.getUserNameFromBindDN(bindDN); err != nil { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err + } + } + if req.BaseDN == "" { + return ldap.ServerSearchResult{make([]*ldap.Entry, 0), []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, 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 + } + } else { + return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error: Filter does not contain objectclass=posixaccount nor objectclass=posixgroup", logger_title) + } + 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 +} + +func (b Backend) Close(bindDN string, conn net.Conn) (err error) { + b.logger.Printf("Close addr=%s", conn.RemoteAddr().String()) + 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.PeopleDN) { + return "", errors.New("bindDN not matched") + } + 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=") { + 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") + } + return username, nil +} + +func (b Backend) getUserNameFromBaseDN(baseDN string) (username string, err error) { + if baseDN == "" { + return "", errors.New("baseDN not specified") + } + if !strings.HasSuffix(baseDN, ","+b.PeopleDN) { + return "", errors.New("baseDN not matched") + } + rest := strings.TrimSuffix(baseDN, ","+b.PeopleDN) + if rest == "" { + return "", errors.New("baseDN format error") + } + if strings.Contains(rest, ",") { + return "", errors.New("baseDN has too much entities") + } + 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("baseDN contains no cn/uid entry") + } + return username, nil +} + +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 { + 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 +} + +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 +} diff --git a/src/pamldapd.go b/src/pamldapd.go deleted file mode 100644 index 9685b3e..0000000 --- a/src/pamldapd.go +++ /dev/null @@ -1,328 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "flag" - "fmt" - "github.com/msteinert/pam" - "github.com/nmcclain/asn1-ber" - "github.com/nmcclain/ldap" - "log" - "net" - "os" - "os/user" - "strings" -) - -type Backend struct { - ldap.Binder - ldap.Searcher - ldap.Closer - logger *log.Logger - Listen string - PAMServiceName string - PeopleDN string - GroupsDN string - BindAdminDN string - BindAdminPassword string -} - -func main() { - var configfile = flag.String("c", "pamldapd.json", "Configuration file") - var logfile = flag.String("l", "", "Log file (STDOUT if blank)") - flag.Parse() - var backend = Backend{} - { - confighandle, err := os.Open(*configfile) - if err != nil { - fmt.Printf("Could not read: %s\n", err) - os.Exit(1) - } - decoder := json.NewDecoder(confighandle) - if err := decoder.Decode(&backend); err != nil { - fmt.Printf("Could not decode configuration configfile %s: %s\n", *configfile, err) - confighandle.Close() - os.Exit(1) - } - confighandle.Close() - } - if *logfile == "" { - backend.logger = log.New(os.Stdout, "", log.LstdFlags) - } else { - loghandle, err := os.OpenFile(*logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - if err != nil { - fmt.Printf("Could not open log file: %s\n", err) - os.Exit(1) - } - defer loghandle.Close() - log.SetOutput(loghandle) - backend.logger = log.New(loghandle, "", log.LstdFlags) - } - - current_user, err := user.Current() - if err != nil { - fmt.Printf("Could not get current user: %s\n", err) - os.Exit(1) - } - if current_user.Uid != "0" { - backend.logger.Printf("WARNING: PAM authentication will fail because not running as root user") - } - - l := ldap.NewServer() - l.EnforceLDAP = true - l.BindFunc("", backend) - l.SearchFunc("", backend) - l.CloseFunc("", backend) - backend.logger.Printf("LDAP server listen: %s", backend.Listen) - if err := l.ListenAndServe(backend.Listen); err != nil { - backend.logger.Printf("LDAP server listen failed: %s", err.Error()) - os.Exit(1) - } -} - -func (b Backend) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) { - var logger_title = fmt.Sprintf("Bind addr=%s bindDN=%s", conn.RemoteAddr().String(), bindDN) - b.logger.Printf("%s begin", logger_title) - if bindDN == b.BindAdminDN { - if bindSimplePw != b.BindAdminPassword { - return ldap.LDAPResultInvalidCredentials, errors.New("Password Incorrect") - } - b.logger.Printf("%s success as administrator", logger_title) - 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 - } - b.logger.Printf("%s success as normal user", logger_title) - return ldap.LDAPResultSuccess, nil - } -} - -func (b Backend) Search(bindDN string, req ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) { - var logger_title = fmt.Sprintf("Search bindDN=%s baseDN=%s filter=%s addr=%s", bindDN, req.BaseDN, req.Filter, conn.RemoteAddr().String()) - b.logger.Printf("%s begin", logger_title) - filterObjectClass, err := ldap.GetFilterObjectClass(req.Filter) - if err != nil { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error parsing ObjectClass: %s", logger_title, req.Filter) - } - var username string - 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("%s error: Filter does not contain objectclass=posixaccount nor objectclass=posixgroup", logger_title) - } - - if bindDN == b.BindAdminDN { - filterUid, err := GetFilterEntity(user_entity_name, req.Filter) - if err != nil { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error find condition uid: %s", logger_title, req.Filter) - } - if binddn_username, err := b.getUserNameFromBaseDN(req.BaseDN); err == nil { - username = binddn_username - } else { - username = filterUid - } - } else { - if username, err = b.getUserNameFromBindDN(bindDN); err != nil { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, err - } - } - if req.BaseDN == "" { - return ldap.ServerSearchResult{make([]*ldap.Entry, 0), []string{}, []ldap.Control{}, ldap.LDAPResultSuccess}, 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 - } - } else { - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("%s error: Filter does not contain objectclass=posixaccount nor objectclass=posixgroup", logger_title) - } - 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 -} - -func (b Backend) Close(bindDN string, conn net.Conn) (err error) { - b.logger.Printf("Close addr=%s", conn.RemoteAddr().String()) - 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.PeopleDN) { - return "", errors.New("bindDN not matched") - } - 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=") { - 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") - } - return username, nil -} - -func (b Backend) getUserNameFromBaseDN(baseDN string) (username string, err error) { - if baseDN == "" { - return "", errors.New("baseDN not specified") - } - if !strings.HasSuffix(baseDN, ","+b.PeopleDN) { - return "", errors.New("baseDN not matched") - } - rest := strings.TrimSuffix(baseDN, ","+b.PeopleDN) - if rest == "" { - return "", errors.New("baseDN format error") - } - if strings.Contains(rest, ",") { - return "", errors.New("baseDN has too much entities") - } - 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("baseDN contains no cn/uid entry") - } - return username, nil -} - -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 { - 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 -} - -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 -} -- cgit v1.2.3