#!/usr/bin/perl -T use strictures; use autodie; use Function::Parameters; use Path::Tiny; use Sys::Syslog qw(:standard :macros); =head1 SYNOPSIS [cat \$file | ] SSH_ORIGINAL_COMMAND="" alass commands: sign Request a signature for the file data =head1 DESCRIPTION The Arch Linux Automated Signing Server (ALASS) is supposed to be called via an SSH forced command. It parses SSH_ORIGINAL_COMMAND, recieves file data via STDIN and returns a signature for the file via STDOUT if the command and file data pass various security checks. =cut fun main() { my $prog_name = 'alass'; my $command = $ENV{SSH_ORIGINAL_COMMAND}; # reset PATH since taint mode requires a secure path $ENV{PATH} = "/usr/local/sbin:/usr/local/bin:/usr/bin"; openlog($prog_name, "ndelay,pid", "local0"); syslog(LOG_NOTICE, "Signing script called with arguments: %s", $command); # when this variable goes out of scope the temp dir is removed! my $tmpdir = Path::Tiny->tempdir(); my $input_file = $tmpdir->child("input"); copy_stdin_to_file($input_file); if (allowed_to_sign($command, $input_file)) { syslog(LOG_NOTICE, "Signing request granted by security checks"); my $signature = get_signature($input_file); print $signature; } else { syslog(LOG_ERR, "Sign requst failed security checks"); exit 1; } } fun copy_stdin_to_file($destination) { my $data; my $bufsize = 4096; my $total_size = 0; while (my $read_size = sysread(STDIN, $data, $bufsize)) { $destination->append_raw($data); $total_size += $read_size; } syslog(LOG_NOTICE, "Input file has %d bytes", $total_size); } fun allowed_to_sign($command, $input_file) { if ($command =~ m/^sign (?[^ ]+)/) { my $filename = $+{filename}; if ($filename =~ m/\.pkg\.tar\.xz$/) { return 1; } } return 0; } fun get_signature($input_file) { execute_command([qw(gpg --detach-sign --use-agent --no-armor), $input_file->stringify]); return $input_file->sibling("input.sig")->slurp_raw(); } fun execute_command($command) { syslog(LOG_NOTICE, "Executing command: %s", join(" ", @$command)); system(@$command); } main();