aboutsummaryrefslogtreecommitdiffstats
path: root/english/security/mk-dsa-dla-list
blob: d4201311f904cfca173cd6cd4c6ac3181b5da687 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#! /usr/bin/perl

# This script is still used in 2024

# Copyright (C) 2022-2023  Thomas Lange <lange@debian.org>

# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.

# create list of recent DSA/DLA items with links to sec-tracker and to the mail

# how it works:
# get the last n DSA entries
# get mapping for DSA to URL on the mailing list archive
# check if there are manual fixes of the URL in data/*.fixes
# print list in text or HTML or CSS feed


use warnings;
use strict;
use Getopt::Std;
use Time::Piece;
use HTML::Entities;
use HTTP::Tiny;
use YAML::Tiny;
use XML::FeedPP;

our ($opt_s,$opt_f,$opt_F,$opt_m);

our $dsafile  = "data/dsa.data";
our $dsafixes = "data/dsa.fixes";
our $mldsa    = "https://lists.debian.org/debian-security-announce";

our $dlafile  = "data/dla.data";
our $dlafixes = "data/dla.fixes";
our $mldla    = "https://lists.debian.org/debian-lts-announce";

# security tracker URL
our $trurl  = "https://security-tracker.debian.org/tracker";

our $datafile = "data/description.yaml"; # yaml store for descriptions
our %data;  # contains the data (link and description for each DSA/DLA

# global variables
our %years;     # which years in the mail list archive must be parsed
our @reports;   # list of recent reports
our $nentries;
our $yaml;
our $write_yaml = 0;
our $feed;

sub usage {

  print << "EOM";

  mk-dsa-dla-list [option] type [count]

  type    can be DSA or DLA
  count   list the last count reports
  -m create apache redirect map for all DSA and DLA (no type needed)
  -s short list, no HTML
  -f create RSS feed short version
  -F create RSS feed long version including the description
EOM
  exit 0;
}



sub create_map {

  # map DSA-1234-1 (or DLA) to URL in mailing list archive

  # args: hash to store data, URL
  my $href = shift;
  my $url  = shift;

  my ($msg, $nr);

  my $response = HTTP::Tiny->new->get("$url");
  warn "Failed fetching $url!\n" unless $response->{success};
  my $cont = $response->{content} if length $response->{content};
  my @lines = split(/\cJ/,$cont);

  foreach (@lines) {
    next until /^<li>/;

    # regex include some special handling of DLA lines including
    # a second string in brackets line this: [SECURITY] [UPDATE] [DLA 2948-1] ...
    ($msg,$nr) = $_ =~ m#href="(msg\d+.html)">\[SECURITY\]\s+(?:\[[A-Z]+\]\s+)?\[(D[SL]A[\s-]+[-\d]+)#;
    warn "DEBUG: NR $url $_\n" unless defined($nr);
    warn "DEBUG: MSG $url $_\n" unless defined($msg);

    next unless defined $nr;
    $nr =~ s/(D[SL]A)\s+/$1-/; # make sure nr always is DSA-1234-1
    $href->{"$nr"}->[0] = "$url$msg";
  }
}

sub get_desc {

  my $dsa = shift;

  unless (defined $data{$dsa}->[1]) {
    $data{$dsa}->[1] = get_desc_text($data{$dsa}->[0]);
    $write_yaml = 1;
  }
  return $data{$dsa}->[1];
}

sub get_desc_text {

  # get the description text of the DSA/DLA

  my $url = shift;
  my $response = HTTP::Tiny->new->get("$url");
  warn "Failed fetching $url!\n" unless $response->{success};
  my $cont = $response->{content} if length $response->{content};

  # get description of announcement
  $cont =~ s/^\s+$/\n/mg;  # workaround for empty line (only with space) in DLA-3398-1
  # this regex should match the text of the security announcement (including CVE descriptions if added)
  $cont =~ /^(?:- )*-----------------.+Package.+?^$(.+?)(For the \S+ distribution |For Debian |We recommend )/ms;
  $_ = $1;

  decode_entities($_);
  s/^\n//;
  s/\n$//;
  return $_;
}

