package PVE::APLInfo; use strict; use IO::File; use PVE::SafeSyslog; use LWP::UserAgent; use POSIX qw(strftime); my $logfile = "/var/log/pveam.log"; my $aplinfodir = "/var/lib/pve-manager/apl-info"; # Default list of GPG keys allowed to sign aplinfo # #pub 1024D/5CAC72FE 2004-06-24 # Key fingerprint = 9ABD 7E02 AD24 3AD3 C2FB BCCC B0C1 CC22 5CAC 72FE #uid Proxmox Support Team #pub 2048R/A16EB94D 2008-08-15 [expires: 2023-08-12] # Key fingerprint = 694C FF26 795A 29BA E07B 4EB5 85C2 5E95 A16E B94D #uid Turnkey Linux Release Key my $valid_keys = { '9ABD7E02AD243AD3C2FBBCCCB0C1CC225CAC72FE' => 1, # fingerprint support@proxmox.com '25CAC72FE' => 1, # keyid support@proxmox.com '694CFF26795A29BAE07B4EB585C25E95A16EB94D' => 1, # fingerprint release@turnkeylinux.com 'A16EB94D' => 1, # keyid release@turnkeylinux.com }; sub import_gpg_keys { my @keyfiles = ('support@proxmox.com.pubkey', 'release@turnkeylinux.com.pubkey'); foreach my $key (@keyfiles) { my $fn = "/usr/share/doc/pve-manager/$key"; system ("/usr/bin/gpg --batch --no-tty --status-fd=1 -q " . "--logger-fd=1 --import $fn >>$logfile"); } } sub logmsg { my ($logfd, $msg) = @_; chomp $msg; my $tstr = strftime ("%b %d %H:%M:%S", localtime); foreach my $line (split (/\n/, $msg)) { print $logfd "$tstr $line\n"; } } sub read_aplinfo { my ($filename, $list, $source, $update) = @_; my $fh = IO::File->new("<$filename") || die "unable to open file '$filename' - $!\n"; local $/ = ""; eval { while (my $rec = <$fh>) { chomp $rec; my $res = {}; while ($rec) { if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) { $res->{headline} = $1; my $long = $2; $long =~ s/\n\s+/ /g; $long =~ s/^\s+//g; $long =~ s/\s+$//g; $res->{description} = $long; } elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) { my $version = $1; if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)-(\d+)$/) { $res->{version} = $version; } else { my $msg = "unable to parse appliance record: version = '$version'\n"; $update ? die $msg : warn $msg; } } elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) { my $type = $1; if ($type =~ m/^(openvz)$/) { $res->{type} = $type; } else { my $msg = "unable to parse appliance record: unknown type '$type'\n"; $update ? die $msg : warn $msg; } } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) { $res->{lc $1} = $2; } else { my $msg = "unable to parse appliance record: $rec\n"; $update ? die $msg : warn $msg; $res = {}; last; } } if ($res->{'package'} eq 'pve-web-news' && $res->{description}) { $list->{'all'}->{$res->{'package'}} = $res; next; } $res->{section} = 'unknown' if !$res->{section}; if ($res->{'package'} && $res->{type} && $res->{os} && $res->{version} && $res->{infopage}) { my $template; if ($res->{location}) { $template = $res->{location}; $template =~ s|.*/([^/]+.tar.gz)|$1|; } else { $template = "$res->{os}-$res->{package}_$res->{version}_i386.tar.gz"; $template =~ s/$res->{os}-$res->{os}-/$res->{os}-/; } $res->{source} = $source; $res->{template} = $template; $list->{$res->{section}}->{$template} = $res; $list->{'all'}->{$template} = $res; } else { my $msg = "found incomplete appliance records\n"; $update ? die $msg : warn $msg; } } }; my $err = $@; close($fh); die $err if $err; return $list; } sub url_get { my ($ua, $url, $file, $logfh) = @_; my $req = HTTP::Request->new(GET => $url); logmsg ($logfh, "start download $url"); my $res = $ua->request($req, $file); if ($res->is_success) { logmsg ($logfh, "download finished: " . $res->status_line); return 0; } logmsg ($logfh, "download failed: " . $res->status_line); return 1; } sub download_aplinfo { my ($ua, $aplurl, $host, $logfd) = @_; my $aplsrcurl = "$aplurl/aplinfo.dat.gz"; my $aplsigurl = "$aplurl/aplinfo.dat.asc"; my $tmp = "$aplinfodir/pveam-${host}.tmp.$$"; my $tmpgz = "$tmp.gz"; my $sigfn = "$tmp.asc"; eval { if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) { die "update failed - no signature file '$sigfn'\n"; } if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) { die "update failed - no data file '$aplsrcurl'\n"; } if (system("zcat -f $tmpgz >$tmp 2>/dev/null") != 0) { die "update failed: unable to unpack '$tmpgz'\n"; } # verify signature my $cmd = "/usr/bin/gpg --verify --trust-model always --batch --no-tty --status-fd=1 -q " . "--logger-fd=1 $sigfn $tmp"; open(CMD, "$cmd|") || die "unable to execute '$cmd': $!\n"; my $line; my $signer = ''; while (defined($line = )) { chomp $line; logmsg($logfd, $line); # code borrowed from SA next if $line !~ /^\Q[GNUPG:]\E (?:VALID|GOOD)SIG (\S{8,40})/; my $key = $1; # we want either a keyid (8) or a fingerprint (40) if (length $key > 8 && length $key < 40) { substr($key, 8) = ''; } # use the longest match we can find $signer = $key if (length $key > length $signer) && $valid_keys->{$key}; } close(CMD); die "unable to verify signature\n" if !$signer; logmsg($logfd, "signature valid: $signer"); # test syntax eval { my $fh = IO::File->new("<$tmp") || die "unable to open file '$tmp' - $!\n"; read_aplinfo($tmp, {}, $aplurl, 1); close($fh); }; die "update failed: $@" if $@; if (system("mv $tmp $aplinfodir/$host 2>/dev/null") != 0) { die "update failed: unable to store data\n"; } logmsg($logfd, "update sucessful"); }; my $err = $@; unlink $tmp; unlink $tmpgz; unlink $sigfn; die $err if $err; } sub get_apl_sources { my $urls = []; push @$urls, "http://download.proxmox.com/appliances"; push @$urls, "http://releases.turnkeylinux.org/pve"; return $urls; } sub update { my ($proxy) = @_; my $size; if (($size = (-s $logfile) || 0) > (1024*50)) { system ("mv $logfile $logfile.0"); } my $logfd = IO::File->new (">>$logfile"); logmsg($logfd, "starting update"); import_gpg_keys(); # this code works for ftp and http # always use passive ftp local $ENV{FTP_PASSIVE} = 1; my $ua = LWP::UserAgent->new; $ua->agent("PVE/1.0"); if ($proxy) { $ua->proxy(['http'], $proxy); } else { $ua->env_proxy; } my $urls = get_apl_sources(); mkdir $aplinfodir; my @dlerr = (); foreach my $aplurl (@$urls) { eval { my $uri = URI->new($aplurl); my $host = $uri->host(); download_aplinfo($ua, $aplurl, $host, $logfd); }; if (my $err = $@) { logmsg ($logfd, $err); push @dlerr, $aplurl; } } close($logfd); return 0 if scalar(@dlerr); return 1; } sub load_data { my $urls = get_apl_sources(); my $list = {}; foreach my $aplurl (@$urls) { eval { my $uri = URI->new($aplurl); my $host = $uri->host(); read_aplinfo("$aplinfodir/$host", $list, $aplurl); }; warn $@ if $@; } return $list; } 1;