diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-06-17 16:59:45 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-06-17 16:59:45 -0400 |
commit | 13c0d03f324c61e12708ee7184cc37554edc92f7 (patch) | |
tree | e3258add66d817486a4066c052a85ea5f0b50cb2 | |
parent | 24224e37b71994dc54c81cfc67dfd423578b5a59 (diff) |
password tracking and changing in etc
-rw-r--r-- | Makefile | 19 | ||||
-rw-r--r-- | src/parabola_hackers/.gitignore | 1 | ||||
-rw-r--r-- | src/parabola_hackers/nslcd_backend/db_pam.go | 68 | ||||
-rw-r--r-- | src/parabola_hackers/nslcd_backend/hackers.go | 13 | ||||
-rw-r--r-- | src/parabola_hackers/passwords.go.in (renamed from src/parabola_hackers/password.go) | 70 |
5 files changed, 146 insertions, 25 deletions
@@ -22,6 +22,7 @@ PACKAGE = parabola-hackers sysusersdir=$(prefix)/lib/sysusers.d systemunitdir=$(prefix)/lib/systemd/system conf_file = $(sysconfdir)/$(PACKAGE).yml +shadow_file = $(sysconfdir)/nshd/shadow NET ?= #NET ?= FORCE user = nshd @@ -43,7 +44,7 @@ scripts = $(filter-out common.rb common.rb.in,$(notdir $(wildcard $(srcdir)/scri std.gen_files += LICENSE.lgpl-2.1.txt LICENSE.gpl-2.txt LICENSE.apache-2.0.txt std.out_files += bin/cmd-nshd nshd.service nshd.sysusers scripts/common.rb test/runner -std.sys_files += $(addprefix $(bindir)/,nshd $(scripts)) $(systemunitdir)/nshd.socket $(systemunitdir)/nshd.service $(sysusersdir)/nshd.conf $(conf_file) +std.sys_files += $(addprefix $(bindir)/,nshd $(scripts)) $(systemunitdir)/nshd.socket $(systemunitdir)/nshd.service $(sysusersdir)/nshd.conf $(conf_file) $(shadow_file) std.clean_files += test/*.o pkg/ .tmp* .var* $(_out) $(srcdir)/LICENSE.lgpl-2.1.txt: $(NET) @@ -59,6 +60,7 @@ _gen += src/lukeshu.com/git/go/libnslcd.git/proto/server/interface_backend.go _gen += src/lukeshu.com/git/go/libnslcd.git/proto/server/func_handlerequest.go _gen += src/lukeshu.com/git/go/libnslcd.git/proto/server/type_nilbackend.go _out += src/parabola_hackers/users.go +_out += src/parabola_hackers/passwords.go _out += src/cmd-nshd/main.go $(outdir)/bin/%-nshd: $(call golang.src,$(srcdir)) $(_gen) $(_out) $(call golang.install,$(topsrcdir),cmd-nshd) @@ -76,21 +78,36 @@ $(outdir)/nshd.sysusers: $(var)user $(outdir)/scripts/common.rb: $(var)conf_file $(outdir)/src/cmd-nshd/main.go: $(var)conf_file $(outdir)/src/parabola_hackers/users.go: $(var)bindir +$(outdir)/src/parabola_hackers/passwords.go: $(var)shadow_file $(DESTDIR)$(bindir)/%: $(outdir)/bin/cmd-% + $(NORMAL_INSTALL) install -TDm755 $< $@ $(DESTDIR)$(bindir)/%: $(srcdir)/scripts/% + $(NORMAL_INSTALL) install -TDm755 $< $@ $(DESTDIR)$(bindir)/common.rb: $(srcdir)/scripts/common.rb + $(NORMAL_INSTALL) install -TDm644 $< $@ $(DESTDIR)$(systemunitdir)/%.socket: $(outdir)/%.socket + $(NORMAL_INSTALL) install -TDm644 $< $@ $(DESTDIR)$(systemunitdir)/%.service: $(outdir)/%.service + $(NORMAL_INSTALL) install -TDm644 $< $@ $(DESTDIR)$(sysusersdir)/%.conf: $(outdir)/%.sysusers + $(NORMAL_INSTALL) install -TDm644 $< $@ $(DESTDIR)$(conf_file): $(srcdir)/parabola-hackers.yml + $(NORMAL_INSTALL) install -TDm644 $< $@ +$(DESTDIR)$(shadow_file): $(var)user $(DESTDIR)$(sysusersdir)/nshd.conf + $(NORMAL_INSTALL) + install -d $(@D) + touch $@ + $(POST_INSTALL) + -systemd-sysusers + -chown $(user):$(user) $(@D) $@ .PHONY: FORCE .SECONDARY: diff --git a/src/parabola_hackers/.gitignore b/src/parabola_hackers/.gitignore index e93884b..3be3f08 100644 --- a/src/parabola_hackers/.gitignore +++ b/src/parabola_hackers/.gitignore @@ -1 +1,2 @@ /users.go +/passwords.go diff --git a/src/parabola_hackers/nslcd_backend/db_pam.go b/src/parabola_hackers/nslcd_backend/db_pam.go index 303a66c..3374170 100644 --- a/src/parabola_hackers/nslcd_backend/db_pam.go +++ b/src/parabola_hackers/nslcd_backend/db_pam.go @@ -17,17 +17,32 @@ 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) @@ -97,3 +112,56 @@ func (o *Hackers) PAM_SessionClose(cred s.Ucred, req p.Request_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/src/parabola_hackers/nslcd_backend/hackers.go b/src/parabola_hackers/nslcd_backend/hackers.go index f7d56e3..bb03862 100644 --- a/src/parabola_hackers/nslcd_backend/hackers.go +++ b/src/parabola_hackers/nslcd_backend/hackers.go @@ -82,14 +82,19 @@ func (o *Hackers) Reload() error { 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 - var _err error - user.Passwd.PwHash, _err = parabola_hackers.LoadUserPassword(user.Passwd.HomeDir + "/.password") - if _err != nil { - logger.Debug("hackers.git: Ignoring password: %v", _err) + 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) diff --git a/src/parabola_hackers/password.go b/src/parabola_hackers/passwords.go.in index 957de1f..0d763b9 100644 --- a/src/parabola_hackers/password.go +++ b/src/parabola_hackers/passwords.go.in @@ -20,9 +20,11 @@ import ( "fmt" "io/ioutil" "os" + "sort" "strings" "lukeshu.com/git/go/libgnulinux.git/crypt" + "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger" ) /* Note that the password hash value should be one of: @@ -32,33 +34,61 @@ import ( often used to indicate that the password is defined elsewhere other - encrypted password, in crypt(3) format */ -func LoadUserPassword(filename string) (hash string, err error) { - file, err := os.Open(filename) +const shadow_file = "@shadow_file@" + +func LoadAllPasswords() (map[string]string, error) { + file, err := os.Open(shadow_file) if err != nil { - return + return nil, err } contents, err := ioutil.ReadAll(file) if err != nil { - return + return nil, err } lines := strings.Split(string(contents), "\n") - switch len(lines) { - case 1: - hash = lines[0] - case 2: - if lines[1] == "" { - hash = lines[0] - } else { - err = fmt.Errorf("Invalid password format in file: %q", filename) + passwords := make(map[string]string, len(lines)) + for i, line := range lines { + cols := strings.SplitN(line, ":", 2) + if len(cols) != 2 { + logger.Err("hackers.git %s:%d: malformed line", shadow_file, i+1) + continue + } + username := cols[0] + hash := cols[1] + if hash != "!" && !crypt.SaltOk(hash) { + hash = "!" + logger.Err("%s:%d: malformed hash for user: %s", shadow_file, i+1, username) } - default: - err = fmt.Errorf("Invalid password format in file (number of lines): %q", filename) - return + passwords[username] = hash + } + return passwords, nil +} + +func SaveAllPasswords(passwords map[string]string) error { + usernames := make([]string, len(passwords)) + i := 0 + for username, _ := range passwords { + usernames[i] = username + i++ + } + sort.Strings(usernames) + + file, err := os.OpenFile(shadow_file+"-", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + + for _, username := range usernames { + fmt.Fprintf(file, "%s:%s\n", username, passwords[username]) } - if hash != "!" && !crypt.SaltOk(hash) { - hash = "!" - err = fmt.Errorf("Invalid password format in file (invalid salt): %q", filename) - return + err = file.Sync() + if err != nil { + return err } - return + err = file.Close() + if err != nil { + return err + } + + return os.Rename(shadow_file+"-", shadow_file) } |