xref: /openbsd/usr.sbin/pkg_add/OpenBSD/PkgSign.pm (revision 264ca280)
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