From 4d12729aa4026229e4e118b924cc3b1c75ca214b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 17 Jun 2016 20:09:33 -0400 Subject: write setuid, move things around --- go/parabola_hackers/nslcd_backend/db_config.go | 40 ++++++ go/parabola_hackers/nslcd_backend/db_group.go | 141 +++++++++++++++++++++ go/parabola_hackers/nslcd_backend/db_pam.go | 167 +++++++++++++++++++++++++ go/parabola_hackers/nslcd_backend/db_passwd.go | 82 ++++++++++++ go/parabola_hackers/nslcd_backend/db_shadow.go | 78 ++++++++++++ go/parabola_hackers/nslcd_backend/hackers.go | 122 ++++++++++++++++++ go/parabola_hackers/nslcd_backend/util.go | 58 +++++++++ 7 files changed, 688 insertions(+) create mode 100644 go/parabola_hackers/nslcd_backend/db_config.go create mode 100644 go/parabola_hackers/nslcd_backend/db_group.go create mode 100644 go/parabola_hackers/nslcd_backend/db_pam.go create mode 100644 go/parabola_hackers/nslcd_backend/db_passwd.go create mode 100644 go/parabola_hackers/nslcd_backend/db_shadow.go create mode 100644 go/parabola_hackers/nslcd_backend/hackers.go create mode 100644 go/parabola_hackers/nslcd_backend/util.go (limited to 'go/parabola_hackers/nslcd_backend') diff --git a/go/parabola_hackers/nslcd_backend/db_config.go b/go/parabola_hackers/nslcd_backend/db_config.go new file mode 100644 index 0000000..e78643b --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/db_config.go @@ -0,0 +1,40 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + s "syscall" + + p "lukeshu.com/git/go/libnslcd.git/proto" +) + +func (o *Hackers) Config_Get(cred s.Ucred, req p.Request_Config_Get) <-chan p.Config { + o.lock.RLock() + ret := make(chan p.Config) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + switch req.Key { + case p.NSLCD_CONFIG_PAM_PASSWORD_PROHIBIT_MESSAGE: + if o.cfg.Pam_password_prohibit_message != "" { + ret <- p.Config{Value: o.cfg.Pam_password_prohibit_message} + } + } + }() + return ret +} diff --git a/go/parabola_hackers/nslcd_backend/db_group.go b/go/parabola_hackers/nslcd_backend/db_group.go new file mode 100644 index 0000000..18e54b1 --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/db_group.go @@ -0,0 +1,141 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + "parabola_hackers" + s "syscall" + + p "lukeshu.com/git/go/libnslcd.git/proto" +) + +func (o *Hackers) groupByName(name string, users bool) p.Group { + members_set, found := o.groups[name] + if !found { + return p.Group{ID: -1} + } + gid := name2gid(name) + if gid < 0 { + return p.Group{ID: -1} + } + var members_list []string + if users { + members_list = parabola_hackers.Set2list(members_set) + } else { + members_list = make([]string, 0) + } + return p.Group{ + Name: name, + PwHash: "x", + ID: gid, + Members: members_list, + } +} + +func (o *Hackers) groupByGid(gid int32, users bool) p.Group { + name, found := gid2name(gid) + if !found { + return p.Group{ID: -1} + } + members_set, found := o.groups[name] + if !found { + return p.Group{ID: -1} + } + var members_list []string + if users { + members_list = parabola_hackers.Set2list(members_set) + } else { + members_list = make([]string, 0) + } + return p.Group{ + Name: name, + PwHash: "x", + ID: gid, + Members: members_list, + } +} + +func (o *Hackers) Group_ByName(cred s.Ucred, req p.Request_Group_ByName) <-chan p.Group { + o.lock.RLock() + ret := make(chan p.Group) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + group := o.groupByName(req.Name, true) + if group.ID < 0 { + return + } + ret <- group + }() + return ret +} + +func (o *Hackers) Group_ByGid(cred s.Ucred, req p.Request_Group_ByGid) <-chan p.Group { + o.lock.RLock() + ret := make(chan p.Group) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + group := o.groupByGid(req.Gid, true) + if group.ID < 0 { + return + } + ret <- group + }() + return ret +} + +// note that the BYMEMBER call returns an empty members list +func (o *Hackers) Group_ByMember(cred s.Ucred, req p.Request_Group_ByMember) <-chan p.Group { + o.lock.RLock() + ret := make(chan p.Group) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + uid := o.name2uid(req.Member) + if uid < 0 { + return + } + for _, name := range o.users[uid].Groups { + group := o.groupByName(name, false) + if group.ID >= 0 { + ret <- group + } + } + }() + return ret +} + +func (o *Hackers) Group_All(cred s.Ucred, req p.Request_Group_All) <-chan p.Group { + o.lock.RLock() + ret := make(chan p.Group) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + for name, _ := range o.groups { + group := o.groupByName(name, true) + if group.ID >= 0 { + ret <- group + } + } + }() + return ret +} diff --git a/go/parabola_hackers/nslcd_backend/db_pam.go b/go/parabola_hackers/nslcd_backend/db_pam.go new file mode 100644 index 0000000..3374170 --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/db_pam.go @@ -0,0 +1,167 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + "fmt" + "parabola_hackers" + s "syscall" + + "lukeshu.com/git/go/libgnulinux.git/crypt" + p "lukeshu.com/git/go/libnslcd.git/proto" + "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger" +) + +func checkPassword(password string, hash string) bool { + return crypt.Crypt(password, hash) == hash +} + +func hashPassword(newPassword string, oldHash string) string { + salt := oldHash + if salt == "!" { + str, err := parabola_hackers.RandomString(crypt.SaltAlphabet, 8) + if err != nil { + logger.Err("Could not generate a random string") + str = "" + } + salt = "$6$" + str + "$" + } + return crypt.Crypt(newPassword, salt) +} + +func (o *Hackers) PAM_Authentication(cred s.Ucred, req p.Request_PAM_Authentication) <-chan p.PAM_Authentication { + o.lock.RLock() + ret := make(chan p.PAM_Authentication) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + uid := o.name2uid(req.UserName) + if uid < 0 { + return + } + + user := o.users[uid] + obj := p.PAM_Authentication{ + AuthenticationResult: p.NSLCD_PAM_AUTH_ERR, + UserName: "", + AuthorizationResult: p.NSLCD_PAM_AUTH_ERR, + AuthorizationError: "", + } + if checkPassword(req.Password, user.Passwd.PwHash) { + obj.AuthenticationResult = p.NSLCD_PAM_SUCCESS + obj.AuthorizationResult = obj.AuthenticationResult + obj.UserName = user.Passwd.Name + } + ret <- obj + }() + return ret +} + +func (o *Hackers) PAM_Authorization(cred s.Ucred, req p.Request_PAM_Authorization) <-chan p.PAM_Authorization { + o.lock.RLock() + ret := make(chan p.PAM_Authorization) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + uid := o.name2uid(req.UserName) + if uid < 0 { + return + } + ret <- p.PAM_Authorization{ + Result: p.NSLCD_PAM_SUCCESS, + Error: "", + } + }() + return ret +} + +const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +func (o *Hackers) PAM_SessionOpen(cred s.Ucred, req p.Request_PAM_SessionOpen) <-chan p.PAM_SessionOpen { + ret := make(chan p.PAM_SessionOpen) + go func() { + defer close(ret) + + sessionid, err := parabola_hackers.RandomString(alphabet, 24) + if err != nil { + return + } + ret <- p.PAM_SessionOpen{SessionID: sessionid} + }() + return ret +} + +func (o *Hackers) PAM_SessionClose(cred s.Ucred, req p.Request_PAM_SessionClose) <-chan p.PAM_SessionClose { + ret := make(chan p.PAM_SessionClose) + go close(ret) + return ret +} + +func (o *Hackers) PAM_PwMod(cred s.Ucred, req p.Request_PAM_PwMod) <-chan p.PAM_PwMod { + ret := make(chan p.PAM_PwMod) + o.lock.Lock() + go func() { + defer close(ret) + defer o.lock.Unlock() + + uid := o.name2uid(req.UserName) + if uid < 0 { + return + } + user := o.users[uid] + + // Check the OldPassword + if req.AsRoot == 1 { + if !checkPassword(req.OldPassword, user.Passwd.PwHash) { + ret <- p.PAM_PwMod{ + Result: p.NSLCD_PAM_PERM_DENIED, + Error: fmt.Sprintf("password change failed: %s", "Old password did not match"), + } + return + } + } + + // Update the PwHash in memory + user.Passwd.PwHash = hashPassword(req.NewPassword, user.Passwd.PwHash) + if user.Passwd.PwHash == "" { + logger.Err("Password hashing failed") + return + } + + // Update the PwHash on disk + passwords := make(map[string]string, len(o.users)) + for _, ouser := range o.users { + passwords[ouser.Passwd.Name] = ouser.Passwd.PwHash + } + passwords[user.Passwd.Name] = user.Passwd.PwHash + err := parabola_hackers.SaveAllPasswords(passwords) + if err != nil { + logger.Err("Writing passwords to disk: %v", err) + return + } + + // Ok, we're done, commit the changes + o.users[uid] = user + ret <- p.PAM_PwMod{ + Result: p.NSLCD_PAM_SUCCESS, + Error: "", + } + }() + return ret +} diff --git a/go/parabola_hackers/nslcd_backend/db_passwd.go b/go/parabola_hackers/nslcd_backend/db_passwd.go new file mode 100644 index 0000000..3f32ddd --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/db_passwd.go @@ -0,0 +1,82 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + s "syscall" + + p "lukeshu.com/git/go/libnslcd.git/proto" +) + +/* Note that the output password hash value should be one of: + - no password set, allow login without password + ! - used to prevent logins + x - "valid" encrypted password that does not match any valid password + often used to indicate that the password is defined elsewhere + other - encrypted password, in crypt(3) format */ + +func (o *Hackers) Passwd_ByName(cred s.Ucred, req p.Request_Passwd_ByName) <-chan p.Passwd { + o.lock.RLock() + ret := make(chan p.Passwd) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + uid := o.name2uid(req.Name) + if uid < 0 { + return + } + passwd := o.users[uid].Passwd + passwd.PwHash = "x" // only put actual hashes in the Shadow DB + ret <- passwd + }() + return ret +} + +func (o *Hackers) Passwd_ByUID(cred s.Ucred, req p.Request_Passwd_ByUID) <-chan p.Passwd { + o.lock.RLock() + ret := make(chan p.Passwd) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + user, found := o.users[req.UID] + if !found { + return + } + passwd := user.Passwd + passwd.PwHash = "x" // only put actual hashes in the Shadow DB + ret <- passwd + }() + return ret +} + +func (o *Hackers) Passwd_All(cred s.Ucred, req p.Request_Passwd_All) <-chan p.Passwd { + o.lock.RLock() + ret := make(chan p.Passwd) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + for _, user := range o.users { + passwd := user.Passwd + passwd.PwHash = "x" // only put actual hashes in the Shadow DB + ret <- passwd + } + }() + return ret +} diff --git a/go/parabola_hackers/nslcd_backend/db_shadow.go b/go/parabola_hackers/nslcd_backend/db_shadow.go new file mode 100644 index 0000000..abfff28 --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/db_shadow.go @@ -0,0 +1,78 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + s "syscall" + + p "lukeshu.com/git/go/libnslcd.git/proto" +) + +func (o *Hackers) Shadow_ByName(cred s.Ucred, req p.Request_Shadow_ByName) <-chan p.Shadow { + o.lock.RLock() + ret := make(chan p.Shadow) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + if cred.Uid != 0 { + return + } + uid := o.name2uid(req.Name) + user := o.users[uid] + ret <- p.Shadow{ + Name: user.Passwd.Name, + PwHash: user.Passwd.PwHash, + LastChangeDate: -1, + MinDays: -1, + MaxDays: -1, + WarnDays: -1, + InactDays: -1, + ExpireDate: -1, + Flag: -1, + } + }() + return ret +} + +func (o *Hackers) Shadow_All(cred s.Ucred, req p.Request_Shadow_All) <-chan p.Shadow { + o.lock.RLock() + ret := make(chan p.Shadow) + go func() { + defer o.lock.RUnlock() + defer close(ret) + + if cred.Uid != 0 { + return + } + + for _, user := range o.users { + ret <- p.Shadow{ + Name: user.Passwd.Name, + PwHash: user.Passwd.PwHash, + LastChangeDate: -1, + MinDays: -1, + MaxDays: -1, + WarnDays: -1, + InactDays: -1, + ExpireDate: -1, + Flag: -1, + } + } + }() + return ret +} diff --git a/go/parabola_hackers/nslcd_backend/hackers.go b/go/parabola_hackers/nslcd_backend/hackers.go new file mode 100644 index 0000000..bb03862 --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/hackers.go @@ -0,0 +1,122 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +// Package hackers_nslcd_backend is an nslcd_server Backend that +// speaks to hackers.git. +package hackers_nslcd_backend + +import ( + "parabola_hackers" + "sync" + + nslcd_server "lukeshu.com/git/go/libnslcd.git/proto/server" + nslcd_systemd "lukeshu.com/git/go/libnslcd.git/systemd" + "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger" +) + +type config struct { + Pam_password_prohibit_message string +} + +type Hackers struct { + nslcd_server.NilBackend + lock sync.RWMutex + + CfgFilename string + + cfg config + users map[int32]parabola_hackers.User + groups map[string]map[string]bool +} + +var _ nslcd_systemd.Backend = &Hackers{} +var _ nslcd_server.Backend = &Hackers{} + +func (o *Hackers) Init() error { + logger.Debug("hackers.git: CfgFilename = %v", o.CfgFilename) + err := o.Reload() + if err != nil { + logger.Err("hackers.git: Could not initialize: %v", err) + return err + } + return nil +} + +func (o *Hackers) Close() { + logger.Info("hackers.git: Closing session") + o.lock.Lock() + defer o.lock.Unlock() + + o.users = make(map[int32]parabola_hackers.User, 0) + o.groups = make(map[string]map[string]bool) +} + +func (o *Hackers) Reload() error { + logger.Info("hackers.git: Loading session") + o.lock.Lock() + defer o.lock.Unlock() + + var err error + o.cfg, err = parse_config(o.CfgFilename) + if err != nil { + return err + } + logger.Info("hackers.git: pam_password_prohibit_message: %#v", o.cfg.Pam_password_prohibit_message) + + logger.Debug("hackers.git: Parsing user data") + o.users, err = parabola_hackers.LoadAllUsers() + if err != nil { + return err + } + + passwords, err := parabola_hackers.LoadAllPasswords() + if err != nil { + return err + } + + o.groups = make(map[string]map[string]bool) + for uid, user := range o.users { + user.Passwd.GID = usersGid + hash, hasHash := passwords[user.Passwd.Name] + if !hasHash { + hash = "!" + } + user.Passwd.PwHash = hash + o.users[uid] = user + for _, groupname := range user.Groups { + o.add_user_to_group(user.Passwd.Name, groupname) + } + } + return nil +} + +func (o *Hackers) name2uid(name string) int32 { + for uid, data := range o.users { + if data.Passwd.Name == name { + return uid + } + } + return -1 +} + +func (o *Hackers) add_user_to_group(username string, groupname string) { + group, found := o.groups[groupname] + if !found { + group = make(map[string]bool) + o.groups[groupname] = group + } + group[username] = true +} diff --git a/go/parabola_hackers/nslcd_backend/util.go b/go/parabola_hackers/nslcd_backend/util.go new file mode 100644 index 0000000..4fb28f3 --- /dev/null +++ b/go/parabola_hackers/nslcd_backend/util.go @@ -0,0 +1,58 @@ +// Copyright 2015-2016 Luke Shumaker . +// +// This is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// This software is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public +// License along with this manual; if not, see +// . + +package hackers_nslcd_backend + +import ( + "io/ioutil" + "os" + + yaml "gopkg.in/yaml.v2" + "lukeshu.com/git/go/libgnulinux.git/getgr" +) + +func name2gid(name string) int32 { + gr, err := getgr.ByName(name) + if gr == nil || err != nil { + return -1 + } else { + return int32(gr.Gid) + } +} + +func gid2name(gid int32) (string, bool) { + gr, err := getgr.ByGid(gid) + if gr == nil || err != nil { + return "", false + } else { + return gr.Name, true + } +} + +var usersGid = name2gid("users") + +func parse_config(filename string) (cfg config, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + contents, err := ioutil.ReadAll(file) + if err != nil { + return + } + err = yaml.Unmarshal(contents, &cfg) + return +} -- cgit v1.2.3-54-g00ecf