summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--.gitmodules12
-rw-r--r--LICENSE.txt24
-rw-r--r--Makefile92
-rw-r--r--README.md76
-rw-r--r--build-aux/Makefile.README.txt164
-rw-r--r--build-aux/Makefile.each.head/.gitignore0
-rw-r--r--build-aux/Makefile.each.tail/.gitignore0
-rw-r--r--build-aux/Makefile.each.tail/10-std.mk46
-rw-r--r--build-aux/Makefile.head.mk63
-rw-r--r--build-aux/Makefile.once.head/.gitignore0
-rw-r--r--build-aux/Makefile.once.head/00-gnuconf.mk149
-rw-r--r--build-aux/Makefile.once.head/10-std.mk39
-rw-r--r--build-aux/Makefile.once.tail/.gitignore0
-rw-r--r--build-aux/Makefile.tail.mk51
-rw-r--r--common.mk55
-rw-r--r--golang.mk26
-rw-r--r--nshd.service.in15
-rw-r--r--nshd.socket.in13
-rw-r--r--parabola-hackers.yml40
-rw-r--r--scripts/common.rb22
-rwxr-xr-xscripts/meta-cat6
-rwxr-xr-xscripts/meta-check44
-rwxr-xr-xscripts/meta-normalize-stdio171
-rwxr-xr-xscripts/pacman-make-keyring150
-rwxr-xr-xscripts/pgp-list-keyids21
-rwxr-xr-xscripts/postfix-generate-virtual-map18
-rwxr-xr-xscripts/ssh-list-authorized-keys25
-rwxr-xr-xscripts/uid-map8
m---------src/gopkg.in/yaml.v20
m---------src/lukeshu.com/git/go/libgnulinux.git0
m---------src/lukeshu.com/git/go/libnslcd.git0
m---------src/lukeshu.com/git/go/libsystemd.git0
-rw-r--r--src/nshd/hackers_git/check_password.go50
-rw-r--r--src/nshd/hackers_git/db_config.go44
-rw-r--r--src/nshd/hackers_git/db_group.go144
-rw-r--r--src/nshd/hackers_git/db_pam.go105
-rw-r--r--src/nshd/hackers_git/db_passwd.go86
-rw-r--r--src/nshd/hackers_git/db_shadow.go82
-rw-r--r--src/nshd/hackers_git/gid.go42
-rw-r--r--src/nshd/hackers_git/hackers.go121
-rw-r--r--src/nshd/hackers_git/hackers_parse.go157
-rw-r--r--src/nshd/hackers_git/set.go32
-rw-r--r--src/nshd/main.go37
-rw-r--r--test/.gitignore1
-rw-r--r--test/runner.c168
46 files changed, 2413 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2ffdf1a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+/pkg
+/bin/
+/nshd.service
+/nshd.socket
+.var.*
+.tmp.*
+/LICENSE.*.txt
+*.o
+
+*~
+output/
+cache/
+parabola-keyring-*.tar.gz
+
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5e18a55
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,12 @@
+[submodule "src/gopkg.in/yaml.v2"]
+ path = src/gopkg.in/yaml.v2
+ url = https://gopkg.in/yaml.v2/
+[submodule "src/lukeshu.com/git/go/libgnulinux.git"]
+ path = src/lukeshu.com/git/go/libgnulinux.git
+ url = https://lukeshu.com/git/go/libgnulinux.git/
+[submodule "src/lukeshu.com/git/go/libnslcd.git"]
+ path = src/lukeshu.com/git/go/libnslcd.git
+ url = https://lukeshu.com/git/go/libnslcd.git/
+[submodule "src/lukeshu.com/git/go/libsystemd.git"]
+ path = src/lukeshu.com/git/go/libsystemd.git
+ url = https://lukeshu.com/git/go/libsystemd.git/
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..6031cd7
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,24 @@
+The software in this repository is under a number of licenses:
+
+ scripts/ GPL v2+
+ src/nshd/ GPL v2+
+ test/ LGPL v2.1+
+
+ src/gopkg.in/yaml.v2 LGPL v3, with linking exception
+ src/lukeshu.com/git/go/libgnulinux.git/dl GPL v2+, with documentation output clarification
+ src/lukeshu.com/git/go/libgnulinux.git/inotify LGPL v2.1+
+ src/lukeshu.com/git/go/libgnulinux.git/getgr LGPL v2.1+
+ src/lukeshu.com/git/go/libnslcd.git/ LGPL v2.1+
+ src/lukeshu.com/git/go/libsystemd.git/ Apache v2.0
+
+The general notion is that 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 library uses v3 of the LGPL because it's a 3rd-party
+ library, and that's how the authors licensed it.
+
+For more details, see each individual file.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7f98070
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,92 @@
+# Copyright 2015 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.
+#
+# The GNU General Public License's references to "object code" and
+# "executables" are to be interpreted to also include the output of
+# any document formatting or typesetting system, including
+# intermediate and printed output.
+#
+# 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/>.
+
+MAKEFLAGS += --no-builtin-rules
+
+prefix = /usr/local
+bindir = $(prefix)/bin
+libdir = $(prefix)/lib
+systemddir = $(libdir)/systemd
+
+Q ?= @
+#NET ?= FORCE
+
+user = nshd
+group = nshd
+
+CFLAGS = -std=c99 -Wall -Wextra -Werror -pedantic
+CGO_CFLAGS = $(CFLAGS) -Wno-unused-parameter
+CGO_ENABLED = 1
+
+deps += gopkg.in/yaml.v2
+deps += lukeshu.com/git/go/libgnulinux.git
+deps += lukeshu.com/git/go/libnslcd.git
+deps += lukeshu.com/git/go/libsystemd.git
+
+srcdir := $(abspath $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST)))))
+topdir := $(srcdir)
+
+subdirs = src/lukeshu.com/git/go/libnslcd.git/proto
+
+generate += $(addprefix $(topdir)/src/,$(deps))
+generate += $(topdir)/LICENSE.lgpl-2.1.txt $(topdir)/LICENSE.gpl-2.txt $(topdir)/LICENSE.apache-2.0.txt
+generate_secondary += $(topdir)/src/*.*/
+build += $(topdir)/bin/nshd $(topdir)/nshd.service $(topdir)/nshd.socket $(topdir)/test/runner
+build_secondary += $(topdir)/bin $(topdir)/pkg $(topdir)/test/*.o .var.*
+install += $(addprefix $(DESTDIR),$(bindir)/nshd $(systemddir)/system/nshd.socket $(systemddir)/system/nshd.service)
+
+ifeq (1,$(words $(MAKEFILE_LIST)))
+ include $(topdir)/common.mk
+endif
+src/lukeshu.com/git/go/libnslcd.git/proto/Makefile: $(topdir)/src/lukeshu.com/git/go/libnslcd.git
+
+$(topdir)/LICENSE.lgpl-2.1.txt: $(NET)
+ curl https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt > $@
+$(topdir)/LICENSE.gpl-2.txt: $(NET)
+ curl https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt > $@
+$(topdir)/LICENSE.apache-2.0.txt: $(NET)
+ curl https://www.apache.org/licenses/LICENSE-2.0 > $@
+$(topdir)/LICENSE.wtfpl-2.txt: $(NET)
+ curl http://www.wtfpl.net/txt/copying/ > $@
+
+include $(topdir)/golang.mk
+
+$(call goget,$(topdir),$(deps))
+
+$(topdir)/bin/nshd: $(generate) $(configure) $(call gosrc,$(topdir))
+ $(call goinstall,$(topdir),nshd)
+
+%.o: %.c .var.CC .var.CPPFLAGS .var.CFLAGS
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $(filter-out .var.%,$^)
+%: %.o .var.CC .var.LDFLAGS .var.LOADLIBES .var.LDLIBS
+ $(CC) $(LDFLAGS) -o $@ $(filter-out .var.%,$^) $(LOADLIBES) $(LDLIBS)
+
+%: %.in
+ < $< sed $(foreach v,$(patsubst .var.%,%,$(filter .var.%,$^)), -e 's|@$v@|$($v)|g' ) > $@
+$(topdir)/nshd.service: .var.bindir .var.user .var.group
+$(topdir)/nshd.socket: .var.user .var.group
+
+$(DESTDIR)$(bindir)/%: bin/%
+ install -TDm755 $< $@
+$(DESTDIR)$(systemddir)/system/%.socket: %.socket
+ install -TDm644 $< $@
+$(DESTDIR)$(systemddir)/system/%.service: %.service
+ install -TDm644 $< $@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..824e74d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+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
+
+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.
+
+## 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 (TODO)
+
+`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.
diff --git a/build-aux/Makefile.README.txt b/build-aux/Makefile.README.txt
new file mode 100644
index 0000000..e06ba52
--- /dev/null
+++ b/build-aux/Makefile.README.txt
@@ -0,0 +1,164 @@
+Luke's AutoMake
+===============
+
+Yo, this document is incomplete. It describes the magical
+automake.{head,tail}.mk Makefiles and how to use them, kinda.
+
+I wrote a "clone" of automake. I say clone, because it works
+differently. Yeah, I need a new name for it.
+
+High-level overview
+-------------------
+
+Now, what this does for you is:
+
+It makes it _easy_ to write non-recursive Makefiles--and ones that are
+similar to plain recursive Makefiles, at that! (search for the paper
+"Recursive Make Considered Harmful") As harmful as recursive make is,
+it's historically been difficult to to write non-recursive Makefiles.
+This makes it easy.
+
+It also makes it easy to follow the GNU standards for your makefiles:
+it takes care of this entire table of .PHONY targets for you:
+
+| this | and this | are aliases for this |
+|------+------------------+--------------------------------------------------------|
+| all | build | $(outdir)/build |
+| | install | $(outdir)/install |
+| | uninstall | $(outdir)/uninstall |
+| | mostlyclean | $(outdir)/mostlyclean |
+| | clean | $(outdir)/clean |
+| | distclean | $(outdir)/distclean |
+| | maintainer-clean | $(outdir)/maintainer-clean |
+| | check | $(outdir)/check (not implemented for you) |
+| | dist | $(topoutdir)/$(PACKAGE)-$(VERSION).tar.gz (not .PHONY) |
+
+(You are still responsible for implementing the `$(outdir)/check`
+target in each of your Makefiles.)
+
+What you have to do is:
+
+In each source directory, you write a `Makefile`, very similarly to if
+you were writing for plain GNU Make, with
+
+ topoutdir ?= ...
+ topsrcdir ?= ...
+ include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ # your makefile
+
+ include $(topsrcdir)/build-aux/Makefile.tail.mk
+
+And in the top-level source directory, Write your own helper makefiles
+that get included:
+ - `common.once.head.mk`: before parsing any of your Makefiles
+ - `common.each.head.mk`: before parsing each of your Makefiles
+ - `common.each.tail.mk`: after parsing each of your Makefiles
+ - `common.each.tail.mk`: after parsing all of your Makefiles
+
+The `common.*.mk` makefiles are nice for including generic pattern
+rules and variables that aren't specific to a directory.
+
+You're probably thinking that this sounds too good to be true!
+Unfortunately, there are two major deviations from writing a plain
+recursive Makefile:
+
+ 1. all targets and prerequisites (including .PHONY targets!) need to
+ be prefixed with
+ `$(srcdir)`/`$(outdir)`/`$(topsrcdir)`/`$(topoutdir)`.
+ * sub-gotcha: this means that if a pattern rule has a
+ prerequisite that may be in srcdir or outdir, then it must be
+ specified twice, once for each case.
+ 2. if a prerequisite is in a directory "owned" by another Makefile,
+ you must filter the pathname through `am_path`:
+ `$(call am_path,YOUR_PATH)`. Further, that path must NOT contain
+ a `..` segment; if you need to refer to a sibling directory, do it
+ relative to `$(topoutdir)` or `$(topsrcdir)`.
+
+Telling automake about your program
+-----------------------------------
+
+You tell automake what to do for you by setting some variables. They
+are all prefixed with `am_`; this prefix may be changed by editing the
+`_am` variable at the top of `automake.head.mk`.
+
+The exception to this is the `am_path` variable, which is a macro that
+is used to make a list of filenames relative to the appropriate
+directory, because unlike normal GNU (Auto)Make, `$(outdir)` isn't
+nescessarily equal to `.`. See above.
+
+There are several commands that generate files; simply record the list
+of files that each command generates as the following variable
+variables:
+
+| Variable | Create Command | Delete Command | Description | Relative to |
+|--------------+----------------+-----------------------------+-----------------------------------+-------------|
+| am_src_files | emacs | rm -rf . | Files that the developer writes | srcdir |
+| am_gen_files | ??? | make maintainer-clean | Files the developer compiles | srcdir |
+| am_cfg_files | ./configure | make distclean | Users' compile-time configuration | outdir |
+| am_out_files | make all | make mostlyclean/make clean | Files the user compiles | outdir |
+| am_sys_files | make install | make uninstall | Files the user installs | DESTDIR |
+
+In addition, there are two more variables that control not how files
+are created, but how they are deleted:
+
+| Variable | Affected command | Description | Relative to |
+|----------------+------------------+------------------------------------------------+-------------|
+| am_clean_files | make clean | A list of things to `rm` in addition to the | outdir |
+| | | files in `$(am_out_files)`. (Example: `*.o`) | |
+|----------------+------------------+------------------------------------------------+-------------|
+| am_slow_files | make mostlyclean | A list of things that (as an exception) should | outdir |
+| | | _not_ be deleted. (otherwise, `mostlyclean` | |
+| | | is the same as `clean`) | |
+
+Finally, there are two variables that express the relationships
+between directories:
+
+| Variable | Description |
+|------------+---------------------------------------------------------|
+| am_subdirs | A list of other directories (containing Makefiles) that |
+| | may be considered "children" of this |
+| | directory/Makefile; building a phony target in this |
+| | directory should also build it in the subdirectory. |
+| | They are not necesarily actually subdirectories of this |
+| | directory in the filesystem. |
+|------------+---------------------------------------------------------|
+| am_depdirs | A list of other directories (containing Makefiles) that |
+| | contain or generate files that are dependencies of |
+| | targets in this directory. They are not necesarily |
+| | actually subdirectories of this directory in the |
+| | filesystem. Except for files that are dependencies of |
+| | files in this directory, things in the dependency |
+| | directory will not be built. |
+
+Tips, notes
+-----------
+
+I like to have the first (non-comment) line in a Makefile be:
+
+ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+
+(adjusting the number of `../` sequences as nescessary). Then, my
+(user-editable) `config.mk` is of the form:
+
+ ifeq ($(topsrcdir),)
+ topoutdir := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+ topsrcdir := $(topoutdir)
+
+ # your configuration
+
+ endif
+
+If the package has a `./configure` script, then I have it modifiy
+topsrcdir as necessary, as well as modifying whatever other parts of
+the configuration. All of the configuration lives in `config.mk`;
+`./configure` doesn't modify any `Makefile`s, it just generates
+`config.mk`, and copies (or (sym?)link?) every `$(srcdir)/Makefile` to
+`$(outdir)/Makefile`.
+
+----
+Copyright (C) 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/build-aux/Makefile.each.head/.gitignore b/build-aux/Makefile.each.head/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.each.head/.gitignore
diff --git a/build-aux/Makefile.each.tail/.gitignore b/build-aux/Makefile.each.tail/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.each.tail/.gitignore
diff --git a/build-aux/Makefile.each.tail/10-std.mk b/build-aux/Makefile.each.tail/10-std.mk
new file mode 100644
index 0000000..5150a71
--- /dev/null
+++ b/build-aux/Makefile.each.tail/10-std.mk
@@ -0,0 +1,46 @@
+# Copyright (C) 2015-2016 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/>.
+# Add some more defaults to the *_files variables
+std.clean_files += $(std.gen_files) $(std.cfg_files) $(std.out_files)
+
+# Fix each variable at its current value to avoid any weirdness
+$(foreach c,src gen cfg out sys clean slow,$(eval std.$c_files := $$(std.$c_files)))
+
+# Make each of the standard variables relative to the correct directory
+std.src_files := $(patsubst ./%,%,$(addprefix $(srcdir)/,$(std.src_files)))
+std.gen_files := $(patsubst ./%,%,$(addprefix $(srcdir)/,$(std.gen_files)))
+std.cfg_files := $(patsubst ./%,%,$(addprefix $(outdir)/,$(std.cfg_files)))
+std.out_files := $(patsubst ./%,%,$(addprefix $(outdir)/,$(std.out_files)))
+std.sys_files := $(addprefix $(DESTDIR),$(std.sys_files))
+std.clean_files := $(patsubst ./%,%,$(addprefix $(outdir)/,$(std.clean_files)))
+std.slow_files := $(patsubst ./%,%,$(addprefix $(outdir)/,$(std.slow_files)))
+
+# Creative targets
+$(outdir)/build : $(std.out_files)
+$(outdir)/install : $(std.sys_files)
+$(outdir)/installdirs: $(sort $(dir $(std.sys_files)))
+
+# Destructive targets
+_std.uninstall/$(outdir) := $(std.sys_files)
+_std.mostlyclean/$(outdir) := $(filter-out $(std.slow_files) $(std.cfg_files) $(std.gen_files) $(std.src_files),$(std.clean_files))
+_std.clean/$(outdir) := $(filter-out $(std.cfg_files) $(std.gen_files) $(std.src_files),$(std.clean_files))
+_std.distclean/$(outdir) := $(filter-out $(std.gen_files) $(std.src_files),$(std.clean_files))
+_std.maintainer-clean/$(outdir) := $(filter-out $(std.src_files),$(std.clean_files))
+$(addprefix $(outdir)/,uninstall mostlyclean clean distclean maintainer-clean): %: %-hook
+ $(RM) -- $(sort $(filter-out %/,$(_std.$(@F)/$(@D))))
+ $(RM) -r -- $(sort $(filter %/,$(_std.$(@F)/$(@D))))
+ $(RMDIR_P) $(sort $(dir $(_std.$(@F)/$(@D)))) 2>/dev/null || $(TRUE)
+$(foreach t,uninstall mostlyclean clean distclean maintainer-clean, $(outdir)/$t-hook)::
+.PHONY: $(foreach t,uninstall mostlyclean clean distclean maintainer-clean, $(outdir)/$t-hook)
diff --git a/build-aux/Makefile.head.mk b/build-aux/Makefile.head.mk
new file mode 100644
index 0000000..63a3462
--- /dev/null
+++ b/build-aux/Makefile.head.mk
@@ -0,0 +1,63 @@
+# Copyright (C) 2015-2016 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 bit only gets evaluated once, at the very beginning
+ifeq ($(_at.NO_ONCE),)
+
+ifeq ($(topsrcdir),)
+$(error topsrcdir must be set before including Makefile.head.mk)
+endif
+ifeq ($(topoutdir),)
+$(error topoutdir must be set before including Makefile.head.mk)
+endif
+
+_at.noslash = $(patsubst %/.,%,$(patsubst %/,%,$1))
+# These are all $(call _at.func,parent,child)
+#at.relto = $(if $2,$(shell realpath -sm --relative-to='$1' $2))
+_at.is_subdir = $(filter $(abspath $1)/%,$(abspath $2)/.)
+_at.relto_helper = $(if $(call _at.is_subdir,$1,$2),$(patsubst $1/%,%,$(addsuffix /.,$2)),$(addprefix ../,$(call _at.relto_helper,$(patsubst %/,%,$(dir $1)),$2)))
+_at.relto = $(call _at.noslash,$(call _at.relto_helper,$(call _at.noslash,$(abspath $1)),$(call _at.noslash,$(abspath $2))))
+at.relto = $(foreach p,$2,$(call _at.relto,$1,$p))
+# Note that _at.is_subdir says that a directory is a subdirectory of
+# itself.
+at.path = $(call at.relto,.,$1)
+
+define at.nl
+
+
+endef
+
+_at.rest = $(wordlist 2,$(words $1),$1)
+_at.reverse = $(if $1,$(call _at.reverse,$(_at.rest))) $(firstword $1)
+
+at.dirlocal += at.subdirs
+at.dirlocal += at.depdirs
+
+include $(sort $(wildcard $(topsrcdir)/build-aux/Makefile.once.head/*.mk))
+
+endif # _at.NO_ONCE
+
+# This bit gets evaluated for each Makefile
+
+## Set outdir and srcdir (assumes that topoutdir and topsrcdir are
+## already set)
+outdir := $(call at.path,$(dir $(lastword $(filter-out %.mk,$(MAKEFILE_LIST)))))
+srcdir := $(call at.path,$(topsrcdir)/$(call _at.relto,$(topoutdir),$(outdir)))
+
+_at.included_makefiles := $(_at.included_makefiles) $(call at.path,$(outdir)/Makefile)
+
+$(foreach v,$(at.dirlocal),$(eval $v=))
+
+include $(sort $(wildcard $(topsrcdir)/build-aux/Makefile.each.head/*.mk))
diff --git a/build-aux/Makefile.once.head/.gitignore b/build-aux/Makefile.once.head/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.once.head/.gitignore
diff --git a/build-aux/Makefile.once.head/00-gnuconf.mk b/build-aux/Makefile.once.head/00-gnuconf.mk
new file mode 100644
index 0000000..79ecc34
--- /dev/null
+++ b/build-aux/Makefile.once.head/00-gnuconf.mk
@@ -0,0 +1,149 @@
+# Copyright (C) 2016 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 file is based on §7.2 "Makefile Conventions" of the release of
+# the GNU Coding Standards dated April 13, 2016.
+
+gnuconf.pkgname ?= $(PACKAGE)
+ifeq ($(gnuconf.pkgname),)
+$(error gnuconf.pkgname must be set)
+endif
+
+# 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?
+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
+
+# These must be user-configurable
+AR ?= ar
+ARFLAGS ?=
+BISON ?= bison
+BISONFLAGS ?=
+CC ?= cc
+CCFLAGS ?= $(CFLAGS)
+FLEX ?= flex
+FLEXFLAGS ?=
+INSTALL ?= install
+INSTALLFLAGS ?=
+LD ?= ld
+LDFLAGS ?=
+LDCONFIG ?= ldconfig #TODO
+LDCONFIGFLAGS ?=
+LEX ?= lex
+LEXFLAGS ?= $(LFLAGS)
+#MAKE
+MAKEINFO ?= makeinfo
+MAKEINFOFLAGS ?=
+RANLIB ?= ranlib #TODO
+RANLIBFLAGS ?=
+TEXI2DVI ?= texi2dvi
+TEXI2DVIFLAGS ?=
+YACC ?= yacc
+YACCFLAGS ?= $(YFLAGS)
+
+CFLAGS ?=
+LFLAGS ?=
+YFLAGS ?=
+
+LN_S ?= ln -s #TODO
+
+CHGRP ?= chgrp
+CHMOD ?= chmod
+CHOWN ?= chown
+MKNOD ?= mknod
+
+# 7.2.3 Variables for Specifying Commands
+# ---------------------------------------
+
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= ${INSTALL} -m 644
+
+# 7.2.5 Variables for Installation Directories
+# --------------------------------------------
+
+# Root for the installation
+prefix ?= /usr/local
+exec_prefix ?= $(prefix)
+# Executable programs
+bindir ?= $(exec_prefix)/bin
+sbindir ?= $(exec_prefix)/sbin
+libexecdir ?= $(exec_prefix)/libexec
+# Data files
+datarootdir ?= $(prefix)/share
+datadir ?= $(datarootdir)
+sysconfdir ?= $(prefix)/etc
+sharedstatedir ?= $(prefix)/com
+localstatedir ?= $(prefix)/var
+runstatedir ?= $(localstatedir)/run
+# Specific types of files
+includedir ?= $(prefix)/include
+oldincludedir ?= /usr/include
+docdir ?= $(datarootdir)/doc/$(gnuconf.pkgname)
+infodir ?= $(datarootdir)/info
+htmldir ?= $(docdir)
+dvidir ?= $(docdir)
+pdfdir ?= $(docdir)
+psdir ?= $(docdir)
+libdir ?= $(exec_prefix)/lib
+lispdir ?= $(datarootdir)/emacs/site-lisp
+localedir ?= $(datarootdir)/locale
+
+mandir ?= $(datarootdir)/man
+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
diff --git a/build-aux/Makefile.once.head/10-std.mk b/build-aux/Makefile.once.head/10-std.mk
new file mode 100644
index 0000000..3e058ec
--- /dev/null
+++ b/build-aux/Makefile.once.head/10-std.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2015-2016 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/>.
+
+# Declare the default target
+all: build
+.PHONY: all noop
+
+# Standard creative PHONY targets
+at.phony += build install installdirs
+# Standard destructive PHONY targets
+at.phony += uninstall mostlyclean clean distclean maintainer-clean
+
+at.dirlocal += std.src_files
+at.dirlocal += std.gen_files
+at.dirlocal += std.cfg_files
+at.dirlocal += std.out_files
+at.dirlocal += std.sys_files
+at.dirlocal += std.clean_files
+at.dirlocal += std.slow_files
+
+# User configuration
+
+DESTDIR ?=
+
+RM ?= rm -f
+RMDIR_P ?= rmdir -p
+TRUE ?= true
diff --git a/build-aux/Makefile.once.tail/.gitignore b/build-aux/Makefile.once.tail/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.once.tail/.gitignore
diff --git a/build-aux/Makefile.tail.mk b/build-aux/Makefile.tail.mk
new file mode 100644
index 0000000..dfbad5a
--- /dev/null
+++ b/build-aux/Makefile.tail.mk
@@ -0,0 +1,51 @@
+# Copyright (C) 2015-2016 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 bit gets evaluated for each Makefile processed
+
+include $(call _at.reverse,$(sort $(wildcard $(topsrcdir)/build-aux/Makefile.each.tail/*.mk)))
+
+at.subdirs := $(patsubst ./%,%,$(addprefix $(outdir)/,$(at.subdirs)))
+at.depdirs := $(patsubst ./%,%,$(addprefix $(outdir)/,$(at.depdirs)))
+
+# Move all of the dirlocal variables to their namespaced version
+$(foreach v,$(at.dirlocal),$(eval $v/$(outdir) := $$($v)))
+$(foreach v,$(at.dirlocal),$(eval undefine $v))
+
+# Remember that this is a directory that we've visited
+_at.outdirs := $(_at.outdirs) $(outdir)
+
+# Generic phony target declarations:
+# mark them phony
+.PHONY: $(addprefix $(outdir)/,$(at.phony))
+# have them depend on subdirs
+$(foreach t,$(at.phony),$(eval $(outdir)/$t: $(addsuffix /$t,$(at.subdirs/$(outdir)))))
+
+# Include Makefiles from other directories
+$(foreach _at.NO_ONCE,y,\
+ $(foreach makefile,$(call at.path,$(addsuffix /Makefile,$(at.subdirs/$(outdir)) $(at.depdirs/$(outdir)))),\
+ $(eval include $(filter-out $(_at.included_makefiles),$(makefile)))))
+
+# This bit only gets evaluated once, after all of the other Makefiles are read
+ifeq ($(_at.NO_ONCE),)
+
+outdir = /bogus
+srcdir = /bogus
+
+$(foreach v,$(at.dirlocal),$(eval $v=))
+
+include $(call _at.reverse,$(sort $(wildcard $(topsrcdir)/build-aux/Makefile.once.tail/*.mk)))
+
+endif # _at.NO_ONCE
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/golang.mk b/golang.mk
new file mode 100644
index 0000000..222bbdf
--- /dev/null
+++ b/golang.mk
@@ -0,0 +1,26 @@
+# Copyright 2015 Luke Shumaker
+
+_golang_cgo_variables = CGO_ENABLED CGO_CFLAGS CGO_CPPFLAGS CGO_CXXFLAGS CGO_LDFLAGS CC CXX
+export $(_golang_cgo_variables)
+_golang_src_cmd = find -L $1/src -name '.*' -prune -o \( -type f \( -false $(foreach e,go c s S cc cpp cxx h hh hpp hxx,-o -name '*.$e') \) -o -type d \) -print
+
+# Iterate over external dependencies, and create a rule to download it
+goget = $(foreach d,$2,$(eval $1/src/$d: $(NET); GOPATH='$(abspath $1)' go get -d -u $d))
+
+#|| { rm -rf -- $$@; false; }))
+
+gosrc = $(shell $(_golang_src_cmd)) $(addprefix .var.,$(_golang_cgo_variables))
+define goinstall
+ $(Q)for target in $(addprefix $1/bin/,$(notdir $2)); do \
+ if test -e $$target; then \
+ for dep in $(filter .var.%,$^); do \
+ if test $$dep -nt $$target; then \
+ rm -rf -- $1/bin $1/pkg || exit $$?; \
+ exit 0; \
+ fi \
+ done \
+ fi \
+ done
+ GOPATH='$(abspath $1)' go install $2
+ $(Q)true $(foreach e,$(notdir $2), && test -f $1/bin/$e -a -x $1/bin/$e && touch $1/bin/$e)
+endef
diff --git a/nshd.service.in b/nshd.service.in
new file mode 100644
index 0000000..caf5508
--- /dev/null
+++ b/nshd.service.in
@@ -0,0 +1,15 @@
+[Unit]
+Description=Parabola hackers.git authentication
+Requires=nshd.socket
+After=syslog.target nshd.socket
+
+[Service]
+Type=notify
+Sockets=nshd.socket
+ExecStart=@bindir@/nshd
+
+User=@user@
+Group=@group@
+
+[Install]
+WantedBy=multi-user.target
diff --git a/nshd.socket.in b/nshd.socket.in
new file mode 100644
index 0000000..a514391
--- /dev/null
+++ b/nshd.socket.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Parabola hackers.git authentication
+
+[Socket]
+ListenStream=/var/run/nslcd/socket
+PassCredentials=yes
+PassSecurity=yes
+
+SocketUser=@user@
+SocketGroup=@group@
+
+[Install]
+WantedBy=sockets.target
diff --git a/parabola-hackers.yml b/parabola-hackers.yml
new file mode 100644
index 0000000..c09f21b
--- /dev/null
+++ b/parabola-hackers.yml
@@ -0,0 +1,40 @@
+---
+# Where to look for "${uid}.yml" files
+#yamldir: "/var/lib/hackers-git/users"
+yamldir: "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
+ - git
+ - ssh
+ - 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
+- git
+
+# 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"
+keyring_cachedir: "cache"
diff --git a/scripts/common.rb b/scripts/common.rb
new file mode 100644
index 0000000..91e14be
--- /dev/null
+++ b/scripts/common.rb
@@ -0,0 +1,22 @@
+require 'yaml'
+
+def cfg
+ @cfg ||= YAML::load(open("parabola-hackers.yml"))
+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/scripts/meta-cat b/scripts/meta-cat
new file mode 100755
index 0000000..e6b9edd
--- /dev/null
+++ b/scripts/meta-cat
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# Usage: meta-cat
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+print load_all_users.to_yaml
diff --git a/scripts/meta-check b/scripts/meta-check
new file mode 100755
index 0000000..4a2981e
--- /dev/null
+++ b/scripts/meta-check
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+. 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/scripts/meta-normalize-stdio b/scripts/meta-normalize-stdio
new file mode 100755
index 0000000..5611ae6
--- /dev/null
+++ b/scripts/meta-normalize-stdio
@@ -0,0 +1,171 @@
+#!/usr/bin/env ruby
+
+# 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}
+ 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/scripts/pacman-make-keyring b/scripts/pacman-make-keyring
new file mode 100755
index 0000000..589984d
--- /dev/null
+++ b/scripts/pacman-make-keyring
@@ -0,0 +1,150 @@
+#!/usr/bin/make -rRf
+# Usage: pacman-make-keyring V=$(date -u +%Y%m%d)
+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/scripts/pgp-list-keyids b/scripts/pgp-list-keyids
new file mode 100755
index 0000000..9682b1a
--- /dev/null
+++ b/scripts/pgp-list-keyids
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+# Usage: pgp-list-keyids
+
+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/scripts/postfix-generate-virtual-map b/scripts/postfix-generate-virtual-map
new file mode 100755
index 0000000..d5c2d21
--- /dev/null
+++ b/scripts/postfix-generate-virtual-map
@@ -0,0 +1,18 @@
+#!/usr/bin/env ruby
+# Usage: postfix-show-virtual-map > ${file} && postmap hash:${file}
+
+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/scripts/ssh-list-authorized-keys b/scripts/ssh-list-authorized-keys
new file mode 100755
index 0000000..5fb1ea1
--- /dev/null
+++ b/scripts/ssh-list-authorized-keys
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+# Usage: ssh-list-authorized-keys [username]
+
+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/scripts/uid-map b/scripts/uid-map
new file mode 100755
index 0000000..10c3fac
--- /dev/null
+++ b/scripts/uid-map
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+# Usage: uid-map
+
+load "#{File.dirname(__FILE__)}/common.rb"
+
+load_all_users.each do |uid,user|
+ puts "#{uid}:#{user["username"]}"
+end
diff --git a/src/gopkg.in/yaml.v2 b/src/gopkg.in/yaml.v2
new file mode 160000
+Subproject f7716cbe52baa25d2e9b0d0da546fcf909fc16b
diff --git a/src/lukeshu.com/git/go/libgnulinux.git b/src/lukeshu.com/git/go/libgnulinux.git
new file mode 160000
+Subproject 58bfc6259f5ced680b5b200687240d2449313b2
diff --git a/src/lukeshu.com/git/go/libnslcd.git b/src/lukeshu.com/git/go/libnslcd.git
new file mode 160000
+Subproject dad3de8e9835a11b1df9e718eef3faa50459ccb
diff --git a/src/lukeshu.com/git/go/libsystemd.git b/src/lukeshu.com/git/go/libsystemd.git
new file mode 160000
+Subproject 89efdfbee5f9a22f9dd1083f7a383daba54d4f1
diff --git a/src/nshd/hackers_git/check_password.go b/src/nshd/hackers_git/check_password.go
new file mode 100644
index 0000000..81ad932
--- /dev/null
+++ b/src/nshd/hackers_git/check_password.go
@@ -0,0 +1,50 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import "unsafe"
+
+/*
+#cgo LDFLAGS: -lcrypt
+#define _GNU_SOURCE // for crypt_r(3) in crypt.h
+#include <stdlib.h> // for free(3)
+#include <crypt.h> // for crypt_r(3)
+#include <string.h> // for strcmp(3) and memset(3)
+int check_password(const char *password, const char *hash)
+{
+ int ret;
+ struct crypt_data data;
+ data.initialized = 0;
+ ret = (strcmp(crypt_r(password, hash, &data), hash) == 0);
+ memset(&data, 0, sizeof(data));
+ return ret;
+}
+*/
+import "C"
+
+func check_password(password string, hash string) bool {
+ cpassword := C.CString(password)
+ defer C.free(unsafe.Pointer(cpassword))
+ chash := C.CString(hash)
+ defer C.free(unsafe.Pointer(chash))
+ return C.check_password(cpassword, chash) != 0
+}
diff --git a/src/nshd/hackers_git/db_config.go b/src/nshd/hackers_git/db_config.go
new file mode 100644
index 0000000..dc3b99e
--- /dev/null
+++ b/src/nshd/hackers_git/db_config.go
@@ -0,0 +1,44 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ p "lukeshu.com/git/go/libnslcd.git/proto"
+ s "syscall"
+)
+
+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/src/nshd/hackers_git/db_group.go b/src/nshd/hackers_git/db_group.go
new file mode 100644
index 0000000..7e97c05
--- /dev/null
+++ b/src/nshd/hackers_git/db_group.go
@@ -0,0 +1,144 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ p "lukeshu.com/git/go/libnslcd.git/proto"
+ s "syscall"
+)
+
+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 = 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 = 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/src/nshd/hackers_git/db_pam.go b/src/nshd/hackers_git/db_pam.go
new file mode 100644
index 0000000..2ef8d9a
--- /dev/null
+++ b/src/nshd/hackers_git/db_pam.go
@@ -0,0 +1,105 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ "crypto/rand"
+ p "lukeshu.com/git/go/libnslcd.git/proto"
+ "math/big"
+ s "syscall"
+)
+
+func (o *Hackers) PAM_Authentication(cred s.Ucred, req p.Request_PAM_Authentication) <-chan p.PAM_Authentication {
+ o.lock.RLock()
+ ret := make(chan p.PAM_Authentication)
+ go func() {
+ defer o.lock.RUnlock()
+ defer close(ret)
+
+ uid := o.name2uid(req.UserName)
+ if uid < 0 {
+ return
+ }
+
+ user := o.users[uid]
+ obj := p.PAM_Authentication{
+ AuthenticationResult: p.NSLCD_PAM_AUTH_ERR,
+ UserName: "",
+ AuthorizationResult: p.NSLCD_PAM_AUTH_ERR,
+ AuthorizationError: "",
+ }
+ if check_password(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"
+
+var alphabet_len = big.NewInt(int64(len(alphabet)))
+
+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)
+
+ var sessionid [24]byte
+ for i := 0; i < len(sessionid); i++ {
+ bigint, err := rand.Int(rand.Reader, alphabet_len)
+ if err != nil {
+ return
+ }
+ sessionid[i] = alphabet[bigint.Int64()]
+ }
+ ret <- p.PAM_SessionOpen{SessionID: string(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
+}
diff --git a/src/nshd/hackers_git/db_passwd.go b/src/nshd/hackers_git/db_passwd.go
new file mode 100644
index 0000000..0be3910
--- /dev/null
+++ b/src/nshd/hackers_git/db_passwd.go
@@ -0,0 +1,86 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ p "lukeshu.com/git/go/libnslcd.git/proto"
+ s "syscall"
+)
+
+/* 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
+ other - encrypted password, in crypt(3) format */
+
+func (o *Hackers) Passwd_ByName(cred s.Ucred, req p.Request_Passwd_ByName) <-chan p.Passwd {
+ o.lock.RLock()
+ ret := make(chan p.Passwd)
+ go func() {
+ defer o.lock.RUnlock()
+ defer close(ret)
+
+ uid := o.name2uid(req.Name)
+ if uid < 0 {
+ return
+ }
+ passwd := o.users[uid].passwd
+ passwd.PwHash = "x" // only put actual hashes in the Shadow DB
+ ret <- passwd
+ }()
+ return ret
+}
+
+func (o *Hackers) Passwd_ByUID(cred s.Ucred, req p.Request_Passwd_ByUID) <-chan p.Passwd {
+ o.lock.RLock()
+ ret := make(chan p.Passwd)
+ go func() {
+ defer o.lock.RUnlock()
+ defer close(ret)
+
+ user, found := o.users[req.UID]
+ if !found {
+ return
+ }
+ passwd := user.passwd
+ passwd.PwHash = "x" // only put actual hashes in the Shadow DB
+ ret <- passwd
+ }()
+ return ret
+}
+
+func (o *Hackers) Passwd_All(cred s.Ucred, req p.Request_Passwd_All) <-chan p.Passwd {
+ o.lock.RLock()
+ ret := make(chan p.Passwd)
+ go func() {
+ defer o.lock.RUnlock()
+ defer close(ret)
+
+ for _, user := range o.users {
+ passwd := user.passwd
+ passwd.PwHash = "x" // only put actual hashes in the Shadow DB
+ ret <- passwd
+ }
+ }()
+ return ret
+}
diff --git a/src/nshd/hackers_git/db_shadow.go b/src/nshd/hackers_git/db_shadow.go
new file mode 100644
index 0000000..c2719b3
--- /dev/null
+++ b/src/nshd/hackers_git/db_shadow.go
@@ -0,0 +1,82 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ p "lukeshu.com/git/go/libnslcd.git/proto"
+ s "syscall"
+)
+
+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/src/nshd/hackers_git/gid.go b/src/nshd/hackers_git/gid.go
new file mode 100644
index 0000000..f7bbac7
--- /dev/null
+++ b/src/nshd/hackers_git/gid.go
@@ -0,0 +1,42 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import "lukeshu.com/git/go/libgnulinux.git/getgr"
+
+func name2gid(name string) int32 {
+ gr, err := getgr.ByName(name)
+ if gr == nil || err != nil {
+ return -1
+ } else {
+ return int32(gr.Gid)
+ }
+}
+
+func gid2name(gid int32) (string, bool) {
+ gr, err := getgr.ByGid(gid)
+ if gr == nil || err != nil {
+ return "", false
+ } else {
+ return gr.Name, true
+ }
+}
diff --git a/src/nshd/hackers_git/hackers.go b/src/nshd/hackers_git/hackers.go
new file mode 100644
index 0000000..b72698f
--- /dev/null
+++ b/src/nshd/hackers_git/hackers.go
@@ -0,0 +1,121 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git is an nslcd_server Backend that speaks to
+// hackers.git.
+package hackers_git
+
+import (
+ "lukeshu.com/git/go/libnslcd.git/proto"
+ "lukeshu.com/git/go/libnslcd.git/proto/server"
+ "lukeshu.com/git/go/libnslcd.git/systemd"
+ "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger"
+ "path/filepath"
+ "sync"
+)
+
+type user struct {
+ passwd nslcd_proto.Passwd
+ groups []string
+}
+
+type Config struct {
+ Pam_password_prohibit_message string
+ Yamldir string
+}
+
+type Hackers struct {
+ nslcd_server.NilBackend
+ Cfg Config
+ lock sync.RWMutex
+
+ users map[int32]user
+ groups map[string]map[string]bool
+}
+
+var _ nslcd_systemd.Backend = &Hackers{}
+var _ nslcd_server.Backend = &Hackers{}
+
+func (o *Hackers) Init() error {
+ err := o.Reload()
+ if err != nil {
+ logger.Err("hackers.git: Could not initialize: %v", err)
+ return err
+ }
+ return nil
+}
+
+func (o *Hackers) Close() {
+ logger.Info("hackers.git: Closing session")
+ o.lock.Lock()
+ defer o.lock.Unlock()
+
+ o.users = make(map[int32]user, 0)
+ o.groups = make(map[string]map[string]bool)
+}
+
+func (o *Hackers) Reload() error {
+ logger.Info("hackers.git: Loading session")
+ o.lock.Lock()
+ defer o.lock.Unlock()
+
+ filenames, err := filepath.Glob(o.Cfg.Yamldir + "/*.yml")
+ if err != nil {
+ return err
+ }
+ o.users = make(map[int32]user, len(filenames))
+ o.groups = make(map[string]map[string]bool)
+ for _, filename := range filenames {
+ logger.Debug("hackers.git: Loading YAML file: %s", filename)
+
+ user, err := parse_user_yaml(filename)
+ if err != nil {
+ logger.Warning("hackers.git: -> File ignored: %v", err)
+ continue
+ }
+ for _, groupname := range user.groups {
+ o.add_user_to_group(user.passwd.Name, groupname)
+ }
+ user.passwd.PwHash = parse_user_password(user.passwd.HomeDir + "/.password")
+ o.users[user.passwd.UID] = user
+ logger.Debug("hackers.git: -> User %d(%s) added", user.passwd.UID, user.passwd.Name)
+ }
+
+ 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/src/nshd/hackers_git/hackers_parse.go b/src/nshd/hackers_git/hackers_parse.go
new file mode 100644
index 0000000..d5370eb
--- /dev/null
+++ b/src/nshd/hackers_git/hackers_parse.go
@@ -0,0 +1,157 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+import (
+ "fmt"
+ yaml "gopkg.in/yaml.v2"
+ "io/ioutil"
+ "lukeshu.com/git/go/libsystemd.git/sd_daemon/logger"
+ "os"
+ "path"
+ "strconv"
+ "strings"
+)
+
+func filename2uid(filename string) int32 {
+ basename := path.Base(filename)
+ parts := strings.SplitN(basename, ".", 2)
+ if len(parts) != 2 || parts[1] != "yml" {
+ return -1
+ }
+ uid, err := strconv.ParseInt(parts[0], 10, 32)
+ if err != nil {
+ return -1
+ }
+ return int32(uid)
+}
+
+var usersGid = name2gid("users")
+
+func parse_user_yaml(filename string) (ret user, err error) {
+ ret.passwd.UID = filename2uid(filename)
+
+ if ret.passwd.UID < 0 {
+ err = fmt.Errorf("Invalid user filename: %q", filename)
+ return
+ }
+ file, err := os.Open(filename)
+ if err != nil {
+ return
+ }
+ contents, err := ioutil.ReadAll(file)
+ 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 {
+ 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 = set2list(groups)
+ }
+ }
+ }
+ if len(errs) > 0 {
+ err = &yaml.TypeError{Errors: errs}
+ }
+
+ ret.passwd.PwHash = "!"
+ ret.passwd.GID = usersGid
+
+ return
+}
+
+func parse_user_password(filename string) (hash string) {
+ hash = "!"
+ file, err := os.Open(filename)
+ if err != nil {
+ logger.Debug("hackers.git: %v", err)
+ return
+ }
+ contents, err := ioutil.ReadAll(file)
+ if err != nil {
+ logger.Debug("hackers.git: Error while reading: %q: %v", filename, err)
+ return
+ }
+ lines := strings.Split(string(contents), "\n")
+ switch len(lines) {
+ case 1:
+ hash = lines[0]
+ case 2:
+ if lines[1] == "" {
+ hash = lines[0]
+ } else {
+ logger.Debug("hackers.git: Invalid password format in file: %q", filename)
+ }
+ default:
+ logger.Debug("hackers.git: Invalid password format in file: %q", filename)
+ }
+ return
+}
diff --git a/src/nshd/hackers_git/set.go b/src/nshd/hackers_git/set.go
new file mode 100644
index 0000000..dc1d443
--- /dev/null
+++ b/src/nshd/hackers_git/set.go
@@ -0,0 +1,32 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 hackers_git
+
+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/src/nshd/main.go b/src/nshd/main.go
new file mode 100644
index 0000000..6871518
--- /dev/null
+++ b/src/nshd/main.go
@@ -0,0 +1,37 @@
+// Copyright 2015 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.
+//
+// The GNU General Public License's references to "object code" and
+// "executables" are to be interpreted to also include the output of
+// any document formatting or typesetting system, including
+// intermediate and printed output.
+//
+// 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 (
+ "lukeshu.com/git/go/libnslcd.git/systemd"
+ "nshd/hackers_git"
+ "os"
+)
+
+func main() {
+ backend := &hackers_git.Hackers{Cfg: hackers_git.Config{
+ Pam_password_prohibit_message: "",
+ Yamldir: "/var/cache/parabola-hackers/users",
+ }}
+ os.Exit(int(nslcd_systemd.Main(backend)))
+}
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..09230a9
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+/runner
diff --git a/test/runner.c b/test/runner.c
new file mode 100644
index 0000000..110819d
--- /dev/null
+++ b/test/runner.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;
+}