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