1#! /usr/bin/perl 2# ex:ts=8 sw=4: 3# $OpenBSD: PkgSign.pm,v 1.18 2023/06/13 09:07:17 espie Exp $ 4# 5# Copyright (c) 2003-2014 Marc Espie <espie@openbsd.org> 6# 7# Permission to use, copy, modify, and distribute this software for any 8# purpose with or without fee is hereby granted, provided that the above 9# copyright notice and this permission notice appear in all copies. 10# 11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 19use v5.36; 20 21use OpenBSD::AddCreateDelete; 22use OpenBSD::Signer; 23 24package OpenBSD::PkgSign::State; 25our @ISA = qw(OpenBSD::CreateSign::State); 26 27sub handle_options($state) 28{ 29 $state->{extra_stats} = 0; 30 $state->{opt} = { 31 'o' => 32 sub($opt) { 33 $state->{output_dir} = $opt; 34 }, 35 'S' => 36 sub($opt) { 37 $state->{source} = $opt; 38 }, 39 's' => 40 sub($opt) { 41 push(@{$state->{signature_params}}, $opt); 42 }, 43 'V' => 44 sub() { 45 $state->{extra_stats}++; 46 }, 47 }; 48 $state->{signature_style} = 'unsigned'; 49 50 $state->SUPER::handle_options('Cij:o:S:s:V', 51 '[-CvV] [-D name[=value]] -s signify2 -s priv', 52 '[-o dir] [-S source] [pkg-name...]'); 53 if (defined $state->{signature_params}) { 54 $state->{signer} = OpenBSD::Signer->factory($state); 55 } 56 if (!defined $state->{signer}) { 57 $state->usage("Can't invoke command without valid signing parameters"); 58 } 59 $state->{output_dir} //= "."; 60 if (!-d $state->{output_dir}) { 61 require File::Path; 62 File::Path::make_path($state->{output_dir}) 63 or $state->usage("can't create dir"); 64 } 65 $state->{wantntogo} = $state->{extra_stats}; 66} 67 68package OpenBSD::PkgSign; 69use OpenBSD::Temp; 70use OpenBSD::PackingList; 71use OpenBSD::PackageInfo; 72 73sub sign_existing_package($self, $state, $pkg) 74{ 75 my $output = $state->{output_dir}; 76 my $dest = $output.'/'.$pkg->name.".tgz"; 77 if ($state->opt('i')) { 78 if (-f $dest) { 79 return; 80 } 81 } 82 my (undef, $tmp) = OpenBSD::Temp::permanent_file($output, "pkg") or 83 die $state->fatal(OpenBSD::Temp->last_error); 84 $state->{signer}->sign($pkg, $state, $tmp); 85 86 chmod((0666 & ~umask), $tmp); 87 rename($tmp, $dest) or 88 $state->fatal("Can't create final signed package: #1", $!); 89 if ($state->opt('C')) { 90 $state->system( 91 sub() { 92 chdir($output); 93 open(STDOUT, '>>', 'SHA256'); 94 }, 95 OpenBSD::Paths->sha256, '-b', $pkg->name.".tgz"); 96 } 97} 98 99sub sign_list($self, $l, $repo, $maxjobs, $state) 100{ 101 $state->{total} = scalar @$l; 102 $maxjobs //= 1; 103 my $code = sub($name) { 104 my $pkg = $repo->find($name); 105 if (!defined $pkg) { 106 $state->errsay("#1 not found", $name); 107 } else { 108 $self->sign_existing_package($state, $pkg); 109 } 110 }; 111 my $display = $state->verbose ? 112 sub($name) { 113 $state->progress->set_header("Signed ".$name); 114 $state->{done}++; 115 $state->progress->next($state->ntogo); 116 } : 117 sub($) { 118 }; 119 if ($maxjobs > 1) { 120 my $jobs = {}; 121 my $n = 0; 122 my $reap_job = sub() { 123 my $pid = wait; 124 if (!defined $jobs->{$pid}) { 125 $state->fatal("Wait returned #1: unknown process", $pid); 126 } 127 if ($? != 0) { 128 $state->fatal("Signature of #1 failed\n", 129 $jobs->{$pid}); 130 } 131 $n--; 132 &$display($jobs->{$pid}); 133 delete $state->{signer}{pubkey}; 134 delete $jobs->{$pid}; 135 }; 136 137 while (@$l > 0) { 138 my $name = shift @$l; 139 my $pid = fork(); 140 if ($pid == 0) { 141 $repo->reinitialize; 142 &$code($name); 143 exit(0); 144 } else { 145 $jobs->{$pid} = $name; 146 $n++; 147 } 148 if ($n >= $maxjobs) { 149 &$reap_job(); 150 } 151 } 152 while ($n != 0) { 153 &$reap_job(); 154 } 155 } else { 156 for my $name (@$l) { 157 &$code($name); 158 &$display($name); 159 delete $state->{signer}{pubkey}; 160 } 161 } 162 if ($state->opt('C')) { 163 $state->system( 164 sub() { 165 chdir($state->{output_dir}); 166 open(STDOUT, '>', 'SHA256.new'); 167 }, 'sort', 'SHA256'); 168 rename($state->{output_dir}.'/SHA256.new', 169 $state->{output_dir}.'/SHA256'); 170 } 171} 172 173sub sign_existing_repository($self, $state, $source) 174{ 175 require OpenBSD::PackageRepository; 176 my $repo = OpenBSD::PackageRepository->new($source, $state); 177 if ($state->{signer}->want_local && !$repo->is_local_file) { 178 $state->fatal("Signing distant source is not supported"); 179 } 180 my @list = sort @{$repo->list}; 181 if (@list == 0) { 182 $state->errsay('Source repository "#1" is empty', $source); 183 } 184 $self->sign_list(\@list, $repo, $state->opt('j'), $state); 185} 186 187 188sub parse_and_run($self, $cmd) 189{ 190 my $state = OpenBSD::PkgSign::State->new($cmd); 191 $state->handle_options; 192 if (!defined $state->{source} && @ARGV == 0) { 193 $state->usage("Nothing to sign"); 194 } 195 if (defined $state->{source}) { 196 $self->sign_existing_repository($state, 197 $state->{source}); 198 } 199 $self->sign_list(\@ARGV, $state->repo, $state->opt('j'), 200 $state); 201 return 0; 202} 203 204