1#! /usr/bin/perl 2# ex:ts=8 sw=4: 3# $OpenBSD: PkgSign.pm,v 1.8 2016/05/09 14:17:24 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 strict; 20use warnings; 21 22use OpenBSD::AddCreateDelete; 23use OpenBSD::Signer; 24 25package OpenBSD::PkgSign::State; 26our @ISA = qw(OpenBSD::CreateSign::State); 27 28sub handle_options 29{ 30 my $state = shift; 31 32 $state->{opt} = { 33 'o' => 34 sub { 35 $state->{output_dir} = shift; 36 }, 37 'S' => 38 sub { 39 $state->{source} = shift; 40 }, 41 }; 42 $state->SUPER::handle_options('Cij:o:S:', 43 '[-Cv] [-D name[=value]] -s x509|signify [-s cert] -s priv', 44 '[-o dir] [-S source] [pkg-name...]'); 45 if (!defined $state->{signer}) { 46 $state->usage("Can't invoke command without valid signing parameters"); 47 } 48 $state->{output_dir} //= "."; 49 if (!-d $state->{output_dir}) { 50 require File::Path; 51 File::Path::make_path($state->{output_dir}) 52 or $state->usage("can't create dir"); 53 } 54} 55 56package OpenBSD::PackingElement; 57sub copy_over 58{ 59} 60 61package OpenBSD::PackingElement::SpecialFile; 62sub copy_over 63{ 64 my ($self, $state, $wrarc, $rdarc) = @_; 65 $wrarc->destdir($rdarc->info); 66 my $e = $wrarc->prepare($self->{name}); 67 $e->write; 68} 69 70package OpenBSD::PackingElement::FileBase; 71sub copy_over 72{ 73 my ($self, $state, $wrarc, $rdarc) = @_; 74 my $e = $rdarc->next; 75 $e->copy($wrarc); 76} 77 78package OpenBSD::PkgSign; 79use OpenBSD::Temp; 80use OpenBSD::PackingList; 81use OpenBSD::PackageInfo; 82 83sub sign_existing_package 84{ 85 my ($self, $state, $pkg) = @_; 86 my $output = $state->{output_dir}; 87 my $dir = $pkg->info; 88 my $plist = OpenBSD::PackingList->fromfile($dir.CONTENTS); 89 my $dest = $output.'/'.$plist->pkgname.".tgz"; 90 # In incremental mode, don't bother signing known packages 91 if ($state->opt('i')) { 92 if (-f $dest) { 93 $pkg->wipe_info; 94 return; 95 } 96 } 97 $plist->set_infodir($dir); 98 $state->add_signature($plist); 99 $plist->save; 100 my (undef, $tmp) = OpenBSD::Temp::permanent_file($output, "pkg"); 101 my $wrarc = $state->create_archive($tmp, "."); 102 103 my $fh; 104 my $url = $pkg->url; 105 my $buffer; 106 107 if (defined $pkg->{length} and 108 $url =~ s/^file:// and open($fh, "<", $url) and 109 $fh->seek($pkg->{length}, 0) and $fh->read($buffer, 2) 110 and $buffer eq "\x1f\x8b" and $fh->seek($pkg->{length}, 0)) { 111 #$state->say("FAST #1", $plist->pkgname); 112 $wrarc->destdir($pkg->info); 113 my $e = $wrarc->prepare('+CONTENTS'); 114 $e->write; 115 close($wrarc->{fh}); 116 delete $wrarc->{fh}; 117 118 open(my $fh2, ">>", $tmp) or 119 $state->fatal("Can't append to #1", $tmp); 120 require File::Copy; 121 File::Copy::copy($fh, $fh2) or 122 $state->fatal("Error in copy #1", $!); 123 close($fh2); 124 } else { 125 #$state->say("SLOW #1", $plist->pkgname); 126 $plist->copy_over($state, $wrarc, $pkg); 127 $wrarc->close; 128 } 129 close($fh) if defined $fh; 130 131 $pkg->wipe_info; 132 chmod((0666 & ~umask), $tmp); 133 rename($tmp, $dest) or 134 $state->fatal("Can't create final signed package: #1", $!); 135 if ($state->opt('C')) { 136 $state->system(sub { 137 chdir($output); 138 open(STDOUT, '>>', 'SHA256'); 139 }, 140 OpenBSD::Paths->sha256, '-b', $plist->pkgname.".tgz"); 141 } 142} 143 144sub sign_list 145{ 146 my ($self, $l, $repo, $maxjobs, $state) = @_; 147 $state->{total} = scalar @$l; 148 $maxjobs //= 1; 149 my $code = sub { 150 my $pkg = $repo->find(shift); 151 $self->sign_existing_package($state, $pkg); 152 }; 153 my $display = $state->verbose ? 154 sub { 155 $state->progress->set_header("Signed ".shift); 156 $state->{done}++; 157 $state->progress->next($state->ntogo); 158 } : 159 sub { 160 }; 161 if ($maxjobs > 1) { 162 my $jobs = {}; 163 my $n = 0; 164 my $reap_job = sub { 165 my $pid = wait; 166 if (!defined $jobs->{$pid}) { 167 $state->fatal("Wait returned #1: unknown process", $pid); 168 } 169 if ($? != 0) { 170 $state->fatal("Signature of #1 failed\n", 171 $jobs->{$pid}); 172 } 173 $n--; 174 &$display($jobs->{$pid}); 175 delete $state->{signer}{pubkey}; 176 delete $jobs->{$pid}; 177 }; 178 179 while (@$l > 0) { 180 my $name = shift @$l; 181 my $pid = fork(); 182 if ($pid == 0) { 183 $repo->reinitialize; 184 &$code($name); 185 exit(0); 186 } else { 187 $jobs->{$pid} = $name; 188 $n++; 189 } 190 if ($n >= $maxjobs) { 191 &$reap_job; 192 } 193 } 194 while ($n != 0) { 195 &$reap_job; 196 } 197 } else { 198 for my $name (@$l) { 199 &$code($name); 200 &$display($name); 201 delete $state->{signer}{pubkey}; 202 } 203 } 204 if ($state->opt('C')) { 205 $state->system(sub { 206 chdir($state->{output_dir}); 207 open(STDOUT, '>', 'SHA256.new'); 208 }, 'sort', 'SHA256'); 209 rename($state->{output_dir}.'/SHA256.new', 210 $state->{output_dir}.'/SHA256'); 211 } 212} 213 214sub sign_existing_repository 215{ 216 my ($self, $state, $source) = @_; 217 require OpenBSD::PackageRepository; 218 my $repo = OpenBSD::PackageRepository->new($source, $state); 219 my @list = sort @{$repo->list}; 220 if (@list == 0) { 221 $state->errsay('Source repository "#1" is empty', $source); 222 } 223 $self->sign_list(\@list, $repo, $state->opt('j'), $state); 224} 225 226 227sub parse_and_run 228{ 229 my ($self, $cmd) = @_; 230 my $state = OpenBSD::PkgSign::State->new($cmd); 231 $state->handle_options; 232 $state->{wantntogo} = $state->config->istrue("ntogo"); 233 if (!defined $state->{source} && @ARGV == 0) { 234 $state->usage("Nothing to sign"); 235 } 236 if (defined $state->{source}) { 237 $self->sign_existing_repository($state, 238 $state->{source}); 239 } 240 $self->sign_list(\@ARGV, $state->repo, $state->opt('j'), 241 $state); 242 return 0; 243} 244 245