summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-06-17 20:09:33 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-06-17 20:09:33 -0400
commit4d12729aa4026229e4e118b924cc3b1c75ca214b (patch)
treeabd9a69ec11504844148b1017f9e9601ef7e90b8 /bin
parent4f175a22cf726bfa09652d8d9ca6374785561348 (diff)
write setuid, move things around
Diffstat (limited to 'bin')
-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-tester.c168
-rwxr-xr-xbin/pacman-make-keyring167
-rwxr-xr-xbin/pgp-list-keyids37
-rwxr-xr-xbin/postfix-generate-virtual-map34
-rw-r--r--bin/setuid.c108
-rwxr-xr-xbin/ssh-list-authorized-keys41
-rwxr-xr-xbin/uid-map24
11 files changed, 895 insertions, 0 deletions
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-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/setuid.c b/bin/setuid.c
new file mode 100644
index 0000000..7ae1105
--- /dev/null
+++ b/bin/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", 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", nss_module_soname,
+ version_info[0],
+ version_info[1]);
+ } else {
+ fprintf(stderr, SD_WARNING "NSS module %s version missing: %s", 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", 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", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ if (setgid(passwd->pw_gid) != 0) {
+ fprintf(stderr, SD_ERR "Could not setgid(%lu): %s",
+ (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",
+ (unsigned long int)passwd->pw_gid, strerror(errno));
+ return EXIT_NOPERMISSION;
+ }
+
+ execvp(argv[2], &argv[2]);
+ fprintf(stderr, SD_ERR "Could not exec: %s", strerror(errno));
+ return EXIT_FAILURE;
+}
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