]>
Commit | Line | Data |
---|---|---|
1 | package PVE::APLInfo; | |
2 | ||
3 | use strict; | |
4 | use IO::File; | |
5 | use PVE::SafeSyslog; | |
6 | use LWP::UserAgent; | |
7 | use POSIX qw(strftime); | |
8 | ||
9 | my $logfile = "/var/log/pveam.log"; | |
10 | my $aplinfodir = "/var/lib/pve-manager/apl-info"; | |
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> | |
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> | |
20 | ||
21 | my $valid_keys = { | |
22 | '9ABD7E02AD243AD3C2FBBCCCB0C1CC225CAC72FE' => 1, # fingerprint support@proxmox.com | |
23 | '25CAC72FE' => 1, # keyid support@proxmox.com | |
24 | '694CFF26795A29BAE07B4EB585C25E95A16EB94D' => 1, # fingerprint release@turnkeylinux.com | |
25 | 'A16EB94D' => 1, # keyid release@turnkeylinux.com | |
26 | }; | |
27 | ||
28 | sub import_gpg_keys { | |
29 | ||
30 | my @keyfiles = ('support@proxmox.com.pubkey', 'release@turnkeylinux.com.pubkey'); | |
31 | ||
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 | } | |
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 | ||
51 | sub read_aplinfo_from_fh { | |
52 | my ($fh, $list, $source, $update) = @_; | |
53 | ||
54 | local $/ = ""; | |
55 | ||
56 | while (my $rec = <$fh>) { | |
57 | chomp $rec; | |
58 | ||
59 | my $res = {}; | |
60 | ||
61 | while ($rec) { | |
62 | ||
63 | if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) { | |
64 | $res->{headline} = $1; | |
65 | my $long = $2; | |
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; | |
72 | if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)-(\d+)$/) { | |
73 | $res->{version} = $version; | |
74 | } else { | |
75 | my $msg = "unable to parse appliance record: version = '$version'\n"; | |
76 | $update ? die $msg : warn $msg; | |
77 | } | |
78 | } elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) { | |
79 | my $type = $1; | |
80 | if ($type =~ m/^(openvz|lxc)$/) { | |
81 | $res->{type} = $type; | |
82 | } else { | |
83 | my $msg = "unable to parse appliance record: unknown type '$type'\n"; | |
84 | $update ? die $msg : warn $msg; | |
85 | } | |
86 | } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) { | |
87 | $res->{lc $1} = $2; | |
88 | } else { | |
89 | my $msg = "unable to parse appliance record: $rec\n"; | |
90 | $update ? die $msg : warn $msg; | |
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}; | |
108 | $template =~ s|.*/([^/]+.tar.gz)|$1|; | |
109 | } else { | |
110 | my $arch = $res->{architecture} || 'i386'; | |
111 | $template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz"; | |
112 | $template =~ s/$res->{os}-$res->{os}-/$res->{os}-/; | |
113 | } | |
114 | $res->{source} = $source; | |
115 | $res->{template} = $template; | |
116 | $list->{$res->{section}}->{$template} = $res; | |
117 | $list->{'all'}->{$template} = $res; | |
118 | } else { | |
119 | my $msg = "found incomplete appliance records\n"; | |
120 | $update ? die $msg : warn $msg; | |
121 | } | |
122 | } | |
123 | } | |
124 | ||
125 | sub read_aplinfo { | |
126 | my ($filename, $list, $source, $update) = @_; | |
127 | ||
128 | my $fh = IO::File->new("<$filename") || | |
129 | die "unable to open file '$filename' - $!\n"; | |
130 | ||
131 | eval { read_aplinfo_from_fh($fh, $list, $source, $update); }; | |
132 | my $err = $@; | |
133 | ||
134 | close($fh); | |
135 | ||
136 | die $err if $err; | |
137 | ||
138 | return $list; | |
139 | } | |
140 | ||
141 | sub url_get { | |
142 | my ($ua, $url, $file, $logfh) = @_; | |
143 | ||
144 | my $req = HTTP::Request->new(GET => $url); | |
145 | ||
146 | logmsg ($logfh, "start download $url"); | |
147 | my $res = $ua->request($req, $file); | |
148 | ||
149 | if ($res->is_success) { | |
150 | logmsg ($logfh, "download finished: " . $res->status_line); | |
151 | return 0; | |
152 | } | |
153 | ||
154 | logmsg ($logfh, "download failed: " . $res->status_line); | |
155 | ||
156 | return 1; | |
157 | } | |
158 | ||
159 | sub download_aplinfo { | |
160 | my ($ua, $aplurl, $host, $logfd) = @_; | |
161 | ||
162 | my $aplsrcurl = "$aplurl/aplinfo.dat.gz"; | |
163 | my $aplsigurl = "$aplurl/aplinfo.dat.asc"; | |
164 | ||
165 | my $tmp = "$aplinfodir/pveam-${host}.tmp.$$"; | |
166 | my $tmpgz = "$tmp.gz"; | |
167 | my $sigfn = "$tmp.asc"; | |
168 | ||
169 | eval { | |
170 | ||
171 | if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) { | |
172 | die "update failed - no signature file '$sigfn'\n"; | |
173 | } | |
174 | ||
175 | if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) { | |
176 | die "update failed - no data file '$aplsrcurl'\n"; | |
177 | } | |
178 | ||
179 | if (system("zcat -f $tmpgz >$tmp 2>/dev/null") != 0) { | |
180 | die "update failed: unable to unpack '$tmpgz'\n"; | |
181 | } | |
182 | ||
183 | # verify signature | |
184 | ||
185 | my $cmd = "/usr/bin/gpg --verify --trust-model always --batch --no-tty --status-fd=1 -q " . | |
186 | "--logger-fd=1 $sigfn $tmp"; | |
187 | ||
188 | open(CMD, "$cmd|") || | |
189 | die "unable to execute '$cmd': $!\n"; | |
190 | ||
191 | my $line; | |
192 | my $signer = ''; | |
193 | while (defined($line = <CMD>)) { | |
194 | chomp $line; | |
195 | logmsg($logfd, $line); | |
196 | ||
197 | # code borrowed from SA | |
198 | next if $line !~ /^\Q[GNUPG:]\E (?:VALID|GOOD)SIG (\S{8,40})/; | |
199 | my $key = $1; | |
200 | ||
201 | # we want either a keyid (8) or a fingerprint (40) | |
202 | if (length $key > 8 && length $key < 40) { | |
203 | substr($key, 8) = ''; | |
204 | } | |
205 | # use the longest match we can find | |
206 | $signer = $key if (length $key > length $signer) && $valid_keys->{$key}; | |
207 | } | |
208 | ||
209 | close(CMD); | |
210 | ||
211 | die "unable to verify signature\n" if !$signer; | |
212 | ||
213 | logmsg($logfd, "signature valid: $signer"); | |
214 | ||
215 | # test syntax | |
216 | eval { | |
217 | my $fh = IO::File->new("<$tmp") || | |
218 | die "unable to open file '$tmp' - $!\n"; | |
219 | read_aplinfo($tmp, {}, $aplurl, 1); | |
220 | close($fh); | |
221 | }; | |
222 | die "update failed: $@" if $@; | |
223 | ||
224 | if (system("mv $tmp $aplinfodir/$host 2>/dev/null") != 0) { | |
225 | die "update failed: unable to store data\n"; | |
226 | } | |
227 | ||
228 | logmsg($logfd, "update sucessful"); | |
229 | }; | |
230 | ||
231 | my $err = $@; | |
232 | ||
233 | unlink $tmp; | |
234 | unlink $tmpgz; | |
235 | unlink $sigfn; | |
236 | ||
237 | die $err if $err; | |
238 | } | |
239 | ||
240 | sub get_apl_sources { | |
241 | ||
242 | my $urls = []; | |
243 | push @$urls, "http://download.proxmox.com/appliances"; | |
244 | push @$urls, "http://releases.turnkeylinux.org/pve"; | |
245 | ||
246 | return $urls; | |
247 | } | |
248 | ||
249 | sub update { | |
250 | my ($proxy) = @_; | |
251 | ||
252 | my $size; | |
253 | if (($size = (-s $logfile) || 0) > (1024*50)) { | |
254 | system ("mv $logfile $logfile.0"); | |
255 | } | |
256 | my $logfd = IO::File->new (">>$logfile"); | |
257 | logmsg($logfd, "starting update"); | |
258 | ||
259 | import_gpg_keys(); | |
260 | ||
261 | # this code works for ftp and http | |
262 | # always use passive ftp | |
263 | local $ENV{FTP_PASSIVE} = 1; | |
264 | my $ua = LWP::UserAgent->new; | |
265 | $ua->agent("PVE/1.0"); | |
266 | ||
267 | if ($proxy) { | |
268 | $ua->proxy(['http'], $proxy); | |
269 | } else { | |
270 | $ua->env_proxy; | |
271 | } | |
272 | ||
273 | my $urls = get_apl_sources(); | |
274 | ||
275 | mkdir $aplinfodir; | |
276 | ||
277 | my @dlerr = (); | |
278 | foreach my $aplurl (@$urls) { | |
279 | eval { | |
280 | my $uri = URI->new($aplurl); | |
281 | my $host = $uri->host(); | |
282 | download_aplinfo($ua, $aplurl, $host, $logfd); | |
283 | }; | |
284 | if (my $err = $@) { | |
285 | logmsg ($logfd, $err); | |
286 | push @dlerr, $aplurl; | |
287 | } | |
288 | } | |
289 | ||
290 | close($logfd); | |
291 | ||
292 | return 0 if scalar(@dlerr); | |
293 | ||
294 | return 1; | |
295 | } | |
296 | ||
297 | sub load_data { | |
298 | ||
299 | my $urls = get_apl_sources(); | |
300 | ||
301 | my $list = {}; | |
302 | ||
303 | foreach my $aplurl (@$urls) { | |
304 | ||
305 | eval { | |
306 | ||
307 | my $uri = URI->new($aplurl); | |
308 | my $host = $uri->host(); | |
309 | read_aplinfo("$aplinfodir/$host", $list, $aplurl); | |
310 | }; | |
311 | warn $@ if $@; | |
312 | } | |
313 | ||
314 | return $list; | |
315 | } | |
316 | ||
317 | 1; | |
318 |