ubuntu-security-advisory.pl 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/perl
  2. ###############################################################################
  3. ##
  4. ## Functions for downloading and parsing Ubuntu Security Advisories (USNs)
  5. ##
  6. ###############################################################################
  7. use strict;
  8. use warnings;
  9. #use Storable; # persistant storage
  10. use LWP::UserAgent;
  11. #use Config::Auto; # libconfig-auto-perl
  12. use Time::ParseDate; # libtime-modules-perl
  13. #use IO::Uncompress::Bunzip2 qw(bunzip2 $Bunzip2Error);
  14. #use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  15. #use Digest::MD5 qw(md5_hex);
  16. use POSIX qw(mktime);
  17. #use File::Path qw(make_path);
  18. use HTML::Parser;
  19. use feature "switch";
  20. ## Fetch USN from ubuntu archive. Can't use tracker since dates are missing.
  21. sub fetchUSN {
  22. my $id= shift;
  23. my $usn_base_url = shift;
  24. my $usn;
  25. trace " Fetching USN-$id\n";
  26. my $url= $usn_base_url . "usn-" . $id;
  27. debug "USN URL: $url\n";
  28. $usn = get $url;
  29. unless (defined $usn) {
  30. note " USN-$id is not available\n";
  31. return "";
  32. }
  33. return $usn;
  34. }
  35. ## Try to find new USNs by iteration, return table of USNs to process
  36. ## Ignores advisory revisions (not clear what to do with them, mostly junk)
  37. sub checkUSNs($$) {
  38. my $state = shift;
  39. my $config = shift;
  40. my %usntable;
  41. my $next_usn = $state->{"next_adv"};
  42. my $rev = 1;
  43. my $id;
  44. trace "Checking for new USNs..\n";
  45. if ($next_usn < $config->{"first_usn"}) {
  46. note "Cache was deleted, starting at USN $next_usn\n";
  47. $next_usn = $config->{"first_usn"};
  48. }
  49. $rev = 0 if ($next_usn == 60); # fuck
  50. $id = sprintf("%d-%d",$next_usn, $rev);
  51. my $usn = fetchUSN($id, $config->{"usn_base_url"});
  52. until ($usn eq "") {
  53. debug " Got USN-$id\n";
  54. # strip and save USN content
  55. $usn =~ s/.*<div\ class="usn">//si;
  56. $usn =~ s/<\/div>.*//si;
  57. $usntable{$id} = $usn;
  58. ### check for advisory revisions?
  59. #until ($usn eq "") {
  60. # msg 10, " Got USN-$id";
  61. # $usn =~ s/.*<div\ class="usn">//si;
  62. # $usn =~ s/<\/div>.*//si;
  63. # $usntable{$id} = $usn;
  64. #
  65. # $rev++;
  66. # $rev++ unless (fixUSNquirks($next_usn,$rev)); # skip USN?
  67. # $id = sprintf("%d-%d", $next_usn, $rev);
  68. # $usn = fetchUSN($id);
  69. #}
  70. $rev = 1;
  71. $next_usn++;
  72. $next_usn++ if (blacklistedUSN("USN-".$next_usn));
  73. $rev = 0 if ($next_usn == 60);
  74. $id = sprintf("%d-%d", $next_usn, $rev);
  75. $usn = fetchUSN($id, $config->{"usn_base_url"});
  76. }
  77. return \%usntable;
  78. }
  79. ## static map to correct errors in USNs
  80. ## returns fixed list of CVE IDs or 0 to skip DSA
  81. sub fixUSNquirks {
  82. my $usn_id = shift;
  83. my $usn_state = shift;
  84. my @new_names = @{@$usn_state[0]};
  85. my $new_date = @$usn_state[1];
  86. my @new_cves = @{@$usn_state[2]};
  87. ## These DSAs are totally screwed up..
  88. given ($usn_id) {
  89. when ($_ eq "291") {
  90. @new_cves = ("CVE-2006-0747", "CVE-2006-1861", "CVE-2006-2661");
  91. }
  92. when ($_ eq "853") {
  93. @new_cves = (
  94. "CVE-2009-0689", "CVE-2009-3274", "CVE-2009-3370", "CVE-2009-3371",
  95. "CVE-2009-3372", "CVE-2009-3373", "CVE-2009-3374", "CVE-2009-3375",
  96. "CVE-2009-3376", "CVE-2009-3377", "CVE-2009-3380", "CVE-2009-3381",
  97. "CVE-2009-3382", "CVE-2009-3383");
  98. }
  99. };
  100. return (\@new_names, $new_date, \@new_cves);
  101. }
  102. ## Should this advisory be skipped?
  103. sub blacklistedUSN {
  104. my $id = shift or die "Advisory blacklist: no id given..";
  105. ## check if Adv is blacklisted..
  106. my @id_blacklist = ("USN-667", "USN-718", "USN-910", "USN-935",
  107. "USN-950", "USN-1034", "USN-1049", "USN-1050");
  108. grep ($_ eq $id, @id_blacklist) and return "true";
  109. return; # FALSE
  110. }
  111. ## Parse USN data and return array
  112. ## (src-pkg-name date (CVE-id)*)
  113. sub parseUSNhtml {
  114. my $usn = shift;
  115. my @usn_names;
  116. my @usn_CVEs;
  117. my $usn_date;
  118. my $usn_type;
  119. my @tmp;
  120. my @lines = split(/\n/,$usn);
  121. LINE: foreach my $line (@lines) {
  122. # Date Reported -> $dsa_date
  123. unless ($usn_date) {
  124. #print "Looking for date\n";
  125. #print "Line: $line\n";
  126. if ($line =~ /^Ubuntu\ Security\ Notice\ .*\d+-\d+\s+(\w+)\s+(\d+),\s+(\d+)/) {
  127. $usn_date = parsedate("$1 $2, $3");
  128. }
  129. next LINE;
  130. }
  131. # Affected Packages -> @dsa_names
  132. unless (@usn_names) {
  133. #print "Looking for name\n";
  134. #print "Line: $line\n";
  135. if ($line =~ m/^CVE-/ || $line =~ m/^CAN-/ || $line =~ m/^http.*launchpad.net\/bugs/|| $line =~ m/^===========/) {
  136. @usn_names = @tmp;
  137. @tmp=();
  138. $usn_type = pop @usn_names;
  139. } else {
  140. foreach my $w (split(/\s+/, $line)) {
  141. $w =~ s/,\s*//;
  142. push @tmp, $w;
  143. }
  144. next LINE;
  145. }
  146. }
  147. # Security database references (CVEs) -> @dsa_CVEs
  148. unless (@usn_CVEs) {
  149. #print "Looking for cve\n";
  150. #print "Line: $line\n";
  151. if ($line =~ m/^===========/) {
  152. @usn_CVEs = @tmp;
  153. } else {
  154. foreach my $w (split(/\s+/, $line)) {
  155. $w =~ s/,\s*//;
  156. push @tmp, $w;
  157. }
  158. }
  159. }
  160. }
  161. return (\@usn_names, $usn_date, \@usn_CVEs);
  162. }
  163. 1;