xref: /openbsd/usr.sbin/pkg_add/OpenBSD/PkgSign.pm (revision 4cfece93)
1#! /usr/bin/perl
2# ex:ts=8 sw=4:
3# $OpenBSD: PkgSign.pm,v 1.17 2019/07/08 10:55:39 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->{extra_stats} = 0;
33	$state->{opt} = {
34	    'o' =>
35		    sub {
36			    $state->{output_dir} = shift;
37		    },
38	    'S' =>
39		    sub {
40			    $state->{source} = shift;
41		    },
42	    's' =>
43		    sub {
44			    push(@{$state->{signature_params}}, shift);
45		    },
46	    'V' =>
47		    sub {
48			    $state->{extra_stats}++;
49		    },
50	};
51	$state->{signature_style} = 'unsigned';
52
53	$state->SUPER::handle_options('Cij:o:S:s:V',
54	    '[-CvV] [-D name[=value]] -s signify2 -s priv',
55	    '[-o dir] [-S source] [pkg-name...]');
56	if (defined $state->{signature_params}) {
57		$state->{signer} = OpenBSD::Signer->factory($state);
58	}
59    	if (!defined $state->{signer}) {
60		$state->usage("Can't invoke command without valid signing parameters");
61	}
62	$state->{output_dir} //= ".";
63	if (!-d $state->{output_dir}) {
64		require File::Path;
65		File::Path::make_path($state->{output_dir})
66		    or $state->usage("can't create dir");
67	}
68	$state->{wantntogo} = $state->{extra_stats};
69}
70
71package OpenBSD::PkgSign;
72use OpenBSD::Temp;
73use OpenBSD::PackingList;
74use OpenBSD::PackageInfo;
75
76sub sign_existing_package
77{
78	my ($self, $state, $pkg) = @_;
79	my $output = $state->{output_dir};
80	my $dest = $output.'/'.$pkg->name.".tgz";
81	if ($state->opt('i')) {
82		if (-f $dest) {
83			return;
84	    	}
85	}
86	my (undef, $tmp) = OpenBSD::Temp::permanent_file($output, "pkg") or
87	    die $state->fatal(OpenBSD::Temp->last_error);
88	$state->{signer}->sign($pkg, $state, $tmp);
89
90	chmod((0666 & ~umask), $tmp);
91	rename($tmp, $dest) or
92	    $state->fatal("Can't create final signed package: #1", $!);
93	if ($state->opt('C')) {
94		$state->system(sub {
95		    chdir($output);
96		    open(STDOUT, '>>', 'SHA256');
97		    },
98		    OpenBSD::Paths->sha256, '-b', $pkg->name.".tgz");
99    	}
100}
101
102sub sign_list
103{
104	my ($self, $l, $repo, $maxjobs, $state) = @_;
105	$state->{total} = scalar @$l;
106	$maxjobs //= 1;
107	my $code = sub {
108		my $name = shift;
109		my $pkg = $repo->find($name);
110		if (!defined $pkg) {
111			$state->errsay("#1 not found", $name);
112		} else {
113			$self->sign_existing_package($state, $pkg);
114		}
115	    };
116	my $display = $state->verbose ?
117	    sub {
118		$state->progress->set_header("Signed ".shift);
119		$state->{done}++;
120		$state->progress->next($state->ntogo);
121	    } :
122	    sub {
123	    };
124	if ($maxjobs > 1) {
125		my $jobs = {};
126		my $n = 0;
127		my $reap_job = sub {
128			my $pid = wait;
129			if (!defined $jobs->{$pid}) {
130				$state->fatal("Wait returned #1: unknown process", $pid);
131			}
132			if ($? != 0) {
133				$state->fatal("Signature of #1 failed\n",
134				    $jobs->{$pid});
135			}
136			$n--;
137			&$display($jobs->{$pid});
138			delete $state->{signer}{pubkey};
139			delete $jobs->{$pid};
140		};
141
142		while (@$l > 0) {
143			my $name = shift @$l;
144			my $pid = fork();
145			if ($pid == 0) {
146				$repo->reinitialize;
147				&$code($name);
148				exit(0);
149			} else {
150				$jobs->{$pid} = $name;
151				$n++;
152			}
153			if ($n >= $maxjobs) {
154				&$reap_job;
155			}
156		}
157		while ($n != 0) {
158			&$reap_job;
159		}
160	} else {
161		for my $name (@$l) {
162			&$code($name);
163			&$display($name);
164			delete $state->{signer}{pubkey};
165		}
166	}
167	if ($state->opt('C')) {
168		$state->system(sub {
169		    chdir($state->{output_dir});
170		    open(STDOUT, '>', 'SHA256.new');
171		    }, 'sort', 'SHA256');
172		rename($state->{output_dir}.'/SHA256.new',
173		    $state->{output_dir}.'/SHA256');
174	}
175}
176
177sub sign_existing_repository
178{
179	my ($self, $state, $source) = @_;
180	require OpenBSD::PackageRepository;
181	my $repo = OpenBSD::PackageRepository->new($source, $state);
182	if ($state->{signer}->want_local && !$repo->is_local_file) {
183		$state->fatal("Signing distant source is not supported");
184	}
185	my @list = sort @{$repo->list};
186	if (@list == 0) {
187		$state->errsay('Source repository "#1" is empty', $source);
188    	}
189	$self->sign_list(\@list, $repo, $state->opt('j'), $state);
190}
191
192
193sub parse_and_run
194{
195	my ($self, $cmd) = @_;
196	my $state = OpenBSD::PkgSign::State->new($cmd);
197	$state->handle_options;
198	if (!defined $state->{source} && @ARGV == 0) {
199		$state->usage("Nothing to sign");
200	}
201	if (defined $state->{source}) {
202		$self->sign_existing_repository($state,
203		    $state->{source});
204	}
205	$self->sign_list(\@ARGV, $state->repo, $state->opt('j'),
206	    $state);
207	return 0;
208}
209
210