From 4d12729aa4026229e4e118b924cc3b1c75ca214b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 17 Jun 2016 20:09:33 -0400 Subject: write setuid, move things around --- bin/common.rb.in | 44 +++++++++ bin/meta-cat | 22 +++++ bin/meta-check | 60 +++++++++++++ bin/meta-normalize-stdio | 190 +++++++++++++++++++++++++++++++++++++++ bin/nshd-tester.c | 168 ++++++++++++++++++++++++++++++++++ bin/pacman-make-keyring | 167 ++++++++++++++++++++++++++++++++++ bin/pgp-list-keyids | 37 ++++++++ bin/postfix-generate-virtual-map | 34 +++++++ bin/setuid.c | 108 ++++++++++++++++++++++ bin/ssh-list-authorized-keys | 41 +++++++++ bin/uid-map | 24 +++++ 11 files changed, 895 insertions(+) create mode 100644 bin/common.rb.in create mode 100755 bin/meta-cat create mode 100755 bin/meta-check create mode 100755 bin/meta-normalize-stdio create mode 100644 bin/nshd-tester.c create mode 100755 bin/pacman-make-keyring create mode 100755 bin/pgp-list-keyids create mode 100755 bin/postfix-generate-virtual-map create mode 100644 bin/setuid.c create mode 100755 bin/ssh-list-authorized-keys create mode 100755 bin/uid-map (limited to 'bin') 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 . +# +# 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 +# . + +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 . +# +# 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 +# . + +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 . +# Copyright 2015 Márcio Alexandre Silva Delgado . +# +# 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 +# . + +. 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 . +# Copyright 2015 Márcio Alexandre Silva Delgado . +# +# 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 +# . + +# 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . +# +# 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 +# . + +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 . +# +# 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 +# . + +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 . +# +# 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 +# . + +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 + + 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 /* for dlopen(3), dlsym(3), and dlerror(3) */ +#include /* for errno */ +#include /* for getpwnam(3) */ +#include /* for printf(3) and fprintf(3) */ +#include /* for strerror(3) */ +#include /* for 'struct passwd' and 'struct group' */ +#include /* for SD_{WARNING,DEBUG} */ +#include /* 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 . +# +# 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 +# . + +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 . +# +# 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 +# . + +load "#{File.dirname(__FILE__)}/common.rb" + +load_all_users.each do |uid,user| + puts "#{uid}:#{user["username"]}" +end -- cgit v1.2.2