sub get_entries {

  # read $nentries entries and determine the years needed
  # save entries into array ref given, and set %years

  my $file = shift;
  my $aref = shift;
  my $i;

  open (F,"< $file") || die "Cannot open $file";
  foreach (<F>) {
    next until /^\[/;
    chomp;
    push @$aref,$_; # save line

    # match year only
    /^\[\d+\s+\w+\s+(\d+)\]/;
    $years{$1} = 1;
    $i++;
    last if ($i >= $nentries);
  }
  close("F");
}

sub mk_list {

  # create html or text output of recent security announcements

  my @list = @_;
  my ($date,$newdate,$dsa,$pkg,$text,$desc);
  my ($dummy,$type);

  foreach (@list) {

    ($date,$dsa,$pkg,$text) = m#\[(\d+\s+\w+\s+\d+)\]\s+(D[SL]A-\d+(?:-\d)?)\s+([\w.+-]+)\s*(?:-\s+)?(.+)?#;
    # no link was found, maybe due to typo on mailing list
    # or when the announcement mail is not yet in the mail list archive
    # then do not list this item, do not write description.yaml
    unless ($data{$dsa}->[0]) {
      warn "WARN: No link to mail for $dsa\n";
      undef $data{$dsa}->[0];
      $write_yaml = 1;
      $dsa =~ /(D[SL]A)/;
      $type = $1;
      open my $dummy, '>>', ".rebuild-$type";
      print $dummy "$dsa\n";
      close $dummy;
      next;
    }
    $data{$dsa}->[0] = $data{'none'}->[0] unless $data{$dsa}->[0];

    if ($opt_F || $opt_f) {
      $desc = "";
      if ($opt_F) {
        $desc = get_desc($dsa);
        $desc =~ s/^\s*$/<p>/msg;
      }
      my $item = $feed->add_item("$data{$dsa}->[0]");
      $item->title("$dsa $pkg - $text");
      $newdate = Time::Piece->strptime($date, '%d %b %Y')->strftime('%Y-%m-%d');
      $item->pubDate($newdate);
      $desc .= "\n<p>\n" if ($opt_F);
      $desc .= "<a href=\"https://security-tracker.debian.org/tracker/$dsa\">https://security-tracker.debian.org/tracker/$dsa</a>";
      $item->description($desc);

    } elsif ($opt_s) {
      # print short list
      print "$dsa $data{$dsa}->[0]\n";
    } else {
      print "<tt>[$date]</tt> <strong><a href=$trurl/$dsa> T</a> &ensp;<a href=\"$data{$dsa}->[0]\">$dsa $pkg</a></strong> $text<br>\n";
    }
  }
}

sub check_fixes {

  my $href = shift;
  my $fixfile = shift;
  my $baseurl = $href->{'none'}->[0];
  my $link;
  my $name;

  # read in all fixes
  open (F,"< $fixfile") || die "Cannot open $fixfile";
  while (<F>) {
    next if /^#/;
    ($name,$link) = m/(^D[SL]A-\S+)\s+(\S+)/;
    # replace entry if a fix is available
    warn "Fixing $name with $link\n";
    $href->{"$name"}->[0] = "$baseurl/$link";
  }
}

sub create_dsa_redirect {

  my $t = Time::Piece->new();
  foreach (2000..$t->year) {
    create_map(\%data, "$mldsa/$_/");
  }
  $data{'none'}->[0] = "$mldsa";
  check_fixes(\%data,$dsafixes);
  foreach (keys %data) {
    print "$_ $data{$_}->[0]\n";
  }
}

sub create_dla_redirect {

  my $t = Time::Piece->new();
  foreach my $y (2014..$t->year) {
    for ("01".."12") {
      # skip the future
      next if ( $t->year == $y && $_ > $t->mon );
      create_map(\%data, "$mldla/$y/$_/");
    }
  }
  $data{'none'}->[0] = "$mldla";
  check_fixes(\%data,$dlafixes);
  foreach (keys %data) {
    print "$_ $data{$_}->[0]\n";
  }
}

sub create_redirect_maps {

  create_dsa_redirect;
  undef %data;
  create_dla_redirect;
  exit 0;
}

# main program

getopts('sfFm');
$opt_m && create_redirect_maps;
my $type = shift;
usage unless $type =~/^D[SL]A$/;

# read how much entries to show from cmdline or use default
$nentries = shift || 49;

# initialize yaml structure if needed
  if ( -f $datafile ) {
    $yaml = YAML::Tiny->read( $datafile);
    %data = %{$yaml->[0]};
  } else {
    $yaml = YAML::Tiny->new( );
    $write_yaml = 1;
  }
if ($opt_f || $opt_F) {
  my $now = time();
  $feed = XML::FeedPP::RDF->new();
  $feed->title("Debian Security");
  $feed->link("https://www.debian.org/security/dsa.rdf");
  $feed->description("Debian Security Advisories");
  $feed->pubDate($now);
}

if ($type eq 'DSA') {
  get_entries($dsafile,\@reports);
  foreach (reverse sort keys %years) {
    create_map(\%data, "$mldsa/$_/");
  }
  # create a generic link for non-existing entries
  $data{'none'}->[0] = "$mldsa";
  check_fixes(\%data,$dsafixes);
  mk_list(@reports);
}

if ($type eq 'DLA') {
  get_entries($dlafile,\@reports);

  # DLA have a monthly index in the mailing list
  my $t = localtime;
  my $y;
  foreach $y (keys %years) {
    for ("01".."12") {
      # skip the future
      next if ( $t->year == $y && $_ > $t->mon );
      create_map(\%data, "$mldla/$y/$_/");
    }
  }
  # create a generic link for non-existing entries
  $data{'none'}->[0] = "$mldla";
  check_fixes(\%data,$dlafixes);
  mk_list(@reports);
}

$feed->to_file("index.rdf") if ($opt_f || $opt_F);
if ($write_yaml) {
  $yaml->[0] = \%data;
  $yaml->write($datafile) ;
}

exit 0;

© 2014-2024 Faster IT GmbH | imprint | privacy policy