]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::APLInfo; |
2 | ||
3 | use strict; | |
4 | use IO::File; | |
5 | use PVE::SafeSyslog; | |
aff192e6 | 6 | use LWP::UserAgent; |
aff192e6 DM |
7 | use POSIX qw(strftime); |
8 | ||
9 | my $logfile = "/var/log/pveam.log"; | |
c9164975 | 10 | my $aplinfodir = "/var/lib/pve-manager/apl-info"; |
aff192e6 DM |
11 | |
12 | # Default list of GPG keys allowed to sign aplinfo | |
13 | # | |
14 | #pub 1024D/5CAC72FE 2004-06-24 | |
15 | # Key fingerprint = 9ABD 7E02 AD24 3AD3 C2FB BCCC B0C1 CC22 5CAC 72FE | |
16 | #uid Proxmox Support Team <support@proxmox.com> | |
c9164975 DM |
17 | #pub 2048R/A16EB94D 2008-08-15 [expires: 2023-08-12] |
18 | # Key fingerprint = 694C FF26 795A 29BA E07B 4EB5 85C2 5E95 A16E B94D | |
19 | #uid Turnkey Linux Release Key <release@turnkeylinux.com> | |
aff192e6 DM |
20 | |
21 | my $valid_keys = { | |
22 | '9ABD7E02AD243AD3C2FBBCCCB0C1CC225CAC72FE' => 1, # fingerprint support@proxmox.com | |
23 | '25CAC72FE' => 1, # keyid support@proxmox.com | |
c9164975 DM |
24 | '694CFF26795A29BAE07B4EB585C25E95A16EB94D' => 1, # fingerprint release@turnkeylinux.com |
25 | 'A16EB94D' => 1, # keyid release@turnkeylinux.com | |
aff192e6 DM |
26 | }; |
27 | ||
28 | sub import_gpg_keys { | |
29 | ||
c9164975 | 30 | my @keyfiles = ('support@proxmox.com.pubkey', 'release@turnkeylinux.com.pubkey'); |
aff192e6 | 31 | |
c9164975 DM |
32 | foreach my $key (@keyfiles) { |
33 | my $fn = "/usr/share/doc/pve-manager/$key"; | |
34 | system ("/usr/bin/gpg --batch --no-tty --status-fd=1 -q " . | |
35 | "--logger-fd=1 --import $fn >>$logfile"); | |
36 | } | |
aff192e6 DM |
37 | } |
38 | ||
39 | sub logmsg { | |
40 | my ($logfd, $msg) = @_; | |
41 | ||
42 | chomp $msg; | |
43 | ||
44 | my $tstr = strftime ("%b %d %H:%M:%S", localtime); | |
45 | ||
46 | foreach my $line (split (/\n/, $msg)) { | |
47 | print $logfd "$tstr $line\n"; | |
48 | } | |
49 | } | |
50 | ||
6de794eb DM |
51 | sub read_aplinfo_from_fh { |
52 | my ($fh, $list, $source, $update) = @_; | |
c9164975 DM |
53 | |
54 | local $/ = ""; | |
55 | ||
6de794eb DM |
56 | while (my $rec = <$fh>) { |
57 | chomp $rec; | |
c9164975 | 58 | |
6de794eb DM |
59 | my $res = {}; |
60 | ||
61 | while ($rec) { | |
62 | ||
63 | if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) { | |
64 | $res->{headline} = $1; | |
648af5ab | 65 | my $long = $2 || ''; |
6de794eb DM |
66 | $long =~ s/\n\s+/ /g; |
67 | $long =~ s/^\s+//g; | |
68 | $long =~ s/\s+$//g; | |
69 | $res->{description} = $long; | |
70 | } elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) { | |
71 | my $version = $1; | |
f6b20cf9 | 72 | if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)(-(\d+))?$/) { |
6de794eb | 73 | $res->{version} = $version; |
c9164975 | 74 | } else { |
6de794eb DM |
75 | my $msg = "unable to parse appliance record: version = '$version'\n"; |
76 | $update ? die $msg : warn $msg; | |
c9164975 | 77 | } |
6de794eb DM |
78 | } elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) { |
79 | my $type = $1; | |
80 | if ($type =~ m/^(openvz|lxc)$/) { | |
81 | $res->{type} = $type; | |
c9164975 | 82 | } else { |
6de794eb DM |
83 | my $msg = "unable to parse appliance record: unknown type '$type'\n"; |
84 | $update ? die $msg : warn $msg; | |
c9164975 | 85 | } |
6de794eb DM |
86 | } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) { |
87 | $res->{lc $1} = $2; | |
c9164975 | 88 | } else { |
6de794eb | 89 | my $msg = "unable to parse appliance record: $rec\n"; |
c9164975 | 90 | $update ? die $msg : warn $msg; |
6de794eb DM |
91 | $res = {}; |
92 | last; | |
93 | } | |
94 | } | |
95 | ||
96 | if ($res->{'package'} eq 'pve-web-news' && $res->{description}) { | |
97 | $list->{'all'}->{$res->{'package'}} = $res; | |
98 | next; | |
99 | } | |
100 | ||
101 | $res->{section} = 'unknown' if !$res->{section}; | |
102 | ||
103 | if ($res->{'package'} && $res->{type} && $res->{os} && $res->{version} && | |
104 | $res->{infopage}) { | |
105 | my $template; | |
106 | if ($res->{location}) { | |
107 | $template = $res->{location}; | |
7a07d675 DM |
108 | $template =~ s|.*/([^/]+.tar.[gx]z)$|$1|; |
109 | if ($res->{location} !~ m|^([a-zA-Z]+)\://|) { | |
110 | # relative localtion (no http:// prefix) | |
111 | $res->{location} = "$source/$res->{location}"; | |
112 | } | |
6de794eb | 113 | } else { |
07c1e6b0 DM |
114 | my $arch = $res->{architecture} || 'i386'; |
115 | $template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz"; | |
6de794eb | 116 | $template =~ s/$res->{os}-$res->{os}-/$res->{os}-/; |
4886fe07 | 117 | $res->{location} = "$source/$res->{section}/$template"; |
c9164975 | 118 | } |
6de794eb DM |
119 | $res->{source} = $source; |
120 | $res->{template} = $template; | |
121 | $list->{$res->{section}}->{$template} = $res; | |
122 | $list->{'all'}->{$template} = $res; | |
123 | } else { | |
124 | my $msg = "found incomplete appliance records\n"; | |
125 | $update ? die $msg : warn $msg; | |
c9164975 | 126 | } |
6de794eb DM |
127 | } |
128 | } | |
129 | ||
130 | sub read_aplinfo { | |
131 | my ($filename, $list, $source, $update) = @_; | |
132 | ||
133 | my $fh = IO::File->new("<$filename") || | |
134 | die "unable to open file '$filename' - $!\n"; | |
135 | ||
136 | eval { read_aplinfo_from_fh($fh, $list, $source, $update); }; | |
c9164975 DM |
137 | my $err = $@; |
138 | ||
139 | close($fh); | |
140 | ||
141 | die $err if $err; | |
142 | ||
143 | return $list; | |
144 | } | |
145 | ||
aff192e6 DM |
146 | sub url_get { |
147 | my ($ua, $url, $file, $logfh) = @_; | |
148 | ||
149 | my $req = HTTP::Request->new(GET => $url); | |
150 | ||
151 | logmsg ($logfh, "start download $url"); | |
152 | my $res = $ua->request($req, $file); | |
153 | ||
154 | if ($res->is_success) { | |
155 | logmsg ($logfh, "download finished: " . $res->status_line); | |
156 | return 0; | |
157 | } | |
158 | ||
159 | logmsg ($logfh, "download failed: " . $res->status_line); | |
160 | ||
161 | return 1; | |
162 | } | |
163 | ||
c9164975 DM |
164 | sub download_aplinfo { |
165 | my ($ua, $aplurl, $host, $logfd) = @_; | |
aff192e6 | 166 | |
aff192e6 DM |
167 | my $aplsrcurl = "$aplurl/aplinfo.dat.gz"; |
168 | my $aplsigurl = "$aplurl/aplinfo.dat.asc"; | |
169 | ||
c9164975 | 170 | my $tmp = "$aplinfodir/pveam-${host}.tmp.$$"; |
aff192e6 DM |
171 | my $tmpgz = "$tmp.gz"; |
172 | my $sigfn = "$tmp.asc"; | |
173 | ||
aff192e6 | 174 | eval { |
c9164975 DM |
175 | |
176 | if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) { | |
177 | die "update failed - no signature file '$sigfn'\n"; | |
aff192e6 DM |
178 | } |
179 | ||
c9164975 DM |
180 | if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) { |
181 | die "update failed - no data file '$aplsrcurl'\n"; | |
aff192e6 DM |
182 | } |
183 | ||
c9164975 | 184 | if (system("zcat -f $tmpgz >$tmp 2>/dev/null") != 0) { |
aff192e6 DM |
185 | die "update failed: unable to unpack '$tmpgz'\n"; |
186 | } | |
187 | ||
188 | # verify signature | |
189 | ||
c9164975 | 190 | my $cmd = "/usr/bin/gpg --verify --trust-model always --batch --no-tty --status-fd=1 -q " . |
aff192e6 DM |
191 | "--logger-fd=1 $sigfn $tmp"; |
192 | ||
c9164975 | 193 | open(CMD, "$cmd|") || |
aff192e6 DM |
194 | die "unable to execute '$cmd': $!\n"; |
195 | ||
196 | my $line; | |
197 | my $signer = ''; | |
c9164975 | 198 | while (defined($line = <CMD>)) { |
aff192e6 | 199 | chomp $line; |
c9164975 | 200 | logmsg($logfd, $line); |
aff192e6 DM |
201 | |
202 | # code borrowed from SA | |
203 | next if $line !~ /^\Q[GNUPG:]\E (?:VALID|GOOD)SIG (\S{8,40})/; | |
204 | my $key = $1; | |
205 | ||
206 | # we want either a keyid (8) or a fingerprint (40) | |
207 | if (length $key > 8 && length $key < 40) { | |
208 | substr($key, 8) = ''; | |
209 | } | |
210 | # use the longest match we can find | |
211 | $signer = $key if (length $key > length $signer) && $valid_keys->{$key}; | |
212 | } | |
213 | ||
c9164975 | 214 | close(CMD); |
aff192e6 DM |
215 | |
216 | die "unable to verify signature\n" if !$signer; | |
217 | ||
c9164975 | 218 | logmsg($logfd, "signature valid: $signer"); |
aff192e6 DM |
219 | |
220 | # test syntax | |
221 | eval { | |
c9164975 | 222 | my $fh = IO::File->new("<$tmp") || |
aff192e6 | 223 | die "unable to open file '$tmp' - $!\n"; |
c9164975 DM |
224 | read_aplinfo($tmp, {}, $aplurl, 1); |
225 | close($fh); | |
aff192e6 DM |
226 | }; |
227 | die "update failed: $@" if $@; | |
228 | ||
c9164975 | 229 | if (system("mv $tmp $aplinfodir/$host 2>/dev/null") != 0) { |
aff192e6 DM |
230 | die "update failed: unable to store data\n"; |
231 | } | |
232 | ||
c9164975 | 233 | logmsg($logfd, "update sucessful"); |
aff192e6 DM |
234 | }; |
235 | ||
236 | my $err = $@; | |
237 | ||
238 | unlink $tmp; | |
239 | unlink $tmpgz; | |
240 | unlink $sigfn; | |
241 | ||
c9164975 | 242 | die $err if $err; |
aff192e6 DM |
243 | } |
244 | ||
c9164975 DM |
245 | sub get_apl_sources { |
246 | ||
247 | my $urls = []; | |
648af5ab | 248 | push @$urls, "http://download.proxmox.com/images"; |
06399fd7 | 249 | push @$urls, "https://releases.turnkeylinux.org/pve"; |
aff192e6 | 250 | |
c9164975 | 251 | return $urls; |
aff192e6 DM |
252 | } |
253 | ||
c9164975 DM |
254 | sub update { |
255 | my ($proxy) = @_; | |
aff192e6 | 256 | |
c9164975 DM |
257 | my $size; |
258 | if (($size = (-s $logfile) || 0) > (1024*50)) { | |
259 | system ("mv $logfile $logfile.0"); | |
260 | } | |
261 | my $logfd = IO::File->new (">>$logfile"); | |
262 | logmsg($logfd, "starting update"); | |
aff192e6 | 263 | |
c9164975 | 264 | import_gpg_keys(); |
aff192e6 | 265 | |
c9164975 DM |
266 | my $ua = LWP::UserAgent->new; |
267 | $ua->agent("PVE/1.0"); | |
aff192e6 | 268 | |
c9164975 | 269 | if ($proxy) { |
49ee2d1a | 270 | $ua->proxy(['http', 'https'], $proxy); |
c9164975 DM |
271 | } else { |
272 | $ua->env_proxy; | |
273 | } | |
aff192e6 | 274 | |
c9164975 | 275 | my $urls = get_apl_sources(); |
aff192e6 | 276 | |
c9164975 | 277 | mkdir $aplinfodir; |
aff192e6 | 278 | |
c9164975 DM |
279 | my @dlerr = (); |
280 | foreach my $aplurl (@$urls) { | |
281 | eval { | |
282 | my $uri = URI->new($aplurl); | |
283 | my $host = $uri->host(); | |
284 | download_aplinfo($ua, $aplurl, $host, $logfd); | |
285 | }; | |
286 | if (my $err = $@) { | |
287 | logmsg ($logfd, $err); | |
288 | push @dlerr, $aplurl; | |
289 | } | |
290 | } | |
aff192e6 | 291 | |
c9164975 | 292 | close($logfd); |
aff192e6 | 293 | |
c9164975 | 294 | return 0 if scalar(@dlerr); |
aff192e6 | 295 | |
c9164975 | 296 | return 1; |
aff192e6 DM |
297 | } |
298 | ||
c9164975 | 299 | sub load_data { |
aff192e6 | 300 | |
75a6a7f5 | 301 | my $urls = get_apl_sources(); |
aff192e6 | 302 | |
c9164975 | 303 | my $list = {}; |
aff192e6 | 304 | |
c9164975 | 305 | foreach my $aplurl (@$urls) { |
aff192e6 | 306 | |
c9164975 | 307 | eval { |
aff192e6 | 308 | |
c9164975 DM |
309 | my $uri = URI->new($aplurl); |
310 | my $host = $uri->host(); | |
311 | read_aplinfo("$aplinfodir/$host", $list, $aplurl); | |
312 | }; | |
313 | warn $@ if $@; | |
314 | } | |
aff192e6 | 315 | |
c9164975 | 316 | return $list; |
aff192e6 DM |
317 | } |
318 | ||
319 | 1; | |
320 |