' . $str[381] . '
';
}
$is_auth = 0;
%STATE = ( 'Username' => '' );
$auth->logout();
}
$| = 1;
print "Content-Type: text/html\015\012\015\012";
$const{'has_ct_header'} = 1;
$| = 0;
if (($STATE{'language'}) and ($STATE{'language'} ne $const{'language'})) {
# hmm... let's try something different...
my $test = [];
$err = &loadlang( $STATE{'language'}, $test );
if ($err) {
$usr_load_warnings .= ".)) {
if ($MODE == 2) {
$_ = &force_eoln( $_, $STATE{'text_eoln'} );
}
unless (print FILE $_) {
$err = &pstr(231, &he( $file, $!, $^E ) );
close(FILE);
close(TEMP);
next Err;
}
}
unless (close(FILE)) {
$err = &pstr(220, &he( $file, $!, $^E ) );
close(TEMP);
next Err;
}
unless (close(TEMP)) {
$err = &pstr(220, &he( $TempFile, $!, $^E ) );
next Err;
}
&Mask( $file, $is_cgi );
my $size = -s $file;
if ($size < 2048) {
$size = "$size bytes";
}
elsif ($size < 1024 * 1024 * 2) {
$size = int( ($size + 1023) / 1024 ) . " kb";
}
else {
$size = int( ($size + 1048575) / (1024 * 1024) ) . ' MB';
}
my $status;
if ($MODE == 2) {
$status = &pstr( 163, &he($file), 'ascii/text', $size );
}
elsif ($MODE == 1) {
$status = &pstr( 163, &he($file), 'binary', $size );
}
&Report( &pstr(4, $status ) );
$upload_success++;
$$p_data{'server file name'} = $file;
}
last Err;
}
return $err;
}
=item password_set
Usage:
$err = &password_set();
next Err if ($err);
This subroutine will *optionally* set the user password if and only if the $FORM{'OldPass'} variable is non-empty.
If that var is non-empty, then it will validate $FORM{'OldPass'} against username $STATE{'Username'} and will then set using $FORM{'NewPass'} and $FORM{'NewPass2'}.
See also, password_force()
=cut
sub password_set {
my $err = '';
Err: {
last Err unless (defined($FORM{'OldPass'}));
last Err unless (length($FORM{'OldPass'}));
# they have initialized their old password; now they *must* set a new one:
my $is_valid;
($err, $is_valid) = $auth->ValidatePassword( $STATE{'Username'}, $FORM{'OldPass'} );
next Err if ($err);
unless ($is_valid) {
$err = $str[33];
next Err;
}
$err = &password_force( $STATE{'Username'}, $FORM{'NewPass'}, $FORM{'NewPass2'} );
next Err if ($err);
last Err;
}
return $err;
};
=item password_force
Usage:
$err = &password_force( $username, $password1, $password2 );
next Err if ($err);
This routine validates that $password1 and $password2 agree with one another and meet the validation rules.
It then updates the password file.
=cut
sub password_force {
my ($username, $password, $confirm) = @_;
my $err = '';
Err: {
if ($password ne $confirm) {
$err = $str[40];
next Err;
}
if (length($password) < $system_eff{'Min Password Length'}) {
$err = &pstr(229,$system_eff{'Min Password Length'});
next Err;
}
if (length($password) > 8) {
$err = "password cannot exceed 8 characters in length";
next Err;
}
$err = $auth->SetPassword( $username, $password );
next Err if ($err);
&Report(&pstr(4,$str[230]));
last Err;
}
return $err;
};
sub detect_sec_mode {
if ($^O =~ m!mswin!is) {
return 1; # do not set perms
}
elsif ((-e $0) and (-w $0)) {
return 2; # set-user-id; tight
}
else {
return 3; # permissive
}
}
sub force_CRLF {
my ($p_text) = @_;
$$p_text =~ s!\015\012!\012!sg;
$$p_text =~ s!\015!\012!sg;
$$p_text =~ s!\012!\015\012!sg;
}
sub query_env {
my ($name,$default) = @_;
if (($ENV{$name}) and ($ENV{$name} =~ m!^(.*)$!s)) {
return $1;
}
elsif (defined($default)) {
return $default;
}
else {
return '';
}
}
sub user_shell {
if ($STATE{'shell'}) {
if ($STATE{'shell'} == 1) {
return 'ListTemplates';
}
elsif ($STATE{'shell'} == 2) {
return 'ListFiles';
}
}
return 'Main';
}
sub recurse {
my ($path, $basename, $depth, $p_start, $p_entry, $p_stop) = @_;
my $err = '';
Err: {
$depth++;
# strip the trailing slash
if ($path =~ m!^(.+)/$!) {
$path = $1;
}
$path = $path . '/' . $basename;
unless (opendir(DIR, $path)) {
$err = "unable to opendir '$path' - $!";
next Err;
}
my @entries = sort readdir(DIR);
closedir(DIR);
$err = &{ $p_start }( $path, $depth );
next Err if ($err);
foreach $basename (@entries) {
next if ($basename =~ m!^\.\.?$!);
my $b_no_dive;
($err, $b_no_dive) = &{ $p_entry }( $path, $basename, $depth );
next Err if ($err);
if ((-d "$path/$basename") and (not $b_no_dive)) {
$err = &recurse( $path, $basename, $depth, $p_start, $p_entry, $p_stop );
next Err if ($err);
}
}
$err = &{ $p_stop }( $path, $depth );
next Err if ($err);
}
return $err;
}
sub extended_f_err {
my ($file) = @_;
my $errstr = $!;
if ($errstr =~ m!Permission denied!i) {
return &pstr( 9, $file, qq!$errstr - $^E [$str[55] ]! );
}
else {
return &pstr( 9, $file, qq!$errstr - $^E! );
}
}
sub priv_check {
my ($b_filesys, @privileges) = @_;
my $err = '';
Err: {
if (($b_filesys) and ($const{'home_dir_err_msg'})) {
$err = "all Genesis authoring functions disabled.Cannot access the file system due to a folder access error on start-up. Error was: $const{'home_dir_err_msg'}";
next Err;
}
local $_;
foreach (@privileges) {
next if ($STATE{$_});
my $name = $_;
if (($user_schema{$name}) and ($user_schema{$name}->[3])) {
$name = $user_schema{$name}->[3];
# converts 'use_html_editor' to 'Use HTML Editor' where possible
}
$err = &pstr( 51, $name );
next Err;
}
last Err;
}
return $err;
}
sub FolderSize {
my ($dir, $b_verbose) = @_;
my $size = 0;
my $err = '';
Err: {
$err = &priv_check(1);
next Err if ($err);
# make all slashes be forward:
$dir =~ s!\\!/!g;
# add trailing slash if necessary:
$dir .= '/' unless ($dir =~ m!/$!);
if (defined($dir_size{$dir})) {
$size = $dir_size{$dir};
last Err;
}
# we treat folder-access errors as warnings. in general, it is okay if there are subfolders which the CGI process
# does not have access to. but we should warn about it
unless (opendir(DIR, $dir)) {
if ($b_verbose) {
print "
Warning: unable to calculate disk usage - unable to open folder '$dir' - $! - $^E.
\n";
}
last Err;
}
my @Files = readdir(DIR);
closedir(DIR);
my $del_size = 0;
my $entry;
foreach $entry (@Files) {
next if ($entry =~ m!^\.\.?$!);
my $abs_path = $dir . $entry;
if ((-d $abs_path) and (not -l $abs_path)) {
($err, $del_size) = &FolderSize( $abs_path );
next Err if ($err);
$size += $del_size;
}
else {
$size += (-s $abs_path);
}
}
$dir_size{$dir} = $size;
last Err;
}
return ($err, $size);
}
sub Report {
my ($message, $do_not_print_to_screen) = @_;
print $message unless ($do_not_print_to_screen);
if (-e $const{'event log'}) {
if (open(LOG, ">>$const{'event log'}")) {
my $time = time();
$message =~ s!<.*?>!!g;
$message =~ s!\,!!g;
print LOG "$private{'REMOTE_ADDR'} , $STATE{'Username'},$time,$message\n";
close(LOG);
}
else {
&ppstr(6,&pstr(10, $const{'event log'}, $! ) );
}
}
}
sub Mask {
my ($abs_file, $is_cgi) = @_;
return unless ($file_sec[0]);
if (-d $abs_file) {
chmod( $file_sec[1], $abs_file);
}
elsif (($is_cgi) and ($const{'mode'} != 0)) {
chmod( $file_sec[3], $abs_file );
}
else {
chmod( $file_sec[2], $abs_file);
}
}
sub CheckFreeSpace {
my ($del_size) = @_;
my $is_allowed = 1;
my $err = '';
Err: {
if ($const{'home_dir_err_msg'}) {
$is_allowed = 0;
next Err;
}
last Err if ($STATE{'allow_no_quota'});
my $Quota = $STATE{'Quota'};
my $DiskBytes;
($err, $DiskBytes) = &FolderSize($STATE{'Author:UserFolder:parsed'});
next Err if ($err);
my $DiskKB = int($DiskBytes/1000);
my $FreeKB = $Quota - $DiskKB;
$is_allowed = (($FreeKB * 1000) > $del_size) ? 1 : 0;
last Err;
}
continue {
$is_allowed = 0;
}
return $is_allowed;
}
sub ReportFreeSpace {
my $err = '';
Err: {
last Err if ($const{'home_dir_err_msg'});
my $DiskBytes;
($err, $DiskBytes) = &FolderSize($STATE{'Author:UserFolder:parsed'});
next Err if ($err);
my $DiskKB = int( (1023 + $DiskBytes) /1024);
if (($STATE{'allow_no_quota'}) or (not ($STATE{'Quota'}))) {
$DiskKB = &FormatNumber( $DiskKB, 0, 0, 0, 1 );
print <<"EOM";
$str[207] $DiskKB kb
EOM
}
else {
my $Quota = $STATE{'Quota'};
my $FreeKB = $Quota - $DiskKB;
my $percent = &FormatNumber( ( 100 * ($DiskBytes / 1000) / $Quota ), 1, 0, 0, 1 );
my $width1 = int( 200 * ($DiskBytes / 1000) / $Quota );
$width1 = 1 unless ($width1);
$width1 = 199 if ($width1 > 199);
my $width2 = 200 - $width1;
$FreeKB = &FormatNumber( $FreeKB, 0, 0, 0, 1 );
$Quota = &FormatNumber( $Quota, 0, 0, 0, 1 );
$DiskKB = &FormatNumber( $DiskKB, 0, 0, 0, 1 );
print <<"EOM";
$str[207] $percent%
EOM
&ppstr(208, $DiskKB, $Quota, $FreeKB);
print ' ';
}
last Err;
}
return $err;
}
sub CheckName {
my ($file, $rq_write, $b_is_username) = @_;
my $is_cgi = 0;
my $max_file_len = 120;
my $err = '';
Err: {
unless ($file) {
$err = $b_is_username ? $str[39] : $str[209];
next Err;
}
if (length($file) > $max_file_len) {
$err = &pstr(210,$max_file_len);
next Err;
}
if ($file =~ m! !) {
$err = $str[211];
next Err;
}
if ($file =~ m!\.\.!) {
$err = $str[212];
next Err;
}
if (($file =~ m!^\.!) and ($file !~ m!\.template$!) and (0 == $STATE{'p_hidden'})) {
$err = $str[213]; #but those .x.template files are ok
next Err;
}
if (($file =~ m!^\.!) and ($file !~ m!\.template$!) and ($STATE{'p_hidden'} < 2) and ($rq_write)) {
$err = $str[214]; #but those .x.template files are ok
next Err;
}
my $V = '';
($V = $file) =~ s/\w//g;
$V =~ s/\.//g;
$V =~ s/\-//g;
if ($V) {
$err = &pstr(215,&he($V));
next Err;
}
my $extension = 'null';
if ($file =~ m!(.*)\.(.*?)$!) {
$extension = lc($2);
}
my $qm_ext = quotemeta( $extension );
my $html_ext = &he( $extension );
if (" $system_eff{'CGI Types'} " =~ m! \* !) {
$is_cgi = 1;
}
else {
$is_cgi = (" $system_eff{'CGI Types'} " =~ m! $qm_ext !i) ? 1 : 0;
}
if ($is_cgi) {
if ($STATE{'allow_cgi'}) {
# okay
}
else {
$err = &pstr(216,$html_ext, $const{'help file'});
next Err;
}
}
elsif (" $system_eff{'Known Types'} $system_eff{'Media Types'} " =~ m! \* !) {
# ok...
}
else {
if (" $system_eff{'Known Types'} $system_eff{'Media Types'} " =~ m! $qm_ext !i) {
# okay
}
else {
$err = &pstr(217,$html_ext, $const{'help file'});
next Err;
}
}
}
if ($err) {
if ($b_is_username) {
$err = &pstr(41, &he($file), $err );
}
else {
$err = &pstr(218,&he($file),$err);
}
}
elsif ($file =~ m!^(.*)$!) {
$file = $1; # untaint
}
return ($err, $is_cgi, $file);
}
sub LoadUserPrefs {
my ($username, $p_prefs) = @_;
my $warnings = '';
my $err = '';
Err: {
local $_;
# Default state:
my %testdata = (
'Username' => $username,
'is_admin' => ($username eq $private{'super user'}) ? 1 : 0,
'is_disabled' => 0,
'days_remaining' => 0,
);
my $UserFile = "$const{'preferences folder'}accounts/$username.txt";
my $text = '';
if (-e $UserFile) {
($err, $text) = &ReadFile( $UserFile );
next Err if ($err);
}
foreach (split(m!\012!s, $text)) {
next unless (m!^(.*?)=(.*?)=!s);
my ($name, $value) = (&url_decode($1), &url_decode($2));
$testdata{$name} = $value;
}
#reverse compat - added for build 0006
unless ($testdata{'_BUILD'}) {
$testdata{'_BUILD'} = 6;
$testdata{'allow_no_quota'} = 0;
if ($testdata{'is_admin'}) {
$testdata{'Author:UserFolder'} = $system_eff{'Base Folder'};
$testdata{'Author:UserURL'} = $system_eff{'Base URL'};
}
else {
$testdata{'Author:UserFolder'} = "$system_eff{'Base Folder'}%username%/";
$testdata{'Author:UserURL'} = "$system_eff{'Base URL'}%username%/";
}
}
#revcompat for the _default account
$testdata{'AccountCreated'} = time() unless ($testdata{'AccountCreated'});
#revcompat for pre-0019
if (($testdata{'Sort'}) and ((not $testdata{'_BUILD'}) or ($testdata{'_BUILD'} < 16))) {
# in pre-0016, we handled sort values differently; just force a valid default by-name value
$testdata{'Sort'} = 't'; # default;
}
if (not defined($testdata{'WaitAdminApprove'})) {
$testdata{'WaitAdminApprove'} = 0;
}
if (not defined($testdata{'p_upload'})) {
if (defined($testdata{'use_file_upload'})) {
if ($testdata{'use_file_upload'}) {
$testdata{'p_upload'} = 2;
}
else {
$testdata{'p_upload'} = 0;
}
}
else {
# set to system default
$testdata{'p_upload'} = 2;
}
}
if (not defined($testdata{'p_hidden'})) {
if ($testdata{'hidden_file_modify'}) {
$testdata{'p_hidden'} = 2;
}
elsif ($testdata{'hidden_file_view'}) {
$testdata{'p_hidden'} = 1;
}
else {
$testdata{'p_hidden'} = 0;
}
}
#/revcompat
# load default values for any key which is not defined:
my $key;
foreach $key (keys %user_schema) {
next if (defined($testdata{$key}));
$testdata{$key} = $user_schema{$key}->[4];
}
foreach ('UserFolder', 'UserURL') {
my $key_p = "Author:$_:parsed";
my $key = "Author:$_";
# convert paths to forward slash
$testdata{$key} =~ s!\\!/!g;
$testdata{$key_p} = $testdata{$key};
$testdata{$key_p} =~ s!\%username\%!$username!ig;
# add a trailing slash if there isn't already one present:
$testdata{$key_p} .= '/' unless ($testdata{$key_p} =~ m!/$!);
}
$testdata{'Quota'} = 1 unless (($testdata{'Quota'} =~ m!^(\d+)$!) and ($1 > 0));
# by default, everyone has allow_folder_change unless it is disabled at the file level
$testdata{'allow_folder_change'} = 1;
# respect file-based restrictions
my $p_sub = $private{'force_user_limits'};
($err, %testdata) = &$p_sub( 0, 1, %testdata );
next Err if ($err);
($warnings, %$p_prefs) = &load_best_values( \%user_schema, \%testdata );
if ($$p_prefs{'account_status'} == 2) {
$$p_prefs{'is_disabled'} = 1;
}
elsif ($$p_prefs{'account_status'} == 1) {
$$p_prefs{'days_alive'} = int( (time() - $$p_prefs{'AccountCreated'}) / 86400 );
$$p_prefs{'days_remaining'} = $$p_prefs{'account_expires_days'} - $$p_prefs{'days_alive'};
if ($$p_prefs{'days_remaining'} < 0) {
$$p_prefs{'is_disabled'} = 1;
}
}
last Err;
}
return ($err, $warnings);
}
sub h1 {
print '', @_, ' ';
}
=item GetUserPrefs
Loads user information for $username into the by-reference hash $p_STATE.
Error scenarios:
* unable to load user information from file; returns fatal $err
* unable to chdir to user's home folder (webmaster)
* returns $warnings;
* sets $const{'home_dir_err_msg'} to disable all filesystem write operations
* this allows the user (if webmaster) to be able to customize his Base Folder setting
* unable to chdir to user's home folder (non-webmaster)
* returns $err
* able to chdir to user home folder, but unable to access a subfolder defined by FORM{'CWD'}
* returns fatal $err - unable to access subfolder CWD
* chooses not to propagate CWD into self-referential URL/FORM
* user should see the error message, but subsequently will be returned to his home root folder instead of the invalid subfolder, and will be able to navigate normally
=cut
sub GetUserPrefs {
my ($username, $p_STATE) = @_;
my $b_cwd_err = 0;
my $warnings = '';
my $err = '';
Err: {
# initialize:
%$p_STATE = ( 'Username' => $username );
# read in information from the userfile:
($err, $warnings) = &LoadUserPrefs( $username, $p_STATE );
next Err if ($err);
# set up the current working folder:
$$p_STATE{'web_path'} = $$p_STATE{'Author:UserURL:parsed'};
$$p_STATE{'file_path'} = $$p_STATE{'Author:UserFolder:parsed'};
# test access to home folder:
unless (chdir($$p_STATE{'file_path'})) {
# uh-oh... cannot access home folder...
$err = &pstr(26, &he($$p_STATE{'file_path'}, $!, $^E) );
if ($$p_STATE{'is_admin'}) {
$const{'home_dir_err_msg'} = $err;
$err = '';
}
next Err;
}
# test2 access to home folder:
if (opendir(DIR,'.')) {
closedir(DIR);
}
else {
# uh-oh... cannot access home folder...
$err = &pstr(22, &he($$p_STATE{'file_path'}, $! ) );
if ($$p_STATE{'is_admin'}) {
$const{'home_dir_err_msg'} = $err;
$err = '';
}
next Err;
}
# test access to current-working-directory subfolder:
if (defined($FORM{'set:CWD'})) {
$FORM{'CWD'} = $FORM{'set:CWD'};
}
if ($FORM{'CWD'}) {
$b_cwd_err = 1;
$err = &priv_check( 0, 'use_chdir' );
next Err if ($err);
# boundary tests (because split() will strip boundary fields)
if ($FORM{'CWD'} =~ m!^/!) {
$err = "CWD value cannot begin with a slash";
next Err;
}
elsif ($FORM{'CWD'} =~ m!/$!) {
$err = "CWD value cannot end with a slash";
next Err;
}
my $component;
foreach $component (split(m!/!s, $FORM{'CWD'})) {
my $is_cgi;
($err, $is_cgi) = &CheckName($component,1);
next Err if ($err);
}
my $hcwd = &he($FORM{'CWD'});
unless (chdir($FORM{'CWD'})) {
$err = "unable to access subfolder '$hcwd' - $! - $^E";
next Err;
}
if (opendir(DIR,'.')) {
closedir(DIR);
}
else {
$err = &pstr(22, &he($FORM{'CWD'}, $! ) );
next Err;
}
$b_cwd_err = 0;
$$p_STATE{'file_path'} .= "$FORM{'CWD'}/";
$$p_STATE{'web_path'} .= "$hcwd/";
$const{'admin_url'} .= "CWD=" . &ue($FORM{'CWD'}) . "&";
$const{'AdminForm'} .= qq!\n !;
$const{'AdminFormFile'} .= qq!\n !;
}
$$p_STATE{'LastLogin'} = 0 unless ($$p_STATE{'LastLogin'});
my $login_age = time() - $$p_STATE{'LastLogin'};
last Err unless (3600 < $login_age);
# skip for demo
last Err if (($const{'mode'} == 0) and ($$p_STATE{'is_admin'}));
# update LastLogin time and host:
my $warn_msg = '';
($err, $warn_msg) = &user_data_save( $username, { 'LastLogin' => $$p_STATE{'LastLogin'}, 'LastLoginFrom' => $$p_STATE{'LastLoginFrom'} }, 1, 1 );
next Err if ($err);
if (($warn_msg) and ($warnings)) {
$warnings = &multi_error_frag( $warnings, $warn_msg );
}
elsif ($warn_msg) {
$warnings = $warn_msg;
}
last Err;
}
if ($b_cwd_err) {
$err .= qq!.Click here to return to the main page !;
}
return ($err, $warnings);
}
sub user_data_from_form($$) {
my ($username, $p_userdata) = @_;
my $err = '';
Err: {
$$p_userdata{'Username'} = $username;
local $_;
foreach (keys %user_schema) {
next unless ($user_schema{$_}->[5] == 2); # deal with user-defined settings
if (not defined($FORM{$_})) {
$err = "user settings '$_' is not defined within the form input";
next Err;
}
$$p_userdata{$_} = $FORM{$_};
}
# perform expensive file-base audit:
my @null = ();
$err = &loadlang( $$p_userdata{'language'}, \@null, 1 );
next Err if ($err);
last Err;
}
return $err;
};
sub user_data_from_form_admin($$) {
my ($username, $p_userdata) = @_;
my $err = '';
Err: {
local $_;
foreach (keys %user_schema) {
# skip derived fields:
next if (($_ eq 'use_templates') and ($FORM{'update_privilege_list'}));
next if (m!\:parsed$!);
next if ($_ eq 'is_admin');
my $value;
if ($user_schema{$_}->[5] == 3) {
# admin/hybrid key -- set to $FORM{val} or use system default
if (defined($FORM{$_})) {
$value = $FORM{$_};
}
else {
$value = $user_schema{$_}->[4];
}
}
elsif ($user_schema{$_}->[5] == 1) {
# admin key -- must be defined:
if (not defined($FORM{$_})) {
$err = "admin setting '$_' is not defined within the form input";
next Err;
}
$value = $FORM{$_};
}
else {
next; # system or user-defined key..
}
$$p_userdata{$_} = $value;
}
if ($FORM{'update_privilege_list'}) {
my $var;
my @template_paths = ();
foreach $var (keys %FORM) {
next unless ($var =~ m!^use_templates_(.+)$!);
next unless ($FORM{$var});
push(@template_paths,$1);
}
$$p_userdata{'use_templates'} = $FORM{'use_templates'} = join(',',@template_paths);
foreach $var (
'allow_cgi',
'allow_no_quota',
'p_hidden',
'p_upload',
'priv_scheme',
'account_status',
'account_expires_days',
'use_he_copy',
'use_he_delfile',
'use_he_delfolder',
'use_he_edit',
'use_he_mkdir',
'use_he_mkdir',
'use_he_mkfile',
'use_he_rename',
'use_he_ri',
'use_he_show',
'use_he_val',
'use_html_editor',
'use_my_account_page',
'use_template_editor',
'use_templates',
'use_htpasswd',
) {
$err = &fd_validate( $FORM{$var}, @{ $user_schema{$var} } );
next Err if ($err);
}
if ($$p_userdata{'priv_scheme'} == 4) {
# platinum
&merge( $p_userdata,
'Quota' => $FORM{'4_Quota'},
'allow_cgi' => 1,
'allow_no_quota' => 0,
'p_hidden' => 0,
'p_upload' => 2,
'use_html_editor' => 1,
'use_my_account_page' => 1,
'use_template_editor' => 1,
'use_chdir' => 1,
'use_htpasswd' => 1,
);
}
elsif ($$p_userdata{'priv_scheme'} == 3) {
# gold
&merge( $p_userdata,
'Quota' => $FORM{'3_Quota'},
'allow_cgi' => 0,
'allow_no_quota' => 0,
'p_hidden' => 0,
'p_upload' => 2,
'use_html_editor' => 1,
'use_my_account_page' => 1,
'use_template_editor' => 1,
'use_chdir' => 1,
'use_htpasswd' => 1,
);
}
elsif ($$p_userdata{'priv_scheme'} == 2) {
# silver
&merge( $p_userdata,
'Quota' => $FORM{'2_Quota'},
'allow_cgi' => 0,
'allow_no_quota' => 0,
'p_hidden' => 0,
'p_upload' => 1,
'use_html_editor' => 0,
'use_my_account_page' => 1,
'use_template_editor' => 1,
'use_chdir' => 1,
'use_htpasswd' => 1,
);
}
elsif ($$p_userdata{'priv_scheme'} == 1) {
# bronze
&merge( $p_userdata,
'Quota' => $FORM{'1_Quota'},
'allow_cgi' => 0,
'allow_no_quota' => 0,
'p_hidden' => 0,
'p_upload' => 0,
'use_html_editor' => 0,
'use_my_account_page' => 1,
'use_template_editor' => 1,
'use_chdir' => 1,
'use_htpasswd' => 1,
);
}
if ($$p_userdata{'use_html_editor'} != 2) {
# if HTML Editor is completely on or off (1 or 0),
# then reset all the sub-privileges to the master value:
$$p_userdata{'use_he_copy'} =
$$p_userdata{'use_he_delfile'} =
$$p_userdata{'use_he_delfolder'} =
$$p_userdata{'use_he_edit'} =
$$p_userdata{'use_he_mkdir'} =
$$p_userdata{'use_he_mkfile'} =
$$p_userdata{'use_he_rename'} =
$$p_userdata{'use_he_ri'} =
$$p_userdata{'use_he_show'} =
$$p_userdata{'use_he_val'} =
$$p_userdata{'use_html_editor'};
}
# don't let webmaster surrender key abilities:
if ($username eq $private{'super user'}) {
if ($$p_userdata{'account_status'} != 0) {
$err = "main account '$private{'super user'}' cannot be Disabled or Expired";
next Err;
}
$$p_userdata{'use_my_account_page'} = 1;
}
}
last Err;
}
return $err;
};
sub user_data_validate {
my ( $username, $p_userdata, $b_check_file_system ) = @_;
my $err = '';
Err: {
# perform validation on the fields being updated
$err = &validate_hash( \%user_schema, $p_userdata );
next Err if ($err);
# optional file system check:
last Err unless ($b_check_file_system);
last Err if ($username eq '_default');
last Err unless (defined($$p_userdata{'Author:UserFolder'}));
my $test_folder = $$p_userdata{'Author:UserFolder'};
$test_folder =~ s!\%username\%!$username!ig;
$test_folder =~ s!^(.+)/$!$1!; # strip trailing slash if present and if that is not the only thing in the path
unless (-e $test_folder) {
$err = &pstr(27, &he($test_folder) );
next Err;
}
unless (-d $test_folder) {
$err = &pstr(28, &he($test_folder) );
next Err;
}
last Err;
}
return $err;
}
sub user_data_save {
my ($username, $p_updates, $is_login, $b_silent) = @_;
my $warnings = '';
my $err = '';
Err: {
# make sure this is a valid username:
# important since we'll eventually write to a file as "accounts/$username.txt"
my $is_cgi;
($err, $is_cgi) = &CheckName( $username, 1, 1 );
next Err if ($err);
# perform first-pass validation on the fields being updated
$err = &validate_hash( \%user_schema, $p_updates, 0 );
next Err if ($err);
local $_;
foreach ('Author:UserFolder', 'Author:UserURL') {
next unless (defined($$p_updates{$_}));
next unless ($$p_updates{$_} =~ m!(/username\%|/\%username(/|$))!i);
$err = "wildcard syntax must be ' %username% ' - you entered $1";
next Err;
}
my %userdata = ();
($err, $warnings) = &LoadUserPrefs( $username, \%userdata );
next Err if ($err);
# Mix in the overrides:
foreach (keys %$p_updates) {
if (defined($$p_updates{$_})) {
$userdata{$_} = $$p_updates{$_};
}
}
# the system would now like to force a few values:
$userdata{'Username'} = $username;
my $build = 1;
if ($VERSION =~ m!(\d+)$!) {
$build = 1 * $1;
}
$userdata{'_BUILD'} = $build;
$userdata{'_VERSION'} = $VERSION;
if ($is_login) {
$userdata{'LastLogin'} = time();
$userdata{'LastLoginFrom'} = $private{'REMOTE_ADDR'};
}
$userdata{'AccountCreated'} = time() unless ($userdata{'AccountCreated'});
# respect file-based restrictions
my $p_sub = $private{'force_user_limits'};
($err, %userdata) = &$p_sub( 1, 2, %userdata );
next Err if ($err);
# now perform a full validation of all fields:
$err = &validate_hash( \%user_schema, \%userdata, 1 );
next Err if ($err);
my @keys = keys %userdata;
foreach (@keys) {
next if (defined($user_schema{$_}));
delete $userdata{$_};
}
# Write to disk
my $text = '';
foreach (sort keys %userdata) {
next if (m!(is_admin|:parsed)$!); # don't save derived fields
$text .= &ue($_) . '=' . &ue($userdata{$_}) . "=\015\012";
}
my $UserFile = "$const{'preferences folder'}accounts/$username.txt";
$err = &WriteFile( $UserFile, $text );
next Err if ($err);
&Mask( $UserFile, 0 );
&Report( &pstr( 4, $str[234] ) ) unless ($b_silent);
last Err;
}
return ($err, $warnings);
}
sub merge {
my ($p_hash, %params) = @_;
my ($name, $value);
while (($name, $value) = each %params) {
$$p_hash{ $name } = $value;
}
}
sub ReadFile {
my ($filename) = @_;
my ($err, $text) = ('', '');
Err: {
unless (open(FILE, "<$filename")) {
$err = &pstr(8, $filename, $! );
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $filename, $! );
next Err;
}
$text = join('', );
unless (close(FILE)) {
$err = &pstr(220, &he( $filename, $!, $^E ) );
next Err;
}
}
return ($err, $text);
}
sub WriteFile {
my ($filename, $text) = @_;
my $err = '';
Err: {
unless (open(FILE, ">$filename")) {
$err = &extended_f_err($filename);
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $filename, $! );
close(FILE);
next Err;
}
unless (print FILE $text) {
$err = &pstr(231, &he( $filename, $!, $^E ) );
close(FILE);
next Err;
}
unless (close(FILE)) {
$err = &pstr(220, &he( $filename, $!, $^E ) );
next Err;
}
}
return $err;
}
sub url_decode {
local $_ = defined($_[0]) ? $_[0] : '';
tr!+! !;
s!\%([a-fA-F0-9][a-fA-F0-9])!pack('C', hex($1))!eg;
return $_;
}
sub WebForm {
my ($p_hash, $p_upload_files, $temp_dir, $b_persist_files) = @_;
my $err = '';
Err: {
unless ('HASH' eq ref($p_hash)) {
$err = "invalid argument - p_hash is not a HASH reference";
next Err;
}
if ($p_upload_files) {
unless ('HASH' eq ref($p_upload_files)) {
$err = "invalid argument - p_upload_files is not a HASH reference";
next Err;
}
}
my $global_unique_id = time() + int( 1000000 * rand() );
my @Pairs = ();
if (&query_env('REQUEST_METHOD') eq 'POST') {
my $buffer = '';
unless (defined($ENV{'CONTENT_LENGTH'})) {
$err = "environment variable CONTENT_LENGTH not defined for POST operation";
next Err;
}
unless ($ENV{'CONTENT_LENGTH'} =~ m!^(\d+)$!) {
$err = "environment variable CONTENT_LENGTH not integer";
next Err;
}
my $len = $1;
my $ctype = &query_env('CONTENT_TYPE');
my $boundary = '';
my $temp_storage = '';
my $b_is_multipart = 0;
if ($ctype =~ m!multipart/form-data; boundary=(.*)!s) {
$boundary = quotemeta($1);
$b_is_multipart = 1;
}
my $bsize = 16384;
if (($b_is_multipart) and ($len > 20 * $bsize)) {
# large multi-part upload... special case write to temp file...
$global_unique_id = 0 unless ($global_unique_id);
$global_unique_id++;
# create a temp file:
my $file_num = $global_unique_id;
for (;;) {
last unless (-e "$temp_dir/fd_webformex_$file_num.tmp");
$file_num++;
}
$temp_storage = "$temp_dir/fd_webformex_$file_num.tmp";
unless (open(FILE, ">$temp_storage")) {
$err = "unable to write to temp file '$temp_storage' - $! - $^E";
next Err;
}
unless (binmode(FILE)) {
$err = "unable to set binmode on temp file '$temp_storage' - $! - $^E";
next Err;
}
my $remain = $len;
my $read = 0;
my $rv;
my $buffer = '';
while (1) {
last if ($remain < 1);
my $inc_read = $bsize;
if ($remain < $inc_read) {
$inc_read = $remain;
}
$rv = read(main::STDIN, $buffer, $inc_read, 0);
unless (defined($rv)) {
$err = "error while reading data over the network - $! - $^E";
next Err;
}
last if ($rv == 0);
if (length($buffer) != $rv) {
$err = "assertion error - Perl read() function returned incorrect value; $rv";
next Err;
}
$read += $rv;
$remain -= $rv;
unless (print FILE $buffer) {
$err = "error while writing to temp file '$temp_storage' - $! - $^E";
next Err;
}
}
unless (close(FILE)) {
$err = "unable to close temp file '$temp_storage' - $! - $^E";
next Err;
}
}
else {
my $bytes_read = read(main::STDIN, $buffer, $len, 0);
if (not (defined($bytes_read))) {
$err = "error while reading input - $! - $^E";
next Err;
}
elsif ($bytes_read != $len) {
$err = "unable to read $len bytes from input - only read $bytes_read - $! - $^E";
next Err;
}
}
# this is to un-taint the incoming data...
if ($buffer =~ m!^(.*)$!s) {
$buffer = $1;
}
else {
$err = "unable to untaint incoming data";
next Err;
}
unless ($b_is_multipart) {
# normal post:
@Pairs = split(m!\&!, $buffer);
}
elsif ($temp_storage) {
# okay, we have a multipart FILE UPLOAD in-temp-data-storage
unless (open(FILE, "<$temp_storage")) {
$err = "unable to open file '$temp_storage' - $! - $^E";
next Err;
}
unless (binmode(FILE)) {
$err = "unable to set binmode";
next Err;
}
my $state = 0;
# 1 waiting for c-d line
# 2 waiting for blank line
# 3 reading data
# 4 writing data to temp file
my $name = '';
my $value = '';
my $write_this = '';
my $b_temp_is_open = 0;
my $datalen = 0;
while (defined($_ = )) {
$datalen += length($_);
if (m!^--$boundary(--)?\015?\012?$!s) {
$state = 1;
$value =~ s!\015\012$!!s;
if ($b_temp_is_open) {
print TEMP $value;
close(TEMP);
my $p_data = $$p_upload_files{$name};
$$p_data{'size'} = -s $$p_data{'temp file'};
}
elsif ($name) {
if (defined($$p_hash{$name})) {
$$p_hash{$name} .= ",$value";
}
else {
$$p_hash{$name} = $value;
}
}
$name = '';
$value = '';
$b_temp_is_open = 0;
next;
}
if ($state == 1) {
if (m!^Content-Disposition: form-data; name="(.+?)"; filename="(.*?)"\015?\012$!) {
$name = $1;
my %filedata = (
'client file name' => $2,
'temp file' => '',
);
# create a temp file:
$global_unique_id = 0 unless ($global_unique_id);
$global_unique_id++;
my $file_num = $global_unique_id;
for (;;) {
last unless (-e "$temp_dir/fd_webformex_$file_num.tmp");
$file_num++;
}
$filedata{'temp file'} = "$temp_dir/fd_webformex_$file_num.tmp";
unless (open(TEMP, ">$filedata{'temp file'}")) {
$err = "unable to open temp file '$filedata{'temp file'}' for writing - $! - $^E";
next Err;
}
binmode(TEMP);
eval "END { unlink('$filedata{'temp file'}'); }\n" unless ($b_persist_files);
$$p_upload_files{$name} = \%filedata;
$b_temp_is_open = 1;
$state = 2;
}
elsif (m!^Content-Disposition: form-data; name="(.+?)"\015?\012?$!) {
$value = '';
$name = $1;
$state = 2;
}
else {
$err = "unknown Content-Disposition header received - " . &he($_);
next Err;
}
next;
}
if (($state == 2) and (m!^Content-Type: (.*?)\015?\012?$!)) {
if ($b_temp_is_open) {
my $p_data = $$p_upload_files{$name};
$$p_data{'content-type'} = $1;
}
}
if (($state == 2) and (m!^\015?\012$!)) {
if ($b_temp_is_open) {
$state = 4;
}
else {
$state = 3;
}
next;
}
if ($state == 3) {
$value .= $_;
}
if ($state == 4) {
if (length($value)) {
unless (print TEMP $value) {
$err = "error while writing to temp file - $! - $^E";
next Err;
}
}
$value = $_;
}
}
$value =~ s!\015\012$!!s;
if ($b_temp_is_open) {
print TEMP $value;
close(TEMP);
my $p_data = $$p_upload_files{$name};
$$p_data{'size'} = -s $$p_data{'temp file'};
}
elsif ($name) {
if (defined($$p_hash{$name})) {
$$p_hash{$name} .= ",$value";
}
else {
$$p_hash{$name} = $value;
}
}
close(FILE);
unless (unlink($temp_storage)) {
$err = "unable to delete temporary file '$temp_storage' - $! - $^E";
next Err;
}
last Err;
}
else {
# okay, we have a multipart FILE UPLOAD in-memory
my @fields = split(m!$boundary!s, $buffer);
my $x;
for ($x = 1; $x < $#fields; $x++) {
local $_ = $fields[$x];
my ($name, $is_file, $filename, $value) = ('', 0, '', '');
if (m!Content-Disposition: form-data; name="(.*?)"; filename="(.*?)"!is) {
($name, $filename) = ($1, $2);
$is_file = 1;
}
elsif (m!Content-Disposition: form-data; name="(.*?)"!is) {
($name) = ($1);
}
else {
$err = "upload data block did not contain a valid form-data name: $_ ";
next Err;
}
if (m!Content-Disposition: form-data; name="$name".*?\015\012\015\012(.*)\015\012--$!is) {
$value = $1;
$value =~ s!\015\012$!!so;
}
else {
$err = "unable to extract VALUE portion of form-data block: $_ ";
next Err;
}
if (($is_file) and ($p_upload_files)) {
my $contenttype = '';
if (m!Content-Type:\s*(\S+)!is) {
$contenttype = $1;
}
my %filedata = (
'client file name' => $filename,
'size' => length($value),
'content' => "'$value'",
'content-type' => $contenttype,
);
unless ($temp_dir) {
$err = "unable to save file - temp_dir parameter not defined";
next Err;
}
unless ((-e $temp_dir) and (-d $temp_dir)) {
$err = "unable to save file - temp_dir '$temp_dir' does not exist or is not a directory";
next Err;
}
$global_unique_id = 0 unless ($global_unique_id);
$global_unique_id++;
# create a temp file:
my $file_num = $global_unique_id;
for (;;) {
last unless (-e "$temp_dir/fd_webformex_$file_num.tmp");
$file_num++;
}
my $TempFile = "$temp_dir/fd_webformex_$file_num.tmp";
$err = &WriteFile( $TempFile, $value );
next Err if ($err);
$filedata{'temp file'} = $TempFile;
delete $filedata{'content'};
# note - this will fail if $temp_dir is relative and their is a chdir later in the prog - use an absolute $temp_dir
eval "END { unlink('$TempFile'); }\n" unless ($b_persist_files);
$$p_upload_files{$name} = \%filedata;
next;
}
if (defined($$p_hash{$name})) {
$$p_hash{$name} .= ",$value";
}
else {
$$p_hash{$name} = $value;
}
}
# Done with multipart form
last Err;
}
}
elsif ($ENV{'QUERY_STRING'}) {
@Pairs = split(m!\&!s, $ENV{'QUERY_STRING'});
}
else {
@Pairs = @ARGV;
}
foreach (@Pairs) {
next unless (m!^(.*?)=(.*)$!s);
my ($name, $value) = (&url_decode($1), &url_decode($2));
if (defined($$p_hash{$name})) {
$$p_hash{$name} .= ",$value";
}
else {
$$p_hash{$name} = $value;
}
}
}
#changed 2001-11-30
foreach (keys %$p_hash) {
next unless (m!^(.*)_udav$!);
next if (defined($$p_hash{$1}));
$$p_hash{$1} = $$p_hash{$_};
}
return $err;
};
sub standard_binmode {
my $err = '';
Err: {
my $OS = $^O;
if (($OS) and ($OS =~ m!(win|dos|os2)!i)) {
unless (binmode(main::STDIN)) {
$err = "unable to set binmode on STDIN - $!";#notranslate
next Err;
}
unless (binmode(main::STDOUT)) {
$err = "unable to set binmode on STDOUT - $!";#notranslate
next Err;
}
unless (binmode(main::STDERR)) {
$err = "unable to set binmode on STDERR - $!";#notranslate
next Err;
}
}
}
return $err;
}
sub GetFiles {
my ($FolderCount, @Folders, @Files, $Pattern) = (0);
($Folders[0], $Pattern) = @_;
# Format with all forward slashes; add a trailing slash to $Directory if
# it's not present:
$Folders[0] = &NixPath($Folders[0]);
$Folders[0] .= '/' unless ($Folders[0] =~ m!/$!);
while ($FolderCount < (scalar @Folders)) {
my $Directory = $Folders[$FolderCount];
$FolderCount++;
unless (opendir(DIR, $Directory)) {
&ppstr(5, &pstr(22, $Directory, $!));
next;
}
foreach (readdir(DIR)) {
next if m!^\.\.?$!; # skip current and higher directories.
my $Path = "$Directory$_";
if (-d $Path) {
push(@Folders, "$Path/");
next;
}
push(@Files, $Path) if ((not $Pattern) or (m!$Pattern!i));
}
closedir(DIR);
}
return @Files;
}
sub FormatNumber {
my ( $expression, $decimal_places, $include_leading_digit, $use_parens_for_negative, $group_digits, $euro_style ) = @_;
my $dec_ch = ($euro_style) ? ',' : '.';
my $tho_ch = ($euro_style) ? '.' : ',';
my $qm_dec_ch = quotemeta( $dec_ch );
local $_ = $expression;
unless (m!^\-?\d*\.?\d*$!) {
$_ = 0;
}
my $exp = 1;
for (1..$decimal_places) {
$exp *= 10;
}
$_ *= $exp;
$_ = int($_);
$_ = ($_ / $exp);
# Add a trailing decimal divider if we don't have one yet
$_ .= '.' unless (m!\.!);
# Pad zero'es if appropriate:
if ($decimal_places) {
if (m!^(.*)\.(.*)$!) {
$_ .= '0' x ($decimal_places - length($2));
}
}
# Re-write with localized decimal divider:
s!\.!$dec_ch!o;
# Group digits:
if ($group_digits) {
while (m!(.*)(\d)(\d\d\d)(\,|\.)(.*)!) {
$_ = "$1$2$tho_ch$3$4$5";
}
}
if ($include_leading_digit) {
s!^$qm_dec_ch!0$dec_ch!o;
}
# Have we somehow ended up with just a decimal point? Make it zero then:
if ("foo$_" eq "foo$dec_ch") {
$_ = "0";
}
# Strip trailing decimal point
s!$qm_dec_ch$!!o;
if ($use_parens_for_negative) {
s!^\-(.*)$!\($1\)!o;
}
return $_;
}
sub FormatDateTime {
my ($time, $format_type, $b_format_as_gmt) = @_;
$format_type = 0 unless ($format_type);
$format_type = 11 if ($format_type eq 'smtp');
my $date_str = '';
$b_format_as_gmt = 1 if ($format_type == 11); # force GMT for SMTP-formatted dates
$time = 0 unless ($time); # force integer
if ($format_type == 13) {
if ($b_format_as_gmt) {
$date_str = scalar gmtime( $time );
}
else {
$date_str = scalar localtime( $time );
}
}
else {
my ($sec, $min, $milhour, $day, $month_index, $year, $weekday_index) = ($b_format_as_gmt) ? gmtime( $time ) : localtime( $time );
$year += 1900;
my $ampm = ( $milhour >= 12 ) ? 'PM' : 'AM';
my $relhour = (($milhour - 1) % 12) + 1;
my $month = $month_index + 1;
# pad with leading zero:
local $_;
foreach ($milhour, $relhour, $min, $sec, $month, $day) {
$_ = "0$_" if (1 == length($_));
}
my @MonthNames = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
my @WeekNames = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
my $full_weekday = $WeekNames[$weekday_index];
my $short_weekday = substr($full_weekday, 0, 3);
my $full_monthname = $MonthNames[$month_index];
my $short_monthname = substr($full_monthname, 0, 3);
if ($format_type == 0) {
$date_str = "$month/$day/$year $relhour:$min:$sec $ampm";
}
elsif ($format_type == 1) {
$date_str = "$full_weekday, $full_monthname $day, $year";
}
elsif ($format_type == 2) {
$date_str = "$month/$day/$year";
}
elsif ($format_type == 3) {
$date_str = "$relhour:$min:$sec $ampm";
}
elsif ($format_type == 4) {
$date_str = "$milhour:$min";
}
elsif ($format_type == 10) {
$date_str = "$short_weekday $month/$day/$year $relhour:$min:$sec $ampm";
}
elsif ($format_type == 11) {
$date_str = "$short_weekday, $day $short_monthname $year $milhour:$min:$sec -0000";
}
elsif ($format_type == 12) {
$date_str = "$year-$month-$day $milhour:$min:$sec";
}
elsif ($format_type == 14) {
$date_str = "$month/$day/$year $milhour:$min";
}
}
unless ($date_str) {
if ($format_type !~ m!^\d+$!) {
$date_str = "invalid argument to FormatDateTime(); second parameter '" . &he($format_type) . "' not numeric";
}
elsif (($format_type > 14)) {
$date_str = "invalid argument to FormatDateTime(); second parameter format_type must be integer less than 15; received $format_type";
}
}
return $date_str;
}
sub NixPath {
local $_ = defined($_[0]) ? $_[0] : '';
s!\\!/!mg;
return $_;
}
sub SetDefaults {
my ($text, $p_params) = @_;
# short-circuit:
if ((ref($p_params) ne 'HASH') or (not (%$p_params))) {
return $text;
}
my @array = split(m!<(INPUT|SELECT|TEXTAREA)([^\>]+?)\>!is, $text);
my $finaltext = $array[0];
my $setval;
my $x = 1;
for ($x = 1; $x < $#array; $x += 3) {
my ($uctag, $origtag, $attribs, $trail) = (uc($array[$x]), $array[$x], $array[$x+1] || '', $array[$x+2] || '');
Tweak: {
my $tag_name = '';
if ($attribs =~ m! NAME\s*=\s*\"([^\"]+?)\"!is) {
$tag_name = $1;
}
elsif ($attribs =~ m! NAME\s*=\s*(\S+)!is) {
$tag_name = $1;
}
else {
# we cannot modify what we do not understand:
last Tweak;
}
last Tweak unless (defined($$p_params{$tag_name}));
$setval = &he($$p_params{$tag_name});
if ($uctag eq 'INPUT') {
# discover VALUE and TYPE
my $type = 'TEXT';
if ($attribs =~ m! TYPE\s*=\s*\"([^\"]+?)\"!is) {
$type = uc($1);
}
elsif ($attribs =~ m! TYPE\s*=\s*(\S+)!is) {
$type = uc($1);
}
# discover VALUE and TYPE
my $value = '';
if ($attribs =~ m! VALUE\s*=\s*\"([^\"]+?)\"!is) {
$value = $1;
}
elsif ($attribs =~ m! VALUE\s*=\s*(\S+)!is) {
$value = $1;
}
# we can only set values for known types:
if (($type eq 'RADIO') or ($type eq 'CHECKBOX')) {
#changed 2001-11-15; strip pre-existing checks
$attribs =~ s! (checked="checked"|checked)($| )!$2!ois;
if ($setval eq $value) {
$attribs = qq! checked="checked"$attribs!;
}
}
elsif (($type eq 'TEXT') or ($type eq 'PASSWORD') or ($type eq 'HIDDEN')) {
# but only hidden fields if value is null:
last Tweak if (($type eq 'HIDDEN') and ($value ne ''));
# replace any existing VALUE tag:
my $qm_value = quotemeta($value);
$attribs =~ s! value\s*=\s*\"$qm_value\"! value="$setval"!iso;
$attribs =~ s! value\s*=\s*$qm_value! value="$setval"!iso;
# add the tag if it's not present (i.e. if no VALUE was present in original tag)
my $qm_setval = quotemeta($setval);
unless ($attribs =~ m! VALUE="$qm_setval"!is) {
$attribs = " value=\"$setval\"$attribs";
}
}
}
elsif ($uctag eq 'SELECT') {
# does not support value syntax, only value
my $lc_set_value = lc($setval);
my @frags = ();
foreach (split(m! !$setval$1>!osi;
}
last Tweak;
}
$finaltext .= "<$origtag$attribs>$trail";
}
return $finaltext;
}
sub where_tf {
my $err = '';
my @paths = ();
Err: {
local $_;
my $abs_file_path = '';
foreach ($file_path_to_script, $0, &query_env('SCRIPT_FILENAME'), &query_env('PATH_TRANSLATED')) {
next if (m!^./!); # local paths won't be sufficient
next if (m!safeperl\d*$!i);
s!\\!/!g;
next unless (m!/|:!);
$abs_file_path = $_;
last;
}
unless ($abs_file_path) {
$err = "unable to determine absolute file path - \$0 or SCRIPT_FILENAME or PATH_TRANSLATED not defined";#notranslate
next Err;
}
unless (-e $abs_file_path) {
$err = "file discovery returned '$abs_file_path' as absolute file path, but -e existence check failed";#notranslate
next Err;
}
if ($abs_file_path =~ m!^./!) {
$err = "file discovery returned local path '$abs_file_path' - an absolute path is needed";
next Err;
}
if (-d $abs_file_path) {
my $test = $abs_file_path . &query_env('SCRIPT_NAME');
$test =~ s!\\!/!g;
if ((-e $test) and (not -d $test)) {
$abs_file_path = $test;
}
}
# untaint - we'll already done all of the file existence checks:
if ($abs_file_path =~ m!^(.+)$!) {
$abs_file_path = $1;
}
my $abs_url = $web_path_to_script;
my $script_name = &query_env('SCRIPT_NAME','/');
unless ($abs_url) {
foreach ('HTTP_HOST', 'SERVER_NAME') {
my $var = &query_env($_);
next unless ($var);
$abs_url = "http://$var$script_name";
last;
}
}
unless ($abs_url) {
my $http_referer = &query_env('HTTP_REFERER');
if ($http_referer) {
$abs_url = $http_referer;
$abs_url =~ s!(\?|\$\|\#)(.*)!!o;
}
}
unless ($abs_url) {
$err = "unable to determine absolute file path - HTTP_HOST or SERVER_NAME or HTTP_REFERER not defined";#notranslate
next Err;
}
my $qm_rel_url = '';
if ($abs_url =~ m!^http://([^/]+)/(.*?)$!) {
$qm_rel_url = quotemeta($2);
}
$paths[0] = $paths[1] = $paths[2] = &NixPath($abs_file_path);
$paths[1] =~ s!/([^/]+)$!!o;
$paths[2] =~ s!/$qm_rel_url!!o;
$paths[3] = $abs_url;
$paths[4] = $abs_url;
$paths[5] = $abs_url;
$paths[4] =~ s!/([^/]+)$!!o;
$paths[5] =~ s!/$qm_rel_url!!o;
last Err;
}
return ($err, @paths);
}
sub Trim {
local $_ = defined($_[0]) ? $_[0] : '';
s!^[\r\n\s]+!!o;
s![\r\n\s]+$!!o;
return $_;
}
sub web_auth_new {
my %options = @_;
my $self = {};
bless($self);
$self->{'data_folder'} = '.';
$self->{'make_starter_accounts'} = 0;
$self->{'seed'} = 'sX';
my ($name, $value) = ();
while (($name, $value) = each %options) {
$self->{$name} = $value;
}
$self->{'data_folder'} =~ s!\\!/!g;
$self->{'data_folder'} .= '/';
$self->{'data_folder'} =~ s!/+$!/!o; #changed 0017 - collapse trailing slashes
$self->{'AuthFile'} = $self->{'data_folder'} . '.webauth_passwd';
$self->{'TokenFile'} = $self->{'data_folder'} . '.webauth_tokens';
return $self;
}
sub InventPassword {
my ($self) = @_;
my $NewPassword = '';
my @consonants = ('b', 'c', 'd', 'f', 'g', 'h', 'j', 'k','l', 'm', 'n', 'p','q', 'r', 's', 't', 'v','w','y','x','z');
my $s_c = scalar @consonants;
$NewPassword .= $consonants[int($s_c * rand())];
$NewPassword .= ('a','e','i','o','u')[int(5 * rand())];
$NewPassword .= $consonants[int($s_c * rand())];
$NewPassword .= $consonants[int($s_c * rand())];
$NewPassword .= ('a','e','i','o','u')[int(5 * rand())];
$NewPassword .= $consonants[int($s_c * rand())];
$NewPassword .= 10 + int(89 * rand());
return $NewPassword;
}
sub DeleteUser {
my ($self, $username, $b_flush_tokens) = @_;
my $err = '';
Err: {
my $file = $self->{'AuthFile'};
my $text = '';
if (-e $file) {
unless (open(FILE, "<$file")) {
$err = &pstr(8, $file, $! );
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
while () {
next unless (m!^(.*?)\:(.*?)\r?$!);
my ($user, $crypt) = ($1, $2);
next if ($user eq $username);
$text .= "$user:$crypt\n";
}
close(FILE);
}
unless (open(FILE, ">$file")) {
$err = &extended_f_err($file);
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
print FILE $text;
close(FILE);
if ($b_flush_tokens) {
$err = $self->flush_tokens( $username );
next Err if ($err);
}
}
return $err;
}
sub SetPassword {
my ($self, $username, $password) = @_;
my $err = '';
Err: {
if (($const{'mode'} == 0) and ($username eq $private{'super user'})) {
$err = &pstr(200,$username);
next Err;
}
my $file = $self->{'AuthFile'};
my $crypt = crypt($password, $self->{'seed'});
$err = $self->DeleteUser( $username, 0 );
next Err if ($err);
unless (open(FILE, ">>$file")) {
$err = &pstr(10, $file, $! );
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
print FILE "$username:$crypt\n";
close(FILE);
}
return $err;
}
sub ValidatePassword {
my ($self, $username, $password) = @_;
my $is_valid = 0;
my $err = '';
Err: {
local $_;
my $file = $self->{'AuthFile'};
unless (open(FILE, "<$file")) {
$err = &pstr(8, $file, $! );
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
while (defined($_ = )) {
next unless (m!^(.*?)\:(.*?)\r?$!);
my ($user, $crypt) = ($1, $2);
if ($user eq $username) {
if ($crypt eq crypt($password, $self->{'seed'})) {
$is_valid = 1;
}
else {
sleep(3);
}
last;
}
}
close(FILE);
}
return ($err, $is_valid);
}
sub logout {
my ($self) = @_;
return ($self->Challenge( 0, 1 ))[4];
}
sub Challenge {
my ($self, $p_FORM, $b_logout) = @_;
my $html_out = '';
my $trace = '';
my ($private_token, $public_token, $form_username, $form_password) = ('', '', '', '');
my $test_cookie = '0';
my $is_cookies_aware = 0;
my $http_cookie = &query_env('HTTP_COOKIE');
if ($http_cookie =~ m!web_auth_cp=([^\;]+)!) {
$is_cookies_aware = 1;
my $auth_cookie = $1;
if ($auth_cookie ne $test_cookie) {
$private_token = $auth_cookie;
}
}
if (($p_FORM) and ('HASH' eq ref($p_FORM))) {
if ($$p_FORM{'web_auth_cp'}) {
$private_token = $$p_FORM{'web_auth_cp'};
}
if ($$p_FORM{'web_auth_user'}) {
$form_username = $$p_FORM{'web_auth_user'};
}
if ($$p_FORM{'web_auth_pass'}) {
$form_password = $$p_FORM{'web_auth_pass'};
}
}
my ($is_auth, $auth_username) = (0, '');
my $script_name = &query_env('SCRIPT_NAME',$0);
my $session_lifetime = 3600; # 1 hour
my $grace_period = 600; # 10 min
my ($status_msg, %auth_tokens) = ('');
my $clear_cookie = 0;
my $present_auth_form = 1;
my $err = '';
Err: {
if ($self->{'make_starter_accounts'}) {
unless (-e $self->{'AuthFile'}) {
$err = $self->SetPassword( $private{'super user'}, '658uwantit' );
if ($err) {
$present_auth_form = 0;
next Err;
}
}
}
next Err if ($b_logout);
if (($form_username) and ($form_password)) {
my $is_valid = 0;
($err, $is_valid) = $self->ValidatePassword( $form_username, $form_password );
next Err if ($err);
unless ($is_valid) {
$err = $str[33];
next Err;
}
# the user provided a valid password; give that man a token!
$err = $self->read_tokens( \%auth_tokens );
next Err if ($err);
$private_token = '';
foreach (1..16) {
$private_token .= chr(ord('a') + int(rand(26)));
}
$public_token = crypt($private_token, $self->{'seed'});
my $expires = time() + $session_lifetime;
$auth_tokens{$public_token} = "$expires,$form_username";
$err = $self->write_tokens( \%auth_tokens );
next Err if ($err);
$auth_username = $form_username;
$is_auth = 1;
last Err;
}
if ($private_token) {
$err = $self->read_tokens( \%auth_tokens );
next Err if ($err);
$public_token = crypt($private_token, $self->{'seed'});
unless ($auth_tokens{$public_token}) {
$clear_cookie = 1 if ($is_cookies_aware);
sleep(3);
$err = $str[31];
next Err;
}
$auth_tokens{$public_token} =~ m!^(\d+),(.*)$!;
my ($expires, $username) = ($1, $2);
if ($expires < time()) {
my $ago = time() - $expires;
$clear_cookie = 1 if ($is_cookies_aware);
$err = $str[32];
next Err;
}
elsif (($expires - $grace_period) < time) {
# this token is about to expire; set a fresh one:
$private_token = '';
foreach (1..8) {
$private_token .= chr(ord('a') + int(rand(26)));
}
$public_token = crypt($private_token, $self->{'seed'});
$expires = time() + $session_lifetime;
$auth_tokens{$public_token} = "$expires,$username";
$err = $self->write_tokens( \%auth_tokens );
next Err if ($err);
}
else {
# is current token
}
$is_auth = 1;
$auth_username = $username;
last Err;
}
}
continue {
my $status_msg = '';
if ($b_logout) {
$status_msg = &pstr(4,$str[30]);
}
elsif ($err) {
$status_msg = &pstr(6,$err);
}
# AUTH_FAIL
print "Set-Cookie: web_auth_cp=; path=$script_name\015\012" if ($clear_cookie);
print "Set-Cookie: web_auth_cp=$test_cookie; path=$script_name\015\012";
my ($v1, $v2) = ('', '');
if ($const{'mode'} == 0) {
$v1 = $private{'super user'};
$v2 = "658uwantit";
}
$html_out = <<"EOM";
EOM
}
else {
$html_out .= "$str[199]
\n";
}
$html_out .= <<"EOM";
$status_msg
EOM
}
if ($is_auth) {
print "Set-Cookie: web_auth_cp=$private_token; path=$script_name\015\012";
}
return ($is_auth, $private_token, $auth_username, $is_cookies_aware, $html_out);
}
sub flush_tokens {
my ($self, $pattern) = @_;
my $err = '';
Err: {
my %auth_tokens = ();
unless ($pattern) {
$err = $self->write_tokens( \%auth_tokens );
last Err;
}
$err = $self->read_tokens( \%auth_tokens );
next Err if ($err);
my @public_tokens = keys %auth_tokens;
foreach (@public_tokens) {
next unless ($auth_tokens{$_} =~ m!^(\d+),(.*)$!);
my ($expires, $username) = ($1, $2);
if ($username =~ m!$pattern!i) {
delete $auth_tokens{$_};
}
}
$err = $self->write_tokens( \%auth_tokens );
next Err if ($err);
}
return $err;
}
sub read_tokens {
my ($self, $p_Tokens) = @_;
my $err = '';
Err: {
my $file = $self->{'TokenFile'};
last Err unless (-e $file);
unless (open(FILE, "<$file")) {
$err = &pstr(8, $file, $! );
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
my $buffer = '';
my $time = time();
while (read(FILE, $buffer, 128)) {
my ($token, $expires, $username) = unpack('A60LA64', $buffer);
next if ($expires < $time);
$$p_Tokens{$token} = "$expires,$username";
}
close(FILE);
}
return $err;
}
sub write_tokens {
my ($self, $p_Tokens) = @_;
my $err = '';
Err: {
my $file = $self->{'TokenFile'};
unless (open(FILE, ">$file")) {
$err = &extended_f_err($file);
next Err;
}
unless (binmode(FILE)) {
$err = &pstr(12, $file, $! );
next Err;
}
my ($name, $value) = ();
while (($name, $value) = each %$p_Tokens) {
next unless ($value =~ m!^(\d+)\,(.*)$!);
my ($expires, $username) = ($1, $2);
print FILE pack('A60LA64', $name, $expires, $username);
}
close(FILE);
}
return $err;
}
sub ParseTemplate {
my ( $file, $start_folder, $p_replace, $p_cache, $p_visited, $b_no_parse_ssi) = @_;
$start_folder = '.' unless ($start_folder);
my $return_text = '';
my $err = '';
Err: {
# Initialize:
unless ($p_replace) {
$p_replace = {};
}
unless ($p_visited) {
$p_visited = {};
}
# Query the cache for the text of the document, or search the filesystem for the file:
my $text = '';
my $basename = '';
if (($p_cache) and ('HASH' eq ref($p_cache)) and (defined($$p_cache{$file}))) {
$text = $$p_cache{$file};
}
else {
my $fullfile = '';
my $max_parents = 12;
for (0..$max_parents) {
$fullfile = $start_folder . '/' . ('../' x $_) . $file;
$fullfile = &fs_path($fullfile);
$fullfile =~ s!/+!/!g;
last if (-e $fullfile);
}
unless (-e $fullfile) {
$err = "unable to find file '$file'";
next Err;
}
if ($fullfile =~ m!([^\\|/]+)$!) {
$basename = $1;
$$p_visited{$basename} = 1;
}
($err, $text) = &ReadFile( $fullfile );
next Err if ($err);
if (($p_cache) and ('HASH' eq ref($p_cache))) {
$$p_cache{$file} = $text;
}
}
# Handling replacement value substitutions:
#conditionals
foreach (reverse sort keys %$p_replace) {
next unless (defined($_));
$$p_replace{$_} = '' if (not defined($$p_replace{$_}));
if ($$p_replace{$_}) {
# true
$text =~ s!<%\s*if\s+$_\s*%>(.*?)<%\s*end\s*if\s*%>!$1!eisg;
$text =~ s!<%\s*(if\s+not|unless)\s+$_\s*%>.*?<%\s*end\s*if\s*%>!!isg;
}
else {
# false
$text =~ s!<%\s*if\s+$_\s*%>.*?<%\s*end\s*if\s*%>!!isg;
$text =~ s!<%\s*(if\s+not|unless)\s+$_\s*%>(.*?)<%\s*end\s*if\s*%>!$2!isg;
}
}
foreach (reverse sort keys %$p_replace) {
$text =~ s!\%$_\%!$$p_replace{$_}!isg;
}
if ($b_no_parse_ssi) {
$return_text = $text;
}
else {
# Now that replacement values are done, parse SSI tags and include files:
my $pattern = '';
while ($text =~ m!^(.*?)$pattern(.*)$!is) {
my ($start, $c1, $incfile, $end) = ($1, lc($2), $3, $4);
$return_text .= $start;
if ($c1 =~ m!echo\s+var!i) {
my $var = uc($incfile);
if ($var eq 'DATE_GMT') {
$return_text .= scalar gmtime();
}
elsif ($var eq 'DATE_LOCAL') {
$return_text .= scalar localtime();
}
elsif ($var eq 'DOCUMENT_NAME') {
$return_text .= $1 if ($0 =~ m!([^\\|/]+)$!);
}
elsif ($var eq 'DOCUMENT_URI') {
$return_text .= $ENV{'SCRIPT_NAME'};
}
elsif ($var eq 'LAST_MODIFIED') {
$return_text .= scalar localtime( (stat($0))[9] );
}
elsif (defined($ENV{$var})) {
$return_text .= $ENV{$var};
}
else {
$return_text .= ""; # passthru
}
}
else {
my $basefile = $incfile;
if ($incfile =~ m!.*(\\|/)(.*?)$!) {
$basefile = $2;
}
# allow only approved file extensions:
my $ok_list = 'txt|htm|html|shtml|stm|inc|css';
if ($basefile !~ m!\.($ok_list)$!i) {
$return_text .= "";
}
elsif ($$p_visited{$basefile}) {
$return_text .= "";
}
else {
$return_text .= &ParseTemplate($incfile, $start_folder, $p_replace, $p_cache, $p_visited, $b_no_parse_ssi );
}
}
$text = $end;
}
$return_text .= $text;
delete $$p_visited{$basename} if ($basename);
}
last Err;
}
continue {
$return_text .= &pstr(6, $err );
}
return $return_text;
}
sub fs_path {
local $_ = defined($_[0]) ? $_[0] : '';
# trim whitespace:
$_ = &Trim($_);
s!\\!/!g;
# strip pound signs and all that follows (links internal to a page)
s!\#.*$!!;
# map trailing "/." to "/"
s!/+\.$!/!g;
# collapase // to /
if (m!^//!) {
# is unc path:
s!/+!/!g;
$_ = "/$_";
}
else {
s!/+!/!g;
}
# map "something/.." to ""
unless ((m!^/!) or (m!^\w+\:!)) {
$_ = "./$_";
}
my $new = '';
while (m!^(.*?)([^/]+)/\.\./(.*?)$!) {
$_ = $3;
if (($2 eq '..') or ($2 eq '.')) {
$new .= "$1/$2/../";
}
else {
$new .= "$1";
}
}
$new .= $_;
$_ = $new;
# map "../something" to ""
$new = '';
while (m!^(.*?)/\.\./([^/]+)/(.*?)$!) {
$_ = $3;
if (($2 eq '..') or ($2 eq '.')) {
$new .= "$1/../$2/";
}
else {
$new .= "$1";
}
}
$new .= $_;
$_ = $new;
# map trailing /.. to /
s!^/+\.\.$!/!;
s!([^/]+)/+\.\.$!!o;
# map "/./" to "/"
s!/+\./+!/!g;
return $_;
}
sub pstr {
local $_ = $str[$_[0]];
my $x = 0;
foreach $x (1..((scalar @_) - 1)) {
my $c = (s!\$s$x!$_[$x]!g);
}
return $_;
}
sub ppstr {
local $_ = $str[$_[0]];
my $x = 0;
foreach $x (1..((scalar @_) - 1)) {
my $c = (s!\$s$x!$_[$x]!g);
}
print;
}
sub pppstr {
local $_ = $str[$_[0]];
my $x = 0;
foreach $x (1..((scalar @_) - 1)) {
my $c = (s!\$s$x!$_[$x]!g);
}
if ($const{'is_cmd'}) {
print "\n$_\n";
}
else {
print "" . $_ . "
\n";
}
}
sub loadlang {
my ($lang, $p_str, $b_partial) = @_;
my $err = '';
Err: {
if ((not defined($lang)) or ($lang eq '')) {
$err = "invalid argument; subroutine loadlang called will null string";
my ($package, $file, $line) = &he(caller());
$err .= "";
next Err;
}
my $full = "$const{'preferences folder'}templates/$lang";
unless (-e $full) {
my $f = &he($full);
$err = "folder '$f' does not exist";
next Err;
}
unless (-d $full) {
my $f = &he($full);
$err = "folder '$f' does not exist as a folder";
next Err;
}
unless (-e "$full/strings.txt") {
my $f = &he($full);
$err = "language folder '$f' does not contain a strings.txt file";
next Err;
}
# Pull in the language settings:
my $file = "$const{'preferences folder'}templates/$lang/strings.txt";
my @new = (''); # Initialize with a null element
unless (open(FILE, "<$file")) {
$err = "unable to read from file '$file' - $!";#notranslate
next Err;
}
binmode(FILE);
local $_;
while (defined($_ = )) {
s!\015|\012!!sg;
push(@new,$_);
if ($b_partial) {
last if ($#new > 10);
}
}
close(FILE);
if ($new[1] =~ m!^VERSION (\d+\.\d+\.\d+\.\d+)$!) {
my $strings_version = $1;
if ($strings_version ne $VERSION) {
$err = "strings '$file' is version $strings_version, but this script is version $VERSION. Versions much match";#notranslate
next Err;
}
}
else {
my $f = &he($full);
$err = "strings.txt file in language folder '$f' did not start with required VERSION $VERSION block; instead contained the following:
" . &text_to_html( join("\n", @new[0..5] ), 1 ) . "
.end";
next Err;
}
@$p_str = @new;
last Err;
}
return $err;
}
sub render_h {
my ($self, $index, @params) = @_;
return $self->render($index, &he(@params));
}
sub render {
my ($self, $index, @params) = @_;
local $_ = '';
Err: {
$_ = $index;
my $x = 0;
while (defined($params[$x])) {
my $value = $params[$x];
$x++;
my $c = (s!\$s$x!$value!sg);
# optional error handling if $c == 0 (no replacements made)
}
last Err;
}
return $_;
}
sub load_best_values {
my ($p_schema, $p_hash) = @_;
my %finish = ();
my @errors = ();
my $err = '';
Err: {
my ($key, $err_key);
foreach $key (keys %$p_schema) {
my $p_valid = $$p_schema{$key};
$err_key = &fd_validate( $$p_hash{$key}, @$p_valid );
if ($err_key) {
push( @errors, $err_key );
$finish{$key} = $$p_valid[4];
}
else {
$finish{$key} = $$p_hash{$key};
}
}
last Err;
}
return ( &multi_error_frag(@errors), %finish);
}
sub multi_error_frag {
my ($first, $last) = ( $str[6], '');
if ($first =~ m!^(.*)\$s1(.*)$!) {
($first, $last) = ($1, $2);
}
return join( "$last$first", @_ );
};
sub validate_hash {
my ($p_schema, $p_hash, $b_full) = @_;
my $err = '';
Err: {
my $key;
foreach $key (keys %$p_schema) {
next if ((not $b_full) and (not defined($$p_hash{$key})));
$err = &fd_validate( $$p_hash{$key}, @{ $p_schema->{$key} } );
next Err if ($err);
}
last Err;
}
return $err;
}
sub fd_validate($$$$$);
sub fd_validate($$$$$) {
my ($value, $type, $min, $max, $name) = @_;
my $err = '';
Err: {
$type = 'any' unless (defined($type));
if ($type =~ m!^\d+$!) {
$type = ('int', 'real', 'string', 'email', 'hostname', 'regex', 'bool')[$type];
}
# type == undef()/any means that anything goes:
last Err if ($type eq 'any');
my %types = (
'int' => 1,
'real' => 2,
'string' => 3,
'email' => 4,
'hostname' => 5,
'regex' => 6,
'bool' => 7,
'file' => 8,
'folder' => 9,
'http_url' => 10,
'string_p' => 11,
);
unless ($types{$type}) {
$err = $fd_lang->render_h( 'unknown type $s1', $type );
next Err;
}
# undefined values are acceptable if and only if the $min param is also undef()
if (not defined($value)) {
if (not defined($min)) {
last Err;
}
else {
$err = $fd_lang->render( 'received undefined value' );
next Err;
}
}
# from here on out, we are guaranteed that $value is defined
if (($type eq 'int') or ($type eq 'real')) {
# integer or real-number
$value =~ s!^\+!!;
}
# store value length for later checks:
my $vlen = length($value);
# under what conditions can the value *not* be blank?
# if int, real, bool
# or
# if $min is defined and $min > 0
if ($vlen == 0) {
if (($type =~ m!^(int|real|bool)$!) or ($min)) {
$err = $fd_lang->render( 'value cannot be blank' );
next Err;
}
else {
last Err;
}
}
# what are the system maximum string limits?
my %sys_max = (
'email' => 255,
'hostname' => 255,
'regex' => 65535,
'file' => 255,
'folder' => 255,
);
if (($sys_max{$type}) and ($sys_max{$type} < $vlen)) {
$err = $fd_lang->render( 'value is $s1 characters long but the maximum supported by this software is $s2 characters', $vlen, $sys_max{$type} );
next Err;
}
if (($type eq 'int') or ($type eq 'real')) {
# integer or real-number
if ($type eq 'int') {
unless ($value =~ m!^(\-?\d+)$!) {
$err = $fd_lang->render( 'must be of the format "$s1"', 'ddd' );
next Err;
}
}
else {
unless ($value =~ m!^-?\d*\.?\d*$!) {
$err = $fd_lang->render( 'must be of the format "$s1"', 'dd.dd' );
next Err;
}
}
if (defined($min)) {
if ($value < $min) {
$err = $fd_lang->render( 'minimum acceptable value is $s1', $min );
next Err;
}
}
if (defined($max)) {
if ($value > $max) {
$err = $fd_lang->render( 'maximum acceptable value is $s1', $max );
next Err;
}
}
last Err;
}
elsif ($type eq 'string') {
if ((defined($min)) and ($min > $vlen)) {
$err = $fd_lang->render( 'string is $s1 characters, but minimum acceptable length is $s2 characters', $vlen, $min );
next Err;
}
if ((defined($max)) and ($max < $vlen)) {
$err = $fd_lang->render( 'string is $s1 characters, but maximum acceptable length is $s2 characters', $vlen, $max );
next Err;
}
}
elsif ($type eq 'string_p') {
# string-plain; cannot contain HTML
if ((defined($min)) and ($min > $vlen)) {
$err = $fd_lang->render( 'string is $s1 characters, but minimum acceptable length is $s2 characters', $vlen, $min );
next Err;
}
if ((defined($max)) and ($max < $vlen)) {
$err = $fd_lang->render( 'string is $s1 characters, but maximum acceptable length is $s2 characters', $vlen, $max );
next Err;
}
if ($value =~ m!\&|\>|\<|\"!s) {
$err = $fd_lang->render( 'string cannot contain any of the following four characters: & < > "' );
next Err;
}
}
elsif ($type eq 'email') {
unless ($value =~ m!^(.+?)\@(.+?)$!) {
$err = $fd_lang->render( 'must be of the format "$s1"', 'user@host' );
next Err;
}
my $domain = $2;
if ($domain =~ m![^a-zA-Z0-9\.\-]!) {
$err = "host portion contains characters outside the allowed character set of A-Z, 0-9, '.' and '-'";
next Err;
}
if ($value =~ m!\s!) {
$err = 'value cannot contain whitespace';
next Err;
}
if (($value =~ m!\.([^\.]+)$!) and (2 < length($1))) {
my $tld = quotemeta(lc($1));
unless (' biz com edu info int museum net org gov mil ' =~ m! $tld !) {
$err = $fd_lang->render_h( 'unrecognized top-level domain "$s1"', $tld );
next Err;
}
}
}
elsif ($type eq 'hostname') {
if ($value =~ m![^a-zA-Z0-9\.\-]!) {
$err = "value contains characters outside the allowed character set of A-Z, 0-9, '.' and '-'";
next Err;
}
if ($max) {
my $addr = (gethostbyname($value))[4];
unless (defined($addr)) {
$err = "value could not be resolved to an IP address; check for proper spelling";
next Err;
}
}
}
elsif ($type eq 'regex') {
if ($value =~ m!\?\{!) {
$err = 'regular expression contains illegal ?{} code-executing regular expression';
next Err;
}
eval '"foo" =~ m!$value!;';
if ($@) {
$err = 'regular expression cannot be evaluated - ' . &he($@);
eval '1;'; # used to set $@ back to ''
next Err;
}
}
elsif ($type eq 'bool') {
unless (($value eq '0') or ($value eq '1')) {
$err = $fd_lang->render( 'value must be either "0" or "1"' );
next Err;
}
}
elsif ($type eq 'file') {
if ((defined($min)) and ($max)) {
unless (-e $value) {
$err = $fd_lang->render( 'file does not exist' );
next Err;
}
if (-d $value) {
$err = $fd_lang->render( 'path points to a folder; must point to a normal file' );
next Err;
}
}
}
elsif ($type eq 'folder') {
if ((defined($min)) and ($max)) {
unless (-e $value) {
$err = $fd_lang->render( 'folder does not exist' );
next Err;
}
unless (-d $value) {
$err = $fd_lang->render( 'path points to a file; must point to a folder' );
next Err;
}
}
}
elsif ($type eq 'http_url') {
if (defined($min)) {
unless ($value =~ m!^http://.+$!) {
$err = $fd_lang->render( 'must be of the format "$s1"', 'http://host.tld/path/' );
next Err;
}
}
}
else {
$err = $fd_lang->render_h( 'validation data type "$s1" not defined', $type );
last Err;
}
last Err;
}
continue {
my %type_names = (
'int' => 'integer value',
'real' => 'real number value',
'string' => 'string',
'email' => 'email address',
'hostname' => 'hostname value',
'file' => 'file',
'folder' => 'folder',
'regex' => 'Perl regular expression',
);
my $type_name = $type_names{$type} || 'value';
$err = $fd_lang->render( '$s1 failed validation; $s2', $type_name, $err );
my $h_value = &he($value);
if ($h_value) {
$err =~ s!$type_name !$type_name '$h_value' !;
}
if ($name) {
$err =~ s!$type_name !$type_name '$name' !;
}
#$err .= $fd_lang->render( '.
Caller: package $s1; file $s2 line $s3', caller() );
}
return $err;
}
sub ue(@);
sub ue(@) {
my @out = @_;
local $_;
foreach (@out) {
$_ = '' if (not defined($_));
s!([^a-zA-Z0-9_.-])!uc(sprintf("%%%02x", ord($1)))!eg;
}
if ((wantarray) or ($#out > 0)) {
return @out;
}
else {
return $out[0];
}
}
=item text_to_html
=cut
sub text_to_html($$);
sub text_to_html($$) {
my ($text, $b_no_url) = @_;
$text = &he($text);
&force_CRLF( \$text );
$text =~ s!http://(\S+)!http://$1 !sg unless ($b_no_url);
$text =~ s!\015\012! \015\012!sg;
return $text;
}
sub get_age_str {
my ($age) = @_;
my $age_str = '';
$age += 59; # round up
if ($age > (2 * 86400)) {
$age_str = &pstr( 390, int($age / 86400) );
}
elsif ($age > (100 * 60)) {
$age_str = &pstr( 389, int($age / 3600) );
}
else {
$age_str = &pstr( 388, int($age / 60) );
}
return $age_str;
}
=item validate_system_settings
Usage:
$err = &validate_system_setting( \%hash );
next Err if ($err);
NOTE: this subroutine performs read-write operations on the input %hash hash.
This subroutine will return $err if there is a problem with the values entered in %hash, but it will also replace the offending value with the system default. In this way, it can be used to audit admin-initiated changes in the UI, and also to audit values as they are read off of the disk at system start-up.
=cut
sub validate_system_settings($);
sub validate_system_settings($) {
my ($p_hash) = @_;
my @errors = ();
Err: {
my $te = '';
$te = &fd_validate( $$p_hash{'Mail Server'}, 'hostname', 0, 0, 'Mail Server' );
if ($te) {
push( @errors, $te );
}
my $b_sec_error = 0;
$te = &fd_validate( $$p_hash{'sec_mode'}, 'int', 0, 4, 'Security Mode' );
if ($te) {
push( @errors, $te );
$b_sec_error = 1;
}
my $var;
foreach $var ('Permission - CGI Scripts', 'Permission - Normal Files', 'Permission - Folder') {
$te = &fd_validate( $$p_hash{$var}, 'string_p', 4, 4, $var );
if ($te) {
push( @errors, $te );
$b_sec_error = 1;
next;
}
$te = &fd_validate( $$p_hash{$var}, 'int', 0, 7777, $var );
if ($te) {
push( @errors, $te );
$b_sec_error = 1;
next;
}
if ($$p_hash{$var} =~ m!(8|9)!) {
push( @errors, &pstr( 288, $var, &he($$p_hash{$var}), $1 ) );
$b_sec_error = 1;
next;
}
}
if ($b_sec_error) {
# re-parse all file-security permissions using auto-detect
$$p_hash{'sec_mode'} = 0;
}
if ($$p_hash{'RegKey'}) {
my $virtual = $const{'code_validate'};
unless (&$virtual( $$p_hash{'RegKey'} )) {
#should not happen
push( @errors, qq!$str[227] [$str[55] ]! );
$$p_hash{'RegKey'} = '';
}
}
$te = &fd_validate( $$p_hash{'mode'}, 'int', 0, 3, 'License Mode' );
if ($te) {
push( @errors, $te );
$$p_hash{'mode'} = 1; # default Trial Shareware
}
foreach $var ('Media Types', 'CGI Types', 'Known Types') {
$te = &fd_validate( $$p_hash{$var}, 'string_p', 0, 1024, $var );
if ($te) {
push( @errors, $te );
$$p_hash{$var} = '';
}
}
$te = &fd_validate( $$p_hash{'Min Password Length'}, 'int', 0, 8, 'Minimum Password Length' );
if ($te) {
push( @errors, $te );
$$p_hash{'Min Password Length'} = 0;
}
# strip leading/trailing whitespace, and force a trailing slash:
foreach $var ('Base Folder', 'Base URL', 'Images URL') {
$$p_hash{$var} = &Trim($$p_hash{$var});
next if ($$p_hash{$var} =~ m!/$!);
$$p_hash{$var} .= '/';
}
last Err;
}
my ($first, $last) = ( $str[6], '' );
if ($first =~ m!^(.*)\$s1(.*)$!) {
($first, $last) = ($1, $2);
}
my $err = join( "$last$first", @errors );
return $err;
};
sub system_settings_load($$$);
sub system_settings_load($$$) {
my ($file, $p_effective, $p_raw) = @_;
my $sys_load_warnings = '';
my $err = '';
Err: {
my $text;
($err, $text) = &ReadFile( $file );
next Err if ($err);
my %merge = %$p_raw;
foreach (split(m!\r?\n!s, $text)) {
next unless (m!^(.+)\==(.*)$!);
my ($name, $value) = ($1, $2);
next unless (defined($merge{$name}));
$value =~ s!\\CRLF!\015\012!sg;
$merge{$name} = $value;
}
%$p_raw = %merge;
# if there are validation failures, we will overwrite them with default values, and then add the warning info to the queue for later display in StartHTML
$sys_load_warnings = &validate_system_settings( \%merge );
%$p_effective = %merge;
$$p_effective{'Images URL'} = &he($$p_effective{'Images URL'});
}
return ($err, $sys_load_warnings);
};
=item file_security_init
Usage:
$err = &file_security_init();
next Err if ($err);
Initializes the global @file_sec array based on the system 'sec_mode' setting. If sec_mode == 0 for auto-detect, then this subroutine handles the auto-detection process.
Currently there is no error handling. The $err return value is for future use.
This routine trusts that the sec_mode and the various Permissions settings from the %system_eff global hash will be valid-format. This subroutine should only be called after &validate_system_settings( \%system_eff ).
=cut
sub file_security_init($);
sub file_security_init($) {
my ($sec_mode) = @_;
my $err = '';
Err: {
unless ($sec_mode) {
$sec_mode = &detect_sec_mode();
}
# @file_sec == b_use_chmod, perm folder, data file, script file
if ($sec_mode == 2) { # setuid
@file_sec = (1, 755, 644, 755);
}
elsif ($sec_mode == 3) { # permissive
@file_sec = (1, 777, 666, 777);
}
elsif ($sec_mode == 4) { # custom
@file_sec = (1, $system_eff{'Permission - Folder'}, $system_eff{'Permission - Normal Files'}, $system_eff{'Permission - CGI Scripts'});
}
elsif ($sec_mode == 1) { # none
@file_sec = (0);
}
if ($file_sec[0]) {
for (1..3) {
unless ($file_sec[$_] =~ m!(\d\d\d)$!) {
$err = "file security descriptor must be of the format ddd";
next Err;
}
$file_sec[$_] = oct("0$1");
}
}
last Err;
}
return $err;
};
sub clean_path {
my $path = &Trim($_[0]);
my ($base, $question, $query) = ($path, '', '');
if ($path =~ m!^(.+?)(\??)(.*)$!s) {
($base, $question, $query) = ($1, $2, $3);
}
local $_ = $base;
# strip pound signs and all that follows (links internal to a page)
s!\#.*$!!;
# map /%7E to /~ (common source of duplicate URL's)
s!\/\%7E!\/\~!ig;
# map "/./" to "/"
s!/+\./+!/!g;
# map trailing "/." to "/"
s!/+\.$!/!g;
# map "/folder/../" => "/"
while (s!([^/]+)/+\.\./+!/!) {}
# map /../foo => /foo
while (s!^/+\.\./+!/!) {}
s!^/+\.\.$!/!;
# collapse back-to-back slashes in the path
s!/+!/!g;
return $_ . $question . $query;
}
1;