Merge remote-tracking branch 'flapflap/de-network_configuration'
[tails-test.git] / config / chroot_local-includes / usr / local / bin / tails-security-check
blobb9909a0064b8ebf16ca2c9d633b016d049f8cd59
1 #! /usr/bin/perl
3 use strict;
4 use warnings FATAL => 'all';
5 use 5.10.1;
7 #man{{{
9 =head1 NAME
11 tails-security-check
13 =cut
16 =head1 DESCRIPTION
18 =head1 SYNOPSIS
20 tails-security-check [ ATOM_FEED_BASE_URL ]
22 ATOM_FEED_BASE_URL will be appended /index.XX.atom,
23 for XX in (current locale's language code, 'en'),
24 until success is reported by the HTTP layer.
26 =head1 AUTHOR
28 Tails developers <tails@boum.org>
29 See https://tails.boum.org/.
31 =cut
33 #}}}
35 use Carp;
36 use Carp::Assert::More;
37 use Desktop::Notify;
38 use Fatal qw{open close};
39 use Locale::gettext;
40 use POSIX;
41 use XML::Atom;
42 use XML::Atom::Feed;
44 ### Initialization
46 use IO::Socket::SSL;
47 use Net::SSLeay;
48 BEGIN {
49 IO::Socket::SSL::set_ctx_defaults(
50 verify_mode => Net::SSLeay->VERIFY_PEER(),
51 ca_file => '/usr/local/etc/ssl/certs/tails.boum.org-CA.pem',
54 use LWP::UserAgent; # needs to be *after* IO::Socket::SSL's initialization
56 setlocale(LC_MESSAGES, "");
57 textdomain("tails");
59 ### configuration
61 my $version_file = '/etc/amnesia/version';
62 my $default_base_url = 'https://tails.boum.org/security/';
64 =head1 FUNCTIONS
66 =head2 current_lang
68 Returns the two-letters language code of the current session.
70 =cut
71 sub current_lang {
72 my ($code) = ($ENV{LANG} =~ m/([a-z]{2}).*/);
74 return $code;
77 =head2 atom_str
79 Argument: an Atom feed URL
81 Returns the Atom's feed content on success, undef on failure.
83 =cut
84 sub atom_str {
85 my $url = shift;
86 assert_defined($url);
88 $ENV{HTTPS_VERSION} = 3;
90 my $ua = LWP::UserAgent->new;
91 $ua->proxy([qw(http https)] => 'socks://127.0.0.1:9062')
92 unless $ENV{DISABLE_PROXY};
93 my $req = HTTP::Request->new('GET', $url);
94 my $res = $ua->request($req);
95 if (defined $res && $res->is_success) {
96 return $res->content;
99 return undef;
102 =head2 get_entries
104 Arguments: the Atom feed URL.
106 Returns the list of XML::Atom::Entry objects from the feed.
108 We use this manual Accept-Language algorithm as the website
109 layout does not allow us to use content negotiation.
111 =cut
112 sub get_entries {
113 my $base_url = shift;
114 assert_defined($base_url);
115 assert_nonblank($base_url);
117 my $separator = '';
118 $separator = '/' unless $base_url =~ m{/\z}xms;
120 my @try_urls = (
121 $base_url . $separator . 'index.' . current_lang() . '.atom',
122 $base_url . $separator . 'index.en.atom',
125 my $feed_str;
126 foreach my $url (@try_urls) {
127 last if ($feed_str = atom_str($url));
129 assert_defined($feed_str);
131 return XML::Atom::Feed->new(\$feed_str)->entries();
134 =head2 notify_user
136 Use the Desktop Notifications framework to notify the user about the
137 Atom entries passed as arguments.
139 =cut
140 sub notify_user {
141 my @entries = @_;
143 my $notify = Desktop::Notify->new();
145 my $summary = gettext('This version of Tails has known security issues:');
146 my $body = '';
148 for (@entries) {
149 $body .= '- ' . '<a href="' . $_->id . '">' . $_->title . '</a>' . "\n";
152 say $body;
154 $notify->create(summary => $summary,
155 body => $body,
156 timeout => 0)->show();
159 =head2 categories
161 Return the list of categories of the input XML::Atom::Entry object.
163 =cut
164 sub categories {
165 my $entry = shift;
166 my $ns = XML::Atom::Namespace->new(
167 dc => 'http://purl.org/dc/elements/1.1/'
169 my @category = ($entry->can('categories'))
170 ? $entry->categories
171 : $entry->category;
172 @category
173 ? (map { $_->label || $_->term } @category)
174 : $entry->getlist($ns, 'subject');
177 =head2 is_not_fixed
179 Returns true iff. the input XML::Atom::Entry object hasn't the
180 security/fixed tag.
182 =cut
183 sub is_not_fixed {
184 my $entry = shift;
185 assert_isa($entry, 'XML::Atom::Entry');
187 ! grep { $_ eq 'security/fixed' } categories($entry);
190 =head2 unfixed_entries
192 Filter the input list of XML::Atom::Entry objects to only keep entries
193 that are not marked as fixed yet.
195 =cut
196 sub unfixed_entries {
197 my @entries = @_;
199 grep { is_not_fixed($_) } @entries;
202 =head1 MAIN
204 =head2 sanity checks
206 =cut
207 if (! -e "$version_file") {
208 die "The Tails version file ($version_file) does not exist."
210 if (! -r "$version_file") {
211 die "The Tails version file ($version_file) is not readable."
214 =head2 parse command line args
216 =cut
217 my $base_url = shift || $default_base_url;
218 my $opt_since = shift;
221 =head2 do the work
223 =cut
224 my @unfixed_entries = unfixed_entries(get_entries($base_url));
226 if (! @unfixed_entries) {
227 exit 0;
229 else {
230 notify_user(@unfixed_entries);