apt-sec 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use Storable; # persistant storage
  5. #use LWP::Simple; # libwww-perl
  6. #use LWP::Debug qw(+);
  7. use LWP::UserAgent;
  8. use Config::Auto; # libconfig-auto-perl
  9. use Time::ParseDate; # libtime-modules-perl
  10. #use Linux::Distribution qw(distribution_name distribution_version); # liblinux-distribution-perl
  11. use IO::Uncompress::Bunzip2 qw(bunzip2 $Bunzip2Error);
  12. use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  13. use Digest::MD5 qw(md5_hex);
  14. use POSIX qw(mktime);
  15. use File::Path qw(make_path);
  16. use HTML::Parser;
  17. # mustn't load the huge SHA1 table into RAM..
  18. use DBI;
  19. use Tie::File;
  20. use feature "switch";
  21. eval {
  22. require "debian-security-advisory.pl";
  23. require "ubuntu-security-advisory.pl";
  24. require "common-vulnerability-entry.pl";
  25. } or do {
  26. require "../debian-security-advisory.pl";
  27. require "../ubuntu-security-advisory.pl";
  28. require "../common-vulnerability-entry.pl";
  29. };
  30. # load user config, but don't parse it as perl
  31. $Config::Auto::DisablePerl = 1;
  32. my $config = Config::Auto::parse("apt-sec.conf");
  33. # setup HTTP client
  34. my $lwp = LWP::UserAgent->new(ssl_opts => {SSL_ca_path => "/etc/ssl/certs/"});
  35. $lwp->agent("apt-sec " . $lwp->agent);
  36. # global variables
  37. my $secperday = 60*60*24;
  38. my $now = mktime(gmtime()); # look, we're atomic! :-)
  39. my $verbosity = 1;
  40. # global lookup tables
  41. my %h_dsatable; # map dsa_id => dsa
  42. my %h_cvetable; # map cve_id => (rel-date, time-to-fix, score1, score2, score3))
  43. my %h_src2dsa; # map src-name => dsa_id
  44. my %h_dsa2cve; # map dsa_id => cve_id
  45. my %h_src2mtbf; # map src-name => MTBFstats
  46. my %h_deb2pkg; # map deb-name => pkg-name
  47. my %h_pkg2src; # map pkg-name => src-name
  48. my %h_sha1map; # map sha1sum => file-path
  49. my %h_pkg2virt; # map pkg => virt. pkgs
  50. my %h_virt2pkg; # map virt. pkgs => pkg
  51. my %h_state; # remember what we've already parsed/downloaded
  52. my $dsatable = \%h_dsatable;
  53. my $cvetable = \%h_cvetable;
  54. my $src2dsa = \%h_src2dsa;
  55. my $dsa2cve = \%h_dsa2cve;
  56. my $src2mtbf = \%h_src2mtbf;
  57. my $deb2pkg = \%h_deb2pkg;
  58. my $pkg2src = \%h_pkg2src;
  59. my $pkg2virt = \%h_pkg2virt;
  60. my $virt2pkg = \%h_virt2pkg;
  61. my $db; # global SQL DB
  62. my $dbargs = {AutoCommit => 0,
  63. PrintError => 0};
  64. my $state = \%h_state;
  65. ## logging
  66. # 1 fatal errors
  67. # 2 errors
  68. # 3 note
  69. # 4 trace
  70. # 5 debug
  71. sub msg {
  72. my $lvl = shift;
  73. my $msg = shift;
  74. if ($lvl <= $config->{"loglevel"}) {
  75. print $msg;
  76. }
  77. }
  78. sub debug { msg(5, shift); }
  79. sub trace { msg(4, shift); }
  80. sub note { msg(3, shift); }
  81. sub error { msg(2, shift); }
  82. sub fatal { msg(1, shift); }
  83. ## load state, different from DBs in that we always need it
  84. sub load_state {
  85. my $cache = $config->{cache_dir};
  86. my $err = 0;
  87. eval { $state = retrieve($cache . "state"); } or do {
  88. ## init with default state
  89. $state->{"next_adv"} = 0;
  90. $state->{"next_fsa"} = 0;
  91. $state->{"Packages"} = "";
  92. $state->{"Sources"} = "";
  93. $state->{"Sha1Sums"} = "";
  94. $state->{"vendor"} = "";
  95. $err++;
  96. };
  97. return 1-$err;
  98. }
  99. # helper function for getting HTTP(S) objects
  100. sub get {
  101. my $url = shift;
  102. #my $req = HTTP::Request->new(GET => $url);
  103. my $ret = $lwp->get($url);
  104. unless ($ret->is_success) {
  105. fatal("Failed: " . $ret->status_line . "\n");
  106. return;
  107. }
  108. return $ret->content;
  109. }
  110. sub detect_distribution {
  111. my $dist = "";
  112. #$dist = distribution_name();
  113. $dist = "debian";
  114. #$dist = "ubuntu";
  115. if ( $dist eq "") {
  116. die "Error: Distribution unknown, not LSB conform, bailing out.\n";
  117. }
  118. if ($state->{"vendor"} eq "" || $state->{"vendor"} ne $dist) {
  119. note "Detected $dist distribution\n";
  120. $state->{"vendor"} = $dist;
  121. }
  122. }
  123. ## save state, different from DBs in that we always need it
  124. sub save_state {
  125. my $cache = $config->{cache_dir};
  126. eval {
  127. store($state, $cache . "state");
  128. return 1;
  129. } or do {
  130. error "Failed to save state: ";
  131. error "$@\n";
  132. };
  133. }
  134. sub load_sha1lists {
  135. my $cache = $config->{cache_dir};
  136. $db = DBI->connect("dbi:SQLite:dbname=" . $cache . "sha1sums.db","","",$dbargs);
  137. eval {
  138. my @row = $db->selectrow_array("select * from SQLITE_MASTER where
  139. name='sha2pkg' and type='table'");
  140. } or do {
  141. # create table and reset last-seen info on Sha1Sums file
  142. $db->do("create table sha2pkg (hash char(20) primary key, deb text, src text)");
  143. die "$DBI::errstr\n" if ($db->err());
  144. $state->{"Sha1Sums"} = "";
  145. };
  146. }
  147. sub save_sha1lists {
  148. my $cache = $config->{cache_dir};
  149. $db->commit();
  150. $db->disconnect();
  151. die "$DBI::errstr\n" if ($db->err());
  152. }
  153. ## persistant storage
  154. sub load_DBs {
  155. my $cache = $config->{cache_dir};
  156. my $err = 0;
  157. eval {
  158. $dsatable = retrieve($cache . "dsatable");
  159. $cvetable = retrieve($cache . "cvetable");
  160. $src2dsa = retrieve($cache . "src2dsa");
  161. $dsa2cve = retrieve($cache . "dsa2cve");
  162. $src2mtbf = retrieve($cache . "src2mtbf");
  163. $pkg2virt = retrieve($cache . "pkg2virt");
  164. $virt2pkg = retrieve($cache . "virt2pkg");
  165. } or do {
  166. $state->{"next_adv"} = 0; # restart parsing of DSAs..
  167. %$dsatable = ();
  168. %$cvetable = ();
  169. %$src2dsa = ();
  170. %$dsa2cve = ();
  171. %$src2mtbf = ();
  172. $err++;
  173. };
  174. eval { $deb2pkg = retrieve($cache . "deb2pkg");
  175. } or do {
  176. # reset last-seen info on Packages
  177. $state->{"Packages"} = "";
  178. %$deb2pkg=();
  179. $err++;
  180. };
  181. eval { $pkg2src = retrieve($cache . "pkg2src");
  182. } or do {
  183. # reset last-seen info on Sources
  184. $state->{"Sources"} = "";
  185. %$pkg2src=();
  186. $err++;
  187. };
  188. return 1-$err;
  189. }
  190. ## persistant storage
  191. sub save_DBs {
  192. my $cache = $config->{cache_dir};
  193. eval {
  194. # program status data
  195. store($state, $cache . "state");
  196. # parsed/evaluated security data
  197. store($dsatable, $cache . "dsatable");
  198. store($cvetable, $cache . "cvetable");
  199. store($src2dsa, $cache . "src2dsa");
  200. store($dsa2cve, $cache . "dsa2cve");
  201. store($src2mtbf, $cache . "src2mtbf");
  202. # parsed Debian packages data
  203. store($deb2pkg, $cache . "deb2pkg");
  204. store($pkg2src, $cache . "pkg2src");
  205. store($pkg2virt, $cache . "pkg2virt");
  206. store($virt2pkg, $cache . "virt2pkg");
  207. return 1;
  208. } or do {
  209. error "Failed to save cache file(s): ";
  210. error "$@\n";
  211. };
  212. }
  213. ## Fetch current Packages, Sources and sha1sums files
  214. ## These are needed to find CVE stats by sha1sums/pkg-names
  215. ## Only Sha1Sums is custom generated, others are from Debian.
  216. ## FIXME: Server might do on-the-fly gzip (but should not for bzip2)
  217. ## Return: 1 on success, to signal that new parsing is needed.
  218. sub fetchMeta {
  219. my $file = shift;
  220. my $urlbase = $config->{"pkg_base_url"};
  221. my $dir = $config->{"cache_dir"};
  222. my $bzFile = $file . ".bz2";
  223. my $url = $urlbase . $bzFile;
  224. debug "Checking meta file from $url\n";
  225. my $req = HTTP::Request->new(GET => $urlbase . $bzFile);
  226. my $ret = $lwp->request($req, $dir . $bzFile);
  227. if ($ret->is_success) {
  228. # check if the file actally changed..
  229. open(FILE, $dir . $bzFile) or die "Can't open '$dir$bzFile': $!";
  230. binmode(FILE);
  231. my $stamp = Digest::MD5->new->addfile(*FILE)->hexdigest;
  232. if ($state->{$file} eq $stamp) {
  233. debug " unchanged..\n";
  234. return 0;
  235. } else {
  236. $state->{$file} = $stamp;
  237. }
  238. # file seems new, unpack for parsing..(TODO: should keep in $tmp..)
  239. bunzip2 $dir . $bzFile => $dir . $file#, AutoClose => 1
  240. or die "bunzip2 failed: $Bunzip2Error\n";
  241. close(FILE);
  242. return 1; # file changed
  243. } else {
  244. error "Failed: " . $ret->status_line . "\n";
  245. return 0; # no updates
  246. }
  247. }
  248. # Sources and Packages are not completely consistent, esp for debian-multimedia
  249. # He we store manual mappings for these..
  250. sub addOrphanPkgs {
  251. my $pkg2src = shift;
  252. $pkg2src->{"liblame-dev"} = "lame";
  253. $pkg2src->{"lame-extras"} = "lame";
  254. $pkg2src->{"moonlight"} = "moon";
  255. $pkg2src->{"libmoon0"} = "moon";
  256. $pkg2src->{"xmms-mp4"} = "xmms2";
  257. $pkg2src->{"xmms-mp4"} = "xmms2";
  258. $pkg2src->{"lazarus-src-0.9.30"} = "lazarus";
  259. $pkg2src->{"lazarus-ide-0.9.30"} = "lazarus";
  260. $pkg2src->{"lcl-qt4-0.9.30"} = "lazarus";
  261. $pkg2src->{"lazarus-ide-qt4-0.9.30"} = "lazarus";
  262. $pkg2src->{"lcl-gtk2-0.9.30"} = "lazarus";
  263. $pkg2src->{"lazarus-ide-gtk2-0.9.30"} = "lazarus";
  264. $pkg2src->{"lcl-units-0.9.30"} = "lazarus";
  265. $pkg2src->{"lazarus-0.9.30"} = "lazarus";
  266. $pkg2src->{"lazarus-doc-0.9.30"} = "lazarus";
  267. $pkg2src->{"lcl-0.9.30"} = "lazarus";
  268. $pkg2src->{"lcl-utils-0.9.30"} = "lazarus";
  269. $pkg2src->{"lcl-nogui-0.9.30"} = "lazarus";
  270. $pkg2src->{"libx264-65"} = "x264";
  271. $pkg2src->{"libx264-114"} = "x264";
  272. $pkg2src->{"libx264-60"} = "x264";
  273. # $pkg2src->{"libmlt3"}
  274. # $pkg2src->{"libgmerlin-avdec0"}
  275. # $pkg2src->{"libxul-dev"}
  276. # $pkg2src->{"libmyth-0.23.1-0"}
  277. # $pkg2src->{"libmpeg3hv"}
  278. # $pkg2src->{"libquicktimehv"}
  279. # $pkg2src->{"libxul0d"}
  280. # $pkg2src->{"acroread-fonts-kor"}
  281. }
  282. ## Parse dpkg Packages file, create map deb-name->pkg-name
  283. sub parsePackages {
  284. my $pkgfile = shift;
  285. my $dir = $config->{"cache_dir"};
  286. my $pkgname;
  287. my @provides;
  288. my @lines;
  289. %$deb2pkg = ();
  290. %$pkg2virt = ();
  291. %$virt2pkg = ();
  292. trace "Parsing Packages file...\n";
  293. $pkgfile = $dir . $pkgfile;
  294. tie @lines, 'Tie::File', $pkgfile or die "Unable to open file $pkgfile";
  295. LINE: foreach my $line (@lines) {
  296. if ($line =~ /^Package:\ (.+)/) {
  297. $pkgname = $1;
  298. } elsif ($line =~ /^Filename:\ .*\/(\S+)/) {
  299. $deb2pkg->{$1} = $pkgname;
  300. } elsif ($line =~ /^Provides:\ +(.+)/) {
  301. @provides = split(/,\ /, $1);
  302. foreach my $virt (@provides) {
  303. $virt =~ s/(\s|,)//g;
  304. push @{$pkg2virt->{$pkgname}}, $virt unless ($virt ~~ @{$pkg2virt->{$pkgname}});
  305. push @{$virt2pkg->{$virt}}, $pkgname unless ($pkgname ~~ @{$virt2pkg->{$virt}});
  306. }
  307. } else {
  308. next LINE;
  309. }
  310. }
  311. untie @lines; # close file
  312. return;
  313. }
  314. ## Parse dpkg Sources file, create map pkg-name->src-name
  315. sub parseSources {
  316. my $srcfile = shift;
  317. my $dir = $config->{"cache_dir"};
  318. my $srcname;
  319. my @pkgnames;
  320. my $checklinecontinuation=0;
  321. my @lines;
  322. %$pkg2src = ();
  323. trace "Parsing Sources file...\n";
  324. $srcfile = $dir . $srcfile;
  325. tie @lines, 'Tie::File', $srcfile or die "Unable to open file $srcfile";
  326. LINE: foreach my $line (@lines) {
  327. ## sometimes, list of binary pkgs has newline, so we need to check next line..
  328. if ($checklinecontinuation == 1) {
  329. if ($line =~ /^[[:alpha:]]+:\ /) {
  330. $checklinecontinuation = 0;
  331. } else {
  332. @pkgnames = split(/,\ /, $line);
  333. foreach my $pkg (@pkgnames) {
  334. $pkg =~ s/(\s|,)//g;
  335. if ($pkg ne "") {
  336. $pkg2src->{$pkg} = unifySrcName($srcname);
  337. }
  338. }
  339. }
  340. next LINE;
  341. }
  342. if ($line =~ /^Package:\ (.+)/) {
  343. $srcname = $1;
  344. } elsif ($line =~ /^Binary:\ (.+)/) {
  345. @pkgnames = split(/,\ /, $1);
  346. foreach my $pkg (@pkgnames) {
  347. $pkg =~ s/(\s|,)//g;
  348. $pkg2src->{$pkg} = unifySrcName($srcname);
  349. }
  350. $checklinecontinuation = 1;
  351. } else {
  352. next LINE;
  353. }
  354. }
  355. untie @lines; # close file
  356. # Sources and Packages are sometimes not really consistent. Here we add some
  357. # manual entries and check for remaining pkgs without srcpkg..
  358. addOrphanPkgs($pkg2src);
  359. foreach my $pkg (values %$deb2pkg) {
  360. print "Orphan Package: $pkg\n" unless (defined $pkg2src->{$pkg});
  361. }
  362. return;
  363. }
  364. sub addSHA1 {
  365. my $hash = shift;
  366. my $deb = shift;
  367. my $src = shift;
  368. my ($thash,$tdeb,$tsrc) = getSHA1($hash);
  369. # if already recorded, extend and/or mark as non-unique
  370. if (defined $thash) {
  371. my @tdebs = split(/,/, $tdeb);
  372. my @tsrcs = split(/,/, $tsrc);
  373. if (! grep {$_ eq $deb} @tdebs) {
  374. #print "Adding deb $deb to $tdeb..\n";
  375. my $tmp = $tdeb . "," . $deb;
  376. $db->do("update sha2pkg set deb='$tmp' where hash='$hash'");
  377. die "$DBI::errstr\n" if ($db->err());
  378. }
  379. if (! grep {$_ eq $src} @tsrcs) {
  380. print "Adding src $src to $tsrc..\n";
  381. my $tmp = $tsrc . "," . $src;
  382. $db->do("update sha2pkg set src='$tmp' where hash='$hash'");
  383. die "$DBI::errstr\n" if ($db->err());
  384. }
  385. } else {
  386. $db->do("insert into sha2pkg (hash, deb, src) VALUES('$hash', '$deb', '$src')");
  387. die "Error inserting $hash/$deb/$src: $DBI::errstr\n" if ($db->err());
  388. }
  389. }
  390. sub getSHA1 {
  391. my $hash = shift;
  392. my @res = $db->selectrow_array("select * from sha2pkg where hash = '$hash'");
  393. return @res;
  394. }
  395. ## Parse Sha1Sums file. Format: "sha1sum::deb-name::unix-file-path"
  396. ## Create 2 maps: sha1sum->file, file->deb-name
  397. sub parseSha1Sums {
  398. my $sha1file = shift;
  399. my $dir = $config->{"cache_dir"};
  400. my @lines;
  401. trace "Parsing Sha1Sums file...\n";
  402. # delete existing table first.. (or else everything will be collisions!)
  403. $db->do("delete from sha2pkg");
  404. $sha1file = $dir . $sha1file;
  405. tie @lines, 'Tie::File', $sha1file or die "Unable to open file $sha1file";
  406. LINE: foreach my $line (@lines) {
  407. unless ($line =~ /^(\w+)::(\S+)$/) {
  408. die "Sha1Sums parse error, line reads: \n>>$line<<";
  409. }
  410. # ignore the hash of '\n' (many collisions due to empty files)
  411. next if ($1 eq 'da39a3ee5e6b4b0d3255bfef95601890afd80709');
  412. # To avoid collisions in the SHA table we try to resolve to src pkg right away.
  413. # But that means we must resolve *all* packets, which often means that we
  414. # have to print errors and manually fix inconsistent meta-data. :-/
  415. # To reduce this problem, addSHA1() keeps track of collisions and then we
  416. # only complain later on, when some actually measured SHA1 value has
  417. # ambigious security info.
  418. # The .deb package names are only stored for informational purposes.
  419. my $binpkg = $deb2pkg->{$2};
  420. if ($binpkg) {
  421. my $srcpkg = $pkg2src->{$binpkg};
  422. if ($srcpkg) {
  423. addSHA1 $1, $2, unifySrcName($srcpkg);
  424. } else {
  425. note "No srcpkg known for pkg $binpkg (inconsistent Packages/Sources data?)\n";
  426. addSHA1 $1, $2, "";
  427. }
  428. } else {
  429. note "No pkg known for deb $2 (stale entries in sha1sums?)\n";
  430. }
  431. }
  432. untie @lines; # close file
  433. }
  434. ## Parse local dpkg status, return list of debs
  435. sub parseStatus {
  436. my $stsfile = shift;
  437. my @pkglist;
  438. my $pkgname;
  439. trace "Parsing dpkg status..\n";
  440. open (PKG, "< $stsfile");
  441. my @lines = <PKG>;
  442. LINE: foreach my $line (@lines) {
  443. if ($line =~ /^Package:\ (.+)/) {
  444. $pkgname = $1;
  445. } elsif ($line =~ /^Status:.*installed/) {
  446. push @pkglist, $pkgname;
  447. } else {
  448. next LINE;
  449. }
  450. }
  451. close(PKG);
  452. error "Could not find any installed packages in status file." if ($#pkglist <= 0);
  453. return \@pkglist;
  454. }
  455. sub parseAdvisory {
  456. my $adv = shift;
  457. given ($state->{"vendor"}) {
  458. when ($_ eq "debian") {
  459. return parseFSA $adv if $adv =~ /FreeBSD-SA/m;
  460. return parseDSAhtml($adv);
  461. }
  462. when ($_ eq "ubuntu") { return parseUSNhtml($adv); }
  463. # when ($_ eq "redhat") { return checkRHSA; }
  464. default { die "Unsupported distribution $_"; }
  465. };
  466. }
  467. sub fixAdvisoryQuirks {
  468. my @arg = @_;
  469. given ($state->{"vendor"}) {
  470. when ($_ eq "debian") { return fixDSAquirks(@arg); }
  471. when ($_ eq "ubuntu") { return fixUSNquirks(@arg); }
  472. # when ($_ eq "redhat") { return checkRHSA; }
  473. default { die "Unsupported distribution $_"; }
  474. };
  475. }
  476. ## Extract CVE ids from new advisories and print URL for mirror script
  477. sub printCVEs {
  478. my $id = shift; # Advisory ID
  479. my $adv = shift; # Advisory to scan
  480. my $url;
  481. my %cves;
  482. trace "Looking for CVEs in advisory.. \n";
  483. my @dsastats = parseAdvisory($adv);
  484. return if !@dsastats;
  485. ## fix DSAs that don't contain correct CVE refs
  486. @dsastats = fixAdvisoryQuirks($id, \@dsastats);
  487. foreach my $cve_id (@{$dsastats[2]}) {
  488. $cve_id =~ s/^CAN/CVE/;
  489. $cves{$cve_id} = 1;
  490. }
  491. foreach my $cve (keys %cves) {
  492. print "NeedCVE: " . $config->{"cve_base_url"} . $cve . "\n";
  493. print "NeedCVE: " . $config->{"cvss_base_url"} . $cve . "\n";
  494. }
  495. }
  496. ## Update internal vuln. DB with new Advisory info
  497. ## Creates CVEtable for MTBF computation:
  498. ## ( cve-id => (date, delay, score1, score2, score3))
  499. sub updateCVETables {
  500. my $id = shift; # Advisory to merge into tables
  501. my @cvestats;
  502. trace "Updating vulnerability database with advisory ".$state->{"vendor"}."/$id\n";
  503. my $adv = $dsatable->{$id};
  504. #print $dsatable->{$id} if $id eq "FSA-301";
  505. my @dsastats = parseAdvisory($adv);
  506. return if !@dsastats;
  507. ## fix DSAs that don't contain correct CVE refs
  508. @dsastats = fixAdvisoryQuirks($id, \@dsastats);
  509. foreach my $srcpkg (@{$dsastats[0]}) {
  510. push @{$src2dsa->{$srcpkg}}, $id;
  511. push @{$dsa2cve->{$id}}, @{$dsastats[2]};
  512. }
  513. foreach my $cve_id (@{$dsastats[2]}) {
  514. my $cve = fetchCVE($cve_id, $config->{"cve_base_url"}, $config->{"cvss_base_url"});
  515. @cvestats = parseCVE($cve_id, $cve);
  516. if ($cvestats[0] > $dsastats[1] || $cvestats[0] == 0) {
  517. $cvestats[0] = $dsastats[1];
  518. }
  519. my @cvedata = ( $cvestats[0], $dsastats[1]-$cvestats[0],
  520. $cvestats[1], $cvestats[2], $cvestats[3] );
  521. $cvetable->{$cve_id} = \@cvedata;
  522. }
  523. }
  524. ## Check for updates on Package information
  525. sub aptsec_update {
  526. my $newAdv;
  527. unless ("--offline" ~~ @ARGV) {
  528. fetchMeta("Packages");
  529. fetchMeta("Sources");
  530. fetchMeta("Sha1Sums");
  531. }
  532. unless ("--cves" ~~ @ARGV) {
  533. parsePackages("Packages");
  534. parseSources("Sources");
  535. unless ("--nosha1" ~~ @ARGV) {
  536. parseSha1Sums("Sha1Sums");
  537. }
  538. }
  539. given ($state->{"vendor"}) {
  540. when ($_ eq "debian") { $newAdv = checkDSAs($state, $config); }
  541. when ($_ eq "ubuntu") { $newAdv = checkUSNs($state, $config); }
  542. #when ($_ eq "redhat") { ($id,$adv) = checkRHSA; }
  543. default { die "Unsupported distribution $_"; }
  544. };
  545. foreach my $id (keys %$newAdv) {
  546. # if not known, process advisory
  547. if ($dsatable->{$id}) {
  548. note $state->{"vendor"} . " advisory $id already known.\n";
  549. }
  550. elsif ("--cves" ~~ @ARGV) {
  551. ## scan for CVE urls only?
  552. printCVEs($id, $newAdv->{$id});
  553. }
  554. else {
  555. ## store advisory and parse it
  556. $dsatable->{$id} = $newAdv->{$id};
  557. updateCVETables($id);
  558. }
  559. }
  560. # recompute all pkg statistics
  561. foreach my $srcpkg (keys %$src2dsa) {
  562. processCVEs($srcpkg);
  563. }
  564. }
  565. ## find list of src pkgs from bin pkgs based on pkg2src
  566. sub resolvePkg2Src {
  567. my $pkglist = shift;
  568. my @srclist;
  569. my %tmp;
  570. my $srcpkg;
  571. foreach my $pkg (@$pkglist) {
  572. $srcpkg = $pkg2src->{$pkg};
  573. if (defined $srcpkg) {
  574. # unique..
  575. $tmp{$pkg2src->{$pkg}} = 1;
  576. } else {
  577. note "Could not find source package for: $pkg\n";
  578. }
  579. }
  580. @srclist = keys %tmp;
  581. return \@srclist;
  582. }
  583. ## compute and store MTBF, MTBR and Scores of each src pkg
  584. ## output: %src2mtbf:
  585. ## (srcpkg=> (begin, num, delaysum, scoresum, maximpact, MTTF, MTTFl))
  586. sub processCVEs {
  587. my $pkg = shift;
  588. my @stats = ($now, 0, 0, 0, 0);
  589. my %cvestats;
  590. my $lambda = $config->{"lambda"};
  591. trace "Processing package $pkg";
  592. ## @cvestats = (date base-score impact-score exploit-score)
  593. foreach my $dsa_id (@{$src2dsa->{$pkg}}) {
  594. foreach my $cve_id (@{$dsa2cve->{$dsa_id}}) {
  595. $cvestats{$cvetable->{$cve_id}[0]}++;
  596. $stats[1]++;
  597. $stats[2]+= $cvetable->{$cve_id}[1];
  598. $stats[3]+= $cvetable->{$cve_id}[2];
  599. if ($stats[4] < $cvetable->{$cve_id}[3]) {
  600. $stats[4] = $cvetable->{$cve_id}[3];
  601. }
  602. }
  603. }
  604. # Ignore pkgs with less than one incident, should not happen..
  605. return if ($stats[1] < 1);
  606. my $date;
  607. my $prev_date=0;
  608. my $delay;
  609. my $months;
  610. my $weight=0;
  611. my @dates = sort (keys %cvestats);
  612. $stats[0] = $dates[0];
  613. $stats[5]=0;
  614. $stats[6]=0;
  615. foreach $date (@dates) {
  616. #print "$cvestats{$date} $date\n";
  617. foreach (1..$cvestats{$date}) {
  618. if ($prev_date > 0) {
  619. $months = ($now - $date)/$secperday/30;
  620. $delay = ($date - $prev_date)/$secperday;
  621. #$delay = 0.0000001 if ($delay == 0);
  622. #print "delay: $delay\n";
  623. $stats[5] += $delay;
  624. $stats[6] += $delay*exp(-$months/$lambda);
  625. $weight += exp(-$months/$lambda);
  626. }
  627. $prev_date = $date;
  628. }
  629. }
  630. ## correct stats in case that last vuln. is so long ago that the
  631. ## current reliability should be increased.
  632. ## only use if we have more than one complete interval already
  633. $delay = ($now - $dates[$#dates])/$secperday;
  634. return if ($stats[1] == 1 && $delay < 50);
  635. if ($delay > $stats[5]/$stats[1] || $delay > $stats[6]/$weight) {
  636. $stats[5] += $delay;
  637. $stats[5] /= $stats[1];
  638. $stats[6] += $delay * exp(-0);
  639. $stats[6] /= $weight + exp(-0);
  640. } else {
  641. $stats[5] /= $stats[1]-1; # intervals = incidents-1
  642. $stats[6] /= $weight;
  643. }
  644. # save
  645. if ($stats[5] > 0 && $stats[6] > 0) {
  646. $src2mtbf->{$pkg} = \@stats;
  647. } else {
  648. die "@stats";
  649. }
  650. }
  651. # print some meta-info on internal data
  652. sub aptsec_about {
  653. my $num_dsa = keys %$dsatable;
  654. my $num_cve = keys %$cvetable;
  655. my $num_pkg = keys %$pkg2src;
  656. my $num_src = keys %$src2dsa;
  657. #printf("\nWorking DB has %d binary packages and SHA-1 file hashes.\n",
  658. printf("\nThe current database records %d binary packages and %d DSAs.\n",
  659. $num_pkg, $num_dsa);
  660. printf("%d CVEs are associated with %d source packages.\n",
  661. $num_cve, $num_src);
  662. }
  663. # use pkg provides info to suggest alternative programs
  664. sub aptsec_alternatives {
  665. my $pkg = shift;
  666. my @pkgs;
  667. my %provs;
  668. my %alts;
  669. my $lines;
  670. ## pkg is normally not src pkg. resolve it to src and then
  671. ## consider all bin pkgs created from it
  672. if ($pkg2src->{$pkg}) {
  673. print "\nResolving $pkg to $pkg2src->{$pkg}\n\n";
  674. $pkg = $pkg2src->{$pkg};
  675. }
  676. foreach (keys %$pkg2src) {
  677. if ($pkg2src->{$_} eq $pkg) {
  678. push @pkgs, $_;
  679. }
  680. }
  681. foreach $pkg (@pkgs) {
  682. #print "Provided by $pkg:\n";
  683. foreach (@{$pkg2virt->{$pkg}}) {
  684. #print "$_\n";
  685. $provs{$_} = 1;
  686. }
  687. }
  688. print "\nSimilar functions are provided by:\n\n";
  689. foreach my $func (keys %provs) {
  690. #print "Function $func also is provided by:\n\t";
  691. foreach (@{$virt2pkg->{$func}}) {
  692. $alts{$pkg2src->{$_}} = 1;
  693. }
  694. }
  695. $lines=0;
  696. foreach (keys %alts) {
  697. print "$_ ";
  698. $lines++;
  699. }
  700. print "-" unless $lines;
  701. print "\n\n";
  702. }
  703. ## print overview for pkg high scores
  704. sub aptsec_hitlist {
  705. my @stats;
  706. my @pkg = (keys %$src2mtbf);
  707. # TODO: Some DSAs have to recognized CVEs, integrate security tracker
  708. # foreach my $pkg (@pkg) {
  709. # print "no dsa for $pkg\n" unless defined ($src2dsa->{$pkg})
  710. # }
  711. print "\nOverall MTBF/MTTF:\n\n";
  712. foreach my $pkg (@pkg) {
  713. my @stats = @{$src2mtbf->{$pkg}};
  714. printf("MTTF:%6.1f, STP(12):%5.1f, MTTFl:%6.1f, STPl(12):%5.1f, Vuln:%3d, Pkg: %s\n",
  715. $stats[5],
  716. 365/$stats[5],
  717. $stats[6],
  718. 365/$stats[6],
  719. $stats[1],
  720. $pkg);
  721. }
  722. }
  723. ## evaluation function wrapper
  724. ## parameter is package or path to status file or nil, in which
  725. ## case we process all pkgs with recorded vulnerabilities
  726. sub aptsec_status_2010 {
  727. my $pkg = shift;
  728. my @stats;
  729. my @pkg = (keys %$src2dsa);
  730. my $year;
  731. my $num_dsa = keys %$dsatable;
  732. my $num_cve = keys %$cvetable;
  733. my $num_pkg = keys %$pkg2src;
  734. my $num_src = keys %$src2dsa;
  735. if (defined $pkg) {
  736. eval {
  737. my $srclist = resolvePkg2Src(parseStatus($pkg));
  738. @pkg = @$srclist;
  739. #printSystemStats($srclist);
  740. } or do {
  741. print "Interpreting parameter $pkg as packet..";
  742. @pkg = ($pkg);
  743. }
  744. }
  745. # individual package stats until end of given year
  746. $year=2009;
  747. print "\n Overall MTBF/MTTF until $year:\n";
  748. simulate_stats($_,$year) foreach (@pkg);
  749. # actual vulnerabilities for a given year
  750. $year=2010;
  751. print "\n Stats of year $year:\n";
  752. show_period($_,$year) foreach (@pkg);
  753. }
  754. sub show_period {
  755. my $pkg = shift;
  756. my $year = shift;
  757. #my $startdate = shift;
  758. #my $enddate = shift;
  759. my $start = parsedate("1.1.$year");
  760. my $end = parsedate("31.12.$year");
  761. my @cvelist;
  762. my %cvestats;
  763. my $num=0;
  764. ## @cvestats = (date base-score impact-score exploit-score)
  765. foreach my $dsa_id (@{$src2dsa->{$pkg}}) {
  766. foreach my $cve_id (@{$dsa2cve->{$dsa_id}}) {
  767. #my $yr = 1900 + (gmtime($cvetable->{$cve_id}[0]))[5];
  768. #if ($startdate < $yr && $enddate >= $yr) {
  769. if ($start < $cvetable->{$cve_id}[0] && $end > $cvetable->{$cve_id}[0]) {
  770. push @{$cvestats{$cvetable->{$cve_id}[0]}}, $cve_id;
  771. $num++;
  772. }
  773. }
  774. }
  775. # Ignore pkgs with less than two incidents
  776. return if ($num < 1);
  777. foreach my $date (sort (keys %cvestats)) {
  778. #print "Date: $date, CVEs: @{$cvestats{$date}}\n";
  779. }
  780. printf(" In %4d: MTBF: %3d, new vuln: %3d, Pkg:%s\n",
  781. $year, ($end-$start)/$secperday/$num, $num, $pkg);
  782. #foreach my $dsa_id (@{$src2dsa->{$pkg}}) {
  783. # foreach my $cve_id (@{$dsa2cve->{$dsa_id}}) {
  784. # my ($sec,$min,$hrs,$day,$mon,$yr) = gmtime($cvetable->{$cve_id}[0]);
  785. # printf "%s: Base Score: %04.1f, %02d.%02d.%04d\n",
  786. # $cve_id, $cvetable->{$cve_id}[2], $day, $mon+1, $yr+1900;
  787. #}
  788. }
  789. ## evaluation helper
  790. ## compute stats until date given in $2, then compute stats
  791. ## for the next year to check accuracy of the prediction.
  792. ## @cvestats = (date base-score impact-score exploit-score)
  793. sub simulate_stats {
  794. my $pkg = shift;
  795. my $year = shift;
  796. my $start = 0;
  797. my $end = parsedate("31.12.$year");
  798. my $now = $end;
  799. my @stats = ($now, 0, 0, 0, 0);
  800. my %cvestats;
  801. my $lambda = $config->{"lambda"};
  802. #unless (defined @{$src2dsa->{$pkg}}) {
  803. # print "No vulnerability record for package $pkg\n";
  804. # return;
  805. #}
  806. foreach my $dsa_id (@{$src2dsa->{$pkg}}) {
  807. foreach my $cve_id (@{$dsa2cve->{$dsa_id}}) {
  808. #my $yr = 1900 + (gmtime($cvetable->{$cve_id}[0]))[5];
  809. #if ($startdate <= $yr && $enddate > $yr) {
  810. if ($start < $cvetable->{$cve_id}[0] && $end > $cvetable->{$cve_id}[0]) {
  811. $cvestats{$cvetable->{$cve_id}[0]}++;
  812. $stats[1]++;
  813. $stats[2]+= $cvetable->{$cve_id}[1];
  814. $stats[3]+= $cvetable->{$cve_id}[2];
  815. if ($stats[4] < $cvetable->{$cve_id}[3]) {
  816. $stats[4] = $cvetable->{$cve_id}[3];
  817. }
  818. }
  819. }
  820. }
  821. # Ignore pkgs with less than two incidents
  822. return if ($stats[1] < 2);
  823. my $date;
  824. my $prev_date=0;
  825. my $delay;
  826. my $months;
  827. my $weight=0;
  828. my @dates = sort (keys %cvestats);
  829. $stats[0] = $dates[0];
  830. $stats[5]=0;
  831. $stats[6]=0;
  832. foreach $date (@dates) {
  833. #print "$cvestats{$date} $date\n";
  834. foreach (1..$cvestats{$date}) {
  835. if ($prev_date > 0) {
  836. $months = ($now - $date)/$secperday/30;
  837. $delay = ($date - $prev_date)/$secperday;
  838. #$delay = 0.0000001 if ($delay == 0);
  839. #print "delay: $delay, age: $months\n";
  840. $stats[5] += $delay;
  841. $stats[6] += $delay*exp(-$months/$lambda);
  842. $weight += exp(-$months/$lambda);
  843. }
  844. $prev_date = $date;
  845. }
  846. }
  847. ## correct stats in case that last vuln. is so long ago that the
  848. ## current reliability should be increased.
  849. $delay = ($now - $dates[$#dates])/$secperday;
  850. return if ($stats[1] == 1 && $delay < 50);
  851. if ($delay > $stats[5]/$stats[1] || $delay > $stats[6]/$weight) {
  852. $stats[5] += $delay;
  853. $stats[5] /= $stats[1];
  854. $stats[6] += $delay * exp(-0);
  855. $stats[6] /= $weight + exp(-0);
  856. #print "delay: $delay, age: 0, weight: 1\n";
  857. } else {
  858. #print "delay: $delay, age: 0, weight: 1\n";
  859. $stats[5] /= $stats[1]-1; # intervals = incidents-1
  860. $stats[6] /= $weight;
  861. }
  862. printf " Til $year ";
  863. #printf("MTTF:%6.1f, STP(12):%5.1f, MTTFl:%6.1f, STPl(12):%5.1f, MTRR:%6.1f, Vuln:%4d, Pkg: %s\n",
  864. printf("MTTF:%6.1f, STP(12):%5.1f, MTTFl:%6.1f, STPl(12):%5.1f, Vuln:%3d, Pkg: %s\n",
  865. $stats[5],
  866. 365/$stats[5],
  867. $stats[6],
  868. 365/$stats[6],
  869. #$stats[2]/$stats[1]/$secperday,
  870. $stats[1],
  871. $pkg);
  872. return @stats;
  873. }
  874. ## Use local system status(dpkg DB) for printing system status report
  875. sub aptsec_status {
  876. my $path;
  877. $path = shift or $path = "/var/lib/dpkg/status";
  878. my $srclist = resolvePkg2Src(parseStatus($path));
  879. printSystemStats($srclist);
  880. }
  881. ## Print 'trustworthiness' of a set(system) of src packages
  882. sub printSystemStats {
  883. my $srclist = shift;
  884. my $mtbf=0;
  885. my $mtrr=0;
  886. my $mttf=0;
  887. my $mttfl=0;
  888. my $num=0;
  889. # we can assume that pkgs are independent (right?)
  890. PKG: foreach my $pkg (@$srclist) {
  891. my $rstats = $src2mtbf->{$pkg} or next PKG;
  892. my @stats = @$rstats;
  893. $mtbf+=1/(($now-$stats[0])/$stats[1]/$secperday);
  894. $mttf+=1/$stats[5];
  895. $mttfl+=1/$stats[6];
  896. $mtrr+=$stats[2]/$stats[1]/$secperday;
  897. if ($verbosity > 0) {
  898. #printf("MTBF: %6.1f, MTTF: %6.1f, MTTFl: %6.1f, MTRR: %6.1f, \t%s\n",
  899. printf("MTTF: %6.1f, MTTFl: %6.1f, Vuln: %3d, Pkg: %s\n",
  900. #($now-$stats[0])/$stats[1]/$secperday,
  901. $stats[5],
  902. $stats[6],
  903. #$stats[2]/$stats[1]/$secperday,
  904. $stats[1],
  905. $pkg);
  906. }
  907. $num++;
  908. }
  909. printf "\n";
  910. printf "Packages with past vulnerabilities installed: %d\n", $num;
  911. if ($num > 0) {
  912. #printf "System MTRR: %5.1f days\n", $mtrr/$num;
  913. #printf "System MTBF: %5.1f days per failure\n", 1/$mtbf;
  914. printf "System MTTF: %7.1f days per failure\n", 1/$mttf;
  915. printf "System MTTFl: %7.1f days per failure\n", 1/$mttfl;
  916. printf "System STP(12) %6.1f failures per year\n", 365*$mttf;
  917. printf "System STPl(12) %5.1f failures per year\n", 365*$mttfl;
  918. printf "\n";
  919. }
  920. }
  921. ## show info on a single src pkg, resolv to src if needed
  922. sub aptsec_show {
  923. my $pkg = shift;
  924. my $ADV;
  925. my $lines;
  926. unless (defined $pkg) {
  927. aptsec_help();
  928. exit;
  929. }
  930. given ($state->{"vendor"}) {
  931. when ($_ eq "debian") { $ADV = "DSA-"; }
  932. when ($_ eq "ubuntu") { $ADV = "USN-"; }
  933. default { die "Unsupported distribution $_"; }
  934. };
  935. if (!($src2dsa->{$pkg}) && $pkg2src->{$pkg}) {
  936. print "\nResolving $pkg to $pkg2src->{$pkg}\n";
  937. $pkg = $pkg2src->{$pkg};
  938. }
  939. print "\nThe following binary packages are created from $pkg:\n\n";
  940. $lines=0;
  941. foreach (keys %$pkg2src) {
  942. if ($pkg2src->{$_} eq $pkg) {
  943. print "$_\n";
  944. $lines++;
  945. }
  946. }
  947. print "-\n" if ($lines < 1);
  948. unless ($src2dsa->{$pkg} && $src2mtbf->{$pkg}) {
  949. print "\nNo vulnerabilities recorded for source package $pkg.\n";
  950. exit;
  951. }
  952. print "\nAdvisories on package $pkg:\n\n";
  953. foreach my $dsa_id (sort @{$src2dsa->{$pkg}}) {
  954. print "$ADV$dsa_id\n";
  955. foreach my $cve_id (@{$dsa2cve->{$dsa_id}}) {
  956. my ($sec,$min,$hrs,$day,$mon,$yr) = gmtime($cvetable->{$cve_id}[0]);
  957. printf "%s: Base Score: %04.1f, %02d.%02d.%04d\n",
  958. $cve_id, $cvetable->{$cve_id}[2], $day, $mon+1, $yr+1900;
  959. }
  960. }
  961. my @stats = @{$src2mtbf->{$pkg}};
  962. my ($sec,$min,$hrs,$day,$mon,$yr) = gmtime($stats[0]);
  963. print "\nOverall vulnerability stats:\n\n";
  964. printf " First vulnerability: %02d.%02d.%04d\n", $day, $mon+1, $yr+1900;
  965. printf " Total vulnerabilities: %d\n", $stats[1];
  966. printf " Average Base Score: %04.2f/10\n", $stats[3]/$stats[1];
  967. printf " Highest Impact Score: %d/10\n", $stats[4];
  968. # printf " MTBF in days: %.2f\n", ($now-$stats[0])/$stats[1]/$secperday;
  969. printf " MTTF: %.1f days/vuln\n", $stats[5];
  970. printf " MTTFl: %.1f days/vuln\n", $stats[6];
  971. printf " STP(12): %.1f vuln/yr\n", 365/$stats[5];
  972. printf " STPl(12): %.1f vuln/yr\n", 365/$stats[6];
  973. # printf " MTRR in days: %.2f\n\n", $stats[2]/$stats[1]/$secperday;
  974. }
  975. sub aptsec_help {
  976. print "\n";
  977. print "Usage:\n";
  978. print "\n";
  979. print "help This cruft\n";
  980. print "update Update vulnerability databases\n";
  981. print "status Compute expected failure rates for local system\n";
  982. print "show <pkg> Show failure rates and vulnerability statistics for <pkg>\n";
  983. print "\n";
  984. }
  985. ## Print system status report from component(files) measurements (sha1sums)
  986. ## Expected input format is Linux IMA. We assume input was validated.
  987. ##
  988. ## Note: aptsec_status(), considers *reportedly installed* packages, while this
  989. ## one looks at *actually loaded* software that influenced the CPU since bootup.
  990. sub aptsec_attest {
  991. my $sha1file = shift;
  992. my %tmp;
  993. my @pkglist;
  994. my @res;
  995. $sha1file = "/sys/kernel/security/ima/ascii_runtime_measurements" unless ($sha1file);
  996. # don't use Tie here since we don't need and sometimes don't have write access..
  997. open (SHA, "< $sha1file") or die "Unable to open file $sha1file";
  998. my @lines = <SHA>;
  999. LINE: foreach my $line (@lines) {
  1000. if ($line =~ /[0-9]{2}\ \w{40}\ ima\ (\w+)\ (\S+)/) { # IMA format?
  1001. } elsif ($line =~ /(\w{40})\ (\S+)/) { # '<sha1> <filename>' format?
  1002. } else { print "Failed to parse attestation data input from $sha1file\n"; return; }
  1003. unless (@res = getSHA1 $1) {
  1004. print "Unknown measured file: $1 $2\n";
  1005. next LINE;
  1006. }
  1007. # if measured hash is ambigious, print warning and over-approximate
  1008. if ($res[2] =~ /,/) {
  1009. print "Ambigious hash $res[0]: $res[2]\n";
  1010. foreach my $pkg (split(/,/, $res[2])) {
  1011. if ($src2dsa->{$pkg}) {
  1012. print "\t$pkg has security record...\n"
  1013. }
  1014. $tmp{$pkg} = 1;
  1015. }
  1016. } else {
  1017. $tmp{$res[2]} = 1;
  1018. }
  1019. }
  1020. close(SHA);
  1021. my @list = keys %tmp;
  1022. my $srclist = \@list;
  1023. printSystemStats($srclist);
  1024. }
  1025. my $action;
  1026. $action = shift or $action = "help";
  1027. load_state();
  1028. detect_distribution();
  1029. make_path($state->{"cache_dir"});
  1030. given ($action) {
  1031. when ($_ eq "update") {
  1032. load_DBs;
  1033. load_sha1lists;
  1034. aptsec_update();
  1035. save_sha1lists;
  1036. save_DBs;
  1037. save_state;
  1038. }
  1039. when ($_ eq "status") {
  1040. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1041. aptsec_status(shift);
  1042. }
  1043. when ($_ eq "status2010") {
  1044. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1045. aptsec_status_2010(shift);
  1046. }
  1047. when ($_ eq "show") {
  1048. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1049. aptsec_show(shift);
  1050. }
  1051. when ($_ eq "attest") {
  1052. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1053. load_sha1lists;
  1054. aptsec_attest(shift);
  1055. }
  1056. # when ($_ eq "altname") {
  1057. # load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1058. # altname(shift);
  1059. # }
  1060. when ($_ eq "hits") {
  1061. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1062. aptsec_hitlist();
  1063. }
  1064. when ($_ eq "about") {
  1065. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1066. aptsec_about();
  1067. }
  1068. when ($_ eq "alt") {
  1069. load_DBs or die "Failed to load some cached file(s), please rebuild with 'apt-sec update'\n";
  1070. aptsec_alternatives(shift);
  1071. }
  1072. default {
  1073. aptsec_help();
  1074. }
  1075. };