summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rw-r--r--.gitmodules15
-rw-r--r--GNUmakefile13
l---------LICENSE.bsd3.txt1
-rw-r--r--LICENSE.txt29
-rw-r--r--[l---------]Makefile141
-rw-r--r--[l---------]README.md103
-rw-r--r--bin/.gitignore4
-rw-r--r--bin/common.rb.in44
-rwxr-xr-xbin/meta-cat22
-rwxr-xr-xbin/meta-check60
-rwxr-xr-xbin/meta-normalize-stdio190
-rw-r--r--bin/nshd-setuid.c108
-rw-r--r--bin/nshd-tester.c168
-rwxr-xr-xbin/pacman-make-keyring167
-rwxr-xr-xbin/pgp-list-keyids37
-rwxr-xr-xbin/postfix-generate-virtual-map34
-rwxr-xr-xbin/ssh-list-authorized-keys41
-rwxr-xr-xbin/uid-map24
-rw-r--r--common.mk55
-rw-r--r--config.mk19
-rw-r--r--config.mk.in198
m---------go/git.lukeshu.com/go/libgnulinux0
m---------go/git.lukeshu.com/go/libnslcd0
m---------go/git.lukeshu.com/go/libsystemd0
m---------go/golang.org/x/sys0
m---------go/gopkg.in/yaml.v20
-rw-r--r--go/nshd/.gitignore1
-rw-r--r--go/nshd/main.go.in33
-rw-r--r--go/nshd/nshd_files/.gitignore2
-rw-r--r--go/nshd/nshd_files/passwords.go.in97
-rw-r--r--go/nshd/nshd_files/users.go.in143
-rw-r--r--go/nshd/nslcd_backend/db_config.go39
-rw-r--r--go/nshd/nslcd_backend/db_group.go141
-rw-r--r--go/nshd/nslcd_backend/db_pam.go204
-rw-r--r--go/nshd/nslcd_backend/db_passwd.go88
-rw-r--r--go/nshd/nslcd_backend/db_shadow.go77
-rw-r--r--go/nshd/nslcd_backend/hackers.go124
-rw-r--r--go/nshd/nslcd_backend/util.go58
-rw-r--r--go/nshd/util/util.go47
-rw-r--r--nshd.service.in30
-rw-r--r--nshd.socket12
-rw-r--r--nshd.sysusers.in1
-rw-r--r--parabola-hackers.yml45
44 files changed, 2426 insertions, 200 deletions
diff --git a/.gitignore b/.gitignore
index 30be33f..5c304e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,14 @@
+/LICENSE.*.txt
+!/LICENSE.bsd3.txt
+/nshd.service
+/nshd.sysusers
+
+/.gopath
+*.o
+*~
+parabola-keyring-*
+parabola-hackers-*
+
.tmp.*
.var.*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..434287e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,15 @@
+[submodule "go/gopkg.in/yaml.v2"]
+ path = go/gopkg.in/yaml.v2
+ url = https://gopkg.in/yaml.v2/
+[submodule "go/git.lukeshu.com/go/libgnulinux"]
+ path = go/git.lukeshu.com/go/libgnulinux
+ url = https://git.lukeshu.com/go/libgnulinux/
+[submodule "go/git.lukeshu.com/go/libnslcd"]
+ path = go/git.lukeshu.com/go/libnslcd
+ url = https://git.lukeshu.com/go/libnslcd/
+[submodule "go/git.lukeshu.com/go/libsystemd"]
+ path = go/git.lukeshu.com/go/libsystemd
+ url = https://git.lukeshu.com/go/libsystemd/
+[submodule "go/golang.org/x/sys"]
+ path = go/golang.org/x/sys
+ url = https://go.googlesource.com/sys
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..cc5a40d
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,13 @@
+# This file is a hack to let us pass whatever flags we want to Make,
+# since adjusting MAKEFLAGS at runtime only half-works.
+#
+# Most of the complexity is dancing around to avoid having any
+# possibly conflicting identifiers.
+
+MAKEFLAGS += --no-print-directory
+rest = $(wordlist 2,$(words $1),$1)
+target = $(or $(firstword $(MAKECMDGOALS)),default)
+$(or $(call rest,$(MAKECMDGOALS)),_$(target)): $(target)
+ @:
+$(target):
+ @+$(MAKE) -f Makefile --no-builtin-rules --no-builtin-variables $(MAKECMDGOALS)
diff --git a/LICENSE.bsd3.txt b/LICENSE.bsd3.txt
new file mode 120000
index 0000000..92d2b94
--- /dev/null
+++ b/LICENSE.bsd3.txt
@@ -0,0 +1 @@
+go/golang.org/x/sys/LICENSE \ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..57a0df1
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,29 @@
+The software in this repository is under a number of licenses:
+
+ scripts/ GPL v2+
+ src/nshd/ GPL v2+
+ test/ LGPL v2.1+
+
+ go/gopkg.in/yaml.v2 LGPL v3, with linking exception
+ go/golang.org/x/sys/unix 3-clause BSD license
+
+ go/git.lukeshu.com/go/libgnulinux/dl GPL v2+, with documentation output clarification
+ go/git.lukeshu.com/go/libgnulinux/inotify LGPL v2.1+
+ go/git.lukeshu.com/go/libgnulinux/getgr LGPL v2.1+
+ go/git.lukeshu.com/go/libnslcd/ LGPL v2.1+
+ go/git.lukeshu.com/go/libsystemd/ Apache v2.0
+
+ build-aux/ AGPL v3+
+
+The general notion is that the build system is AGPL (v3+), the core
+application is GPL (v2+), while supporting libraries that might be
+useful outside of this specific application are LGPL (v2.1+), with the
+exceptions:
+ - The systemd package is Apache licensed because of code taken from
+ CoreOS and Docker.
+ - The dl package is GPL v2+ licensed because of wording in comments
+ taken from the Linux Programmer's Manual.
+ - The YAML and sys/unix libraries use other licenses because they are
+ 3rd-party libraries, and that's how the authors licensed them.
+
+For more details, see each individual file.
diff --git a/Makefile b/Makefile
index 4ecdba5..f7f51ac 120000..100644
--- a/Makefile
+++ b/Makefile
@@ -1 +1,140 @@
-build-aux/Makefile.README.mk \ No newline at end of file
+# Copyright 2015-2017 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+include config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+files.src.src := $(filter-out go/golang.org/x/sys/plan9/% go/golang.org/x/sys/windows/%,$(files.src.src))
+var = .var.
+programs = nshd nshd-setuid nshd-tester common.rb
+scripts = $(filter-out %.c %.o $(programs) common.rb common.rb.in,$(notdir $(wildcard $(srcdir)/bin/*)))
+
+.PHONY: FORCE
+.SECONDARY:
+.DELETE_ON_ERROR:
+
+#
+# Go language noise
+
+CGO_CPPFLAGS = $(CPPFLAGS) -U_FORTIFY_SOURCE
+CGO_CFLAGS = $(CFLAGS) -O0 -Wno-unused-parameter
+CGO_ENABLED = 1
+
+cgo_variables = CGO_ENABLED CGO_CFLAGS CGO_CPPFLAGS CGO_CXXFLAGS CGO_LDFLAGS CC CXX
+$(foreach v,$(cgo_variables),$(eval $v ?=))
+export $(cgo_variables)
+
+$(outdir)/.gopath/src:
+ rm -rf -- $(@D)
+ mkdir $(@D)
+ ln -sr $(srcdir)/go $@
+ touch $@
+
+#
+# Generate (pre-tarball)
+
+files.src.gen += LICENSE.lgpl-2.1.txt LICENSE.gpl-2.txt LICENSE.apache-2.0.txt
+
+$(srcdir)/LICENSE.lgpl-2.1.txt: $(NET)
+ curl https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt > $@
+$(srcdir)/LICENSE.gpl-2.txt: $(NET)
+ curl https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt > $@
+$(srcdir)/LICENSE.apache-2.0.txt: $(NET)
+ curl https://www.apache.org/licenses/LICENSE-2.0 > $@
+$(srcdir)/LICENSE.wtfpl-2.txt: $(NET)
+ curl http://www.wtfpl.net/txt/copying/ > $@
+
+files.generate: go-generate
+go-generate: .gopath/src
+ +GOPATH=$(abspath $(outdir)/.gopath) go generate git.lukeshu.com/...
+.PHONY: go-generate
+files.src.int += .gopath/ .gopath/*
+at.targets += go-generate
+
+#
+# Build (post-tarball)
+
+files.out.all += $(addprefix bin/,$(programs)) nshd.service nshd.sysusers
+files.out.int += bin/*.o .gopath/ .gopath/* .tmp* .var*
+
+# Dependencies
+$(outdir)/bin/nshd-setuid: -ldl
+$(outdir)/nshd.service: $(var)user $(var)bindir
+$(outdir)/nshd.sysusers: $(var)user
+$(outdir)/bin/common.rb: $(var)conf_file
+
+$(outdir)/.gopath/bin/nshd: \
+ $(outdir)/go/nshd/main.go \
+ $(outdir)/go/nshd/nshd_files/users.go \
+ $(outdir)/go/nshd/nshd_files/passwords.go
+$(outdir)/go/nshd/main.go: $(var)conf_file
+$(outdir)/go/nshd/nshd_files/users.go: $(var)bindir
+$(outdir)/go/nshd/nshd_files/passwords.go: $(var)shadow_file
+
+# Go: copy out of .gopath
+$(outdir)/bin/nshd: \
+$(outdir)/bin/%: $(outdir)/.gopath/bin/%
+ cp -T $< $@
+# Go: compile+link
+$(outdir)/.gopath/bin/nshd: \
+$(outdir)/.gopath/bin/%: FORCE go-generate
+ GOPATH=$(abspath $(outdir)/.gopath) go install $(@F)
+
+# C: compile
+$(outdir)/%.o: $(srcdir)/%.c $(var)CC $(var)CPPFLAGS $(var)CFLAGS
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(filter-out $(var)%,$^)
+# C: link
+$(outdir)/bin/nshd-tester $(outdir)/bin/nshd-setuid: \
+$(outdir)/%: $(outdir)/%.o $(var)CC $(var)LDFLAGS
+ $(CC) $(LDFLAGS) -o $@ $(filter-out $(var)%,$^)
+
+# Basic variable substitution
+$(outdir)/%: $(srcdir)/%.in
+ < $< sed $(foreach v,$(patsubst $(var)%,%,$(filter $(var)%,$^)), -e 's|@$v@|$($v)|g' ) > $@
+
+
+# Install
+
+files.sys.all += $(addprefix $(bindir)/,$(programs) $(scripts)) $(systemunitdir)/nshd.socket $(systemunitdir)/nshd.service $(sysusersdir)/nshd.conf $(conf_file) $(shadow_file)
+
+$(DESTDIR)$(bindir)/%: $(outdir)/bin/%
+ $(NORMAL_INSTALL)
+ install -TDm755 $< $@
+$(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 $< $@
+
+# Specific files
+$(DESTDIR)$(bindir)/common.rb: $(srcdir)/bin/common.rb
+ $(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) $@
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/README.md b/README.md
index 5e5ea4a..132a7da 120000..100644
--- a/README.md
+++ b/README.md
@@ -1 +1,102 @@
-build-aux/Makefile.README.txt \ No newline at end of file
+This repository contains tools for working with hackers.git
+information.
+
+The most important 4 programs are:
+
+ - `meta-check`: sanity-check hackers.git data
+ - `ssh-list-authorized-keys`: configure sshd to use this for
+ AuthorizedKeysCommand to have it get SSH keys directly from
+ hackers.git
+ - `postfix-generate-virtual-map`: generate a virtual map
+ for Postfix that provides email aliases for users in hackers.git
+ - `pacman-make-keyring` generate a tarball with the pacman-keyring
+ files for the users in hackers.git
+ - `nshd` implements the nshld protocol of nss-pam-ldapd, but talks to
+ hackers.git instead of LDAP.
+
+The others are:
+
+ - `meta-normalize-stdio`: used by `meta-check`
+ - `meta-cat`: used by `nshd`
+ - `pgp-list-keyids`: used by `pacman-make-keyring`
+ - `uid-map`: used by `pacman-make-keyring`
+
+Each of the programs looks for `parabola-hackers.yml` in he current
+directory (except for `meta-normalize-stdio`, which has no
+configuration).
+
+# Configuration
+
+The main two things programs at are `yamldir` which tells them where
+to find `hackers.git/users`, and `groupgroups` which augments the
+`groups` array for each user. You may override the setting for
+`yamldir` by setting the environment variable
+`PARABOLA_HACKERS_YAMLDIR`
+
+## pacman-make-keyring
+
+`pacman-make-keyring` also looks at `keyring_cachedir` to see where to
+store files that can be cached between versions of the keyring.
+
+## ssh-list-authorized-keys
+
+`ssh-list-authorized-keys` also looks at `ssh_pseudo_users`.
+System users (`/etc/passwd`) mentioned in this variable may be SSH'ed
+into by hackers.git users who are in a group of the same name.
+
+## nshd
+
+`nshd` also looks at `pam_password_prohibit_message` to decide what to
+say when prohibiting a user from being changed via PAM.
+
+# Usage
+
+## meta-check
+
+Just run it, it will report any problems with hackers.git data.
+
+## ssh-list-authorized-keys
+
+Configure `sshd_config:AuthorizedKeysCommand` to be this program.
+`sshd` will run it as `ssh-list-authorized-keys ${USERNAME}`
+
+## postfix-generate-virtual-map
+
+ postfix-show-virtual-map > /etc/postfix/virtual-parabola.nu
+ postmap hash:/etc/postfix/virtual-parabola.nu
+
+## pacman-make-keyring
+
+ pacman-make-keyring V=$(date -u +%Y%m%d)
+ scp parabola-keyring-$(date -u +%Y%m%d).tar.gz repo.parabola.nu:/srv/repo/main/other/parabola-keyring/
+
+or
+
+ cd $(. "$(librelib conf)" && load_files makepkg && echo "$SRCDEST")
+ pacman-make-keyring V=$(date -u +%Y%m%d)
+
+In the latter case, it would get uploaded automagically by
+`librerelease` when you release a parabola-keyring with the matching
+version.
+
+## nshd
+
+Either reboot, or run `systemd-sysusers` to create the nshd user.
+
+Add `ldap` to the `passwd`, `group`, and `shadow` fields in
+`/etc/nsswitch.conf`:
+
+ passwd: files ldap
+ group: files ldap
+ shadow: files ldap
+
+Then enable and start `nshd.socket`:
+
+ systemctl enable --now nshd.socket
+
+----
+Copyright (C) 2014, 2016 Luke Shumaker
+
+This documentation file is placed into the public domain. If that is
+not possible in your legal system, I grant you permission to use it in
+absolutely every way that I can legally grant to you.
diff --git a/bin/.gitignore b/bin/.gitignore
new file mode 100644
index 0000000..b73f922
--- /dev/null
+++ b/bin/.gitignore
@@ -0,0 +1,4 @@
+/common.rb
+/nshd
+/nshd-setuid
+/nshd-tester
diff --git a/bin/common.rb.in b/bin/common.rb.in
new file mode 100644
index 0000000..7c457b8
--- /dev/null
+++ b/bin/common.rb.in
@@ -0,0 +1,44 @@
+# Copyright 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+require 'yaml'
+
+def cfg
+ if @cfg.nil?
+ @cfg = YAML::load(open("@conf_file@"))
+ if ENV['PARABOLA_HACKERS_YAMLDIR']
+ @cfg["yamldir"] = ENV['PARABOLA_HACKERS_YAMLDIR']
+ end
+ end
+ return @cfg
+end
+
+def load_user_yaml(filename)
+ user = YAML::load(open(filename))
+ groups = user["groups"] || []
+ user["groups"] = groups.concat((groups & cfg["groupgroups"].keys).map{|g|cfg["groupgroups"][g]}.flatten)
+ return user
+end
+
+def load_all_users
+ users = {}
+ Dir.glob("#{cfg["yamldir"]}/*.yml").map{|filename|
+ uid = File.basename(filename).sub(/^([0-9]*)\.yml$/, "\\1").to_i
+ user = load_user_yaml(filename)
+ users[uid] = user
+ }
+ return users
+end
diff --git a/bin/meta-cat b/bin/meta-cat
new file mode 100755
index 0000000..5e7097e
--- /dev/null
+++ b/bin/meta-cat
@@ -0,0 +1,22 @@
+#!/usr/bin/env ruby
+# Usage: meta-cat
+
+# Copyright 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+print load_all_users.to_yaml
diff --git a/bin/meta-check b/bin/meta-check
new file mode 100755
index 0000000..4add9d3
--- /dev/null
+++ b/bin/meta-check
@@ -0,0 +1,60 @@
+#!/bin/bash
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+# Copyright 2015 Márcio Alexandre Silva Delgado <coadde@parabola.nu>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+mydir="$(dirname "$0")"
+PATH="$mydir:$PATH"
+
+check-yaml() {
+ file=$1
+ msg 'Inspecting %q' "$file"
+ norm=$(mktemp --tmpdir)
+ trap "rm -f -- $(printf '%q' "$norm")" RETURN
+ meta-normalize-stdio < "$file" > "$norm" || return $?
+ colordiff -u "$file" "$norm" || return $?
+}
+
+main() {
+ declare -i ret=0
+
+ yamldir="$(ruby -e "load '$mydir/common.rb'; print cfg['yamldir']")"
+
+ # Check the user YAML files
+ for file in "$yamldir"/*.yml; do
+ check-yaml "$file" || ret=$?
+ done
+
+ msg 'Checking for duplicate usernames'
+ dups=($(sed -n 's/^username: //p' -- "$yamldir"/*.yml| sort | uniq -d))
+ if (( ${#dups[@]} )); then
+ error 'Duplicate usernames:'
+ plain '%s' "${dups[@]}"
+ ret=1
+ fi
+
+ msg 'Checking PGP keys'
+ if pgp-list-keyids | grep -Ev '^(trusted|secondary|revoked)/[a-z][a-z0-9-]* [0-9A-F]{40}$'; then
+ error 'Bad pgp keys ^^^'
+ ret=1
+ fi
+
+ return $ret
+}
+
+main "$@"
diff --git a/bin/meta-normalize-stdio b/bin/meta-normalize-stdio
new file mode 100755
index 0000000..a7ca381
--- /dev/null
+++ b/bin/meta-normalize-stdio
@@ -0,0 +1,190 @@
+#!/usr/bin/env ruby
+# coding: utf-8
+
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+# Copyright 2015 Márcio Alexandre Silva Delgado <coadde@parabola.nu>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+# First we define a bunch of code-generators, then at the end is a
+# very neat and readable definition of the format of the YAML files.
+
+require 'yaml'
+
+def error(msg)
+ $stderr.puts "ERROR: #{msg}"
+ @err = 1
+end
+
+def warning(msg)
+ $stderr.puts "WARNING: #{msg}"
+end
+
+
+# Generic validators/formatters
+
+def semiordered_list(cnt, validator)
+ lambda {|name,ary|
+ if ary.class != Array
+ error "`#{name}' must be a list"
+ else
+ ary.each_index{|i| ary[i] = validator.call("#{name}[#{i}]", ary[i])}
+ ary = ary.first(cnt).concat(ary.last(ary.count-cnt).sort)
+ end
+ ary
+ }
+end
+
+def unordered_list(validator)
+ semiordered_list(0, validator)
+end
+
+def _unknown(map_name, key)
+ error "Unknown item: #{map_name}[#{key.inspect}]"
+ 0
+end
+def unordered_map1(validator)
+ lambda {|name,hash|
+ if hash.class != Hash
+ error "`#{name}' must be a map"
+ else
+ order = Hash[[*validator.keys.map.with_index]]
+ hash = Hash[hash.sort_by{|k,v| order[k] || _unknown(name,k) }]
+ hash.keys.each{|k|
+ if validator[k]
+ hash[k] = validator[k].call("#{name}[#{k.inspect}]", hash[k])
+ end
+ }
+ end
+ hash
+ }
+end
+
+def unordered_map2(key_validator, val_validator)
+ lambda {|name,hash|
+ if hash.class != Hash
+ error "`#{name}' must be a map"
+ else
+ hash = Hash[hash.sort_by{|k,v| k}]
+ hash.keys.each{|k|
+ key_validator.call("#{name} key #{k.inspect}", k)
+ hash[k] = val_validator.call("#{name}[#{k.inspect}]", hash[k])
+ }
+ end
+ hash
+ }
+end
+
+string = lambda {|name,str|
+ if str.class != String
+ error "`#{name}' must be a string"
+ else
+ str
+ end
+}
+
+# Regular Expression String
+def restring(re)
+ lambda {|name,str|
+ if str.class != String
+ error "`#{name}' must be a string"
+ else
+ unless re =~ str
+ error "`#{name}' does not match #{re.inspect}: #{str}"
+ end
+ str
+ end
+ }
+end
+
+
+# Specific validators/formatters
+
+year = lambda {|name, num|
+ if num.class != Fixnum
+ error "`#{name}' must be a year"
+ else
+ if (num < 1900 || num > 3000)
+ error "`#{name}' is a number, but doesn't look like a year"
+ end
+ num
+ end
+}
+
+# This regex is taken from http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
+_email_regex = /^[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
+email_list = lambda {|name, ary|
+ if ary.class != Array
+ error "`#{name}' must be a list"
+ elsif not ary.empty?
+ preserve = 1
+ if ary.first.end_with?("@parabola.nu") and ary.count >= 2
+ preserve = 2
+ end
+ ary = semiordered_list(preserve, restring(_email_regex)).call(name, ary)
+ end
+ ary
+}
+
+shell = lambda {|name, sh|
+ if sh.class != String
+ error "`#{name}' must be a string"
+ else
+ @valid_shells ||= open("/etc/shells").read.split("\n")
+ .find_all{|line| /^[^\#]/ =~ line}
+ .push("/usr/bin/nologin")
+ unless @valid_shells.include?(sh)
+ warning "shell not listed in /etc/shells: #{sh}"
+ end
+ end
+ sh
+}
+
+
+# The format of the YAML files
+
+format = unordered_map1(
+ {
+ "username" => restring(/^[a-z][a-z0-9-]*$/),
+ "fullname" => string,
+ "email" => email_list,
+ "groups" => semiordered_list(1, string),
+ "pgp_keyid" => restring(/^[0-9A-F]{40}$/),
+ "pgp_revoked_keyids" => unordered_list(restring(/^[0-9A-F]{40}$/)),
+ "ssh_keys" => unordered_map2(string, string),
+ "shell" => shell,
+ "extra" => unordered_map1(
+ {
+ "alias" => string,
+ "other_contact" => string,
+ "roles" => string,
+ "website" => string,
+ "occupation" => string,
+ "yob" => year,
+ "location" => string,
+ "languages" => string,
+ "interests" => string,
+ "favorite_distros" => string,
+ })
+ })
+
+
+
+@err = 0
+user = format.call("user", YAML::load(STDIN))
+if @err != 0
+ exit @err
+end
+print user.to_yaml
diff --git a/bin/nshd-setuid.c b/bin/nshd-setuid.c
new file mode 100644
index 0000000..aa8847b
--- /dev/null
+++ b/bin/nshd-setuid.c
@@ -0,0 +1,108 @@
+/*
+ Copyright (C) 2006 West Consulting
+ Copyright (C) 2006-2015 Arthur de Jong
+ Copyright (C) 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA
+*/
+
+#include <dlfcn.h> /* for dlopen(3), dlsym(3), and dlerror(3) */
+#include <errno.h> /* for errno */
+#include <pwd.h> /* for getpwnam(3) */
+#include <stdio.h> /* for printf(3) and fprintf(3) */
+#include <string.h> /* for strerror(3) */
+#include <sys/types.h> /* for 'struct passwd' and 'struct group' */
+#include <systemd/sd-daemon.h> /* for SD_{WARNING,DEBUG} */
+#include <unistd.h> /* for setuid(3), setgid(3), and dup2(3) */
+
+#define EXIT_SUCCESS 0
+#define EXIT_FAILURE 1
+#define EXIT_INVALIDARGUMENT 2
+#define EXIT_NOPERMISSION 4
+
+const char *nss_module_soname = "libnss_ldap.so.2";
+const char *nss_module_sym_version = "_nss_ldap_version";
+const char *nss_module_sym_enablelookups = "_nss_ldap_enablelookups";
+
+static void disable_nss_module(void) {
+ char *err;
+
+ dlerror();
+ void *handle = dlopen(nss_module_soname, RTLD_LAZY | RTLD_NODELETE);
+ err = dlerror();
+ if (handle == NULL) {
+ fprintf(stderr, SD_WARNING "NSS module %s not loaded: %s\n", nss_module_soname, err);
+ return;
+ }
+
+ dlerror();
+ char **version_info = dlsym(handle, nss_module_sym_version);
+ err = dlerror();
+ if ((version_info != NULL) && (err == NULL)) {
+ fprintf(stderr, SD_DEBUG "NSS module %s version %s %s\n", nss_module_soname,
+ version_info[0],
+ version_info[1]);
+ } else {
+ fprintf(stderr, SD_WARNING "NSS module %s version missing: %s\n", nss_module_soname, err);
+ }
+
+ dlerror();
+ int *enable_flag = dlsym(handle, nss_module_sym_enablelookups);
+ err = dlerror();
+ if ((enable_flag == NULL) || (err != NULL)) {
+ fprintf(stderr, SD_WARNING "Unable to disable NSS ldap module for nslcd process: %s\n", err);
+ dlclose(handle);
+ return;
+ }
+ *enable_flag = 0;
+ dlclose(handle);
+}
+
+void usage(char *cmd) {
+ printf("Usage: %s USERNAME COMMAND...\n", cmd);
+ printf("A simple setuid(3) wrapper that runs with the `ldap' NSS module disabled\n");
+}
+
+int main(int argc, char *argv[]) {
+ if (argc < 3) {
+ dup2(2, 1);
+ usage(argv[0]);
+ return EXIT_INVALIDARGUMENT;
+ }
+
+ disable_nss_module();
+
+ struct passwd *passwd = getpwnam(argv[1]);
+ if (passwd == NULL) {
+ fprintf(stderr, SD_ERR "Could not look up user: %s\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ if (setgid(passwd->pw_gid) != 0) {
+ fprintf(stderr, SD_ERR "Could not setgid(%lu): %s\n",
+ (unsigned long int)passwd->pw_gid, strerror(errno));
+ return EXIT_NOPERMISSION;
+ }
+ if (setuid(passwd->pw_uid) != 0) {
+ fprintf(stderr, SD_ERR "Could not setuid(%lu): %s\n",
+ (unsigned long int)passwd->pw_gid, strerror(errno));
+ return EXIT_NOPERMISSION;
+ }
+
+ execvp(argv[2], &argv[2]);
+ fprintf(stderr, SD_ERR "Could not exec: %s\n", strerror(errno));
+ return EXIT_FAILURE;
+}
diff --git a/bin/nshd-tester.c b/bin/nshd-tester.c
new file mode 100644
index 0000000..110819d
--- /dev/null
+++ b/bin/nshd-tester.c
@@ -0,0 +1,168 @@
+/* Copyright (C) 2015 Luke Shumaker <lukeshu@sbcglobal.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <error.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#define _(s) s
+
+const char *xgetenv(const char *name, const char *unset) {
+ const char *val = getenv(name);
+ if (!val)
+ val = unset;
+ return val;
+}
+
+char *xasprintf(const char *format, ...) {
+ va_list arg;
+ int len;
+ char *str = NULL;
+
+ va_start(arg, format);
+ len = vasprintf(&str, format, arg);
+ va_end(arg);
+
+ if (len < 0)
+ error(EXIT_FAILURE, errno, _("Could not allocate memory in vasprintf"));
+
+ return str;
+}
+
+#define xasprintfa(...) (__extension__ ({ char *heap = xasprintf(__VA_ARGS__); char *stack = strdupa(heap); free(heap); stack; }))
+
+int pid = -1;
+void
+sigchld_handler(int sig __attribute__((__unused__))) {
+ int status;
+ pid = waitpid(pid, &status, WNOHANG);
+ int exited = WEXITSTATUS(status);
+ error(exited, 0, "%ld exited with status %d", (long)pid, exited);
+ exit(0);
+}
+
+union addr {
+ struct sockaddr gen;
+ struct sockaddr_un un;
+};
+
+int new_unix_sock(const char *filename, int type) {
+ union addr addr;
+ addr.un.sun_family = AF_UNIX;
+ strcpy(addr.un.sun_path, filename);
+
+ int sock = socket(AF_UNIX, type, 0);
+ if (sock < 0)
+ error(EXIT_FAILURE, errno, "socket(%d, %d)", AF_UNIX, type);
+ unlink(filename);
+ if (bind(sock, &addr.gen, sizeof(addr)))
+ error(EXIT_FAILURE, errno, "bind(%d, sockaddr(\"%s\"))", sock, filename);
+ switch (type) {
+ case SOCK_STREAM:
+ case SOCK_SEQPACKET:
+ if (listen(sock, 5))
+ error(EXIT_FAILURE, errno, "listen(%d /* \"%s\" */, %d)", sock, filename, 5);
+ break;
+ case SOCK_DGRAM:
+ break;
+ default:
+ error(EXIT_FAILURE, errno, "new_unix_sock: Unrecognized type: %d", type);
+ }
+ return sock;
+}
+
+char *cmdname = "nshd_runner";
+const char *notify_sockname = "notify.sock";
+const char *nslcd_sockname = "nslcd.sock";
+void cleanup(void) {
+ if (nslcd_sockname)
+ unlink(nslcd_sockname);
+ if (notify_sockname)
+ unlink(notify_sockname);
+ fprintf(stderr, "%s: Exiting\n", cmdname);
+}
+
+int main(int argc, char *argv[]) {
+ cmdname = argv[0];
+ if (argc != 2) {
+ error(2, 0, _("Usage: %s NSHD_PROGRAM"), argv[0]);
+ }
+
+ atexit(&cleanup);
+ int nslcd_sock = new_unix_sock(nslcd_sockname , SOCK_STREAM);
+ int notify_sock = new_unix_sock(notify_sockname, SOCK_DGRAM );
+
+ struct sigaction act;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ act.sa_handler = sigchld_handler;
+ if (sigaction(SIGCHLD, &act, 0))
+ error(EXIT_FAILURE, errno, "sigaction");
+
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ error(EXIT_FAILURE, errno, "fork");
+ case 0:
+ close(notify_sock);
+ dup2(nslcd_sock, 3);
+ if (nslcd_sock != 3)
+ close(nslcd_sock);
+ pid = getpid();
+ setenv("LISTEN_PID", xasprintfa("%ld", (long)pid), 1);
+ setenv("LISTEN_FDS", "1", 1);
+ setenv("NOTIFY_SOCKET", notify_sockname, 1);
+ execl(argv[1], argv[1], NULL);
+ error(EXIT_FAILURE, errno, "execl");
+ }
+
+ while (1) {
+ union addr client_addr;
+ socklen_t client_size;
+ char buf[4097];
+ ssize_t bytes_read = recvfrom(notify_sock, buf, sizeof(buf)-1, 0, &client_addr.gen, &client_size);
+ if (bytes_read < 1)
+ error(EXIT_FAILURE, errno, "recvfrom");
+ if (buf[bytes_read-1] != '\n') {
+ buf[bytes_read] = '\n';
+ bytes_read++;
+ }
+ ssize_t bytes_written = 0;
+ while (bytes_written < bytes_read) {
+ ssize_t n = write(2, &(buf[bytes_written]), bytes_read-bytes_written);
+ if (n < 0) {
+ bytes_written = -1;
+ break;
+ }
+ bytes_written += n;
+ }
+ if (bytes_written < 0)
+ error(EXIT_FAILURE, errno, "write");
+ }
+ error(EXIT_FAILURE, 0, "not reached");
+ return EXIT_FAILURE;
+}
diff --git a/bin/pacman-make-keyring b/bin/pacman-make-keyring
new file mode 100755
index 0000000..702ea69
--- /dev/null
+++ b/bin/pacman-make-keyring
@@ -0,0 +1,167 @@
+#!/usr/bin/make -rRf
+# Usage: pacman-make-keyring V=$(date -u +%Y%m%d)
+
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+ifeq ($(origin V),undefined)
+$(info Usage: pacman-make-keyring V=$$(date -u +%Y%m%d))
+$(error You must set V= on the command line)
+endif
+
+bin := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+yamldir := $(shell ruby -e "load '$(bin)/common.rb'; print cfg['yamldir']")
+cachedir := $(shell ruby -e "load '$(bin)/common.rb'; print cfg['keyring_cachedir']")
+
+outputdir = $(cachedir)/$(KEYRING_NAME)-keyring-$(V)
+KEYRING_NAME = parabola
+
+all: $(KEYRING_NAME)-keyring-$(V).tar.gz
+.PHONY: all
+
+export SHELL = /bin/bash -o pipefail
+.PHONY: FORCE
+.SECONDARY:
+.DELETE_ON_ERROR:
+
+dirs = \
+ $(outputdir) \
+ $(cachedir) \
+ $(cachedir)/gpghome \
+ $(cachedir)/keys/trusted \
+ $(cachedir)/keys/secondary \
+ $(cachedir)/keys/revoked
+
+$(dirs):
+ mkdir -p $@
+
+$(cachedir)/var.%: FORCE | $(cachedir)
+ @$(file >$(@D)/tmp.$(@F),$($*))
+ @sed -i 's|^|#|' $(@D)/tmp.$(@F)
+ @if cmp -s $(@D)/tmp.$(@F) $@; then \
+ rm -f $(@D)/tmp.$(@F) || :; \
+ else \
+ mv -f $(@D)/tmp.$(@F) $@; \
+ fi
+-include $(wildcard $(cachedir)/var.*)
+$(cachedir)/txt.%: $(cachedir)/var.%
+ sed 's|^#||' < $< > $@
+var=$(cachedir)/var.
+
+keyring-files = \
+ $(outputdir)/Makefile \
+ $(outputdir)/${KEYRING_NAME}.gpg \
+ $(outputdir)/${KEYRING_NAME}-trusted \
+ $(outputdir)/${KEYRING_NAME}-revoked
+
+$(KEYRING_NAME)-keyring-$(V).tar.gz: %.tar.gz: $(keyring-files)
+ bsdtar --format=ustar -cf - -C $(cachedir) $(addprefix $*/,$(notdir $^)) | gzip -9 > $@
+
+define Makefile.in
+V=@V@
+
+prefix = /usr/local
+PREFIX = $$(prefix)
+
+install:
+ install -dm755 $$(DESTDIR)$$(PREFIX)/share/pacman/keyrings/
+ install -m0644 @KEYRING_NAME@{.gpg,-trusted,-revoked} $$(DESTDIR)$$(PREFIX)/share/pacman/keyrings/
+
+uninstall:
+ rm -f $$(DESTDIR)$$(PREFIX)/share/pacman/keyrings/@KEYRING_NAME@{.gpg,-trusted,-revoked}
+ rmdir -p --ignore-fail-on-non-empty $$(DESTDIR)$$(PREFIX)/share/pacman/keyrings/
+
+.PHONY: install uninstall
+endef
+
+$(outputdir)/Makefile: $(cachedir)/txt.Makefile.in $(var)V $(var)KEYRING_NAME | $(outputdir)
+ sed $(foreach v,$(patsubst $(var)%,%,$(filter $(var)%,$^)), -e 's|@$v@|$($v)|' ) < $< > $@
+
+
+users := $(sort $(shell find $(yamldir))) $(var)users
+
+# Assemble the list of .asc files needed to generate the keyring
+$(cachedir)/deps.mk: ${users} $(var)outputdir $(var)cachedir $(var)KEYRING_NAME| $(cachedir)
+ { \
+ echo $(outputdir)/${KEYRING_NAME}.gpg: $$($(bin)/pgp-list-keyids | sed -r 's|(\S+) .*|$$(cachedir)/keys/\1.asc|') && \
+ echo $(cachedir)/stamp.ownertrust: $$($(bin)/pgp-list-keyids | sed -rn 's|^(trusted/\S+) .*|$$(cachedir)/keys/\1.asc|p') && \
+ $(bin)/pgp-list-keyids | sed -rn 's|^trusted/(\S+) (.*)|keyid.\1 = \2|p' && \
+ $(bin)/uid-map | sed 's|.*|trusted:&\nsecondary:&\nrevoked:&|' | sed -r 's|(.*):(.*):(.*)|$$(cachedir)/keys/\1/\3.asc: $$(yamldir)/\2.yml|' && \
+ :; }> $@
+-include $(cachedir)/deps.mk
+
+# The remainder of file is mostly just a translation of the shell
+# script `update-keys`.
+#
+# https://git.archlinux.org/archlinux-keyring.git/tree/update-keys
+
+export LANG=C
+
+KEYSERVER = hkp://pool.sks-keyservers.net
+
+GPG = gpg --quiet --batch --no-tty --no-permission-warning --keyserver ${KEYSERVER} --homedir $(cachedir)/gpghome
+
+define gpg-init
+%echo Generating Parabola Keyring keychain master key...
+Key-Type: RSA
+Key-Length: 1024
+Key-Usage: sign
+Name-Real: Parabola Keyring Keychain Master Key
+Name-Email: parabola-keyring@localhost
+Expire-Date: 0
+%no-protection
+%commit
+%echo Done
+endef
+$(cachedir)/stamp.gpg-init: $(cachedir)/txt.gpg-init $(var)GPG | $(cachedir)/gpghome
+ ${GPG} --gen-key < $<
+ touch $@
+
+# The appropriate ${uid}.yml file is added as a dependency to
+# ${username}.yml by deps.mk
+keyid=$(keyid.$(patsubst %.asc,%,$(notdir $@)))
+
+# In 'update-keys', this is the 'master-keyids' loop
+$(outputdir)/${KEYRING_NAME}-trusted: ${users} | $(outputdir)
+ $(bin)/pgp-list-keyids | sed -rn 's|^trusted/\S+ (\S+)|\1:4:|p' > $@
+$(cachedir)/keys/trusted/%.asc : $(cachedir)/stamp.gpg-init | $(cachedir)/keys/trusted
+ ${GPG} --recv-keys ${keyid} &>/dev/null
+ printf 'minimize\nquit\ny\n' | ${GPG} --command-fd 0 --edit-key ${keyid}
+ printf 'y\ny\n' | ${GPG} --command-fd 0 --lsign-key ${keyid} &>/dev/null
+ ${GPG} --armor --no-emit-version --export ${keyid} > $@
+
+$(cachedir)/stamp.ownertrust: $(outputdir)/${KEYRING_NAME}-trusted $(cachedir)/deps.mk
+ ${GPG} --import-ownertrust < $< 2>/dev/null
+ touch $@
+
+# In 'update-keys', this is the 'packager-keyids' loop
+$(cachedir)/keys/secondary/%.asc: $(cachedir)/stamp.ownertrust | $(cachedir)/keys/secondary
+ ${GPG} --recv-keys ${keyid} &>/dev/null
+ printf 'clean\nquit\ny\n' | ${GPG} --command-fd 0 --edit-key ${keyid}
+ ${GPG} --list-keys --with-colons ${keyid} 2>/dev/null | grep -q '^pub:f:' # make sure it is trusted
+ ${GPG} --armor --no-emit-version --export ${keyid} > $@
+
+# In 'update-keys', this is the 'packager-revoked-keyids' loop
+$(outputdir)/${KEYRING_NAME}-revoked: ${users} | $(outputdir)
+ $(bin)/pgp-list-keyids | sed -rn 's|^revoked/\S+ ||p' > $@
+$(cachedir)/keys/revoked/%.asc : $(cachedir)/stamp.ownertrust | $(cachedir)/keys/revoked
+ ${GPG} --recv-keys ${keyid} &>/dev/null
+ printf 'clean\nquit\ny\n' | ${GPG} --command-fd 0 --edit-key ${keyid}
+ ! ${GPG} --list-keys --with-colons ${keyid} 2>/dev/null | grep -q '^pub:f:' # make sure it isn't trusted
+ ${GPG} --armor --no-emit-version --export ${keyid} > $@
+
+$(outputdir)/${KEYRING_NAME}.gpg: $(cachedir)/deps.mk | $(outputdir)
+ cat $(filter %.asc,$^) > $@
diff --git a/bin/pgp-list-keyids b/bin/pgp-list-keyids
new file mode 100755
index 0000000..749cb7b
--- /dev/null
+++ b/bin/pgp-list-keyids
@@ -0,0 +1,37 @@
+#!/usr/bin/env ruby
+# Usage: pgp-list-keyids
+
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+load_all_users.each do |uid,user|
+ if user["groups"]
+ if user["groups"].include?("keyring-trusted")
+ puts "trusted/#{user["username"]} #{user["pgp_keyid"]}"
+ elsif user["groups"].include?("keyring-secondary")
+ puts "secondary/#{user["username"]} #{user["pgp_keyid"]}"
+ elsif user["pgp_keyid"]
+ #puts "revoked/#{user["username"]} #{user["pgp_keyid"]}"
+ end
+ end
+ if user["pgp_revoked_keyids"]
+ user["pgp_revoked_keyids"].each do |keyid|
+ puts "revoked/#{user["username"]} #{keyid}"
+ end
+ end
+end
diff --git a/bin/postfix-generate-virtual-map b/bin/postfix-generate-virtual-map
new file mode 100755
index 0000000..f2fb8ec
--- /dev/null
+++ b/bin/postfix-generate-virtual-map
@@ -0,0 +1,34 @@
+#!/usr/bin/env ruby
+# Usage: postfix-show-virtual-map > ${file} && postmap hash:${file}
+
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+users = load_all_users.values.find_all{|u|u["groups"].include?("email")}
+
+users.each do |user|
+ if user["email"] and user["email"].length > 0
+ if user["email"][0] =~ /.*@parabola.nu$/
+ if user["email"].length > 1
+ puts "#{user["username"]}@parabola.nu #{user["email"][1]}"
+ end
+ else
+ puts "#{user["username"]}@parabola.nu #{user["email"][0]}"
+ end
+ end
+end
diff --git a/bin/ssh-list-authorized-keys b/bin/ssh-list-authorized-keys
new file mode 100755
index 0000000..5364ac2
--- /dev/null
+++ b/bin/ssh-list-authorized-keys
@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+# Usage: ssh-list-authorized-keys [username]
+
+# Copyright 2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+all_users = load_all_users.values
+
+groupnames = ARGV & cfg["ssh_pseudo_users"]
+usernames = ARGV & all_users.map{|u|u["username"]}
+
+users = all_users.find_all{|u|
+ # [ username was listed ] or [ the user is in a listed group ]
+ usernames.include?(u["username"]) or not (u["groups"] & groupnames).empty?
+}
+
+# Buffer the output to avoid EPIPE when the reader hangs up early
+output=""
+users.each do |user|
+ if user["ssh_keys"]
+ user["ssh_keys"].each do |addr,key|
+ output+="#{key} #{user["fullname"]} (#{user["username"]}) <#{addr}>\n"
+ end
+ end
+end
+print output
diff --git a/bin/uid-map b/bin/uid-map
new file mode 100755
index 0000000..e759c30
--- /dev/null
+++ b/bin/uid-map
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+# Usage: uid-map
+
+# Copyright 2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+#
+# 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
+# <http://www.gnu.org/licenses/>.
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+load_all_users.each do |uid,user|
+ puts "#{uid}:#{user["username"]}"
+end
diff --git a/common.mk b/common.mk
new file mode 100644
index 0000000..41f5c79
--- /dev/null
+++ b/common.mk
@@ -0,0 +1,55 @@
+# Copyright © 2015 Luke Shumaker
+# This work is free. You can redistribute it and/or modify it under the
+# terms of the Do What The Fuck You Want To Public License, Version 2,
+# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
+
+rel = $(patsubst $(abspath .)/%,./%,$(abspath $1))
+
+all: build
+.PHONY: all
+
+include $(addsuffix /Makefile,$(subdirs))
+
+generate: $(generate)
+.PHONY: generate
+
+configure: generate $(configure)
+.PHONY: configure
+
+build: configure $(build)
+.PHONY: build
+
+install: build $(install)
+.PHONY: install
+
+# un-build
+clean:
+ rm -rf -- $(build) $(build_secondary)
+.PHONY: clean
+
+# un-configure
+distclean: clean
+ rm -rf -- $(configure) $(configure_secondary)
+.PHONY: distclean
+
+# un-generate
+maintainer-clean: distclean
+ rm -rf -- $(generate) $(generate_secondary)
+.PHONY: maintainer-clean
+
+# un-install
+uninstall:
+ rm -f -- $(install)
+ rmdir -p -- $(sort $(dir $(install))) 2>/dev/null || true
+.PHONY: uninstall
+
+
+# Now, this is magic. It stores the values of environment variables,
+# so that if you change them in a way that would cause something to be
+# rebuilt, then Make knows.
+.var.%: FORCE
+ $(Q)printf '%s' '$($*)' > .tmp$@ && { cmp -s .tmp$@ $@ && rm -f -- .tmp$@ || mv -Tf .tmp$@ $@; } || { rm -f -- .tmp$@; false; }
+
+.DELETE_ON_ERROR:
+.SECONDARY:
+.PHONY: FORCE
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..5815d64
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,19 @@
+ifeq ($(origin topsrcdir),undefined)
+topoutdir := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+topsrcdir := $(topoutdir)
+
+PACKAGE = parabola-hackers
+VERSION = 20170117
+
+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
+CFLAGS += -Wall -Wextra -Werror -pedantic
+CC = gcc -std=c99
+.LIBPATTERNS = lib%.so lib%.a
+
+endif
diff --git a/config.mk.in b/config.mk.in
deleted file mode 100644
index be95ab4..0000000
--- a/config.mk.in
+++ /dev/null
@@ -1,198 +0,0 @@
-# Copyright (C) 2016-2017 Luke Shumaker
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program 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 Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# This is based on §7.2 "Makefile Conventions" of the July 25, 2016
-# release of the GNU Coding Standards.
-#
-# Grep for '^##' in this file to see which Autoconf macros it depends
-# on.
-
-dist.pkgname = @PACKAGE_TARNAME@
-gnuconf.pkgname = @PACKAGE_NAME@
-
-# 7.2.2: Utilities in Makefiles
-# -----------------------------
-
-# It's ok to hard-code these commands in rules, but who wants to
-# memorize the list of what's ok?
-
-## AC_PROG_AWK
-## AC_PROG_GREP
-## AC_PROG_EGREP
-## AC_PROG_SED
-
-AWK = @AWK@
-CAT = cat
-CMP = cmp
-CP = cp
-DIFF = diff
-ECHO = echo
-EGREP = @EGREP@
-EXPR = expr
-FALSE = false
-GREP = @GREP@
-INSTALL_INFO = install-info
-LN = ln
-LS = ls
-MKDIR = mkdir
-MV = mv
-PRINTF = printf
-PWD = pwd
-RM = rm
-RMDIR = rmdir
-SED = @SED@
-SLEEP = sleep
-SORT = sort
-TAR = tar
-TEST = test
-TOUCH = touch
-TR = tr
-TRUE = true
-
-# 7.2.2: Utilities in Makefiles/7.2.3: Variables for Specifying Commands
-# ----------------------------------------------------------------------
-
-# Standard user-configurable programs.
-#
-# The list of programs here is specified in §7.2.2, but the associated FLAGS
-# variables are specified in §7.2.3. I found it cleaner to list them together.
-
-## AC_PROG_INSTALL # @INSTALL@ @INSTALL_PROGRAM@ @INSTALL_SCRIPT@ @INSTALL_DATA@
-## AC_PROG_LEX # @LEX@ @LEXLIB@
-## AC_PROG_RANLIB
-## AC_PROG_YACC
-## AC_PROG_CC
-#
-# TODO: What causes Autoconf to define @AR@?
-
-AR = @AR@
-ARFLAGS =
-BISON = bison
-BISONFLAGS =
-CC = @CC@
-CFLAGS = @CFLAGS@ # CFLAGS instead of CCFLAGS
-FLEX = flex
-FLEXFLAGS =
-INSTALL = @INSTALL@
-# There is no INSTALLFLAGS[0]
-LD = ld
-LDFLAGS = @LDFLAGS@
-LDCONFIG = ldconfig # TODO[1]
-LDCONFIGFLAGS =
-LEX = @LEX@
-LFLAGS = #LFLAGS instead of LEXFLAGS
-#MAKE
-MAKEINFO = makeinfo
-MAKEINFOFLAGS =
-RANLIB = @RANLIB@
-RANLIBFLAGS =
-TEXI2DVI = texi2dvi
-TEXI2DVIFLAGS =
-YACC = @YACC@
-YFLAGS = # YFLAGS instead of YACCFLAGS
-
-CPPFLAGS = @CPPFLAGS@
-
-LN_S = @LN_S@
-
-CHGRP = chgrp
-CHGRPFLAGS =
-CHMOD = chmod
-CHMODFLAGS =
-CHOWN = chown
-CHOWNFLAGS =
-MKNOD = mknod
-MKNODFLAGS =
-
-# [0]: There is no INSTALLFLAGS because it would be inconsistent with how the
-# standards otherwise recommend using $(INSTALL); with INSTALL_PROGRAM and
-# INSTALL_DATA; which are specified in a way precluding the use of
-# INSTALLFLAGS. To have the variable, but to ignore it in the common case
-# would be confusing.
-#
-# [1]: The RANLIB and LDCONFIG variables need some extra smarts; §7.2.2 says:
-#
-# > When you use ranlib or ldconfig, you should make sure nothing bad
-# > happens if the system does not have the program in question. Arrange
-# > to ignore an error from that command, and print a message before the
-# > command to tell the user that failure of this command does not mean a
-# > problem. (The Autoconf ‘AC_PROG_RANLIB’ macro can help with this.)
-
-# 7.2.3: Variables for Specifying Commands
-# ----------------------------------------
-
-INSTALL_PROGRAM = @INSTALL_PROGRAM@
-INSTALL_DATA = @INSTALL_DATA@
-
-# 7.2.5: Variables for Installation Directories
-# ---------------------------------------------
-
-# Root for the installation
-prefix = @prefix@
-exec_prefix = @exec_prefix@
-# Executable programs
-bindir = @bindir@
-sbindir = @sbindir@
-libexecdir = @libexecdir@
-# Data files (Autoconf won't support runstatedir until version 2.70)
-datarootdir = @datarootdir@
-datadir = @datadir@
-sysconfdir = @sysconfdir@
-sharedstatedir = @sharedstatedir@
-localstatedir = @localstatedir@
-runstatedir = $(localstatedir)/run
-# Specific types of files
-includedir = @includedir@
-oldincludedir = @oldincludedir@
-docdir = @docdir@
-infodir = @infodir@
-htmldir = @htmldir@
-dvidir = @dvidir@
-pdfdir = @pdfdir@
-psdir = @psdir@
-libdir = @libdir@
-lispdir = $(datarootdir)/emacs/site-lisp
-localedir = @localedir@
-
-mandir = @mandir@
-man1dir = $(mandir)/man1
-man2dir = $(mandir)/man2
-man3dir = $(mandir)/man3
-man4dir = $(mandir)/man4
-man5dir = $(mandir)/man5
-man6dir = $(mandir)/man6
-man7dir = $(mandir)/man7
-man8dir = $(mandir)/man8
-
-manext = .1
-man1ext = .1
-man2ext = .2
-man3ext = .3
-man4ext = .4
-man5ext = .5
-man6ext = .6
-man7ext = .7
-man8ext = .8
-
-# 7.2.7: Install Command Categories
-# ---------------------------------
-
-PRE_INSTALL =
-POST_INSTALL =
-NORMAL_INSTALL =
-
-PRE_UNINSTALL =
-POST_UNINSTALL =
-NORMAL_UNINSTALL =
diff --git a/go/git.lukeshu.com/go/libgnulinux b/go/git.lukeshu.com/go/libgnulinux
new file mode 160000
+Subproject b2bae3c73817740b48a4698c6aa863d70234a64
diff --git a/go/git.lukeshu.com/go/libnslcd b/go/git.lukeshu.com/go/libnslcd
new file mode 160000
+Subproject 939ef442f33dadf17f60ebf9f0c1fbaaa27f0c6
diff --git a/go/git.lukeshu.com/go/libsystemd b/go/git.lukeshu.com/go/libsystemd
new file mode 160000
+Subproject 1ac9db65fda0693d13336e6471a858914348f2f
diff --git a/go/golang.org/x/sys b/go/golang.org/x/sys
new file mode 160000
+Subproject d75a52659825e75fff6158388dddc6a5b04f9ba
diff --git a/go/gopkg.in/yaml.v2 b/go/gopkg.in/yaml.v2
new file mode 160000
+Subproject f7716cbe52baa25d2e9b0d0da546fcf909fc16b
diff --git a/go/nshd/.gitignore b/go/nshd/.gitignore
new file mode 100644
index 0000000..00870e2
--- /dev/null
+++ b/go/nshd/.gitignore
@@ -0,0 +1 @@
+/main.go
diff --git a/go/nshd/main.go.in b/go/nshd/main.go.in
new file mode 100644
index 0000000..5564128
--- /dev/null
+++ b/go/nshd/main.go.in
@@ -0,0 +1,33 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// Command nshd is an implementation of nslcd that talks to
+// hackers.git instead of LDAP.
+package main
+
+import (
+ "os"
+ "nshd/nslcd_backend"
+
+ "git.lukeshu.com/go/libnslcd/nslcd_systemd"
+)
+
+func main() {
+ backend := &nslcd_backend.Hackers{
+ CfgFilename: "@conf_file@",
+ }
+ os.Exit(int(nslcd_systemd.Main(backend)))
+}
diff --git a/go/nshd/nshd_files/.gitignore b/go/nshd/nshd_files/.gitignore
new file mode 100644
index 0000000..3be3f08
--- /dev/null
+++ b/go/nshd/nshd_files/.gitignore
@@ -0,0 +1,2 @@
+/users.go
+/passwords.go
diff --git a/go/nshd/nshd_files/passwords.go.in b/go/nshd/nshd_files/passwords.go.in
new file mode 100644
index 0000000..679f7c0
--- /dev/null
+++ b/go/nshd/nshd_files/passwords.go.in
@@ -0,0 +1,97 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nshd_files
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sort"
+ "strings"
+
+ "git.lukeshu.com/go/libgnulinux/crypt"
+ "git.lukeshu.com/go/libsystemd/sd_daemon"
+)
+
+/* Note that the password hash value should be one of:
+ <empty> - 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 */
+
+const shadow_file = "@shadow_file@"
+
+func LoadAllPasswords() (map[string]string, error) {
+ file, err := os.Open(shadow_file)
+ if err != nil {
+ return nil, err
+ }
+ contents, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+ lines := strings.Split(string(contents), "\n")
+ passwords := make(map[string]string, len(lines))
+ for i, line := range lines {
+ if line == "" {
+ continue
+ }
+ cols := strings.SplitN(line, ":", 2)
+ if len(cols) != 2 {
+ sd_daemon.Log.Err(fmt.Sprintf("hackers.git %s:%d: malformed line", shadow_file, i+1))
+ continue
+ }
+ username := cols[0]
+ hash := cols[1]
+ if hash != "!" && !crypt.SaltOk(hash) {
+ hash = "!"
+ sd_daemon.Log.Err(fmt.Sprintf("%s:%d: malformed hash for user: %s", shadow_file, i+1, username))
+ }
+ 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])
+ }
+ err = file.Sync()
+ if err != nil {
+ return err
+ }
+ err = file.Close()
+ if err != nil {
+ return err
+ }
+
+ return os.Rename(shadow_file+"-", shadow_file)
+}
diff --git a/go/nshd/nshd_files/users.go.in b/go/nshd/nshd_files/users.go.in
new file mode 100644
index 0000000..51703fd
--- /dev/null
+++ b/go/nshd/nshd_files/users.go.in
@@ -0,0 +1,143 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nshd_files
+
+import (
+ "fmt"
+ "os/exec"
+
+ "nshd/util"
+
+ yaml "gopkg.in/yaml.v2"
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+ "git.lukeshu.com/go/libsystemd/sd_daemon"
+)
+
+/* Note that the password hash value should be one of:
+ <empty> - 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 */
+
+type User struct {
+ Passwd p.Passwd
+ Groups []string
+}
+
+func LoadAllUsers() (users map[int32]User, err error) {
+ contents, err := exec.Command("@bindir@/meta-cat").Output()
+ if err != nil {
+ return
+ }
+
+ var _data interface{}
+ err = yaml.Unmarshal(contents, &_data)
+ if err != nil {
+ return
+ }
+
+ data, isMap := _data.(map[interface{}]interface{})
+ errs := []string{}
+ if !isMap {
+ errs = append(errs, "root node is not a map")
+ } else {
+ users = make(map[int32]User, len(data))
+ for _uid, _user := range data {
+ uid, isInt := _uid.(int)
+ if !isInt {
+ errs = append(errs, fmt.Sprintf("UID is not an int: %T ( %#v )", _uid, _uid))
+ continue
+ }
+ user, _err := parseUser(_user)
+ if _err != nil {
+ errs = append(errs, fmt.Sprintf("Could not parse data for UID %d: %v", uid, _err))
+ continue
+ }
+ user.Passwd.UID = int32(uid)
+ sd_daemon.Log.Debug(fmt.Sprintf("hackers.git: -> User %d(%s) parsed", user.Passwd.UID, user.Passwd.Name))
+ users[user.Passwd.UID] = user
+ }
+ }
+ if len(errs) > 0 {
+ users = nil
+ err = &yaml.TypeError{Errors: errs}
+ }
+ return
+}
+
+func parseUser(_data interface{}) (ret User, err error) {
+ data, isMap := _data.(map[interface{}]interface{})
+ errs := []string{}
+ if !isMap {
+ errs = append(errs, "root node is not a map")
+ } else {
+ if iface, isSet := data["username"]; !isSet {
+ errs = append(errs, "\"username\" is not set")
+ } else if str, isTyp := iface.(string); !isTyp {
+ errs = append(errs, "\"username\" is not a string")
+ } else {
+ ret.Passwd.Name = str
+ ret.Passwd.HomeDir = "/home/" + str
+ }
+
+ if iface, isSet := data["fullname"]; !isSet {
+ errs = append(errs, "\"fullname\" is not set")
+ } else if str, isTyp := iface.(string); !isTyp {
+ errs = append(errs, "\"fullname\" is not a string")
+ } else {
+ ret.Passwd.GECOS = str
+ }
+
+ if iface, isSet := data["shell"]; !isSet {
+ errs = append(errs, "\"shell\" is not set")
+ } else if str, isTyp := iface.(string); !isTyp {
+ errs = append(errs, "\"shell\" is not a string")
+ } else {
+ ret.Passwd.Shell = str
+ }
+
+ if iface, isSet := data["groups"]; !isSet {
+ ret.Groups = make([]string, 0)
+ } else if ary, isTyp := iface.([]interface{}); !isTyp {
+ errs = append(errs, "\"groups\" is not an array")
+ } else {
+ groups := make(map[string]bool, len(ary))
+ e := false
+ for _, iface := range ary {
+ if str, isTyp := iface.(string); !isTyp {
+ errs = append(errs, "\"group\" item is not an array")
+ e = true
+ break
+ } else {
+ groups[str] = true
+ }
+ }
+ if !e {
+ ret.Groups = util.Set2list(groups)
+ }
+ }
+ }
+ if len(errs) > 0 {
+ err = &yaml.TypeError{Errors: errs}
+ }
+
+ ret.Passwd.PwHash = string("x") // look in shadow for the password hash
+ ret.Passwd.GID = -1
+
+ return
+}
diff --git a/go/nshd/nslcd_backend/db_config.go b/go/nshd/nslcd_backend/db_config.go
new file mode 100644
index 0000000..d00bf02
--- /dev/null
+++ b/go/nshd/nslcd_backend/db_config.go
@@ -0,0 +1,39 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+ s "golang.org/x/sys/unix"
+)
+
+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/nshd/nslcd_backend/db_group.go b/go/nshd/nslcd_backend/db_group.go
new file mode 100644
index 0000000..04c7e3b
--- /dev/null
+++ b/go/nshd/nslcd_backend/db_group.go
@@ -0,0 +1,141 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ "nshd/util"
+
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+ s "golang.org/x/sys/unix"
+)
+
+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 = util.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 = util.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/nshd/nslcd_backend/db_pam.go b/go/nshd/nslcd_backend/db_pam.go
new file mode 100644
index 0000000..b704620
--- /dev/null
+++ b/go/nshd/nslcd_backend/db_pam.go
@@ -0,0 +1,204 @@
+// Copyright 2015-2016 Luke Shumaker <git.lukeshu@sbcglobal>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ "fmt"
+ "os"
+
+ "nshd/util"
+ "nshd/nshd_files"
+
+ s "golang.org/x/sys/unix"
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+
+ "git.lukeshu.com/go/libgnulinux/crypt"
+ "git.lukeshu.com/go/libsystemd/sd_daemon"
+)
+
+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 := util.RandomString(crypt.SaltAlphabet, 8)
+ if err != nil {
+ sd_daemon.Log.Err("Could not generate a random string")
+ str = ""
+ }
+ salt = "$6$" + str + "$"
+ }
+ return crypt.Crypt(newPassword, salt)
+}
+
+func dirExists(path string) bool {
+ stat, err := os.Stat(path)
+ if err != nil {
+ return false
+ }
+ return stat.IsDir()
+}
+
+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)
+
+ if len(req.UserName) == 0 && len(req.Password) == 0 && cred.Uid == 0 {
+ ret <- p.PAM_Authentication{
+ AuthenticationResult: p.NSLCD_PAM_SUCCESS,
+ UserName: "",
+ AuthorizationResult: p.NSLCD_PAM_SUCCESS,
+ AuthorizationError: "",
+ }
+ return
+ }
+
+ 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 := util.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 && cred.Uid == 0 {
+ goto update
+ }
+ // special hack: if the old password is not
+ // set, but the home directory exists, let the
+ // user set their password
+ if user.Passwd.PwHash == "!" && dirExists(user.Passwd.HomeDir) {
+ goto update
+ }
+ 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:
+ if len(req.NewPassword) == 0 {
+ ret <- p.PAM_PwMod{
+ Result: p.NSLCD_PAM_PERM_DENIED,
+ Error: "password cannot be empty",
+ }
+ return
+ }
+
+ // Update the PwHash in memory
+ user.Passwd.PwHash = hashPassword(req.NewPassword, user.Passwd.PwHash)
+ if len(user.Passwd.PwHash) == 0 {
+ sd_daemon.Log.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 := nshd_files.SaveAllPasswords(passwords)
+ if err != nil {
+ sd_daemon.Log.Err(fmt.Sprintf("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/nshd/nslcd_backend/db_passwd.go b/go/nshd/nslcd_backend/db_passwd.go
new file mode 100644
index 0000000..ace127e
--- /dev/null
+++ b/go/nshd/nslcd_backend/db_passwd.go
@@ -0,0 +1,88 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+ s "golang.org/x/sys/unix"
+)
+
+/* Note that the output password hash value should be one of:
+ <empty> - 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 (i.e., in the shadow database)
+ other - encrypted password, in crypt(3) format
+
+ A "!" prefix on a password hash "locks" the account; therefore a a
+ hash of "!" says "no login"; while a hash of "x" says "you may log
+ in", but fails to authorize; passing the buck to the next database.
+
+ */
+
+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/nshd/nslcd_backend/db_shadow.go b/go/nshd/nslcd_backend/db_shadow.go
new file mode 100644
index 0000000..ab1d68f
--- /dev/null
+++ b/go/nshd/nslcd_backend/db_shadow.go
@@ -0,0 +1,77 @@
+// Copyright 2015-2016 Luke Shumaker <git.lukeshu@sbcglobal>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ p "git.lukeshu.com/go/libnslcd/nslcd_proto"
+ s "golang.org/x/sys/unix"
+)
+
+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/nshd/nslcd_backend/hackers.go b/go/nshd/nslcd_backend/hackers.go
new file mode 100644
index 0000000..44107b2
--- /dev/null
+++ b/go/nshd/nslcd_backend/hackers.go
@@ -0,0 +1,124 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+// Package nslcd_backend is an nslcd_server Backend that
+// speaks to hackers.git.
+package nslcd_backend
+
+import (
+ "fmt"
+ "sync"
+
+ "nshd/nshd_files"
+
+ "git.lukeshu.com/go/libnslcd/nslcd_server"
+ "git.lukeshu.com/go/libnslcd/nslcd_systemd"
+ "git.lukeshu.com/go/libsystemd/sd_daemon"
+)
+
+type config struct {
+ Pam_password_prohibit_message string
+}
+
+type Hackers struct {
+ nslcd_server.NilBackend
+ lock sync.RWMutex
+
+ CfgFilename string
+
+ cfg config
+ users map[int32]nshd_files.User
+ groups map[string]map[string]bool
+}
+
+var _ nslcd_systemd.Backend = &Hackers{}
+var _ nslcd_server.Backend = &Hackers{}
+
+func (o *Hackers) Init() error {
+ sd_daemon.Log.Debug(fmt.Sprintf("hackers.git: CfgFilename = %v", o.CfgFilename))
+ err := o.Reload()
+ if err != nil {
+ sd_daemon.Log.Err(fmt.Sprintf("hackers.git: Could not initialize: %v", err))
+ return err
+ }
+ return nil
+}
+
+func (o *Hackers) Close() {
+ sd_daemon.Log.Info("hackers.git: Closing session")
+ o.lock.Lock()
+ defer o.lock.Unlock()
+
+ o.users = make(map[int32]nshd_files.User, 0)
+ o.groups = make(map[string]map[string]bool)
+}
+
+func (o *Hackers) Reload() error {
+ sd_daemon.Log.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
+ }
+ sd_daemon.Log.Info(fmt.Sprintf("hackers.git: pam_password_prohibit_message: %#v", o.cfg.Pam_password_prohibit_message))
+
+ sd_daemon.Log.Debug("hackers.git: Parsing user data")
+ o.users, err = nshd_files.LoadAllUsers()
+ if err != nil {
+ return err
+ }
+
+ passwords, err := nshd_files.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/nshd/nslcd_backend/util.go b/go/nshd/nslcd_backend/util.go
new file mode 100644
index 0000000..aa29b03
--- /dev/null
+++ b/go/nshd/nslcd_backend/util.go
@@ -0,0 +1,58 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package nslcd_backend
+
+import (
+ "io/ioutil"
+ "os"
+
+ yaml "gopkg.in/yaml.v2"
+ "git.lukeshu.com/go/libgnulinux/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
+}
diff --git a/go/nshd/util/util.go b/go/nshd/util/util.go
new file mode 100644
index 0000000..4d3fd57
--- /dev/null
+++ b/go/nshd/util/util.go
@@ -0,0 +1,47 @@
+// Copyright 2015-2016 Luke Shumaker <lukeshu@sbcglobal.net>.
+//
+// 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
+// <http://www.gnu.org/licenses/>.
+
+package util
+
+import (
+ "crypto/rand"
+ "math/big"
+)
+
+func RandomString(alphabet string, n uint) (str string, err error) {
+ var alphabet_len = big.NewInt(int64(len(alphabet)))
+ var bigint *big.Int
+ _str := make([]byte, n)
+ for i := 0; i < len(_str); i++ {
+ bigint, err = rand.Int(rand.Reader, alphabet_len)
+ if err != nil {
+ return
+ }
+ _str[i] = alphabet[bigint.Int64()]
+ }
+ str = string(_str[:])
+ return
+}
+
+func Set2list(set map[string]bool) []string {
+ list := make([]string, len(set))
+ i := uint(0)
+ for item, _ := range set {
+ list[i] = item
+ i++
+ }
+ return list
+}
diff --git a/nshd.service.in b/nshd.service.in
new file mode 100644
index 0000000..7760687
--- /dev/null
+++ b/nshd.service.in
@@ -0,0 +1,30 @@
+[Unit]
+Description=Name Service hackers.git Daemon
+Requires=nshd.socket
+Wants=nss-user-lookup.target
+Before=nss-user-lookup.target
+
+[Service]
+Type=notify
+# Use our setuid program instead of setting User= because otherwise it
+# would deadlock as it tries to poll the nslcd socket to ask if it
+# knows who the user is. It would time out after 1 minute, but that's
+# still not good.
+ExecStart=@@bindir@/nshd-setuid nshd @user@ @bindir@/nshd
+ExecReload=/bin/kill -HUP $MAINPID
+
+[Install]
+Also=nshd.socket
+
+# The following is a workaround for a scheduling deadlock in systemd
+# v230 (and probably other versions). What "naturally" happens is
+# that we would be socket-activated by systemd-logind which is
+# dbus-activated during systemd' dbus_init(), which is called when
+# dbus.service starts. However, systemd is buggy and won't activate
+# any services during dbus_init(), so the whole thing deadlocks. A
+# timeout means that boot eventually continues, but we are left with a
+# half-working system.
+[Unit]
+Before=dbus.service
+[Install]
+WantedBy=dbus.service
diff --git a/nshd.socket b/nshd.socket
new file mode 100644
index 0000000..372852b
--- /dev/null
+++ b/nshd.socket
@@ -0,0 +1,12 @@
+[Unit]
+Description=Name Service hackers.git Daemon socket
+Wants=nss-user-lookup.target
+Before=nss-user-lookup.target
+
+[Socket]
+ListenStream=/var/run/nslcd/socket
+PassCredentials=yes
+PassSecurity=yes
+
+[Install]
+WantedBy=sockets.target
diff --git a/nshd.sysusers.in b/nshd.sysusers.in
new file mode 100644
index 0000000..c522b07
--- /dev/null
+++ b/nshd.sysusers.in
@@ -0,0 +1 @@
+u @user@ - "Name Service hackers.git Daemon"
diff --git a/parabola-hackers.yml b/parabola-hackers.yml
new file mode 100644
index 0000000..04c5992
--- /dev/null
+++ b/parabola-hackers.yml
@@ -0,0 +1,45 @@
+---
+# Where to look for "${uid}.yml" files
+yamldir: "/var/lib/hackers-git/users"
+
+# Which groups imply membership in other groups (since UNIX groups
+# can't be nested). Only one level of nesting is supported ATM.
+#
+# That is, if you are in the 'hackers' group, you are also in the
+# 'repo' and 'git' groups, even if they aren't listed.
+groupgroups:
+ hackers:
+ - repo
+ - http
+ - git
+ - bzr
+ - ssh
+ - wheel
+ - log
+ - systemd-journal
+ - email
+ - keyring-trusted
+ fellows:
+ - email
+ trustedusers:
+ - keyring-secondary
+ bots:
+ - keyring-trusted
+
+# Groups that are system users that can be ssh'ed into.
+#
+# So, if 'lukeshu' is in the 'repo' group, he can ssh to
+# 'repo'@hostname.
+ssh_pseudo_users:
+- repo
+- http
+- git
+- bzr
+
+# The message, if any, that is presented to the user when password
+# modification through PAM is prohibited.
+pam_password_prohibit_message: ''
+
+# Where to keep files that can be cached between versions when making
+# the pacman keyring.
+keyring_cachedir: "/var/cache/parabola-hackers"