fix: harden saytime.pl temp files and playback.
0005-fix-harden-saytime.pl-temp-files-and-playback.patch
--- diff --git a/bin/saytime.pl b/bin/saytime.pl --- a/bin/saytime.pl +++ b/bin/saytime.pl @@ -1,6 +1,7 @@ +use File::Temp qw(tempfile); @@ -31,7 +32,7 @@ if (defined $arg1 && !defined $arg2) { - elsif ($arg3 =~ /^[012]$/) { $silent = $arg3; } + elsif ($arg3 =~ /^[012]$/) { $silent = 0 + $arg3; } @@ -44,17 +45,37 @@ if (!$node || $node eq "YOUR_NODE_NUMBER") { -my $outdir = "/tmp"; -my $outfile = "$outdir/current-time.gsm"; +validate_dir_path($base, 'SOUNDS_DIR'); +validate_dir_path($cache_dir, 'CACHE_DIR'); + +if ($location && -f $env_file) { + my %cached = read_ini($env_file); + if ($cached{LOCATION_QUERY} && $cached{LOCATION_QUERY} ne $location) { + unlink $env_file; + } +} + - system("/usr/local/sbin/asl3-weatherapi-update.sh >/dev/null 2>&1"); + { + local *STDOUT; + local *STDERR; + open STDOUT, '>>', '/dev/null'; + open STDERR, '>>', '/dev/null'; + system('/usr/local/sbin/asl3-weatherapi-update.sh'); + } -my %wx = (-f $env_file) ? read_ini($env_file) : (); +my %wx; +if (-f $env_file) { + my %cached = read_ini($env_file); + if (!$location || !$cached{LOCATION_QUERY} || $cached{LOCATION_QUERY} eq $location) { + %wx = %cached; + } +} @@ -68,14 +89,26 @@ push @sounds, build_weather_sounds(\%wx) if %wx; -unlink $outfile if -f $outfile; -my $cat = "cat " . join(' ', map { shell_quote($_) } @sounds) . " > " . shell_quote($outfile); -system($cat) == 0 or die "Could not create $outfile\n"; -chmod 0644, $outfile; +my ($outfh, $outfile) = tempfile( + 'saytime-XXXXXX', + SUFFIX => '.gsm', + DIR => $cache_dir, + UNLINK => 0, +); +close $outfh; + +concat_gsm_files(\@sounds, $outfile) or die "Could not create $outfile\n"; +chmod 0640, $outfile; + +my $play_base = $outfile; +$play_base =~ s/\.gsm\z//; - system('/usr/sbin/asterisk', '-rx', "rpt localplay $node $outdir/current-time"); - sleep 5; + my $rc = system('/usr/sbin/asterisk', '-rx', "rpt localplay $node $play_base"); + warn "asterisk localplay failed with exit " . ($rc >> 8) . "\n" if $rc != 0; + my $sleep_secs = 5 + scalar(@sounds); + $sleep_secs = 30 if $sleep_secs > 30; + sleep $sleep_secs; @@ -85,6 +118,33 @@ if ($silent == 0) { +sub validate_dir_path { + my ($path, $label) = @_; + return if defined $path + && $path ne '/' + && $path =~ m{\A/(?:[^/]+/)*[^/]+\z} + && $path !~ /\.\./; + die "$label must be an absolute path without .. segments\n"; +} + +sub concat_gsm_files { + my ($files, $dest) = @_; + open my $out, '>', $dest or return 0; + binmode $out; + for my $file (@$files) { + open my $in, '<', $file or return 0; + binmode $in; + my $buf; + while (defined(my $read = read($in, $buf, 65536))) { + last if $read == 0; + print $out $buf or return 0; + } + close $in; + } + close $out; + return 1; +} + @@ -103,12 +163,6 @@ sub read_ini { -sub shell_quote { - my ($s) = @_; - $s =~ s/'/'\\''/g; - return "'$s'"; -} - --