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