#!/usr/bin/perl use strict; use File::Temp; use Getopt::Std; #use Smart::Comments; use Term::ReadLine; my %opts; getopts('ln:fhi:t:Tca:e:', \%opts); if ($opts{h}) { print <<'EOF'; downloads allitems.txt from cve.mitre.org and shows full decription for each "TODO: check" item (2003 and newer). Then - tries to guess product name and php filename and does apt-cache and apt-file search - waits for input: * blank line to skip to next issue * .fname to do "apt-file search name" * .cname to do "apt-cache search name" * .mpackage to search data/embedded-code-copies for "package" * v or e to launch an editor with the current item * q to save and quit * CTRL-C to quit without saving * everything else is inserted as product name for a NOT-FOR-US Use "svn diff" and "svn revert" as needed ;-) OPTIONS: [ -l [-n ] [-f] ] -l : just list issues -n : show max n lines of each description (default 2) -f : show full CVE/list entry as well -i regexp : use regexp to select issues (default: 'CVE-200[3-9]' ) -t regexp : use regexp to select todos (default: '^\s+TODO: check$' ) -T : same as -t '^\s+TODO: check' (note the missing $) -c : only do syntax check of embedded-code-copies -e : use for embedded-code-copies, "-" for STDIN -a : If automatic apt-cache/apt-file search gives more than n results, display only the count (default 10) EOF exit(0); } # TODO/BUGS: # - go back to previous issue / undo # - handle entries with several TODO lines # - handle claimed-by # - look for ITPs? my $basedir; if (-e "secure-testing/data/CVE/list") { $basedir="secure-testing"; } elsif (-e "data/CVE/list") { $basedir="."; } elsif (-e "../data/CVE/list") { $basedir=".."; } my $embed_code = {}; my $embed_pkg = {}; my $embed_errors; read_embedded_copies(); if ($opts{c}) { exit($embed_errors); } my $datafile="$basedir/data/CVE/list"; my $allitemsfile="gunzip -c $basedir/../allitems.txt.gz|"; my $allitemsurl="http://cve.mitre.org/data/downloads/allitems.txt.gz"; my $issue_regexp= $opts{i} || 'CVE-200[3-9]'; my $todo_regexp= $opts{t} || ( $opts{T} ? '^\s+TODO: check' : '^\s+TODO: check$' ); my $auto_display_limit = 10; $auto_display_limit = $opts{a} if defined $opts{a}; my $editor=$ENV{EDITOR} || $ENV{VISUAL} || "vi"; system "cd $basedir/.. ; wget -N $allitemsurl"; print "Reading data...\n"; my $entries=read_file($datafile, qr/^CVE/ ); my $CVEs=read_file($allitemsfile, qr/^=+$/ ); my $data; my @todos; my %afcache; foreach my $cve (@{$CVEs}) { $cve =~ /^Name:\s*(CVE\S+)/m or next; $data->{$1}->{CVE}=\$cve; } foreach my $entry (@{$entries}) { my $name; if ( $entry =~ /^(CVE-....-\d\d\d\d)/ ) { $name=$1; } elsif ( $entry =~ /^(CVE-....-XXXX.*)\n/ ){ $name=$1; } else { die "invlid entry:\n$entry"; } $data->{$name}->{entry}=\$entry; if ( $entry =~ /$todo_regexp/m and $name =~ /$issue_regexp/ ) { push @todos, $name; } } print scalar(@{$CVEs}), " CVEs, ", scalar(@{$entries}) - scalar(@{$CVEs}), " temp issues, ", scalar(@todos), " todos matching /$todo_regexp/\n"; if ($opts{l}) { #list only foreach my $todo (reverse sort @todos) { my $desc=description($todo); if ($desc) { my $lines=$opts{n} || 2; if ($desc =~ /((?:.*\n){1,$lines})/) { $desc = $1; $desc =~ s/^/ /mg; if ($opts{f}) { print ${$data->{$todo}->{entry}}, $desc; } else { print "$todo:\n$desc"; } } } else { print "${$data->{$todo}->{entry}}"; } } exit 0; } my $term = new Term::ReadLine 'check-new-issues'; if ($term->ReadLine() eq 'Term::ReadLine::Stub') { print "Install libterm-readline-gnu-perl to get readline support!\n"; } TODO: foreach my $todo (reverse sort @todos) { print ${$data->{$todo}->{CVE}} if $data->{$todo}->{CVE}; print ${$data->{$todo}->{entry}}; auto_search($todo); READ: while (my $r=$term->readline(">") ) { chomp $r; if ($r =~ /^\s*$/) { next TODO; } elsif ($r=~ /^\.c(.*)$/ ) { my $s = $1; $s =~ tr{a-zA-Z0-9_@-}{ }cs; print "=== apt-cache search $s :\n"; system("apt-cache search $s|less -FX"); print "===\n"; next READ; } elsif ($r=~ /^\.f(.*)$/ ) { my $s = $1; $s =~ s/^\s*(.*?)\s*$/$1/; $s = quotemeta($s); print "=== apt-file search $s:\n"; system("apt-file search $s|less -FX"); print "===\n"; next READ; } elsif ($r=~ /^\.m(.*)$/ ) { my $s = $1; $s =~ s/^\s+//; $s =~ s/\s+$//; print "references to $s in embedded-code-copies:\n"; search_embed($s) or print "none\n"; next READ; } elsif ($r=~ /^q$/i ) { last TODO; } elsif ($r=~ /^[ve]$/i ) { my $newentry=edit_entry(${$data->{$todo}->{entry}}); if ( $newentry eq ${$data->{$todo}->{entry}} ) { print "Not changed.\n"; next READ; } else { ${$data->{$todo}->{entry}}=$newentry; print "New entry set to:\n$newentry"; next TODO; } } else { ${$data->{$todo}->{entry}} =~ s/^\s*TODO: check/\tNOT-FOR-US: $r/m ; print "New entry set to:\n${$data->{$todo}->{entry}}"; next TODO; } } } open(my $fh, ">", $datafile); print $fh @{$entries}; close($fh); sub description { my $name=shift; defined $data->{$name}->{CVE} or return ""; ${$data->{$name}->{CVE}} =~ /\n\n(.*)^Current Votes:/ms; my $desc = $1; $desc =~ s/\n\n+/\n/; return $desc; } sub read_file { my $file=shift; my $re=shift; open(my $fh, $file) or die "could not open $file"; my @data; my $cur=""; while (my $line=<$fh>) { if ($line =~ $re and $cur) { push @data, $cur; $cur = ""; } $cur.=$line; } push @data, $cur if $cur; close($fh); return \@data; } sub edit_entry { my $entry=shift; my $tmp=new File::Temp(); my $tmpname=$tmp->filename; print $tmp $entry; close $tmp; system "$editor $tmpname"; local $/; #slurp open($tmp, $tmpname); return <$tmp>; } sub auto_search { my $name=shift; my $desc=description($name); $desc =~ s/[\s\n]+/ /g; my $file; my $prog; if ( $desc =~ /^(\S+(?: [A-Z]\w*)*) \d/ ) { $prog = $1; } elsif ( $desc =~ / in (\S+\.\S+) in (?:the )?(\S+) / ) { $file = $1; $prog = $2; } elsif ( $desc =~ / in (?:the )?(\S+) / ) { $prog = $1; } if ($prog) { my $prog_esc =$prog; $prog_esc =~ tr{a-zA-Z0-9_@/-}{ }cs; print "doing apt-cache search..."; my @ac=`apt-cache search $prog_esc`; if (scalar @ac > $auto_display_limit || scalar @ac == 0) { print "\r", scalar @ac, " results from apt-cache search $prog_esc\n"; } else { print "\r=== apt-cache search $prog_esc:\n", @ac, "===\n"; } foreach my $p (split /\s+/, $prog) { search_embed($p); } } if ( $file =~ /^(?:index|default|login|search|admin)\.(?:php3?|asp|cgi|pl)$/i ) { return; } if ( $file =~ /(php3?|asp|cgi|pl)$/ ) { if (! exists $afcache{$file}) { my $file_esc = quotemeta($file); print "doing apt-file search..."; $afcache{$file}=[`apt-file -i search $file_esc`]; if (scalar @{$afcache{$file}} > $auto_display_limit) { # replace with empty array to save mem my $num = scalar @{$afcache{$file}}; $afcache{$file} = []; $afcache{$file}->[$num-1] = undef; } } if (scalar @{$afcache{$file}} > $auto_display_limit || scalar @{$afcache{$file}} == 0) { print "\r", scalar @{$afcache{$file}}, " results from apt-file -i search $file\n"; } else { print "\r=== apt-file -i search $file:\n", @{$afcache{$file}}, "===\n"; } } } sub read_embedded_copies { my $emb_file = $opts{e} || "$basedir/data/embedded-code-copies"; open(my $fh, $emb_file); # skip comments while (<$fh>) { last if /^---BEGIN/; } my ($code, $pkg); while (my $line = <$fh>) { if ($line =~ /^([-\w]+)/) { $code = lc($1); $pkg = undef; if (exists $embed_code->{$code}) { syntax_error("Duplicate embedded code $code") } } elsif ($line =~ /^\s*$/) { $code = undef; $pkg = undef; } elsif ($line =~ /^\s+(?:\[\w+\]\s+)?-\s+(\w[\w.-]+)/) { $pkg = $1; $line =~ s/^\s+//; if ($embed_code->{$code}->{$pkg}) { $embed_code->{$code}->{$pkg} .= $line; } else { $embed_code->{$code}->{$pkg} = $line; push @{$embed_pkg->{$pkg}}, $code; } } elsif ($line =~ /^\s+(?:NOTE|TODO)/) { $line =~ s/^\s+//; if ($pkg) { $embed_code->{$code}->{$pkg} .= $line; } } else { syntax_error("Cannot parse $line"); } } } sub syntax_error { $embed_errors=1; print STDERR "embedded-code-copies:$.: @_\n"; } sub search_embed { my $text = shift; my $found = 0; $text = lc($text); if (exists $embed_code->{$text}) { print "$text is embedded by: ", join(" ", sort keys %{$embed_code->{$text}}), "\n"; $found = 1; } if (exists $embed_pkg->{$text}) { print "$text embeds: ", join(" ", sort @{$embed_pkg->{$text}}), "\n"; $found = 1; } return $found; }