#!/usr/bin/perl -wT
# Prints out a 50% random sampling of upcoming days.
#
# Set the 'key' parameter to a string to tweak the RNG.
#
# Set the 'r' parameter to a number 1..256 to randomly
# select r/256 instead of 50% .
#
# Set 'salt' and 'rounds' for crypt(3).
#
# Copyright 2020 Ken Takusagawa
#
# This program is free software: you can redistribute it
# and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General
# Public License along with this program. If not, see
# .
use POSIX qw(mktime strftime);
use CGI qw(:standard);
use Digest::SHA qw(sha1 sha512_hex);
use URI::Escape qw(uri_escape);
use Scalar::Util(looks_like_number);
print header;
$r=param('r');
$r=128 unless defined$r;
unless ($r =~ /^\d+$/){
&errorr();
}
unless (1 <= $r and $r <= 256){
&errorr();
}
die if $r<1; #prevent infinite loop
die if $r>256;
if($r==128){
$proportion="50%";
}else{
$proportion="$r/256";
}
$andr="-$r/256";
print start_html("random $proportion of days");
print << 'EOF';
EOF
$debug=param('debug');
unless(defined$debug){
$debug=0;
}
unless(looks_like_number$debug){
# needs to be numeric because we will compare numerically later
$debug=1
}
$now=time;
# beware that TZ is local time zone of the web server
print "\n" if $debug;
# go backward one day for people in west time zones
$now -= 24*60*60;
@brokendowntime=localtime($now);
$day=$brokendowntime[3];
$month=$brokendowntime[4]+1;
$year=$brokendowntime[5]+1900;
$second=$minute=0;
$hour=12; # avoid weirdness of daylight saving time changes
# initialization vector, perhaps interpretable as a random
# seed, perhaps interpretable as a secret key
$key=param('key');
unless (defined$key){
$key="";
$andkey="";
} else {
if($debug){
print"\n";
}
# make some attempt at avoiding length-extension attacks against sha-2 family by hashing twice, inspired by HMAC-SHA
#$key=sha512_hex($key);
# let's use crypt in case the user wants to keep key
# secret but the output is observed by an adversary.
$type=6; # sha512
$rounds=param('rounds');
$defaultrounds=200000; # avoid overloading web server
$rounds=$defaultrounds unless defined($rounds);
unless ($rounds =~ /^\d*$/){
print"\n" if$debug;
$rounds=$defaultrounds;
}
unless($rounds eq ""){
#crypt will automatically clip to minimum 1000
if ($rounds>$defaultrounds){
print"\n" if$debug;
$rounds=$defaultrounds;
}
$rounds="\$rounds=$rounds"
}
# passing an empty string for rounds does 5000
$salt=param('salt');
$salt='randomdays' unless defined($salt); # thwart rainbow tables
# salt characters outside the range are technically accepted by
# __sha512_crypt_r even though the man page says not. we follow
# the principal of "be liberal in what you accept".
# uncomment this to restrict salt characters $salt =~ s,[^0-9A-Za-z/.],,g;
# crypt will automatically truncate salt to maximum 16 characters
$full='$'.$type.$rounds.'$'.$salt;
$key=crypt $key,$full;
$key="-".$key;
$andkey="-crypt(key)";
if($debug){
die unless ($part1,$salt,$part2)=($key =~ /-(.+\$)([^\$]*)(\$[^\$]+)$/);
print"\n";
}
}
if($debug){
print "\n";
}
print"\n";
$ct=0;
while($ct<=366){ # limit number matching days to avoid excessive CPU load at r=1
#$t=mktime($second,$minute,$hour,$day,$month-1,$year-1900);
# sadly srand gives chunks 4 identical days in a row
# srand$t;
# for(0..999){rand 1}
# if(rand 1 <0.5){
# print scalar(localtime($t)),"
\n";
# $ct++;
# }
$iso8601=strftime('%F', $second,$minute,$hour,$day,$month-1,$year-1900);
$out=strftime('%F - %B %e %Y %A',$second,$minute,$hour,$day,$month-1,$year-1900);
$x=$iso8601.$andr.$key;
@F=unpack("C*",sha1($x));
if($debug>1){
print "\n";
}
if($F[0]<$r){
#print scalar(localtime($t))," ($iso8601)
\n";
if($debug==1){
print "\n";
}
print "$out
\n";
$ct++;
}
# because of the way strftime is implemented, this works even if the day is outside of the range of numbers for the month.
$day++;
}
print end_html;
sub possiblyescape {
# prevent XSS attacks
die unless@_;
my$escaped;
for($_[0]){
$escaped=uri_escape($_);
if($_ ne $escaped){
$escaped="uri_unescape($escaped)";
}
}
$escaped;
}
sub errorr {
print start_html("ERROR, random proportion of days");
print << 'EOF';
ERROR: r parameter should be integer, 1 <= r <= 256
EOF
print end_html;
exit;
}