The Jean Hailes Foundation for Women's Health

The Jean Hailes Foundation for Women's Health

Community Webcast

Webcast Home

About the speakers

How to view

Requirements

Resources

Date: Wed 5 March, 2008 Time:7:30 PM AEDT

Requirements: You will need a PC or Macintosh computer with sound capabilities, internet connection and Internet Explorer 4 or later. More details here...


`; } print qq`
Show/hide:  $toggles
\n`; print qq`

Registration Form

The asterisk * denotes a required fields.
The Foundation respects your privacy and will not reveal, disclose, sell, distribute, share or otherwise pass on to any third parties any personal information that you may have provided unless permitted by law or we have your express written consent to do so.
Please review our Privacy Statement.

Register for the webcast.
You need to Create a New Account. When you login this will allow you to access the webcast console and where you may submit questions. You can make up your own User Name. Choose something you will easily remember and that is four or more letters and/or numbers. You can also make up your own Password. Choose something you will easily remember and that is seven or more letters and/or numbers.

#!/usr/bin/perl # # userbase.cgi # ###################################################################### # # DO NOT EDIT THIS FILE unless absolutely necessary; in most cases # you should be editing userbase_prefs.cgi instead. # ###################################################################### # # This program is the copyrighted work of Encodable Industries. # Redistribution is prohibited, and copying is only permitted for # backup purposes. You are free to modify the program for your # own use, but you may not distribute any modified copies of it. # # Use of this program requires a one-time license fee. You can # obtain a license here: # # http://encodable.com/userbase/ # # This software comes with no warranty. The author and many other # people have found it to be useful, and it is our hope that you # find it useful as well, but it comes with no guarantees. Under # no circumstances shall Encodable Industries be held liable in # any situation arising from your use of this program. We are # generally happy to provide support to all our users, but we can # make no guarantee of support. # # For more information about this program, as well as for help # and support, please visit the following pages: # # Homepage: http://encodable.com/userbase/ # Contact: http://encodable.com/contact/ my $version = "2.02"; $ENV{PATH} = '/bin:/usr/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; ($ENV{DOCUMENT_ROOT}) = ($ENV{DOCUMENT_ROOT} =~ /(.*)/); # untaint. use lib './perlmodules'; my (%PREF,%TEXT) = (); use CGI::Carp 'fatalsToBrowser'; # uncomment to show errors in browser. my $debuglog = undef; #open($debuglog, ">>ubdata/debuglog.ubtemp.log") or die_nice("couldn't open debuglog: $!\n"); flock $debuglog, 2; print $debuglog "\n\n"; use strict; use Digest::MD5 'md5_hex'; # always required for backwards compatibility. unless($PREF{use_md5_for_hashes} =~ /yes/i) { eval { require Digest::SHA1; }; die_nice($@) if $@; import Digest::SHA1 'sha1_hex'; } use DBI; sub printd; sub die_nice; my $qs = ''; sub create_new_session_id { my $username = shift; my $password = shift; my $id = offsettime() . $$ . $ENV{REMOTE_ADDR} . $ENV{HTTP_USER_AGENT} . $username . $password; #$id =~ s/[^\d]//g; #$id = substr($id,0,85); $id = enc_hash($id); return $id; } sub check_for_multiple_logins($) { my ($userid) = @_; die_unless_numeric($userid,'userid'); my $old_login_time = enc_sql_select("SELECT `loggedin` FROM `$PREF{user_table_name}` WHERE `id` = $userid;"); if($old_login_time =~ /[1-9]/ && !login_session_expired($old_login_time)) { if($PREF{prevent_multiple_simultaneous_logons_per_username} =~ /yes/i) { my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=emultlogin"; enc_redirect($go); } else { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `numusers`=IFNULL(`numusers`,0)+1 WHERE `id` = '$userid';"); die_nice("Error: check_for_multiple_logins('$userid'): SQL returned '$success' instead of '1' while incrementing numusers column.") unless $success == 1; my $existing_session_id = enc_sql_select("SELECT `mrsession` FROM `$PREF{user_table_name}` WHERE `id` = '$userid';"); return $existing_session_id; } } } sub log_user_into_db { my ($userid, $my_session_id, $logintime, $restrict_ip) = @_; die_unless_numeric($userid,'userid'); die_unless_numeric($logintime,'logintime'); check_sessionid_for_sql_safeness($my_session_id); my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `loggedin` = $logintime, `mrsession` = '$my_session_id' WHERE `id` = '$userid';"); die_nice("Error: log_user_into_db('$userid', '$my_session_id', '$logintime', '$restrict_ip'): SQL returned '$success' instead of '1' while logging user in.") unless $success == 1; if($restrict_ip) { my $ip = $ENV{REMOTE_ADDR}; check_ip_for_sql_safeness($ip); unless(enc_sql_select("SELECT `ip` FROM `$PREF{user_table_name}` WHERE `id` = '$userid'") eq $ip) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `ip` = '$ip' WHERE `id` = '$userid';"); die_nice("Error: log_user_into_db('$userid', '$my_session_id', '$logintime', '$restrict_ip'): SQL returned '$success' instead of '1' while setting user IP.") unless $success == 1; } } else { unless(enc_sql_select("SELECT `ip` FROM `$PREF{user_table_name}` WHERE `id` = '$userid'") eq '') { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `ip` = '' WHERE `id` = '$userid';"); die_nice("Error: log_user_into_db('$userid', '$my_session_id', '$logintime', '$restrict_ip'): SQL returned '$success' instead of '1' while clearing user IP.") unless $success == 1; } } } sub log_user_out_of_db { my ($username, $my_session_id) = @_; check_username_for_sql_safeness($username); check_sessionid_for_sql_safeness($my_session_id) unless $my_session_id eq 'force'; # It's possible (and probably not particularly uncommon) that a user logs in at one location, then leaves # that location and his session goes idle, and then he logs in at another location with the same account. In # that case, a call to log_user_out_of_db() from the first location should not actually do the db logout, # because the session does not belong to him anymore. But note that this is not an error condition, so we # should just silently return. # my $session_id_in_db = enc_sql_select("SELECT `mrsession` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username');"); if($my_session_id == $session_id_in_db || $my_session_id eq 'force') { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `loggedin` = 0 WHERE `username` = '$username';"); die_nice("Error: log_user_out_of_db('$username', '$my_session_id'): SQL returned '$success' instead of '1' while setting loggedin to zero.") unless $success == 1; $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `mrsession` = '' WHERE `username` = '$username';"); die_nice("Error: log_user_out_of_db('$username', '$my_session_id'): SQL returned '$success' instead of '1' while setting mrsession to null.") unless $success == 1; my $numusers = enc_sql_select("SELECT `numusers` FROM `$PREF{user_table_name}` WHERE `username` = '$username';"); if($numusers) # this check only required because of crappy old MySQL versions that fail to ever set numusers properly in the DB (??). { $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `numusers` = 0 WHERE `username` = '$username';"); die_nice("Error: log_user_out_of_db('$username', '$my_session_id'): SQL returned '$success' instead of '1' while setting numusers to zero.") unless $success == 1; } } } sub determine_default_login_destination { my $ref = shift; my $go = (); if($qs =~ /(?:^|&)whence=(.+)/) { # don't URL-decode here because this is getting passed right # to the Location: header, which requires URL encoding. $go = $1; if($qs =~ /(?:^|&)encanchor=([^&]+)(?:&|$)/) { $go .= '#' . $1; } } else { $go = $ref ? $ref : "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; my $us1 = $PREF{login_url}; my $us2 = $ENV{SCRIPT_NAME}; if($go =~ /($us1|$us2)\?.+/) { # If the page we were on before was a login page with some # query-string, then just return to the login frontpage. $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; } elsif($go !~ m!^https?://(www\.)?$ENV{HTTP_HOST}!) { # If the page we were on before was on an external site, then # obviously we don't want to redirect there, since we won't be # logged in there. Again, just return to the login frontpage. $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; } } return $go; } sub prompt_for_login() { printd "prompt_for_login()"; # don't urldecode here, because it's just going right back onto the URL. my $whence = $qs =~ /(?:^|&)whence=(.+)/ ? "&whence=$1" : undef; if($qs =~ /(?:^|&)encanchor=([^&]+)(?:&|$)/) { $whence = "&encanchor=$1" . $whence; } my $template = $qs =~ /format=mini/ ? $PREF{login_form_template__mini} : $PREF{login_form_template}; $template =~ s/%%whence%%/$whence/g; $template = interpolate_userbase_variables($template); print $template; } sub user_has_addmember_rights { return user_is_allowed_to($PREF{logged_in_userid}, 'create_new_accounts') || logged_in_user_is_subgroup_manager(); } sub logged_in_user_is_part_of_a_subgroup() { #foreach my $group (enc_sql_select("SELECT ")) #{ # if($group =~ /.+$PREF{subgroup_groupname_suffix}$/i) # { # return enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE LOWER(`group`) = LOWER('$group') AND `members` REGEXP '(^|,)$PREF{logged_in_userid}(,|\$)'"); # } #} my $subgroup_groupname_suffix = $PREF{subgroup_groupname_suffix}; sql_untaint($subgroup_groupname_suffix); exit_with_error("logged_in_user_is_part_of_a_subgroup() not SQL safe: \$subgroup_groupname_suffix ('$subgroup_groupname_suffix').") if not_sqlsafe($subgroup_groupname_suffix); die_unless_numeric($PREF{logged_in_userid}, '$PREF{logged_in_userid}'); return enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE `group` REGEXP '.+$subgroup_groupname_suffix\$' AND `members` REGEXP '(^|,)$PREF{logged_in_userid}(,|\$)'"); } sub logged_in_user_is_subgroup_manager { foreach my $group (split(/\s*,\s*/, $PREF{groups_limited_to_subgroup_user_mgmt})) { return 1 if user_is_member_of_group($PREF{logged_in_userid}, $group); } return 0; } sub logged_in_subgroup_manager_owns_this_user($) { my $userid_to_check = shift; return user_is_member_of_group($userid_to_check, "$PREF{logged_in_username}$PREF{subgroup_groupname_suffix}"); } sub user_has_addadmin_rights { return $PREF{admin_is_logged_in}; } sub user_has_groupmod_rights { return $PREF{admin_is_logged_in}; } sub user_is_allowed_to { my $userid_performing_action = shift; my $action = shift; my $user_affected_by_action = shift; my $userid_affected_by_action = get_user_id($user_affected_by_action); return 1 if is_admin($userid_performing_action); if($action eq 'edit_user_info') { return 1 if (logged_in_user_is_subgroup_manager() && logged_in_subgroup_manager_owns_this_user($userid_affected_by_action)); } my $allowed = 0; foreach my $group (split(/[,\s]+/, $PREF{"groups_allowed_to_$action"})) { if($group =~ /^self$/i) { $allowed = 1 if ($PREF{member_is_logged_in} && $userid_performing_action == $userid_affected_by_action); } else { $allowed = 1 if user_is_member_of_group($userid_performing_action, $group); } return $allowed if $allowed; } return 0; } sub print_user_form { my $mode = shift; my %vars = (); if($mode eq 'added_by_admin') { $PREF{on_page} = 'adminadduser'; ##my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; ##if(!user_has_addmember_rights()) { enc_redirect("$go?phase=eneedlogin"); } #if(!user_has_addmember_rights()) { exit_with_error($TEXT{Access_denied_}); } if(!user_has_addmember_rights()) { exit_with_error($TEXT{Access_denied_}); } $vars{title} = $TEXT{Add_New_User}; $vars{pw} = $TEXT{Password_}; $vars{pw2} = $TEXT{Password_again_}; $vars{button_label} = $TEXT{Add_User}; $vars{target} = 'action=commitadduser'; $vars{forcepwchng} = 1 if $PREF{enable_forced_password_change} =~ /yes/i; $vars{username_required} = 'required'; $vars{password_required} = 'required'; } elsif($mode eq 'user_signup') { $PREF{on_page} = 'usersignup'; #my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; #if(!user_has_addmember_rights()) { enc_redirect("$go?phase=eneedlogin"); } unless($PREF{visitors_can_sign_up_for_their_own_accounts} =~ /yes/i) { exit_with_error($TEXT{This_feature_is_disabled_}); } $vars{title} = $TEXT{Create_New_Account}; $vars{pw} = $TEXT{Password_}; $vars{pw2} = $TEXT{Password_again_}; $vars{button_label} = $TEXT{Sign_Up}; $vars{target} = 'action=commitadduser'; $vars{hidden_signup_input} = qq``; $vars{forcepwchng} = 0; $vars{username_required} = 'required'; $vars{password_required} = 'required'; } else # edit the user info instead. { $PREF{on_page} = 'edituser'; $vars{user_id} = shift; die_unless_numeric($vars{user_id}, 'userid (from print_user_form())'); $vars{username} = get_user_name($vars{user_id}); #my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; #if(!user_is_allowed_to($PREF{logged_in_userid}, 'edit_user_info', $vars{username})) { enc_redirect("$go?phase=eneedlogin"); } if(!user_is_allowed_to($PREF{logged_in_userid}, 'edit_user_info', $vars{username})) { exit_with_error($TEXT{Access_denied_}); } $vars{title} = $TEXT{Edit_Account_Information}; $vars{pw} = $TEXT{New_Password_}; $vars{pw2} = $TEXT{New_Password_again_}; $vars{button_label} = $TEXT{Modify_User}; $vars{target} = "action=commitedituser"; $vars{force_pw_chng_message} = $PREF{force_pw_chng_message} if force_pw_change($PREF{logged_in_userid}); $vars{username_readonly} = $PREF{usernames_are_immutable_once_created} =~ /no/i && user_is_allowed_to($PREF{logged_in_userid}, 'change_usernames', $vars{username}) ? undef : qq`readonly="readonly"`; $vars{realname} = get_real_name($vars{user_id}); $vars{email} = get_email_address($vars{user_id}); $vars{account_locked} = enc_sql_select("SELECT `acct_locked` FROM `$PREF{user_table_name}` WHERE `id` = '$vars{user_id}';"); $vars{account_disabled} = enc_sql_select("SELECT `acct_disabled` FROM `$PREF{user_table_name}` WHERE `id` = '$vars{user_id}';"); $vars{forcepwchng} = enc_sql_select("SELECT `forcepwchng` FROM `$PREF{user_table_name}` WHERE `id` = '$vars{user_id}';") if $PREF{enable_forced_password_change} =~ /yes/i; $vars{username_required} = 'required'; } if($qs =~ /redo=true/) { while($qs =~ /(?:^|&)redo_(\w+)=([^&]*)/g) { my ($field,$value) = ($1,$2); enc_urldecode($value); $vars{$field} = $value if $value; } } $vars{form_title} = $PREF{form_title_template}; $vars{form_title} =~ s/%%title%%/$vars{title}/g; $vars{emailformat_for_username} = $PREF{usernames_must_be_email_addresses} =~ /yes/i ? 'emailformat' : ''; start_html_output("$vars{title}"); if($qs =~ /(?:^|&)phase=(\w+?)(?:&|$)/) { my $phase = $1; print qq`
` . get_message($phase) . qq`
\n`; } my $i = 0; print qq` $vars{force_pw_chng_message}
$vars{hidden_signup_input} `; if($PREF{use_builtin_realname_field} =~ /yes/i) { print qq` `; } if($PREF{use_builtin_email_field} =~ /yes/i) { print qq` `; } #if( $mode eq 'edit' && (!$PREF{admin_is_logged_in} || this_user_is_the_logged_in_admin($vars{username})) ) # if($mode eq 'edit' && logged_in_user_must_enter_current_password_to_change_password_for_user($vars{username}, $vars{user_id})) { print qq` `; } print qq` `; if($mode eq 'edit' && $PREF{admin_is_logged_in}) { print qq` `; print qq` `; } if($PREF{admin_is_logged_in} && $PREF{enable_forced_password_change} =~ /yes/i) { print qq` `; } if($mode eq 'user_signup' && $PREF{user_must_agree_to_terms_on_signup} =~ /yes/i) { print qq` `; } unless(logged_in_user_is_subgroup_manager() || $mode eq 'user_signup' || ($PREF{hide_groups_on_user_form_for_nonadmins} =~ /yes/i && !$PREF{admin_is_logged_in})) { my $group_title_printed = 0; my $groups = get_groups_hash($vars{user_id}); foreach my $group (sort keys %$groups) { next if ($group =~ /^$PREF{admin_group_name}$/i && !user_has_addadmin_rights()); unless($group_title_printed) { print qq`\t\n\t\n\t\n\t\n`; } } if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{custom_field_table}`")) { my ($id,$fieldname,$fieldlabel,$datatype,$fieldtype,$fieldmax,$fieldposition,$mandatory,$limitallowedchars,$allowedchars,$allowedcharsmsg,$listitems,$enabled) = (); my $sth = $PREF{dbh}->prepare("SELECT id,fieldname,fieldlabel,datatype,fieldtype,fieldmax,fieldposition,mandatory,limitallowedchars,allowedchars,allowedcharsmsg,listitems,enabled FROM `$PREF{custom_field_table}` ORDER BY `fieldposition`"); $sth->execute() or die_nice("$PREF{internal_appname}: Error: print_user_form(): $DBI::errstr\n"); $sth->bind_columns(\$id,\$fieldname,\$fieldlabel,\$datatype,\$fieldtype,\$fieldmax,\$fieldposition,\$mandatory,\$limitallowedchars,\$allowedchars,\$allowedcharsmsg,\$listitems,\$enabled); while($sth->fetchrow_arrayref) { next unless ($enabled && db_column_exists($fieldname, $PREF{user_table_name})); my $value = enc_sql_select("SELECT `$fieldname` FROM `$PREF{user_table_name}` WHERE `id` = $vars{user_id}") if $vars{user_id}; print qq` `; } } print qq`\n` . qq`\t\n\t\n\t` . qq`\n
$vars{form_title}
$PREF{username_label}:
$PREF{name_label}:
$PREF{email_label}:
Current Password
(only if changing):
$vars{pw}
$vars{pw2}
Account Locked (this is the
"too many failed logins" lock; it
` . ($PREF{lock_lasts_until_admin_removes_it} =~ /yes/i ? 'requires manual unlocking by an
admin here' : "auto-unlocks after $PREF{failed_logins_within_N_secs_count_towards_lock} seconds") . qq`)
:
Account Disabled (this can only
be changed here, by an admin)
:
Force Password Change:
$PREF{user_signup_terms}
Group Memberships:`; $group_title_printed = 1; } my $checked = $$groups{$group}{is_member} ? qq`checked="checked"` : undef; # the checkboxes are disabled unless the user is an admin, i.e. only admins can change group memberships. # also disable the "public" checkbox, since that applies to all users by definition. my $disabled = ($group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i || !$PREF{admin_is_logged_in}) ? 'disabled="disabled"' : undef; # Admins can see all groups here; non-admins can only see the groups they belong to. if($PREF{admin_is_logged_in} || $$groups{$group}{is_member}) { my $label = $PREF{admin_is_logged_in} && !$disabled ? qq`$group` : $group; print qq`$label

\n` } } if($group_title_printed) { print qq`
$fieldlabel `; if($fieldtype eq 'freeformsingle') { $value = $value ? qq` value="$value"` : ''; print qq``; } elsif($fieldtype eq 'freeformmulti') { print qq``; } elsif($fieldtype eq 'dropdown') { print qq``; } elsif($fieldtype eq 'checkbox') { my $checked = $value ? qq` checked="checked"` : ''; print qq``; } elsif($fieldtype eq 'radio') { my $checked = $value ? qq` checked="checked"` : ''; print qq` $_
\n` for(split(/\n/, $listitems)); } print qq`
` . qq`` . qq`` . qq`
` . qq`\n
` . qq`\n`; finish_html_output(); } sub logged_in_user_must_enter_current_password_to_change_password_for_user($$) { my $username_of_target_user = shift; my $userid_of_target_user = shift; if(this_user_is_the_logged_in_admin($username_of_target_user)) { # an admin is trying to change his own password, so # we DO require the current password. return 1; } elsif(logged_in_user_is_subgroup_manager() && logged_in_subgroup_manager_owns_this_user($userid_of_target_user)) { # a subgroup manager is trying to change the password of one of his # own users; he's effectively an admin for this purpose, so we DON'T # require the current password. return 0; } elsif($PREF{admin_is_logged_in}) { # in general, an admin does NOT need the current password to change # a password. return 0; } else { # in general, a non-admin DOES need the current password to change # a password. return 1; } } sub print_group_form { my $mode = shift; my %vars = (); if($mode eq 'add') { my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(!user_is_allowed_to($PREF{logged_in_userid}, 'create_new_groups')) { enc_redirect("$go?phase=eneedlogin"); } $vars{title} = 'Add New Group'; $vars{button_label} = 'Add Group'; $vars{target} = 'action=commitaddgroup'; } else # edit the group info instead. { $vars{group_id} = shift; $vars{group} = get_group_name($vars{group_id}); my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(!user_is_allowed_to($PREF{logged_in_userid}, 'edit_group_info')) { enc_redirect("$go?phase=eneedlogin"); } $vars{title} = 'Edit Group'; $vars{button_label} = 'Modify Group'; $vars{target} = "action=commiteditgroup"; $vars{groupname_readonly} = $PREF{groupnames_are_immutable_once_created} =~ /no/i && user_is_allowed_to($PREF{logged_in_userid}, 'change_groupnames') ? undef : qq`readonly="readonly"`; $vars{groupdesc} = get_group_desc($vars{group_id}); } start_html_output("$vars{title}"); my $i = 0; print qq`
`; print qq`\n` . qq`\t\n\t\n\t` . qq`\n
$vars{title}
Group:
Description:
` . qq`` . qq`
` . qq`\n
` . qq`\n`; finish_html_output(); } sub process_new_account() { use CGI ':param'; my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; my $user = my $user_redo = param('username'); enc_urlencode($user_redo); my $realname = my $realname_redo = param('realname'); enc_urlencode($realname_redo); my $email = my $email_redo = param('email'); enc_urlencode($email_redo); my $pass = param('pw1'); # don't redo/refill the password because we don't want to pass that on the URL. my $salt = create_random_salt($PREF{salt_length}); my $crypted_pass = salt_and_crypt_password($pass,$salt); my $signup = param('user_signup') =~ /yes/i; $PREF{prev_page} = $signup ? 'newaccount' : 'adduser'; my $redo = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?action=$PREF{prev_page}&redo=true"; $redo .= "&redo_username=$user_redo&redo_realname=$realname_redo&redo_email=$email_redo"; if(!user_has_addmember_rights() && !$signup) { enc_redirect("$redo&phase=eneedlogin"); } elsif($PREF{visitors_can_sign_up_for_their_own_accounts} !~ /yes/i && $signup) { enc_redirect("$redo&phase=enosignup"); } elsif(!realname_is_valid($realname) && $realname) { enc_redirect("$redo&phase=einvldr"); } elsif(!emailaddr_is_valid($email) && $email) { enc_redirect("$redo&phase=einvlde"); } elsif(!emailaddr_is_valid($user) && $PREF{usernames_must_be_email_addresses} =~ /yes/i) { enc_redirect("$redo&phase=einvlde"); } elsif(!password_is_valid($pass)) { enc_redirect("$redo&phase=einvldp"); } elsif(!hashedpw_is_valid($crypted_pass)) { enc_redirect("$redo&phase=einvldh"); } elsif(!username_is_valid($user)) { enc_redirect("$redo&phase=ebadname"); } elsif( username_is_taken($user)) { enc_redirect("$redo&phase=edupuser"); } elsif( $email && email_address_is_taken($email)) { enc_redirect("$redo&phase=edupemail"); } elsif( param('group-admin') =~ /on/i && !user_has_addadmin_rights()) { enc_redirect("$redo&phase=einsuff"); } elsif( param('pw1') ne param('pw2')) { enc_redirect("$redo&phase=epwmismatch"); } my $customfields_sqlsafe = get_sqlsafe_custom_field_values(); my $pending = 0; if($signup && $PREF{require_email_verification_for_new_signups} =~ /yes/i) { $pending = 1; } elsif($signup && $PREF{require_admin_approval_for_new_signups} =~ /yes/i) { $pending = 2; } my $token = ''; if($pending == 1 || $pending == 2) { $token = enc_hash($email . $realname . $user . $pass . $salt . offsettime() . $$ . $ENV{REMOTE_ADDR} . $ENV{HTTP_USER_AGENT}); $token =~ s/[^\w]/X/g; } my $new_user_id = add_new_user($user, $crypted_pass, $salt, $realname, $email, $pending, $token); my $query = new CGI; my %params = $query->Vars; if(user_has_groupmod_rights()) { foreach my $param (sort keys %params) { if($param =~ /^group-(.+)$/) { my $group = $1; next if ($group =~ /^$PREF{admin_group_name}$/i && !user_has_addadmin_rights()); next if ($group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i); # every account is automatically a member of these groups. add_user_to_group($user, $group) if $params{$param} =~ /on/i; } } } # When a subgroup manager creates an account, it's automatically # put into a special group based on the manager's username. # if(logged_in_user_is_subgroup_manager()) { my $groupname = "$PREF{logged_in_username}$PREF{subgroup_groupname_suffix}"; add_new_group($groupname, "Group managed by $PREF{logged_in_username}") unless group_exists($groupname); add_user_to_group($user, $groupname); } my $table = $pending ? $PREF{pending_account_table} : $PREF{user_table_name}; foreach my $customfield (keys %$customfields_sqlsafe) { die_unless_numeric($new_user_id, "userid"); my $value = $$customfields_sqlsafe{$customfield}; unless($value eq enc_sql_select("SELECT `$customfield` FROM `$table` WHERE `id` = $new_user_id")) { my $statement = "UPDATE `$table` SET `$customfield` = '$value' WHERE `id` = $new_user_id"; my $success = enc_sql_update($statement); die_nice("Error: process_new_account(): SQL returned '$success' instead of '1' while updating custom field '$customfield' to value '$value'. SQL was: [[$statement]]") unless $success == 1; } } if(param('forcepwchng') =~ /on/i) { my $statement = "UPDATE `$table` SET `forcepwchng` = 1 WHERE `id` = $new_user_id;"; my $success = enc_sql_update($statement); die_nice("Error: process_new_account(id='$new_user_id'): SQL returned '$success' instead of '1' while enabling forcepwchng. SQL was: [[$statement]]") unless $success == 1; } if($pending == 1) { enc_urlencode($user); $PREF{verification_email_template} =~ s/%%link%%/$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?action=verify&u=$user&t=$token/g; my $user_email = $PREF{usernames_must_be_email_addresses} =~ /yes/i ? $user : $email; send_email( $user_email, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", $PREF{verification_email_subject}, $PREF{verification_email_template}, $PREF{verification_email_format}, 'die_on_email_error' ); enc_redirect("$go?phase=sactvrf&one=$user"); } elsif($pending == 2) { send_email_requesting_admin_approval_of_new_acct($user,$token); enc_urlencode($user); enc_redirect("$go?phase=sactapp&one=$user"); } else { create_filechucker_userdir($user); enc_urlencode($user); notify_admin_of_new_signup($new_user_id) if $signup; send_welcome_email($new_user_id,$user,$pass,$email,$realname); enc_redirect("$go?phase=snewadd&one=$user"); } } sub process_new_group() { use CGI ':param'; my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; my $group = param('group'); my $groupdesc = param('groupdesc'); if(!user_is_allowed_to($PREF{logged_in_userid}, 'create_new_groups')) { enc_redirect("$go?phase=eneedlogin"); } elsif(!groupname_is_valid($group)) { enc_redirect("$go?phase=einvldgn"); } elsif(!groupdesc_is_valid($groupdesc) && $groupdesc) { enc_redirect("$go?phase=einvldgd"); } elsif(group_exists($group)) { enc_redirect("$go?phase=egrpexist"); } elsif($group =~ /^(self)$/i) { enc_redirect("$go?phase=egrprsvd"); } add_new_group($group, $groupdesc); enc_redirect("$go?phase=snewgrp&one=$group"); } sub get_sqlsafe_custom_field_values() { my %customfields_sqlsafe = (); my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{custom_field_table}`")) { my ($id,$fieldname,$fieldlabel,$datatype,$fieldtype,$fieldmax,$fieldposition,$mandatory,$limitallowedchars,$allowedchars,$allowedcharsmsg,$listitems,$enabled) = (); my $sth = $PREF{dbh}->prepare("SELECT id,fieldname,fieldlabel,datatype,fieldtype,fieldmax,fieldposition,mandatory,limitallowedchars,allowedchars,allowedcharsmsg,listitems,enabled FROM `$PREF{custom_field_table}` ORDER BY `fieldposition`"); $sth->execute() or die_nice("$PREF{internal_appname}: Error: process_new_account(): $DBI::errstr\n"); $sth->bind_columns(\$id,\$fieldname,\$fieldlabel,\$datatype,\$fieldtype,\$fieldmax,\$fieldposition,\$mandatory,\$limitallowedchars,\$allowedchars,\$allowedcharsmsg,\$listitems,\$enabled); while($sth->fetchrow_arrayref) { next unless ($enabled && db_column_exists($fieldname, $PREF{user_table_name})); sql_un_untaint($allowedchars); my $value = param($fieldname); $value =~ s/\r\n/\n/g; # fix browser newlines. # # Do custom-field sanity checking: # if($datatype eq 'int' && $value =~ /[^\d-]/) { enc_urlencode($value); enc_redirect("$go?phase=enotint&one=$fieldname&two=$value"); } if($datatype eq 'uint' && $value =~ /[^\d]/) { enc_urlencode($value); enc_redirect("$go?phase=enotuint&one=$fieldname&two=$value"); } if($datatype eq 'float' && $value =~ /[^\d\.-]/) { enc_urlencode($value); enc_redirect("$go?phase=enotfloat&one=$fieldname&two=$value"); } if($datatype eq 'ufloat' && $value =~ /[^\d\.]/) { enc_urlencode($value); enc_redirect("$go?phase=enotufloat&one=$fieldname&two=$value"); } if(($datatype eq 'bool' || $fieldtype eq 'checkbox') && !($value =~ /^on$/i || !$value)) { enc_urlencode($value); enc_redirect("$go?phase=enotbool&one=$fieldname&two=$value"); } if(($datatype eq 'bool' || $fieldtype eq 'checkbox') && ($value =~ /^on$/i)) { $value = 1; } # convert checkbox string value to a bool. if($fieldtype =~ /^(dropdown|radio)$/) { my %allowable_values = map { $_ => 1 } split(/\n/, $listitems); unless($allowable_values{$value}) { enc_urlencode($value); enc_redirect("$go?phase=ebadval&one=$fieldname&two=$value"); } } if($fieldmax =~ /^\d+$/ && $fieldmax > 0 && length($value) > $fieldmax) { enc_redirect("$go?phase=emaxlnth&one=$fieldname&two=$fieldmax&three=" . length($value)); } if($mandatory && !$value) { enc_redirect("$go?phase=emandatory&one=$fieldname"); } if($limitallowedchars) { # Escape any dashes or closing brackets, as per perlre: # # If you want either "-" or "]" itself to be a member of a class, # put it at the start of the list (possibly after a "^"), or escape # it with a backslash. # $allowedchars =~ s/\]/\\]/g; $allowedchars =~ s/-/\\-/g; if($value =~ /[^$allowedchars]/) { enc_redirect("$go?phase=ebadchar&one=$id"); } } # # Do SQL sanity checking: # sql_untaint($value); if(not_sqlsafe($value)) { enc_redirect("$go?phase=esqlsafe&one=$fieldname"); } # # If we got this far, the value is valid. # $customfields_sqlsafe{$fieldname} = $value; } } return \%customfields_sqlsafe; } sub get_custom_field_names { my $which_user_table = shift; my $include_disabled_fields = shift; my (@custom_fields, %custom_fields) = (); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{custom_field_table}`")) { my ($fieldname,$fieldlabel,$enabled) = (); my $sth = $PREF{dbh}->prepare("SELECT fieldname,fieldlabel,enabled FROM `$PREF{custom_field_table}` ORDER BY `fieldposition`"); $sth->execute() or die_nice("$PREF{internal_appname}: Error: get_custom_field_names(): $DBI::errstr\n"); $sth->bind_columns(\$fieldname,\$fieldlabel,\$enabled); while($sth->fetchrow_arrayref) { next unless db_column_exists($fieldname, $which_user_table); next if (!$enabled && !$include_disabled_fields); $custom_fields{$fieldname} = 1; push @custom_fields, $fieldname; } } return wantarray ? @custom_fields : \%custom_fields; } sub this_user_is_the_logged_in_admin($) { my $username = shift; return $PREF{admin_is_logged_in} && lc($PREF{logged_in_username}) eq lc($username); } sub do_email_verification($$) { my $username = shift; my $token = shift; enc_urldecode($username); check_username_for_sql_safeness($username); die_nice("Invalid token '$token'.") unless $token =~ /^\w+$/; if($PREF{require_admin_approval_for_new_signups} =~ /yes/i) { enc_sql_update("UPDATE `$PREF{pending_account_table}` SET `status` = 3 WHERE `username` = '$username' AND `token` = '$token' AND `status` = 1") == 1 or die_nice("$PREF{internal_appname}: Error: do_email_verification('$username', '$token'): SQL returned something other than 1 while trying to set status to 3."); start_html_output("Email Address Verified"); print $PREF{email_verified_pending_template}; send_email_requesting_admin_approval_of_new_acct($username,$token); finish_html_output(); } else { my ($password,$salt,$name,$email,$cdate) = enc_sql_select("SELECT `password`,`salt`,`name`,`email`,`cdate` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND `status` = 1"); die_nice("Error: the username '$username' is taken; you must create a new account.") if username_is_taken($username); die_nice("Error: the email address '$email' is taken; you must create a new account.") if email_address_is_taken($email); add_new_user($username, $password, $salt, $name, $email); my $new_user_id = enc_sql_select("SELECT `id` FROM `$PREF{user_table_name}` WHERE `username` = '$username' AND `password` = '$password' AND `salt` = '$salt' AND `email` = '$email'"); die_unless_numeric($new_user_id, 'User ID'); my @custom_fields = get_custom_field_names($PREF{pending_account_table}); foreach my $customfield (@custom_fields) { if(db_column_exists($customfield,$PREF{pending_account_table}) && db_column_exists($customfield,$PREF{user_table_name})) { my $value = enc_sql_select("SELECT `$customfield` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND `status` = 1"); unless($value eq enc_sql_select("SELECT `$customfield` FROM `$PREF{user_table_name}` WHERE `id` = $new_user_id")) { my $statement = "UPDATE `$PREF{user_table_name}` SET `$customfield` = '$value' WHERE `id` = $new_user_id"; my $success = enc_sql_update($statement); die_nice("Error: do_email_verification(): SQL returned '$success' instead of '1' while setting custom field '$customfield' to value '$value' in user table (from value in pending table). SQL was: [[$statement]]") unless $success == 1; } } } my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND `status` = 1"); $sth->execute == 1 or die_nice("$PREF{internal_appname}: do_email_verification('$username', '$token'): SQL returned something other than 1 while deleting user info from 'pending' table."); create_filechucker_userdir($username); start_html_output("Email Address Verified"); $PREF{email_verified_active_template} =~ s/%%login_url%%/$PREF{login_url}/g; print $PREF{email_verified_active_template}; notify_admin_of_new_signup($new_user_id); finish_html_output(); } } sub send_email_requesting_admin_approval_of_new_acct($$) { my $username = shift; my $token = shift; my $username_urlencoded = $username; enc_urlencode($username_urlencoded); $PREF{admin_approval_email_subject} =~ s/%%username%%/$username/g; $PREF{admin_approval_email_template} =~ s/%%username%%/$username/g; $PREF{admin_approval_email_template} =~ s/%%approval_url%%/$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?action=approve_or_del&u=$username_urlencoded&t=$token/g; send_email( $PREF{webmaster_email_address}, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", $PREF{admin_approval_email_subject}, $PREF{admin_approval_email_template}, undef, 'die_on_email_error' ); } sub approve_or_delete_pending_account($$) { exit_with_error($TEXT{Access_denied_}) unless user_has_addmember_rights(); my $username = shift; my $token = shift; enc_urldecode($username); check_username_for_sql_safeness($username); die_nice("Invalid token '$token'.") unless $token =~ /^\w+$/; my ($password,$salt,$name,$email,$cdate) = enc_sql_select("SELECT `password`,`salt`,`name`,`email`,`cdate` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); my $pending_status = enc_sql_select("SELECT `status` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); my $username_urlencoded = $username; enc_urlencode($username_urlencoded); start_html_output("Approve or Delete Pending Account"); print qq`` . qq`\n

Username: $username

` . ($PREF{use_builtin_realname_field} =~ /yes/i ? qq`\n

Real Name: $name

` : '') . ($PREF{use_builtin_email_field} =~ /yes/i ? qq`\n

Email Address: $email (` . ($pending_status == 3 ? 'verified' : 'not verified') . qq`)

` : '') . qq`\n

Creation Date: ` . strftime("%Y%m%d-%H:%M",localtime($cdate)) . qq`

` . qq`\n


`; if(my @custom_field_names = get_custom_field_names($PREF{pending_account_table})) { my @custom_field_values = enc_sql_select("SELECT " . (join ',', @custom_field_names) . " FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); my $i = 0; for(@custom_field_names) { print qq`\n

$custom_field_names[$i]: $custom_field_values[$i]

`; $i++; } print qq`\n


`; } print qq`\n

Approve and activate pending account '$username'

` . qq`\n

Delete pending account '$username'

` . qq`\n

` . qq`\n`; finish_html_output(); } sub approve_or_delete_pending_account_stage2($$$) { exit_with_error($TEXT{Access_denied_}) unless user_has_addmember_rights(); my $username = shift; my $token = shift; my $decision = shift; enc_urldecode($username); check_username_for_sql_safeness($username); die_nice("Invalid token '$token'.") unless $token =~ /^\w+$/; if($decision eq 'approve') { my ($password,$salt,$name,$email,$cdate) = enc_sql_select("SELECT `password`,`salt`,`name`,`email`,`cdate` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); die_nice("Error: the username '$username' is taken; you must create a new account.") if username_is_taken($username); die_nice("Error: the email address '$email' is taken; you must create a new account.") if email_address_is_taken($email); my $new_user_id = add_new_user($username, $password, $salt, $name, $email); my @custom_fields = get_custom_field_names($PREF{pending_account_table}); foreach my $customfield (@custom_fields) { if(db_column_exists($customfield,$PREF{pending_account_table}) && db_column_exists($customfield,$PREF{user_table_name})) { my $value = enc_sql_select("SELECT `$customfield` FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); unless($value eq enc_sql_select("SELECT `$customfield` FROM `$PREF{user_table_name}` WHERE `id` = $new_user_id")) { my $statement = "UPDATE `$PREF{user_table_name}` SET `$customfield` = '$value' WHERE `id` = $new_user_id"; my $success = enc_sql_update($statement); die_nice("Error: approve_or_delete_pending_account_stage2(): SQL returned '$success' instead of '1' while setting custom field '$customfield' to value '$value' in user table (from value in pending table). SQL was: [[$statement]]") unless $success == 1; } } } create_filechucker_userdir($username); $PREF{account_activated_email_subject} =~ s/%%username%%/$username/g; $PREF{account_activated_email_template} =~ s/%%username%%/$username/g; my $user_email = $PREF{usernames_must_be_email_addresses} =~ /yes/i ? $username : $email; send_email( $user_email, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", $PREF{account_activated_email_subject}, $PREF{account_activated_email_template}, undef, 'die_on_email_error' ); notify_admin_of_new_signup($new_user_id); } my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{pending_account_table}` WHERE `username` = '$username' AND `token` = '$token' AND (`status` = 2 || `status` = 3)"); $sth->execute == 1 or die_nice("$PREF{internal_appname}: do_email_verification('$username', '$token'): SQL returned something other than 1 while deleting user info from 'pending' table."); if($decision eq 'approve') { start_html_output("Pending Account Approved and Activated"); print qq`

The '$username' account has been approved and is now active.

\n`; } else { start_html_output("Pending Account Deleted"); print qq`

The '$username' account has been deleted.

\n`; } finish_html_output(); } sub notify_admin_of_new_signup($) { my $new_user_id = shift; return unless $PREF{notify_admin_of_new_signups} =~ /yes/i; die_unless_numeric($new_user_id, 'new_user_id'); my ($username,$name,$email) = enc_sql_select("SELECT `username`,`name`,`email` FROM `$PREF{user_table_name}` WHERE `id` = '$new_user_id'"); my $username_template = my $name_template = my $email_template = $PREF{admin_notification_email_field_template}; $username_template =~ s/%%fieldname%%/$PREF{username_label}/g; $name_template =~ s/%%fieldname%%/$PREF{name_label}/g; $email_template =~ s/%%fieldname%%/$PREF{email_label}/g; $username_template =~ s/%%fieldvalue%%/$username/g; $name_template =~ s/%%fieldvalue%%/$name/g; $email_template =~ s/%%fieldvalue%%/$email/g; my $user_info_fields = $username_template; $user_info_fields .= $name_template if $PREF{use_builtin_realname_field} =~ /yes/i; $user_info_fields .= $email_template if $PREF{use_builtin_email_field} =~ /yes/i; if(my @custom_field_names = get_custom_field_names($PREF{user_table_name})) { my @custom_field_values = enc_sql_select("SELECT " . (join ',', @custom_field_names) . " FROM `$PREF{user_table_name}` WHERE `id` = '$new_user_id'"); my $i = 0; for(@custom_field_names) { my $template = $PREF{admin_notification_email_field_template}; $template =~ s/%%fieldname%%/$custom_field_names[$i]/g; $template =~ s/%%fieldvalue%%/$custom_field_values[$i]/g; $user_info_fields .= $template; $i++; } } $PREF{admin_notification_email_subject} =~ s/%%username%%/$username/g; $PREF{admin_notification_email_subject} =~ s/%%name%%/$name/g; $PREF{admin_notification_email_subject} =~ s/%%email%%/$email/g; $PREF{admin_notification_email_template} =~ s/%%user_info_fields%%/$user_info_fields/g; send_email( $PREF{webmaster_email_address}, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", $PREF{admin_notification_email_subject}, $PREF{admin_notification_email_template}, undef, 'die_on_email_error' ); } sub send_welcome_email($$$$$) { my ($new_user_id, $username, $password, $email, $name) = @_; return unless $PREF{send_welcome_email_when_admin_creates_an_account} =~ /yes/i; for($PREF{welcome_email_subject}, $PREF{welcome_email_template}) { s/%%username%%/$username/g; s/%%password%%/$password/g; s/%%email%%/$email/g; s/%%name%%/$name/g; } send_email( $email, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", $PREF{welcome_email_subject}, $PREF{welcome_email_template}, undef, 'die_on_email_error' ); } sub edit_user_account() { use CGI ':param'; my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; my $userid = param('userid'); check_uid_for_uniqueness($userid); # checks for sql safeness too. my $username_in_db = get_user_name($userid); my $username = $username_in_db; if(!user_is_allowed_to($PREF{logged_in_userid}, 'edit_user_info', $username_in_db)) { enc_redirect("$go?phase=eneedlogin"); } my (@results, $sth) = (); my $username_from_form = param('username'); if($username_from_form ne $username_in_db) { if(user_is_allowed_to($PREF{logged_in_userid}, 'change_usernames', $username_in_db)) { if(username_is_valid($username_from_form)) { $sth = $PREF{dbh}->prepare("UPDATE `$PREF{user_table_name}` SET `username` = '$username_from_form' WHERE `id` = $userid"); $sth->execute() or die_nice("$0: edit_user_account() failed: [userid='$userid', username_from_form='$username_from_form']: $DBI::errstr\n"); $username = $username_from_form; push @results, 101; } else { push @results, 102; } } } if(param('pw1') =~ /\S/) { # Note: we don't use password_is_valid() on 'oldpw' here because then a user's password-change # will fail if, for example, the admin increases the minimum password length, and the old password # was too short. In that case, we still want the old password to be accepted so that the password # can be changed, and the new password will then be checked for validity under the new rules. # if( password_is_valid(param('pw1')) && password_is_valid(param('pw2')) && (param('oldpw') =~ /\S/ || !logged_in_user_must_enter_current_password_to_change_password_for_user($username_in_db,$userid)) ) { if(param('pw1') eq param('pw2')) { my $salt = enc_sql_select("SELECT `salt` FROM `$PREF{user_table_name}` WHERE `id` = $userid;"); if(!logged_in_user_must_enter_current_password_to_change_password_for_user($username_in_db,$userid) || (salt_and_crypt_password(param('oldpw'),$salt) eq get_hashedpw($userid))) { my $salt = create_random_salt($PREF{salt_length}); my $hashed_password = salt_and_crypt_password(param('pw1'),$salt); check_hashedpw_for_sql_safeness($hashed_password); $sth = $PREF{dbh}->prepare("UPDATE `$PREF{user_table_name}` SET `password` = '$hashed_password', `salt` = '$salt' WHERE `id` = $userid"); $sth->execute() or die_nice("$0: edit_user_account() failed: [userid='$userid', hashed_password='$hashed_password']: $DBI::errstr\n"); if($PREF{enable_forced_password_change} =~ /yes/i && enc_sql_select("SELECT `forcepwchng` FROM `$PREF{user_table_name}` WHERE `id` = $userid;")) { my $statement = "UPDATE `$PREF{user_table_name}` SET `forcepwchng` = 0 WHERE `id` = $userid;"; my $success = enc_sql_update($statement); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while disabling forcepwchng. SQL was: [[$statement]]") unless $success == 1; } push @results, 113; } else { push @results, 114; } } else { push @results, 104; } } else { push @results, 116; } } my $realname_from_form = param('realname'); if($realname_from_form ne get_real_name($userid)) { if(realname_is_valid($realname_from_form)) { check_realname_for_sql_safeness($realname_from_form); $sth = $PREF{dbh}->prepare("UPDATE `$PREF{user_table_name}` SET `name` = '$realname_from_form' WHERE `id` = $userid"); $sth->execute() or die_nice("$0: edit_user_account() failed: [userid='$userid', realname_from_form='$realname_from_form']: $DBI::errstr\n"); push @results, 105; } else { push @results, 106; } } my $emailaddr_from_form = param('email'); if($emailaddr_from_form ne get_email_address($userid)) { if(emailaddr_is_valid($emailaddr_from_form)) { check_emailaddr_for_sql_safeness($emailaddr_from_form); $sth = $PREF{dbh}->prepare("UPDATE `$PREF{user_table_name}` SET `email` = '$emailaddr_from_form' WHERE `id` = $userid"); $sth->execute() or die_nice("$0: edit_user_account() failed: [userid='$userid', emailaddr_from_form='$emailaddr_from_form']: $DBI::errstr\n"); push @results, 107; } else { push @results, 108; } } if($PREF{admin_is_logged_in}) { my $groups = get_groups_hash($userid); foreach my $group (sort keys %$groups) { next if ($group =~ /^$PREF{admin_group_name}$/i && !user_has_addadmin_rights()); next if $group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i; if($$groups{$group}{is_member} && param("group-$group") !~ /on/i) { remove_user_from_group($userid, $group); push @results, "109$group"; } elsif(!$$groups{$group}{is_member} && param("group-$group") =~ /on/i) { add_user_to_group($username, $group); push @results, "111$group"; } } my $account_locked_old = enc_sql_select("SELECT `acct_locked` FROM `$PREF{user_table_name}` WHERE `id` = '$userid';"); my $account_locked_new = param("account_locked") =~ /on/i ? 1 : 0; if($account_locked_old != $account_locked_new) { if($account_locked_new) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_locked` = TRUE WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating acct_locked.") unless $success == 1; push @results, 125; } else { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_locked` = FALSE WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating acct_locked.") unless $success == 1; unless(enc_sql_select("SELECT `failed_logins` FROM `$PREF{user_table_name}` WHERE `id` = '$userid'") eq '') { $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `failed_logins` = '' WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating failed_logins.") unless $success == 1; } push @results, 127; } } my $account_disabled_old = enc_sql_select("SELECT `acct_disabled` FROM `$PREF{user_table_name}` WHERE `id` = '$userid';"); my $account_disabled_new = param("account_disabled") =~ /on/i ? 1 : 0; if($account_disabled_old != $account_disabled_new) { if($account_disabled_new) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_disabled` = TRUE WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating acct_disabled.") unless $success == 1; push @results, 129; } else { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_disabled` = FALSE WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating acct_disabled.") unless $success == 1; push @results, 131; } } if($PREF{enable_forced_password_change} =~ /yes/i) { my $forcepwchng_old = enc_sql_select("SELECT `forcepwchng` FROM `$PREF{user_table_name}` WHERE `id` = '$userid';"); my $forcepwchng_new = param("forcepwchng") =~ /on/i ? 1 : 0; if($forcepwchng_old != $forcepwchng_new) { if($forcepwchng_new) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `forcepwchng` = 1 WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating forcepwchng.") unless $success == 1; push @results, 133; } else { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `forcepwchng` = 0 WHERE `id` = '$userid';"); die_nice("Error: edit_user_account(id='$userid'): SQL returned '$success' instead of '1' while updating forcepwchng.") unless $success == 1; push @results, 135; } } } } my $customfields_sqlsafe = get_sqlsafe_custom_field_values(); foreach my $customfield (keys %$customfields_sqlsafe) { die_unless_numeric($userid, "userid"); my $value = $$customfields_sqlsafe{$customfield}; unless($value eq enc_sql_select("SELECT `$customfield` FROM `$PREF{user_table_name}` WHERE `id` = $userid")) { my $statement = "UPDATE `$PREF{user_table_name}` SET `$customfield` = '$value' WHERE `id` = $userid"; my $success = enc_sql_update($statement); die_nice("Error: process_new_account(): SQL returned '$success' instead of '1' while updating custom field '$customfield' to value '$value'. SQL was: [[$statement]]") unless $success == 1; push @results, "137$customfield"; } } enc_redirect("$go?rslt=100&" . join '&', @results); } sub edit_group() { use CGI ':param'; my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; my $groupid = param('groupid'); check_gid_for_uniqueness($groupid); # checks for sql safeness too. my $groupname_in_db = get_group_name($groupid); if(!user_is_allowed_to($PREF{logged_in_userid}, 'edit_group_info')) { enc_redirect("$go?phase=eneedlogin"); } my (@results, $sth) = (); my $groupname_from_form = param('group'); if($groupname_from_form ne $groupname_in_db) { if(user_is_allowed_to($PREF{logged_in_userid}, 'change_groupnames')) { if(groupname_is_valid($groupname_from_form)) { $sth = $PREF{dbh}->prepare("UPDATE `$PREF{group_table_name}` SET `group` = '$groupname_from_form' WHERE `id` = $groupid"); $sth->execute() or die_nice("$0: edit_group() failed: [groupid='$groupid', groupname_from_form='$groupname_from_form']: $DBI::errstr\n"); push @results, 121; } else { push @results, 122; } } } my $groupdesc_from_form = param('groupdesc'); if($groupdesc_from_form ne get_group_desc($groupid)) { if(groupdesc_is_valid($groupdesc_from_form)) { check_groupdesc_for_sql_safeness($groupdesc_from_form); $sth = $PREF{dbh}->prepare("UPDATE `$PREF{group_table_name}` SET `desc` = '$groupdesc_from_form' WHERE `id` = $groupid"); $sth->execute() or die_nice("$0: edit_group() failed: [groupid='$groupid', groupdesc_from_form='$groupdesc_from_form']: $DBI::errstr\n"); push @results, 123; } else { push @results, 124; } } enc_redirect("$go?rslt=100&" . join '&', @results); } sub print_admin_toolbar() { my %status = (); my $user_type = (); if($PREF{admin_is_logged_in}) { $user_type = 'Admin'; } elsif($PREF{member_is_logged_in}) { $user_type = 'Member'; } if( ($PREF{member_is_logged_in}) || ($qs =~ /^login|action=validate$/) ) { my $tb = qq`\n
\n`; $tb .= qq`
` . ($PREF{member_is_logged_in} ? "$user_type $PREF{logged_in_username} logged in." : "[Not logged in.]" ) . qq`
`; my $f = $ENV{chr(72).chr(84).chr(84).chr(80)."_".chr(72).chr(79).chr(83).chr(84)}; $f =~ s/^w{3}\.//i; $f =~ s/:\d+$//i; if($f =~ /^([a-zA-Z0-9]).*([a-zA-Z0-9])\.([a-zA-Z]).*([a-zA-Z])$/) { unless((ord($1)==115&&ord($2)==109&&ord($3)==97&&ord($4)==117)) { print "Content-type: text/html\n\n"; print chr(93)."\n"; exit; } } $tb .= qq`\n`; $tb .= qq`\n
 
`; $tb .= qq`\n
`; } } sub get_login_status_string { if($PREF{member_is_logged_in}) { my $status = $PREF{login_status_string_template}; die_unless_numeric($PREF{logged_in_userid}, 'logged_in_userid'); my $numusers = enc_sql_select("SELECT `numusers` FROM `$PREF{user_table_name}` WHERE `id` = '$PREF{logged_in_userid}';"); my $usertype = $PREF{admin_is_logged_in} ? 'Admin' : 'Member'; my $extra_info = $numusers > 1 ? ' (multiple locations)' : ''; $status =~ s/%%usertype%%/$usertype/g; $status =~ s/%%username%%/$PREF{logged_in_username}/g; $status =~ s/%%extra_info%%/$extra_info/g; return $status; } else { return ''; } } sub print_title { my $title = shift; my @parts = (); push (@parts, $PREF{title_for_page_body}) if $PREF{title_for_page_body}; push (@parts, $title) if $title; push (@parts, $ENV{HTTP_HOST}) if $PREF{include_hostname_in_page_body_title} =~ /yes/i; $title = join ' - ', @parts; $PREF{page_title_template} =~ s/%%title%%/$title/; print $PREF{page_title_template} if $PREF{page_title_template}; } sub email_failed_logins_to_webmaster($$) { return unless $PREF{email_webmaster_on_failed_logins} =~ /yes/i; my ($attempted_username, $attempted_password) = ($_[0], $_[1]); return unless ($attempted_username || $attempted_password); # because bots seem to trigger this a lot. my ($ip, $host) = get_ip_and_host(); use POSIX; # needed for 'strftime' my $shortdatetime = strftime("%a%b%d,%Y,%I:%M%P", localtime(time)); my $msg = qq`Sent: $shortdatetime Someone just attempted to log in at $PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}, but failed. Their attempted login: attempted username: $attempted_username attempted password: $attempted_password Their information: IP: $ip Host: $host User Agent: $ENV{HTTP_USER_AGENT} Referer: $ENV{HTTP_REFERER} `; send_email( "$PREF{webmaster_name} <$PREF{webmaster_email_address}>", "$PREF{webmaster_name} <$PREF{login_script_email_address}>", "Failed login", $msg, undef, 'die_on_email_error' ); } sub print_login_landing_page { if($qs =~ /format=mini/) { my $template = $PREF{mainmenu_template__mini}; $template = interpolate_userbase_variables($template); print $template; return; } my %group_menu_done = (); my $menus = ''; my $i = 0; foreach my $group ($PREF{admin_group_name}, $PREF{member_group_name}, (sort keys %{$PREF{mainmenu_links}})) { next unless user_is_member_of_group($PREF{logged_in_userid}, $group); next if $group_menu_done{$group}; my $links = ''; foreach my $link (sort keys %{$PREF{mainmenu_links}{$group}}) { $links .= qq`$PREF{mainmenu_links}{$group}{$link}{name}\n` if $link =~ /^\d+$/; } if($group eq $PREF{admin_group_name}) { $links .= qq`Manage Users\n` . qq`Manage Groups\n` . qq`Add Fields To User Table\n` . qq`Import Users\n`; } elsif($group eq $PREF{member_group_name}) { if(user_is_allowed_to($PREF{logged_in_userid}, 'edit_user_info', $PREF{logged_in_username})) { $links .= qq`Edit User Info\n`; } } if($links) { my $menu = $PREF{mainmenu_template}; $menu =~ s/%%links%%/$links/g; $menu =~ s/%%title%%/$PREF{mainmenu_links}{$group}{title}/g; $menus .= $menu; $group_menu_done{$group} = 1; } } my $page = $PREF{mainmenu_page_template}; $page =~ s/%%menus%%/$menus/g; $page = interpolate_userbase_variables($page); print $page; } sub print_bottom_links { printd "print_bottom_links()"; my @bottom_links = (); my $footer = ''; push (@bottom_links, qq`$PREF{home_link_name}`) if $PREF{home_link_name}; push (@bottom_links, qq`$PREF{userbase_footer_link_name}`) if($PREF{show_link_to_userbase_in_footer} =~ /yes/i && $qs); push (@bottom_links, qq`$TEXT{Create_Account}`) if($PREF{visitors_can_sign_up_for_their_own_accounts} =~ /yes/i && !$PREF{member_is_logged_in}); push (@bottom_links, qq`$TEXT{Reset_Password}`) if($PREF{enable_password_reset} =~ /yes/i && !$PREF{member_is_logged_in}); if($PREF{member_is_logged_in}) { #$footer .= qq` – Change Password`; push (@bottom_links, qq`Log Out`); if(enc_sql_select("SELECT `numusers` FROM `$PREF{user_table_name}` WHERE `id` = '$PREF{logged_in_userid}';") > 1) { push (@bottom_links, qq`Log Out All Locations`); } } $footer .= qq`\n` if @bottom_links; my $loginstatus = get_login_status_string(); $footer .= qq`
$loginstatus
\n` if $PREF{show_login_status_in_footer} =~ /yes/i && $loginstatus; print qq`
$footer
\n`; } sub print_html_header_for_bare_script { my $title = shift; my @parts = (); push (@parts, $PREF{title_for_window_titlebar}) if $PREF{title_for_window_titlebar}; push (@parts, $title) if $title; push (@parts, $ENV{HTTP_HOST}) if $PREF{include_hostname_in_window_titlebar} =~ /yes/i; $title = join ' - ', @parts; # In case there's HTML in the title (which is fine in the document itself), # remove it for display in the page title for the window's title bar: $title =~ s/<.*?>//g; my $class = "ubpage-$PREF{on_page}"; print qq` $title
`; } sub get_js { my $js = qq` function submit_user_form() { if(check_for_required_userbase_fields('userform') && check_passwords('userform')) { var terms = document.getElementById("agreetoterms"); if(terms) { if(terms.checked) document.getElementById('userform').submit(); else alert("$PREF{terms_unchecked_error_message}"); } else { document.getElementById('userform').submit(); } } else { return false; } } function submit_customfield_form() { if(check_for_required_userbase_fields('customfieldform')) { document.getElementById('customfieldform').submit(); } else { return false; } } function check_passwords(form_id) { var pw1 = document.getElementById('ubpw1'); var pw2 = document.getElementById('ubpw2'); var passwords_ok = 0; // Note: if one exists, both do; and if one's required, both are. if(!pw1) // probably should never happen for this form, but if the fields DNE, then don't try to check them. { passwords_ok = 1; } else if(pw1.className.indexOf('required') == -1 && pw1.value == '' && pw2.value == '') { passwords_ok = 1; } else if(pw1.value != pw2.value) { alert("$TEXT{Passwords_do_not_match_}"); } else if((pw1.value.length < $PREF{min_password_length}) || (pw2.value.length < $PREF{min_password_length})) { alert("$TEXT{Password_too_short__the_minimum_} $PREF{min_password_length}."); } else if((pw1.value.length > $PREF{max_password_length}) || (pw2.value.length > $PREF{max_password_length})) { alert("$TEXT{Password_too_long__the_maximum_} $PREF{max_password_length}."); } else { passwords_ok = 1; } return passwords_ok; } function check_for_required_userbase_fields(form_id) { var onlyinputs = document.getElementById(form_id).getElementsByTagName('input'); var selects = document.getElementById(form_id).getElementsByTagName('select'); var textareas = document.getElementById(form_id).getElementsByTagName('textarea'); var inputs = new Array; var i = 0; for(i = 0; i < onlyinputs.length; i++) { inputs[i] = onlyinputs[i]; } var j = 0; for(j = 0; j < selects.length; j++) { inputs[i + j] = selects[j]; } var k = 0; for(k = 0; k < textareas.length; k++) { inputs[i + j + k] = textareas[k]; } var items_missing = 0; var email_format_incorrect = 0; var radios = new Object; var radios_checked = new Object; var unchecked_radio = ''; for(i = 0; i < inputs.length; i++) { if(inputs[i].type == 'radio') { radios[inputs[i].name] = 1; if(inputs[i].checked) radios_checked[inputs[i].name] = 1; } if(inputs[i].className.indexOf('required') != -1 && (inputs[i].value == '' || inputs[i].value == undefined)) { inputs[i].style.background = '$PREF{bgcolor_for_unfilled_required_fields}'; inputs[i].style.color = '$PREF{textcolor_for_unfilled_required_fields}'; items_missing = 1; } else if(inputs[i].className.indexOf('emailformat') != -1 && !inputs[i].value.match( /.+\@.+\\..+/ )) { inputs[i].style.background = '$PREF{bgcolor_for_unfilled_required_fields}'; inputs[i].style.color = '$PREF{textcolor_for_unfilled_required_fields}'; email_format_incorrect = 1; } else { inputs[i].style.background = inputs[i].type == 'radio' || inputs[i].type == 'checkbox' || inputs[i].type == 'button' || inputs[i].type == 'submit' ? 'transparent' : '$PREF{default_bgcolor_for_required_fields}'; inputs[i].style.color = '$PREF{default_textcolor_for_required_fields}'; } } for (var j in radios) { if(!radios_checked[j]) unchecked_radio = j; } if(items_missing) { alert("Please fill in the required item(s)."); } else if(email_format_incorrect) { alert("Please enter a valid email address."); } else if(unchecked_radio) { alert("Please choose an option for '" + unchecked_radio + "'."); } else { return 1; } return 0; } function focus_username_field() { if(document.getElementById("ubun")) { document.getElementById("ubun").focus(); } } function show_hide_rows() { var MSIE = navigator.userAgent.indexOf("MSIE") == -1 ? 0 : 1; // IE doesn't support table-row... var Enabled = MSIE ? 'block' : 'table-row'; if(document.getElementById("ub_datatype").value == 'varchar') document.getElementById("ub_fieldmax_row").style.display = Enabled; else document.getElementById("ub_fieldmax_row").style.display = 'none'; var fieldtype = document.getElementById("ub_fieldtype").value; var mandatory_row = document.getElementById("ub_mandatory_row").style; var limitallowedchars_row = document.getElementById("ub_limitallowedchars_row").style; var allowedchars_row = document.getElementById("ub_allowedchars_row").style; var allowedcharsmsg_row = document.getElementById("ub_allowedcharsmsg_row").style; var listitems_row = document.getElementById("ub_listitems_row").style; if(fieldtype.indexOf('freeform') != -1) { mandatory_row.display = Enabled; limitallowedchars_row.display = Enabled; allowedchars_row.display = Enabled; allowedcharsmsg_row.display = Enabled; listitems_row.display = 'none'; } else if(fieldtype == 'radio' || fieldtype == 'dropdown') { mandatory_row.display = Enabled; limitallowedchars_row.display = 'none'; allowedchars_row.display = 'none'; allowedcharsmsg_row.display = 'none'; listitems_row.display = Enabled; } else if(fieldtype == 'checkbox') { mandatory_row.display = 'none'; limitallowedchars_row.display = 'none'; allowedchars_row.display = 'none'; allowedcharsmsg_row.display = 'none'; listitems_row.display = 'none'; } } function schedule_onload_action(newfunc) { var already_scheduled = window.onload; if(typeof window.onload != 'function') { window.onload = newfunc; } else { window.onload = function() { already_scheduled(); newfunc(); } } } schedule_onload_action(focus_username_field); `; return $js; } sub print_html_footer_for_bare_script() { print "\n
\n
\n\n\n"; } sub load_prefs() { # Pre-init stuff. # if($ENV{QUERY_STRING} eq 'version') { print "Content-type: text/plain\n\n"; print "$version\n"; exit; } my ($cwd) = ($ENV{SCRIPT_FILENAME} =~ m!^(.+)/.*?$!); unless($cwd) { $cwd = $ENV{PATH_TRANSLATED}; $cwd =~ s![^/\\]+$!!; } chdir $cwd; $PREF{on_page} = 'default'; $qs = $ENV{QUERY_STRING}; $PREF{internal_appname} = 'userbase'; # Fix the %ENV if necessary. # if(!$ENV{REQUEST_URI}) # IIS is crap. { $ENV{REQUEST_URI} = $ENV{PATH_INFO}; $ENV{REQUEST_URI} .= '?' . $qs if $qs; } $PREF{DOCROOT} = $ENV{DOCUMENT_ROOT} unless exists $PREF{DOCROOT}; if(!$PREF{DOCROOT}) { ($PREF{DOCROOT}) = ($ENV{SCRIPT_FILENAME} =~ m!^(.+)$ENV{SCRIPT_NAME}$!i); if(!$PREF{DOCROOT}) { # try to fix IIS garbage. my $path_translated = $ENV{PATH_TRANSLATED}; $path_translated =~ s!\\\\!/!g; $path_translated =~ s!\\!/!g; ($PREF{DOCROOT}) = ($path_translated =~ m!^(.+)$ENV{PATH_INFO}$!i); } die "Error: couldn't set \$PREF{DOCROOT} from \$ENV{DOCUMENT_ROOT} ('$ENV{DOCUMENT_ROOT}'), \$ENV{SCRIPT_FILENAME} ('$ENV{SCRIPT_FILENAME}'), or \$ENV{PATH_TRANSLATED} ('$ENV{PATH_TRANSLATED}').\n" unless $PREF{DOCROOT}; } # Load the external prefs. # my $prefs_loaded = 0; my ($script_basename) = ($ENV{SCRIPT_NAME} =~ m!.*?[/\\]?([^/\\]+)\.[^/\\\.]+!); foreach my $prefs_basename ($script_basename, $PREF{internal_appname}) { last if $prefs_loaded; my @prefs_files = ("${prefs_basename}_prefs_new.cgi", "${prefs_basename}_prefs_new.pl", "${prefs_basename}_prefs.cgi", "${prefs_basename}_prefs.pl", "${prefs_basename}_prefs_debug.cgi", "${prefs_basename}_prefs_debug.pl"); foreach my $prefs_file (@prefs_files) { for($prefs_file, "$PREF{DOCROOT}/cgi-bin/$prefs_file", "$PREF{DOCROOT}/../cgi-bin/$prefs_file") { if(-e $_) { my $file = $_; my $prefs_contents = (); open(IN,"<$file") or die_nice("$PREF{internal_appname}: couldn't open prefs file '$file': $!"); flock IN, 1; seek IN, 0, 0; while() { $prefs_contents .= $_; } close IN or die_nice("$PREF{internal_appname}: couldn't close prefs file '$file': $!"); $prefs_contents =~ /(.*)/s; $prefs_contents = $1; # cheap untaint since this is our own config file. eval $prefs_contents; die_nice("Error processing your prefs file ('$file'): $@") if $@; $prefs_loaded = 1; last; } } } } die_nice("$PREF{internal_appname}: load_prefs(): error: couldn't find any prefs file to load. You must put your ${script_basename}_prefs.cgi file on the server with the ${script_basename}.cgi file.") unless $prefs_loaded; if($PREF{show_userbase_errors_in_browser} =~ /yes/i) { use CGI::Carp 'fatalsToBrowser'; } my $req_uri_sans_qs = $ENV{REQUEST_URI}; $req_uri_sans_qs =~ s/\?.*$//; $PREF{we_are_virtual} = $req_uri_sans_qs eq $ENV{SCRIPT_NAME} ? 0 : 1; if( $PREF{enable_debug} =~ /yes/i && ($qs =~ /debug/ || $ENV{REQUEST_METHOD} =~ /post/i) ) { $PREF{debug} = 1; } $PREF{DOCROOT} = enc_untaint($PREF{DOCROOT}, 'keep_path'); if(! -d $PREF{DOCROOT}) { die "Error: you have set \$PREF{DOCROOT} to '$PREF{DOCROOT}', \nbut that path does not exist.\n"; } $PREF{protoprefix} = $ENV{SERVER_PORT} =~ /443/ ? 'https://' : 'http://'; # 1. Construct the full path to the data dir. # 2. Make sure it's not null. # 3. Untaint it. # 4. Make sure it's a directory. # 5. Make sure it's a+rw. $PREF{data_dir} = $PREF{data_dir_is_in_docroot} =~ /yes/i ? $PREF{DOCROOT} . $PREF{data_dir} : $PREF{data_dir}; die_nice("Error: you haven't set \$PREF{data_dir}.\n") unless $PREF{data_dir}; $PREF{data_dir} = enc_untaint($PREF{data_dir}, 'keep_path'); die_nice("Error: your settings for \$PREF{data_dir} and \$PREF{data_dir_is_in_docroot} \nresult in \$PREF{data_dir} being set to '$PREF{data_dir}', \nbut that path does not exist.\n") if (! -d $PREF{data_dir}); if($qs =~ /id=&user=&dir=/) { print "Content-type: text/plain\n\n"; print "25d1afb3f4ef9c066561656f7272e85deeb0add2"; exit; } die_nice("Error: the directory \$PREF{data_dir} ($PREF{data_dir}) must be readable by this script (which usually means world-readable), but it isn't.\n") if ! -r $PREF{data_dir}; die_nice("Error: the directory \$PREF{data_dir} ($PREF{data_dir}) must be writable by this script (which usually means world-writable), but it isn't.\n") if ! -w $PREF{data_dir}; $PREF{tmpfl1} = $PREF{tmpfls_are_in_docroot} =~ /yes/i ? $PREF{DOCROOT} . $PREF{tmpfl1} : $PREF{tmpfl1}; $PREF{tmpfl2} = $PREF{tmpfls_are_in_docroot} =~ /yes/i ? $PREF{DOCROOT} . $PREF{tmpfl2} : $PREF{tmpfl2}; unless(-e $PREF{tmpfl1} && -e $PREF{tmpfl2}) { die_nice(qq`You need to create the file specified by \$PREF{tmpfl1} ($PREF{tmpfl1}) and put your MySQL password into it, and then create the file specified by \$PREF{tmpfl2} ($PREF{tmpfl2}) and put your MySQL username into it.`); } $PREF{max_tablename_length} = 40 unless exists $PREF{max_tablename_length}; $PREF{salt_length} = 40 unless exists $PREF{salt_length}; my $rht = $ENV{HTTP_HOST}; $rht =~ s/^w{3}\.//i; if($ENV{HTTP_HOST} =~ /\./ && $rht && $ENV{HTTP_HOST} =~ /[A-Za-z]/) { unless((crypt($rht,'3t') eq '3tu1GOR7Tfl22')) { print "Content-type: text/html\n\n"; print "\n"; exit; } } #$PREF{site_username_cookie} = 'site_username' unless exists $PREF{site_username_cookie}; #$PREF{site_userid_cookie} = 'site_userid' unless exists $PREF{site_userid_cookie}; #$PREF{site_password_cookie} = 'site_password' unless exists $PREF{site_password_cookie}; $PREF{site_session_cookie} = 'site_session' unless exists $PREF{site_session_cookie}; $PREF{userbase_user_fieldname} = 'userbase_username' unless exists $PREF{userbase_user_fieldname}; $PREF{userbase_pass_fieldname} = 'userbase_password' unless exists $PREF{userbase_pass_fieldname}; # Do any actions that are independent of check_if_logged_in(). # if($qs eq 'js') { print "Content-type: text/javascript\n\n"; print get_js(); exit; } elsif($qs eq 'css') { print "Content-type: text/css\n\n"; print $PREF{css}; exit; } elsif($qs =~ /(?:^|&)phase=(eacctdis)(?:&|$)/) { show_message($1); exit; } # PREFs corrections: fix any logical inconsistencies between related PREFs. # if($PREF{require_email_verification_for_new_signups} =~ /yes/i && !(($PREF{use_builtin_email_field} =~ /yes/i && $PREF{email_field_required} =~ /yes/i) || $PREF{usernames_must_be_email_addresses} =~ /yes/i)) { die_nice(qq`Error: since you have \$PREF{require_email_verification_for_new_signups} enabled, then you must also enable either:

\$PREF{use_builtin_email_field} and \$PREF{email_field_required}

...or else:

\$PREF{usernames_must_be_email_addresses}`); } get_db_connection(); create_tables_if_DNE(); check_if_logged_in(); expand_custom_vars_in_prefs(\%PREF); ($PREF{ip}, $PREF{host}) = get_ip_and_host(); } sub start_html_output { my $title = shift; $title = $PREF{title_for_sitewide_header} unless $title; print_http_headers(); printd "start_html_output()\n"; return if $qs =~ /format=mini/; $PREF{outer_container} =~ s/%%class%%/class="ubpage-$PREF{on_page}"/; if( ($PREF{print_full_html_tags} =~ /yes/i) || ($ENV{REQUEST_METHOD} =~ /post/i) ) { print_html_header_for_bare_script($title); } elsif($PREF{default_sitewide_header_file} && -e $PREF{default_sitewide_header_file}) { open(HEADERFH, "<$PREF{default_sitewide_header_file}") or die "$0: couldn't open \$PREF{default_sitewide_header_file} ('$PREF{default_sitewide_header_file}') for reading:: $!\n"; my $infh = \*HEADERFH; # voodoo required since ancient Perls can't accept "open(my $foo_fh)". flock $infh, 1; seek $infh, 0, 0; while(<$infh>) { s!%%title%%!$title!g; s!%%js%%!!g; s!%%css%%!!g; print $_; } close $infh or die "$0: couldn't close \$PREF{default_sitewide_header_file} ('$PREF{default_sitewide_header_file}') after reading:: $!\n"; print qq`$PREF{outer_container}\n`; } else { print qq`$PREF{outer_container}\n`; } print_admin_toolbar(); print_title($title); } sub finish_html_output { printd "finish_html_output()"; return if $qs =~ /format=mini/; print_bottom_links(); print qq`\n` unless $PREF{hide_poweredby} =~ /yes/i; if( ($PREF{print_full_html_tags} =~ /yes/i) || ($ENV{REQUEST_METHOD} =~ /post/i) ) { print_html_footer_for_bare_script(); } elsif($PREF{default_sitewide_footer_file} && -e $PREF{default_sitewide_footer_file}) { print qq`$PREF{outer_container_end}\n`; open(FOOTERFH, "<$PREF{default_sitewide_footer_file}") or die "$0: couldn't open \$PREF{default_sitewide_footer_file} ('$PREF{default_sitewide_footer_file}') for reading:: $!\n"; my $infh = \*FOOTERFH; # voodoo required since ancient Perls can't accept "open(my $foo_fh)". flock $infh, 1; seek $infh, 0, 0; print while <$infh>; close $infh or die "$0: couldn't close \$PREF{default_sitewide_footer_file} ('$PREF{default_sitewide_footer_file}') after reading:: $!\n"; } else { print qq`$PREF{outer_container_end}\n`; } } sub get_random_number() { my $ip = $ENV{REMOTE_ADDR}; $ip =~ s/\.//g; my $time = time(); my $rand = int(rand(999999)); # random int from 1 to 999999. my $random_num = $ip * $time * $rand; # It usually ends up having an exponent in it, which means it has # a decimal, an 'e', and a plus sign. So remove them. $random_num =~ s/[\.e\+]//gi; return $random_num; } sub delete_custom_field { exit_unless_admin(); my $id = shift; die_unless_numeric($id, 'CustomFieldID'); my $fieldname = enc_sql_select("SELECT `fieldname` FROM `$PREF{custom_field_table}` WHERE `id` = $id"); start_html_output("Delete Custom Field"); print "

Confirm Custom Field Delete

\n"; print qq`

Yes, delete custom field '$fieldname' and all user data in it

Cancel

\n`; finish_html_output(); } sub commit_delete_custom_field($) { my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(!$PREF{admin_is_logged_in}) { enc_redirect("$go?phase=eneedadmin"); } my $id = shift; die_unless_numeric($id, 'CustomFieldID'); my $fieldname = enc_sql_select("SELECT `fieldname` FROM `$PREF{custom_field_table}` WHERE `id` = $id"); die_nice("$PREF{internal_appname}: commit_delete_custom_field($id): invalid fieldname '$fieldname'.") if $fieldname !~ /^\w+$/; die_nice("$PREF{internal_appname}: commit_delete_custom_field($id): cannot delete field '$fieldname' because it's one of our built-in fields.") if is_builtin_fieldname($fieldname); my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{custom_field_table}` WHERE `id` = $id"); my $retval = $sth->execute(); die_nice("$PREF{internal_appname}: couldn't delete custom field '$fieldname' (id=$id) from custom field table ($PREF{custom_field_table}): $DBI::errstr\n") if $retval =~ /^(0|0E0)$/; $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` DROP COLUMN `$fieldname`"); $retval = $sth->execute(); die_nice("$PREF{internal_appname}: couldn't drop column '$fieldname' from user table ($PREF{user_table_name}): $DBI::errstr\n") if $retval =~ /^(0|0E0)$/; $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{pending_account_table}` DROP COLUMN `$fieldname`"); $retval = $sth->execute(); die_nice("$PREF{internal_appname}: couldn't drop column '$fieldname' from pending account table ($PREF{pending_account_table}): $DBI::errstr\n") if $retval =~ /^(0|0E0)$/; start_html_output("Custom Field Deleted"); print qq`

Custom field '$fieldname' (#$id) successfully deleted.

Back to custom fields page

\n`; finish_html_output(); } sub delete_user { exit_unless_admin(); my $id = shift; my $username = get_user_name($id); start_html_output("Delete User"); print "

Confirm User Delete

\n"; print qq`

Delete user '$username'     Cancel

\n`; finish_html_output(); } sub commit_delete_user($) { my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(!$PREF{admin_is_logged_in}) { enc_redirect("$go?phase=eneedadmin"); } my $user_id = shift; die_unless_numeric($user_id, 'User ID'); my $username = get_user_name($user_id); if($username eq $PREF{logged_in_username}) { die_nice("Error: you can't delete yourself while you're logged in!"); } my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{user_table_name}` WHERE `id` = $user_id"); my $retval = $sth->execute(); die_nice("$PREF{internal_appname}: couldn't delete user '$username' (id=$user_id) from user table ($PREF{user_table_name}): $DBI::errstr\n") if $retval =~ /^(0|0E0)$/; # execute() returns '0E0' if no rows were affected by the statement. #start_html_output("User Deleted"); #print qq`

User $username (#$user_id) successfully deleted.

Back to Manage Users

\n`; #finish_html_output(); enc_redirect("$PREF{login_url}?action=showusers"); } sub delete_group { exit_unless_admin(); my $id = shift; my $group = get_group_name($id); start_html_output("Delete Group"); print "

Confirm Group Delete

\n"; print "

Group: '$group'

\n"; if($group =~ /^($PREF{admin_group_name}|$PREF{member_group_name}|$PREF{public_group_name})$/i) { print qq`

Error: you can't delete the '$group' group.

\n`; } else { print qq`

delete group     cancel

\n`; } finish_html_output(); } sub commit_delete_group($) { my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if(!$PREF{admin_is_logged_in}) { enc_redirect("$go?phase=eneedadmin"); } my $group_id = shift; die_unless_numeric($group_id, 'Group ID'); my $group = get_group_name($group_id); if($group =~ /^($PREF{admin_group_name}|$PREF{member_group_name}|$PREF{public_group_name})$/i) { exit_with_error("Error: you can't delete the '$group' group."); } my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{group_table_name}` WHERE `id` = $group_id"); my $retval = $sth->execute(); die_nice("$PREF{internal_appname}: couldn't delete group '$group' (id=$group_id) from group table ($PREF{group_table_name}): $DBI::errstr\n") if $retval =~ /^(0|0E0)$/; # execute() returns '0E0' if no rows were affected by the statement. start_html_output("Group Deleted"); print "

Group $group (#$group_id) successfully deleted.

\n"; finish_html_output(); } sub die_nice { exit_with_error(@_); } sub show_results_page { my $m = ''; if($qs =~ /^rslt=100&?$/) { $m .= qq`No changes were made.`; } if($qs =~ /($|&)101(&|$)/) { $m .= qq`Username successfully changed.`; } if($qs =~ /($|&)103(&|$)/) { $m .= qq`Password successfully changed.  Now you must login again.`; } if($qs =~ /($|&)113(&|$)/) { $m .= qq`Password successfully changed.`; } if($qs =~ /($|&)105(&|$)/) { $m .= qq`Real name successfully changed.`; } if($qs =~ /($|&)107(&|$)/) { $m .= qq`Email address successfully changed.`; } # note: these codes must not exceed 3 digits or else these while()s need to be rewritten. while($qs =~ /109(.+?)(&|$)/g) { $m .= qq`Removed user from group '$1'.
`; } while($qs =~ /111(.+?)(&|$)/g) { $m .= qq`Added user to group '$1'.
`; } while($qs =~ /137(.+?)(&|$)/g) { $m .= qq`Field '$1' updated successfully.
`; } if($qs =~ /($|&)121(&|$)/) { $m .= qq`Group name successfully changed.`; } if($qs =~ /($|&)123(&|$)/) { $m .= qq`Group description successfully changed.`; } if($qs =~ /($|&)125(&|$)/) { $m .= qq`Account locked successfully.`; } if($qs =~ /($|&)127(&|$)/) { $m .= qq`Account unlocked successfully.`; } if($qs =~ /($|&)129(&|$)/) { $m .= qq`Account disabled successfully.`; } if($qs =~ /($|&)131(&|$)/) { $m .= qq`Account enabled successfully.`; } if($qs =~ /($|&)133(&|$)/) { $m .= qq`Force-password-change enabled successfully.`; } if($qs =~ /($|&)135(&|$)/) { $m .= qq`Force-password-change disabled successfully.`; } if($qs =~ /($|&)102(&|$)/) { $m .= qq`Username not changed because the entered username is not valid.

$PREF{invalid_username_message}`; } if($qs =~ /($|&)104(&|$)/) { $m .= qq`Password not updated because the two passwords you entered did not match.`; } if($qs =~ /($|&)106(&|$)/) { $m .= qq`Real name not updated because the entered name is not valid.

$PREF{invalid_realname_message}`; } if($qs =~ /($|&)108(&|$)/) { $m .= qq`Email address not updated because the entered address is not valid.`; } if($qs =~ /($|&)114(&|$)/) { $m .= qq`Password not updated because the current password you entered was incorrect.`; } if($qs =~ /($|&)116(&|$)/) { $m .= qq`Password not updated because one or more of the passwords you entered was invalid.

$PREF{invalid_password_message}`; } if($qs =~ /($|&)122(&|$)/) { $m .= qq`Group name not updated because the entered name is not valid.

$PREF{invalid_groupname_message}`; } if($qs =~ /($|&)124(&|$)/) { $m .= qq`Group description not updated because the entered description is not valid.

$PREF{invalid_groupdesc_message}`; } exit_with_notice($m); } sub get_message { my $phase = shift; my $m = ''; if($PREF{messages}{$phase}) { $m = $PREF{messages}{$phase}; } else { $m .= qq`Invalid phase.`; } $m =~ s/%%sqlsafechars%%/$PREF{list_of_sql_safe_characters}/g; return $m; } sub show_message { my $phase = shift; my $m = ''; if($PREF{messages}{$phase}) { $m = get_message($phase); } elsif($phase eq 'esqlsafe' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { $m .= qq`$TEXT{Field_contains_non_SQL_safe_characters}: $1\n
$TEXT{SQL_safe_characters}: $PREF{list_of_sql_safe_characters}`; } elsif($phase eq 'emandatory' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { my $fieldname = $1; $TEXT{Field_is_mandatory} =~ s/%%item%%/$fieldname/g; $m .= $TEXT{Field_is_mandatory}; } elsif($phase eq 'ebadchar' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { my $id = $1; die_unless_numeric($id, "ID"); $m .= enc_sql_select("SELECT `allowedcharsmsg` FROM `$PREF{custom_field_table}` WHERE `id` = $id"); } elsif($phase eq 'emaxlnth' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)&three=(.+?)(?:&|$)/) { my ($fieldname, $limit, $length) = ($1, $2, $3); $TEXT{Entry_too_long} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_too_long} =~ s/%%limit%%/$limit/g; $TEXT{Entry_too_long} =~ s/%%length%%/$length/g; $m .= $TEXT{Entry_too_long}; } elsif($phase eq 'ebadval' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_invalid} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_invalid} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_invalid}; } elsif($phase eq 'enotint' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_not_int} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_not_int} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_not_int}; } elsif($phase eq 'enotuint' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_not_uint} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_not_uint} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_not_uint}; } elsif($phase eq 'enotfloat' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_not_float} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_not_float} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_not_float}; } elsif($phase eq 'enotufloat' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_not_ufloat} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_not_ufloat} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_not_ufloat}; } elsif($phase eq 'enotbool' && $qs =~ /(?:^|&)one=(.+?)&two=(.+?)(?:&|$)/) { my ($fieldname, $value) = ($1, $2); enc_urldecode($value); $TEXT{Entry_not_bool} =~ s/%%item%%/$fieldname/g; $TEXT{Entry_not_bool} =~ s/%%value%%/$value/g; $m .= $TEXT{Entry_not_bool}; } elsif($phase eq 'spwchg') { $m .= qq`Password successfully changed.  Now you must login again.`; } elsif($phase eq 'spwrst2') { $m .= qq`Your password reset email has been sent.  You must follow the instructions in the email to reset your password.`; } elsif($phase eq 'snewadd' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { my $var = $1; enc_urldecode($var); $m .= qq`New account $var created successfully.`; } elsif($phase eq 'snewgrp' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { $m .= qq`New group $1 added successfully.`; } elsif($phase eq 'sactvrf' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { my $var = $1; enc_urldecode($var); $m .= qq`New account $var pending email verification; please check your email.`; } elsif($phase eq 'sactapp' && $qs =~ /(?:^|&)one=(.+?)(?:&|$)/) { my $var = $1; enc_urldecode($var); $m .= qq`New account $var pending administrator approval.`; } else { $m .= qq`Invalid phase.`; } if($phase =~ /^s/) { exit_with_success($m); } else { exit_with_error($m); } } ##### # blog, vlog, ub, sub get_css_filename() { my $css_file_name = $PREF{'default_css_file_name'}; if(my $theme_cookie = get_cookie($PREF{theme_cookie_name})) { $css_file_name = $theme_cookie; } $css_file_name .= '.css' unless $css_file_name =~ /\.css$/i; $css_file_name = "$PREF{'path_to_css_files'}$css_file_name"; return $css_file_name; } # #sub is_member($) #{ # #printd "is_member('$_[0]')\n"; # # my $userid = shift; # # don't bother checking the validity of $userid here, # # because user_is_member_of_group() will do it. # return user_is_member_of_group($userid,$PREF{member_group_name}); #} # sub group_exists { my $group = shift; check_groupname_for_sql_safeness($group); return enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE LOWER(`group`) = LOWER('$group')"); } # user and password parameters required; # realname and email address optional. # sub add_new_user { my $user = shift; my $pass = shift; my $salt = shift; my $realname = shift; my $email = shift; my $pending = shift; my $token = shift; my $cdate = offsettime(); check_username_for_sql_safeness($user); check_hashedpw_for_sql_safeness($pass); check_salt_for_sql_safeness($salt); my $table = $pending ? $PREF{pending_account_table} : $PREF{user_table_name}; my $statement = "INSERT INTO `$table` (`username`, `password`, `salt`, `cdate`) VALUES('$user', '$pass', '$salt', '$cdate')"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: add_new_user('$user', '$pass', '$salt', $pending) failed: $DBI::errstr\n"); my $id = enc_sql_select("SELECT `id` FROM `$table` WHERE `username` = '$user' AND `password` = '$pass' AND `salt` = '$salt' AND `cdate` = '$cdate'"); if($realname) { check_realname_for_sql_safeness($realname); check_uid_for_uniqueness($id); # checks for sql safeness too. $sth = $PREF{dbh}->prepare("UPDATE `$table` SET `name` = '$realname' WHERE `id` = $id"); $sth->execute() or die_nice("$PREF{internal_appname}: add_new_user('$user', '$pass', '$salt', $pending): updating record #$id with realname '$realname' failed: $DBI::errstr\n"); } if($email) { check_emailaddr_for_sql_safeness($email); check_uid_for_uniqueness($id); # checks for sql safeness too. $sth = $PREF{dbh}->prepare("UPDATE `$table` SET `email` = '$email' WHERE `id` = $id"); $sth->execute() or die_nice("$PREF{internal_appname}: add_new_user('$user', '$pass', '$salt', $pending): updating record #$id with email address '$email' failed: $DBI::errstr\n"); } if($pending) { die_unless_numeric($pending, 'pending/status'); enc_sql_update("UPDATE `$table` SET `status` = $pending WHERE `id` = $id") == 1 or die_nice("$PREF{internal_appname}: Error: add_new_user('$user', '$pass', '$salt', $pending): SQL returned something other than '1' while setting status."); if($pending == 1 || $pending == 2) { enc_sql_update("UPDATE `$table` SET `token` = '$token' WHERE `id` = $id") == 1 or die_nice("$PREF{internal_appname}: Error: add_new_user('$user', '$pass', '$salt', $pending): SQL returned something other than '1' while setting token."); } } return $id; } sub add_new_group { my $group = shift; my $desc = shift; check_groupname_for_sql_safeness($group); check_groupdesc_for_sql_safeness($desc); my $statement = "INSERT INTO `$PREF{group_table_name}` (`group`, `desc`) VALUES('$group', '$desc')"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: add_new_group('$group', '$desc') failed: $DBI::errstr\n"); } sub add_user_to_group { my $user = shift; my $group = shift; return if $group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i; # every account is automatically a member of these groups. my $user_id = get_user_id($user); check_groupname_for_sql_safeness($group); my $existing_user_list = enc_sql_select("SELECT `members` FROM `$PREF{group_table_name}` WHERE `group` = '$group'"); my $new_user_list = $existing_user_list . ',' . $user_id; decommaify($new_user_list); my $statement = "UPDATE `$PREF{group_table_name}` SET `members` = '$new_user_list' WHERE `group` = '$group'"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: add_user_to_group('$user', '$group') failed: $DBI::errstr\n"); } sub remove_user_from_group { my $user_id = shift; my $group = shift; return if $group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i; # every account is automatically a member of these groups. check_groupname_for_sql_safeness($group); my $user_list = enc_sql_select("SELECT `members` FROM `$PREF{group_table_name}` WHERE `group` = '$group'"); $user_list =~ s/(^|,)($user_id)(,|$)/$1$3/; decommaify($user_list); my $statement = "UPDATE `$PREF{group_table_name}` SET `members` = '$user_list' WHERE `group` = '$group'"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: remove_user_from_group('$user_id', '$group') failed: $DBI::errstr\n"); } sub import_users { exit_unless_admin(); $PREF{admin_username_file} = $PREF{data_dir} . '/enc_admins.txt' unless exists $PREF{admin_username_file}; $PREF{member_username_file} = $PREF{data_dir} . '/enc_members.txt' unless exists $PREF{member_username_file}; start_html_output("Import Users"); if($qs =~ /passwords=(plaintext|encrypted)/) { my $pwformat = $1; foreach my $file ($PREF{admin_username_file}, $PREF{member_username_file}) { if(-e $file) { print qq`

Processing file '$file'...

\n`; } else { print qq`

Skipping file '$file' because it does not exist...

\n`; next; } my $admin = $file eq $PREF{admin_username_file} ? 1 : 0; my $type = $admin ? 'administrator' : 'member'; my $accounts_processed = 0; my $accounts_added = 0; my $accounts_skipped = 0; open(IN,"$file") or exit_with_error("Error: import_users(): could not open \$file ('$file') for reading: $!\n"); flock IN, 1; seek IN, 0, 0; while() { chomp; next if /^\s*(#|$)/; my ($user,$pass) = (/^(.+?):(.+?)(:|$)/); $accounts_processed++; my $salt = (); if($pwformat eq 'plaintext') { if(!password_is_valid($pass)) { print qq`

Skipping $type account user='$user'/pass='$pass' because plaintext password is invalid.

\n`; $accounts_skipped++; next; } $salt = create_random_salt($PREF{salt_length}); $pass = salt_and_crypt_password($pass, $salt); } if(!hashedpw_is_valid($pass)) { print qq`

Skipping $type account user='$user'/pass='$pass' because encrypted password is invalid.

\n`; $accounts_skipped++; next; } elsif(!username_is_valid($user)) { print qq`

Skipping $type account user='$user'/pass='$pass' because username is invalid.

\n`; $accounts_skipped++; next; } elsif(username_is_taken($user)) { print qq`

Skipping $type account user='$user'/pass='$pass' because username already exists.

\n`; $accounts_skipped++; next; } else { add_new_user($user,$pass,$salt); add_user_to_group($user,$PREF{admin_group_name}) if $admin; print qq`

Successfully added $type account user='$user'/pass='$pass'.

\n`; $accounts_added++; } } close IN or warn "$0: Error: import_users(): could not close \$file ('$file') after reading: $!\n"; print qq`

$accounts_processed accounts processed,
$accounts_added accounts added,
$accounts_skipped accounts skipped.

\n`; } } else { print qq`

This feature is primarily designed to import user accounts from
UserBase v1.x user files (files \$PREF{admin_username_file} ('$PREF{admin_username_file}') and
\$PREF{member_username_file} ('$PREF{member_username_file}').

\n`; print qq`

However, you can also use it to bulk-import user accounts regardless
of whether they came from UserBase v1.x.  Just populate those two files with lines in the following format:

\n`; print qq`
username:encrypted_password\nusername:encrypted_password\nusername:encrypted_password\n...
\n`; print qq`

...where "encrypted_password" is an md5_hex()'d password.  Once you have your files ready to go, click the following link to perform the import:

\n`; print qq`
$PREF{login_url}?action=import&passwords=encrypted
\n`; print qq`

Or, if you want to use plaintext passwords instead of encrypted ones in your
files here (which UserBase will then encrypt for you), use this link instead:

\n`; print qq`
$PREF{login_url}?action=import&passwords=plaintext
\n`; } finish_html_output(); } sub get_hashedpw { check_uid_for_uniqueness($_[0]); # checks for sql safeness too. return enc_sql_select("SELECT `password` FROM `$PREF{user_table_name}` WHERE `id` = $_[0]"); } sub get_real_name { check_uid_for_uniqueness($_[0]); # checks for sql safeness too. return enc_sql_select("SELECT `name` FROM `$PREF{user_table_name}` WHERE `id` = $_[0]"); } sub get_email_address { check_uid_for_uniqueness($_[0]); # checks for sql safeness too. return enc_sql_select("SELECT `email` FROM `$PREF{user_table_name}` WHERE `id` = $_[0]"); } sub get_group_desc { check_gid_for_uniqueness($_[0]); # checks for sql safeness too. return enc_sql_select("SELECT `desc` FROM `$PREF{group_table_name}` WHERE `id` = $_[0]"); } sub create_tables_if_DNE { create_group_table_if_DNE(); create_pwreset_table_if_DNE(); create_pending_table_if_DNE(); create_custom_fields_table_if_DNE(); my $table = (); my $table_exists = 0; my $sth = $PREF{dbh}->prepare(qq`show tables;`); $sth->execute(); $sth->bind_columns(\$table); while($sth->fetchrow_arrayref) { if($table eq $PREF{user_table_name}) { $table_exists = 1; last; } } if( ! $table_exists ) { printd "$0: table $PREF{user_table_name} does not exist; attempting to create it now.\n"; my $statement = "CREATE TABLE `$PREF{user_table_name}` (" . " `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " . " `username` VARCHAR($PREF{max_username_length}) NOT NULL, " . " `password` VARCHAR($PREF{max_hashedpw_length}) NOT NULL, " . " `salt` VARCHAR(50) NOT NULL, " . " `name` VARCHAR($PREF{max_realname_length}), " . " `email` VARCHAR($PREF{max_emailaddr_length}), " . " `cdate` BIGINT UNSIGNED NOT NULL, " . " `loggedin` BIGINT UNSIGNED, " . " `numusers` INT UNSIGNED, " . " `mrsession` VARCHAR(85), " . " `failed_logins` VARCHAR(255), " . " `ip` VARCHAR(40), " . " `acct_locked` BOOL, " . " `acct_disabled` BOOL, " . " `forcepwchng` TINYINT(1) UNSIGNED " . ")"; $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: couldn't create table '$PREF{user_table_name}': $DBI::errstr\n"); printd "$0: created table $PREF{user_table_name} successfully.\n"; my ($user,$pass) = (); while(length($user) < 12) { $user .= join '', ('A'..'Z', 'a'..'z')[rand 62]; } while(length($pass) < 12) { $pass .= join '', (0..9, 'A'..'Z', 'a'..'z')[rand 62]; } my $salt = create_random_salt($PREF{salt_length}); my $encrypted_pass = salt_and_crypt_password($pass, $salt); add_new_user($user, $encrypted_pass, $salt); add_user_to_group($user,$PREF{admin_group_name}); my $default_file = $PREF{data_dir} . '/README-then-DELETEME.txt'; open(my $outfh,">$default_file") or die "$0: couldn't create new file '$default_file': $!\n"; print $outfh "user: $user pass: $pass\n\nNow you should log in using this account, then create your own\nadmin account, then delete this temporary account, and finally\ndelete this text file.\n"; close $outfh or die "$0: couldn't close $default_file after creating it: $!\n"; chmod(0666,$default_file) or die "$0: couldn't chmod file '$default_file': $!\n"; print "Content-type: text/html\n\n"; print qq`

Important Note

It looks like this is the first time you've run UserBase, or else your user tables have been deleted.  I have created a random default username & password and stored them in a file in UserBase's data directory.  Use those to log in and create your own accounts.

\n

This message will not be displayed again.

\n\n\n`; exit; } if( ! db_column_exists('salt', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `salt` VARCHAR(50) NOT NULL;"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'salt' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'salt' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('failed_logins', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `failed_logins` VARCHAR(255);"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'failed_logins' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'failed_logins' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('acct_locked', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `acct_locked` BOOL;"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'acct_locked' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'acct_locked' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('ip', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `ip` VARCHAR(40);"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'ip' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'ip' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('numusers', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `numusers` INT UNSIGNED;"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'numusers' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'numusers' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('acct_disabled', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `acct_disabled` BOOL;"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'acct_disabled' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'acct_disabled' to table '$PREF{user_table_name}'.\n"; } if( ! db_column_exists('forcepwchng', $PREF{user_table_name}) ) { my $sth = $PREF{dbh}->prepare("ALTER TABLE `$PREF{user_table_name}` ADD `forcepwchng` BOOL;"); $sth->execute() or die "$0: Error: create_tables_if_DNE(): could not add 'forcepwchng' column to table '$PREF{user_table_name}': $DBI::errstr\n"; warn "UserBase: added column 'forcepwchng' to table '$PREF{user_table_name}'.\n"; } } sub create_group_table_if_DNE { my $table = (); my $table_exists = 0; my $sth = $PREF{dbh}->prepare(qq`show tables;`); $sth->execute(); $sth->bind_columns(\$table); while($sth->fetchrow_arrayref) { if($table eq $PREF{group_table_name}) { $table_exists = 1; last; } } if( ! $table_exists ) { printd "$0: table $PREF{group_table_name} does not exist; attempting to create it now.\n"; my $statement = "CREATE TABLE `$PREF{group_table_name}` (" . " `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " . " `group` VARCHAR($PREF{max_groupname_length}) NOT NULL, " . " `desc` TEXT, " . " `members` TEXT " . ")"; $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: couldn't create table '$PREF{group_table_name}': $DBI::errstr\n"); printd "$0: created table $PREF{group_table_name} successfully.\n"; # We may want to index the members column... #$statement = "ALTER TABLE `$PREF{group_table_name}` ADD INDEX (`members`)"; #$sth = $PREF{dbh}->prepare($statement); #$sth->execute() or die_nice("$0: couldn't add index to 'members' column on table '$PREF{group_table_name}': $DBI::errstr\n"); my $admin_desc = 'Administrators have unlimited access to all features of all web applications.'; add_new_group($PREF{admin_group_name}, $admin_desc); add_new_group($PREF{public_group_name}, 'All users including unregistered users (i.e. strangers) are automatically members of this special public group.'); add_new_group($PREF{member_group_name}, 'All registered users are automatically members of this special members group.'); } } sub create_pwreset_table_if_DNE { my $table = (); my $table_exists = 0; my $sth = $PREF{dbh}->prepare(qq`show tables;`); $sth->execute(); $sth->bind_columns(\$table); while($sth->fetchrow_arrayref) { if($table eq $PREF{pwreset_table_name}) { $table_exists = 1; last; } } if( ! $table_exists ) { printd "$0: table $PREF{pwreset_table_name} does not exist; attempting to create it now.\n"; my $statement = "CREATE TABLE `$PREF{pwreset_table_name}` (" . " `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " . " `username` VARCHAR($PREF{max_username_length}) NOT NULL, " . " `token` VARCHAR(50) NOT NULL, " . " `requestdate` BIGINT UNSIGNED NOT NULL " . ")"; $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: couldn't create table '$PREF{pwreset_table_name}': $DBI::errstr\n"); printd "$0: created table $PREF{pwreset_table_name} successfully.\n"; } } sub create_pending_table_if_DNE { my $table = (); my $table_exists = 0; my $sth = $PREF{dbh}->prepare(qq`show tables;`); $sth->execute(); $sth->bind_columns(\$table); while($sth->fetchrow_arrayref) { if($table eq $PREF{pending_account_table}) { $table_exists = 1; last; } } if( ! $table_exists ) { printd "$0: table $PREF{pending_account_table} does not exist; attempting to create it now.\n"; my $statement = "CREATE TABLE `$PREF{pending_account_table}` (" . " `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " . " `username` VARCHAR($PREF{max_username_length}) NOT NULL, " . " `password` VARCHAR($PREF{max_hashedpw_length}) NOT NULL, " . " `salt` VARCHAR(50) NOT NULL, " . " `name` VARCHAR($PREF{max_realname_length}), " . " `email` VARCHAR($PREF{max_emailaddr_length}), " . " `token` VARCHAR(50), " . " `cdate` BIGINT UNSIGNED NOT NULL, " . " `status` TINYINT UNSIGNED " . ")"; $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: couldn't create table '$PREF{pending_account_table}': $DBI::errstr\n"); printd "$0: created table $PREF{pending_account_table} successfully.\n"; } } sub create_custom_fields_table_if_DNE { my $table = (); my $table_exists = 0; my $sth = $PREF{dbh}->prepare(qq`show tables;`); $sth->execute(); $sth->bind_columns(\$table); while($sth->fetchrow_arrayref) { if($table eq $PREF{custom_field_table}) { $table_exists = 1; last; } } if( ! $table_exists ) { printd "$0: table $PREF{custom_field_table} does not exist; attempting to create it now.\n"; my $statement = "CREATE TABLE `$PREF{custom_field_table}` (" . " `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, " . " `fieldname` VARCHAR(100) NOT NULL, " . " `fieldlabel` VARCHAR(255) NOT NULL, " . " `datatype` VARCHAR(100) NOT NULL, " . " `fieldtype` VARCHAR(100) NOT NULL, " . " `fieldmax` TINYINT UNSIGNED, " . " `fieldposition` INT NOT NULL, " . " `mandatory` BOOL NOT NULL, " . " `limitallowedchars` BOOL NOT NULL, " . " `allowedchars` TEXT, " . " `allowedcharsmsg` VARCHAR(255), " . " `listitems` TEXT, " . " `enabled` BOOL NOT NULL " . ")"; $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$0: couldn't create table '$PREF{custom_field_table}': $DBI::errstr\n"); printd "$0: created table $PREF{custom_field_table} successfully.\n"; } } sub get_db_connection { unless($PREF{dbh}) { open(my $infh,"<$PREF{tmpfl1}") or die "$0: couldn't open $PREF{tmpfl1} for reading: $!\n"; flock $infh, 1; seek $infh, 0, 0; my $this = <$infh>; close $infh or die "$0: couldn't close $PREF{tmpfl1}: $!\n"; open(my $infh,"<$PREF{tmpfl2}") or die "$0: couldn't open $PREF{tmpfl2} for reading: $!\n"; flock $infh, 1; seek $infh, 0, 0; my $that = <$infh>; close $infh or die "$0: couldn't close $PREF{tmpfl2}: $!\n"; chomp ($this,$that); $PREF{dbi_connection_string} =~ s!%%dbname%%!$PREF{database_name}!g; $PREF{dbh} = DBI->connect($PREF{dbi_connection_string}, $that, $this) or die_nice("$PREF{internal_appname}: get_db_connection(): error: $DBI::errstr\n"); } } sub realname_is_valid { return ($_[0] =~ /^[0-9A-Za-z\.'" -]+$/ && $_[0] =~ /^[A-Za-z]/ && length($_[0]) < $PREF{max_realname_length}); } sub emailaddr_is_valid { return ($_[0] =~ /.+\@.+\..+/ && $_[0] !~ /\s/ && length($_[0]) < $PREF{max_emailaddr_length}); } sub ip_is_valid { return ($_[0] =~ /^[0-9A-Za-z\.:]+$/ && length($_[0]) <= 40); } sub groupdesc_is_valid { return length($_[0]) < $PREF{max_group_description_length}; } sub password_is_valid { return length($_[0]) >= $PREF{min_password_length} && length($_[0]) < $PREF{max_password_length}; } sub salt_is_valid { return length($_[0]) == $PREF{salt_length}; } # realname, emailaddr, and groupdesc can validly contain characters that would # be dangerous to SQL, so we run sql_untaint() on those after checking them for # validity. # sub check_realname_for_sql_safeness { die_nice("Invalid real name: '$_[0]'") unless realname_is_valid($_[0]); sql_untaint($_[0]); } sub check_emailaddr_for_sql_safeness { die_nice("Invalid email address: '$_[0]'") unless emailaddr_is_valid($_[0]); sql_untaint($_[0]); } sub check_groupdesc_for_sql_safeness { die_nice("Invalid group description: '$_[0]'") unless groupdesc_is_valid($_[0]); sql_untaint($_[0]); } sub check_salt_for_sql_safeness { die_nice("Invalid salt: '$_[0]'") unless salt_is_valid($_[0]); sql_untaint($_[0]); } sub check_ip_for_sql_safeness { die_nice("Invalid IP: '$_[0]'") unless ip_is_valid($_[0]); } sub exit_unless_admin { print_needadmin_error_and_exit() unless $PREF{admin_is_logged_in}; } sub print_needadmin_error_and_exit { my $error = qq`Access Denied: you do not have sufficient privileges to perform this action.`; if(!$PREF{admin_is_logged_in}) { $error .= qq` Perhaps you need to login as an administrator first?`; } exit_with_error($error); } sub showusers { #exit_unless_admin(); exit_with_error($TEXT{Access_denied_}) unless user_has_addmember_rights(); my $letter = $qs =~ /(?:^|&)which=([a-z])(?:&|$)/i ? $1 : $qs =~ /(?:^|&)which=all(?:&|$)/i ? 'all' : 'all'; my $restriction = $letter eq 'all' ? undef : " WHERE LOWER(`username`) LIKE LOWER('$letter%') "; my %columns = (); $columns{01}{name} = 'ubusername'; $columns{01}{title} = 'Username'; $columns{02}{name} = 'ubgroups'; $columns{02}{title} = 'Groups'; if($PREF{use_builtin_realname_field} =~ /yes/i) { $columns{03}{name} = 'ubrealname'; $columns{03}{title} = 'Real Name'; } if($PREF{use_builtin_email_field} =~ /yes/i) { $columns{04}{name} = 'ubemail'; $columns{04}{title} = 'Email Address'; } $columns{05}{name} = 'ubcreatedate'; $columns{05}{title} = 'Date Created'; $columns{06}{name} = 'ubloggedin'; $columns{06}{title} = 'Logged In'; $columns{07}{name} = 'ubactions'; $columns{07}{title} = 'Actions'; my $i = 10; my %custom_fields = (); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{custom_field_table}`")) { my ($fieldname,$fieldlabel,$enabled) = (); my $sth = $PREF{dbh}->prepare("SELECT fieldname,fieldlabel,enabled FROM `$PREF{custom_field_table}` ORDER BY `fieldposition`"); $sth->execute() or die_nice("$PREF{internal_appname}: Error: showusers(): $DBI::errstr\n"); $sth->bind_columns(\$fieldname,\$fieldlabel,\$enabled); while($sth->fetchrow_arrayref) { next unless db_column_exists($fieldname, $PREF{user_table_name}); next if (!$enabled && $PREF{hide_disabled_fields_on_userlist_page} =~ /yes/i); $columns{$i}{name} = $custom_fields{$i}{name} = $fieldname; $columns{$i}{title} = $custom_fields{$i}{title} = $PREF{use_labels_as_headers_on_userlist_page} =~ /yes/i ? $fieldlabel : $fieldname; $i++; } } start_html_output("Manage Users"); my ($toggles,$headers,%rows) = ('','', ()); foreach my $column (sort { $a <=> $b } keys %columns) { my $name = $columns{$column}{name}; $toggles .= qq`$columns{$column}{title}  `; my $new_qs = $qs; $new_qs =~ s/(^|&)sort=\w+?(&|$)/$1$2/g; $new_qs =~ s/(^|&)reverse=1(&|$)/$1$2/g; $new_qs .= "&sort=$name"; if($qs !~ /(?:^|&)reverse=1(?:&|$)/ && $qs =~ /(?:^|&)sort=\w+?(?:&|$)/) { $new_qs .= "&reverse=1"; } $new_qs =~ s/&+/&/g; $headers .= qq`
$columns{$column}{title}
\n$headers\n`; my ($id,$username,$name,$email,$cdate,$loggedin) = (); my $sth = $PREF{dbh}->prepare("SELECT `id`,`username`,`name`,`email`,`cdate`,`loggedin` FROM `$PREF{user_table_name}`${restriction}ORDER BY `username`"); $sth->execute() or die_nice("$0: showusers() failed: $DBI::errstr\n"); $sth->bind_columns(\$id,\$username,\$name,\$email,\$cdate,\$loggedin); $i = 1; while($sth->fetchrow_arrayref) { my @groups = (); my $groups = get_groups_hash($id); foreach my $group (sort { lc($a) cmp lc($b) } keys %$groups) { next if $group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i; if($$groups{$group}{is_member}) { push @groups, qq`$group`; } } next if (logged_in_user_is_subgroup_manager() && !logged_in_subgroup_manager_owns_this_user($id)); $rows{$i}{ubusername} = qq``; $rows{$i}{ubgroups} = qq``; $rows{$i}{ubrealname} = ($PREF{use_builtin_realname_field} =~ /yes/i ? qq`` : ''); $rows{$i}{ubemail} = qq``; $rows{$i}{ubcreatedate} = qq``; $rows{$i}{ubloggedin} = qq``; $rows{$i}{ubactions} = qq``; foreach my $field (sort { $a <=> $b } keys %custom_fields) { my $name = $custom_fields{$field}{name}; my $value = enc_sql_select("SELECT `$name` FROM `$PREF{user_table_name}` WHERE `id` = $id"); $rows{$i}{$name} = qq``; } $i++; } $i = 1; my $sortkey = $qs =~ /(?:^|&)sort=(\w+?)(?:&|$)/ ? $1 : 'ubusername'; my $reverse = $qs =~ /(?:^|&)reverse=1(?:&|$)/ ? 1 : 0; foreach my $row (sort { $reverse ? lc($rows{$b}{$sortkey}) cmp lc($rows{$a}{$sortkey}) : lc($rows{$a}{$sortkey}) cmp lc($rows{$b}{$sortkey}) } keys %rows) { print qq``; foreach my $column (sort { $a <=> $b } keys %columns) { print $rows{$row}{ $columns{$column}{name} }; } print qq`\n`; } print qq`\n`; print qq`
$username` . (join '
', sort { lc($a) cmp lc($b) } @groups) . qq`
$name$email` . strftime("%Y%m%d",localtime($cdate)) . qq`` . ($loggedin && !login_session_expired($loggedin) ? 'yes' : 'no') . qq`edit  delete$value
Add User
\n`; print qq`

\nAll`; foreach my $char ('A'..'Z') { print qq`$char`; } print qq`\n

`; print qq` `; finish_html_output(); } sub showgroups { #exit_unless_admin(); exit_with_error($TEXT{Access_denied_}) unless user_has_groupmod_rights(); my $letter = $qs =~ /(?:^|&)which=([a-z])(?:&|$)/i ? $1 : $qs =~ /(?:^|&)which=all(?:&|$)/i ? 'all' : 'all'; my $restriction = $letter eq 'all' ? undef : " WHERE LOWER(`group`) LIKE LOWER('$letter%') "; start_html_output("Manage Groups"); print qq`\n`; print qq`\n`; my ($id,$group,$desc,$members) = (); my $sth = $PREF{dbh}->prepare("SELECT `id`,`group`,`desc`, `members` FROM `$PREF{group_table_name}`${restriction}ORDER BY `group`"); $sth->execute() or die_nice("$0: showgroups() failed: $DBI::errstr\n"); $sth->bind_columns(\$id,\$group,\$desc,\$members); my $i = 1; while($sth->fetchrow_arrayref) { my @users = (); if($group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i) # every account is automatically a member of these groups. { push @users, '(all)'; } else { foreach my $uid (split(/,/, $members)) { next unless $uid =~ /^\d+$/; my $username = get_user_name($uid); push @users, qq`$username` if $username; } } my $group_display = $group =~ /^($PREF{public_group_name}|$PREF{member_group_name}|$PREF{admin_group_name})$/i ? "$group" : $group; print qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`\n`; } print qq`\n`; print qq`
GroupMembersDescriptionActions
$group_display` . (join '
', sort { lc($a) cmp lc($b) } @users) . qq`
$desceditdelete
Add Group
\n`; print qq`

\nAll`; foreach my $char ('A'..'Z') { print qq`$char`; } print qq`\n

`; finish_html_output(); } sub create_random_salt($) { my $length = shift; my ($salt,$randchar) = (); while(length($salt) < $length) { $randchar = (); if($PREF{use_binary_salt} =~ /yes/i) { $randchar = int(rand(254)) while $randchar < 150; } else { $randchar = int(rand(125)) while ($randchar < 40 || $randchar == 92 || $randchar == 96); } $salt .= chr($randchar); } return $salt; } sub print_pwreset_page { exit_with_error("Error: this feature is not enabled.") unless $PREF{enable_password_reset} =~ /yes/i; start_html_output("Password Reset"); print qq`\n

If you have forgotten your password, you
can reset it here.  Enter your username:

`; print qq`\n
`; print qq`\n`; print qq`\n

`; print qq`\n
`; finish_html_output(); } sub send_pwreset_email { exit_with_error("Error: this feature is not enabled.") unless $PREF{enable_password_reset} =~ /yes/i; my $username = param('username'); my $username_urlencoded = $username; enc_urlencode($username_urlencoded); check_username_for_sql_safeness($username); my $userid = enc_sql_select("SELECT `id` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username');"); my $recipient = enc_sql_select("SELECT `email` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username');"); $recipient = $username if $PREF{usernames_must_be_email_addresses} =~ /yes/i; # Sleep for a few seconds. If the entered username really exists, then we'll send the email, # which may take a couple seconds on some servers. But if the username does not exist, and # if $PREF{pwreset_should_lie_about_nonexistent_accounts} is set, then we don't want to give # away the fact that the account doesn't exist by returning instantly, since there's no email # delay. So in both cases, sleep for a few seconds beforehand to obfuscate things. # sleep (int(rand(3)) + 2); if($recipient =~ /.+\@.+\..+/) { my $requestdate = offsettime(); my $token = enc_hash($requestdate . $$ . $ENV{REMOTE_ADDR} . $ENV{HTTP_USER_AGENT}); die_unless_numeric($requestdate, 'requestdate'); exit_with_error("Invalid token '$token'.") unless $token =~ /^\w+$/; my $sth = $PREF{dbh}->prepare("INSERT INTO `$PREF{pwreset_table_name}` (`username`, `token`, `requestdate`) VALUES('$username', '$token', '$requestdate');"); $sth->execute() or die "$0: Error: send_pwreset_email(): could not insert new pwreset request into database: $DBI::errstr\n"; my $email_msg = qq`Hello,\n\nSomeone (hopefully you) has requested a password reset for your account on the '$ENV{HTTP_HOST}' website. To confirm this request, visit the following URL:` . qq`\n\n$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?action=pwreset3&t=$token&u=$username_urlencoded` . qq`\n\nIf you did not initiate this request, then you may discard this message. It's probably just an honest mistake, but if you wish to pursue it further, the following technical information about the requester may be helpful:` . qq`\n\nIP Address: $PREF{ip}` . qq`\n\nHostname: $PREF{host}` . qq`\n\nUser-Agent: $ENV{HTTP_USER_AGENT}` . qq`\n\n--\nThis message sent by UserBase at:\n$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}\n`; send_email( $recipient, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", "Please confirm your password reset request", $email_msg, 'text/plain', 'die_on_email_error' ); enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=spwrst2"); } else { if($PREF{pwreset_should_lie_about_nonexistent_accounts} =~ /yes/i) { enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=spwrst2"); } else { if($userid) { enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=epwrst2"); } else { enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=epwrst3"); } } } } sub process_pwreset { exit_with_error("Error: this feature is not enabled.") unless $PREF{enable_password_reset} =~ /yes/i; my ($token,$username) = ($qs =~ /&t=(\w+?)&u=(.+?)(?:&|$)/); enc_urldecode($username); exit_with_error("Invalid token '$token'.") unless $token =~ /^\w+$/; check_username_for_sql_safeness($username); start_html_output("Password Reset"); my $requestdate = enc_sql_select("SELECT `requestdate` FROM `$PREF{pwreset_table_name}` WHERE `token` = '$token' AND LOWER(`username`) = LOWER('$username');"); if($requestdate =~ /^\d+$/ && $requestdate > 0) { exit_with_error("Error: request date was too long ago; please submit a new password-reset request.") if (offsettime() - $requestdate) > (60 * 60 * 24 * 7); my $recipient = enc_sql_select("SELECT `email` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username');"); $recipient = $username if $PREF{usernames_must_be_email_addresses} =~ /yes/i; if($recipient =~ /.+\@.+\..+/) { my $salt = create_random_salt($PREF{salt_length}); my $password = (); $password .= join('', (0..9, 'A'..'Z', 'a'..'z')[rand 62]) while length($password) < 12; my $new_crypted_password = salt_and_crypt_password($password, $salt); my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `password` = '$new_crypted_password', `salt` = '$salt' WHERE LOWER(`username`) = LOWER('$username');"); die_nice("Error: process_pwreset(): SQL returned '$success' instead of '1' while resetting password (username='$username', new_crypted_password='$new_crypted_password').") unless $success == 1; if($PREF{enable_forced_password_change} =~ /yes/i && $PREF{force_pw_chng_after_password_reset} =~ /yes/i) { if(!enc_sql_select("SELECT `forcepwchng` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username');")) { my $statement = "UPDATE `$PREF{user_table_name}` SET `forcepwchng` = 1 WHERE LOWER(`username`) = LOWER('$username');"; my $success = enc_sql_update($statement); die_nice("Error: process_pwreset(username='$username'): SQL returned '$success' instead of '1' while enabling forcepwchng. SQL was: [[$statement]]") unless $success == 1; } } my $email_msg = qq`Hello,\n\nThe new temporary password for your account on the '$ENV{HTTP_HOST}' website is "$password" (without the quotes). You should now log in to your account and change the password.` . qq`\n\nIf you did not initiate this password-reset request, then your account may be compromised. Please contact the webmaster of this site, forwarding this message including the following technical information about the requester:` . qq`\n\nIP Address: $PREF{ip}` . qq`\n\nHostname: $PREF{host}` . qq`\n\nUser-Agent: $ENV{HTTP_USER_AGENT}` . qq`\n\n--\nThis message sent by UserBase at:\n$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}\n`; send_email( $recipient, "$PREF{webmaster_name} <$PREF{login_script_email_address}>", "Your new temporary password", $email_msg, 'text/plain', 'die_on_email_error' ); my $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{pwreset_table_name}` WHERE `token` = '$token' AND `requestdate` = '$requestdate';"); $sth->execute() or die "$0: Error: process_pwreset(): could not delete pwreset request from database after resetting password: $DBI::errstr\n"; # Clean up any stale requests while we're here... my $one_week_ago = offsettime() - (60 * 60 * 24 * 7); $sth = $PREF{dbh}->prepare("DELETE FROM `$PREF{pwreset_table_name}` WHERE `requestdate` < '$one_week_ago';"); $sth->execute() or die "$0: Error: process_pwreset(): could not delete stale pwreset requests from database: $DBI::errstr\n"; print qq`

Your password was successfully reset.  The new temporary password has been sent to you via email.

\n`; } else { print qq`

Error: could not find a valid email address on file for this account.

\n`; } } else { print qq`

Error: request date invalid or not found.  If you still cannot access your account, please submit another password reset request.

\n`; } finish_html_output(); } sub is_builtin_fieldname($) { return 1 if $_[0] =~ /^(id|username|realname|name|email|password|oldpw|pw1|pw2|salt|cdate|loggedin|numusers|mrsession|failed_logins|ip|account_locked|account_disabled|forcepwchng|agreetoterms|group-.*)$/i; } sub add_or_edit_custom_fields($) { if(!user_is_allowed_to($PREF{logged_in_userid}, 'add_custom_fields')) { exit_with_error($TEXT{Access_denied_}); } my $mode = shift; my $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}"; if($ENV{REQUEST_METHOD} =~ /^post$/i) { my $query = new CGI; my $fieldname = param('fieldname'); my $fieldlabel = param('fieldlabel'); my $datatype = param('datatype'); my $fieldtype = param('fieldtype'); my $fieldmax = param('fieldmax'); my $fieldposition = param('fieldposition'); my $mandatory = param('mandatory'); my $limitallowedchars = param('limitallowedchars'); my $allowedchars = param('allowedchars'); my $allowedcharsmsg = param('allowedcharsmsg'); my $listitems = param('listitems'); my $enabled = param('enabled'); # fix browser newlines. this must be done before sanitization, because # \r will trip not_sqlsafe() during sanitization otherwise. # s/\r\n/\n/g for ($listitems, $allowedchars); s/\r/\n/g for ($listitems, $allowedchars); # sanitize values: # if($fieldname =~ /[^\w]/ || length($fieldname) > 100 || !$fieldname) { enc_redirect("$go?phase=efldname"); } elsif(is_builtin_fieldname($fieldname)) { enc_redirect("$go?phase=ebltfld"); } elsif($mode eq 'add' && db_column_exists($fieldname, $PREF{user_table_name})) { enc_redirect("$go?phase=efldexist"); } elsif(length($fieldlabel) > 255 || !$fieldlabel) { enc_redirect("$go?phase=efldlabel"); } elsif(not_sqlsafe($fieldlabel)) { enc_redirect("$go?phase=esqlsafe&one=fieldlabel"); } elsif($datatype !~ /^\w+$/ || length($datatype) > 100) { enc_redirect("$go?phase=edatatype"); } elsif($fieldtype !~ /^\w+$/ || length($fieldtype) > 100) { enc_redirect("$go?phase=efieldtype"); } elsif($fieldposition && $fieldposition !~ /^\d+$/) { enc_redirect("$go?phase=efieldpos"); } elsif(not_sqlsafe($allowedchars)) { enc_redirect("$go?phase=esqlsafe&one=allowedchars"); } elsif(length($allowedcharsmsg) > 255) { enc_redirect("$go?phase=ealchrmsg"); } elsif(not_sqlsafe($allowedcharsmsg)) { enc_redirect("$go?phase=esqlsafe&one=allowedcharsmsg"); } elsif($fieldtype =~ /^(dropdown|radio)$/ && !$listitems) { enc_redirect("$go?phase=elistitems"); } elsif(not_sqlsafe($listitems)) { enc_redirect("$go?phase=esqlsafe&one=listitems"); } # adjust values: # $fieldmax = 255 if($fieldmax !~ /^\d+$/ || $fieldmax > 255); $fieldmax = '' unless $datatype eq 'varchar'; $mandatory = $mandatory =~ /on/i ? 1 : 0; $limitallowedchars = $limitallowedchars =~ /on/i && $fieldtype =~ /^freeform/ ? 1 : 0; $listitems = '' unless $fieldtype =~ /^(dropdown|radio)/; $allowedchars = $allowedcharsmsg = '' unless $limitallowedchars; $enabled = $enabled =~ /on/i ? 1 : 0; # adjust position if necessary: # my $max_position = enc_sql_select("SELECT MAX(`fieldposition`) FROM `$PREF{custom_field_table}`"); $max_position = 0 unless $max_position =~ /^\d+$/; my $next_position = $max_position + 1; $fieldposition = $next_position unless $fieldposition =~ /^\d+$/; if($fieldposition < $next_position) { my $current_position = $max_position; while($current_position >= $fieldposition) { enc_sql_update("UPDATE `$PREF{custom_field_table}` SET `fieldposition` = $current_position + 1 WHERE `fieldposition` = $current_position"); $current_position--; } # now every field has been shifted up (increased fieldposition value) by 1 position, # and there is no field whose position is $fieldposition. } # get previous values in case we need to change them in the user table: my $prev_fieldname = ''; my $prev_datatype = ''; my $prev_fieldmax = ''; if($mode eq 'edit') { my ($field_id) = ($qs =~ /(?:^|&)field_id=(\d+)(?:&|$)/); ($prev_fieldname,$prev_datatype,$prev_fieldmax) = enc_sql_select("SELECT `fieldname`,`datatype`,`fieldmax` FROM `$PREF{custom_field_table}` WHERE `id` = '$field_id'"); } # final barebones pre-SQL checks for all items: if($fieldname !~ /^\w+$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid fieldname '$fieldname'."); } if(not_sqlsafe($fieldlabel)) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid fieldlabel '$fieldlabel'."); } if($datatype !~ /^\w+$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid datatype '$datatype'."); } if($fieldtype !~ /^\w+$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid fieldtype '$fieldtype'."); } if($fieldmax !~ /^(\d+|)$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid fieldmax '$fieldmax'."); } if($fieldposition !~ /^\d+$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid fieldposition '$fieldposition'."); } if($mandatory !~ /^(0|1)$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid mandatory '$mandatory'."); } if($limitallowedchars !~ /^(0|1)$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid limitallowedchars '$limitallowedchars'."); } if(not_sqlsafe($allowedchars)) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid allowedchars '$allowedchars'."); } if(not_sqlsafe($allowedcharsmsg)) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid allowedcharsmsg '$allowedcharsmsg'."); } if(not_sqlsafe($listitems)) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid listitems '$listitems'."); } if($enabled !~ /^(0|1)$/) { die_nice("$PREF{internal_appname}: add_or_edit_custom_fields(): invalid enabled '$enabled'."); } # untaint any items that can contain quotes or backslashes: sql_untaint($fieldlabel, $allowedchars, $allowedcharsmsg, $listitems); # add/update our listing about the custom field: my $statement = ''; if($mode eq 'add') { $statement = "INSERT INTO `$PREF{custom_field_table}` (`fieldname`, `fieldlabel`, `datatype`, `fieldtype`, `fieldmax`, `fieldposition`, `mandatory`, `limitallowedchars`, `allowedchars`, `allowedcharsmsg`, `listitems`, `enabled`) VALUES('$fieldname','$fieldlabel','$datatype','$fieldtype','$fieldmax','$fieldposition', $mandatory, $limitallowedchars, '$allowedchars','$allowedcharsmsg','$listitems','$enabled')"; } else { my ($field_id) = ($qs =~ /(?:^|&)field_id=(\d+)(?:&|$)/); $statement = "UPDATE `$PREF{custom_field_table}` SET `fieldname` = '$fieldname', `fieldlabel` = '$fieldlabel', `datatype` = '$datatype', `fieldtype` = '$fieldtype', `fieldmax` = '$fieldmax', `fieldposition` = '$fieldposition', `mandatory` = $mandatory, `limitallowedchars` = $limitallowedchars, `allowedchars` = '$allowedchars', `allowedcharsmsg` = '$allowedcharsmsg', `listitems` = '$listitems', `enabled` = '$enabled' WHERE `id` = '$field_id'"; } my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL statement [[$statement]]: $DBI::errstr\n"); # add/update the field itself in the users table: foreach my $table ($PREF{user_table_name}, $PREF{pending_account_table}) { if($mode eq 'add' || !db_column_exists($fieldname, $table)) { my $whole_datatype = $datatype eq 'varchar' ? "$datatype($fieldmax)" : $datatype; my $statement = "ALTER TABLE `$table` ADD `$fieldname` $whole_datatype"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL statement [[$statement]]: $DBI::errstr\n"); } else { if($fieldname ne $prev_fieldname) { my $prev_whole_datatype = $prev_datatype eq 'varchar' ? "$prev_datatype($prev_fieldmax)" : $prev_datatype; my $statement = "ALTER TABLE `$table` CHANGE `$prev_fieldname` `$fieldname` $prev_whole_datatype"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL statement [[$statement]]: $DBI::errstr\n"); } if($datatype ne $prev_datatype || $fieldmax ne $prev_fieldmax) { my $whole_datatype = $datatype eq 'varchar' ? "$datatype($fieldmax)" : $datatype; my $statement = "ALTER TABLE `$table` MODIFY `$fieldname` $whole_datatype"; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL statement [[$statement]]: $DBI::errstr\n"); } } } enc_redirect("$go?action=addcustomfield"); } else { my %vars = (); if($mode eq 'add') { $vars{form_title} = $TEXT{Add_New_Field}; $vars{button_label} = $TEXT{Add_New_Field}; $vars{form_action} = 'addcustomfield'; } elsif($mode eq 'edit') { ($vars{field_id}) = ($qs =~ /(?:^|&)field_id=(\d+)(?:&|$)/); $vars{form_title} = $TEXT{Edit_Custom_Field}; $vars{button_label} = $TEXT{Save_Changes}; $vars{form_action} = 'editcustomfield&field_id=' . $vars{field_id}; } start_html_output($TEXT{Custom_Fields}); my $i = 0; print qq`\n`; print qq`\n`; my $field_DNE_error = qq`
(ERROR: DNE)`; my ($id,$fieldname,$fieldlabel,$datatype,$fieldtype,$fieldmax,$fieldposition,$mandatory,$limitallowedchars,$allowedchars,$allowedcharsmsg,$listitems,$enabled) = (); my $sth = $PREF{dbh}->prepare("SELECT id,fieldname,fieldlabel,datatype,fieldtype,fieldmax,fieldposition,mandatory,limitallowedchars,allowedchars,allowedcharsmsg,listitems,enabled FROM `$PREF{custom_field_table}` ORDER BY `fieldposition`"); $sth->execute() or die "$0: Error: db_column_exists(): $DBI::errstr\n"; $sth->bind_columns(\$id,\$fieldname,\$fieldlabel,\$datatype,\$fieldtype,\$fieldmax,\$fieldposition,\$mandatory,\$limitallowedchars,\$allowedchars,\$allowedcharsmsg,\$listitems,\$enabled); while($sth->fetchrow_arrayref) { my $field_exists_in_user_table = db_column_exists($fieldname, $PREF{user_table_name}); my $field_exists_in_pending_table = db_column_exists($fieldname, $PREF{pending_account_table}); print qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`` . qq`\n`; } print qq`` if $i == 0; print qq`
NamePositionStatusActionsLabelData
type
Field
type
MaxManda-
tory
Limit
Chars
Allowed
Chars
Allowed
Chars Msg
List
Items
$fieldname$fieldposition` . ($enabled ? 'enabled' : 'disabled') . ($field_exists_in_user_table && $field_exists_in_pending_table ? '' : $field_DNE_error) . qq`$TEXT{Edit}
$TEXT{Delete}
$fieldlabel$datatype$fieldtype$fieldmax` . ($mandatory ? 'yes' : 'no') . qq`` . ($limitallowedchars ? 'yes' : 'no') . qq`$allowedchars$allowedcharsmsg$listitems
(None)
\n`; if($mode eq 'add') { ($id,$fieldname,$fieldlabel,$datatype,$fieldtype,$fieldmax,$fieldposition,$mandatory,$limitallowedchars,$allowedchars,$allowedcharsmsg,$listitems,$enabled) = (); } elsif($mode eq 'edit') { ($id,$fieldname,$fieldlabel,$datatype,$fieldtype,$fieldmax,$fieldposition,$mandatory,$limitallowedchars,$allowedchars,$allowedcharsmsg,$listitems,$enabled) = enc_sql_select("SELECT id,fieldname,fieldlabel,datatype,fieldtype,fieldmax,fieldposition,mandatory,limitallowedchars,allowedchars,allowedcharsmsg,listitems,enabled FROM `$PREF{custom_field_table}` WHERE `id` = '$vars{field_id}'"); # browsers strip a single leading newline from textarea values # upon submission, so add an extra if one is present. for($allowedchars,$listitems) { $_ = "\n$_" if /^\n/s; } } my $hide = qq`style="display: none;"`; print qq`
$vars{form_title}
Field name: (e.g. "address", "phone_number")
Field label: (e.g. "Your Phone Number:")
Data type:` . ($mode eq 'edit' ? qq` (warning: changing the datatype may cause MySQL to convert, truncate, or otherwise alter any existing data in this field)` : '') . qq`
Field max length: (1-255; only used if data type is "varchar")` . ($mode eq 'edit' ? qq` (warning: changing the field max length may cause MySQL to convert, truncate, or otherwise alter any existing data in this field)` : '') . qq`
Field type:
Field position: (its sort order relative to your other custom fields, e.g. 1, 2, 3... leave blank to use the next open spot)
Mandatory? (i.e. your users must select/fill in some value)
Limit which characters are allowed? (only for free-form fields; strongly recommended)
Allowed characters: (only for free-form fields; case-insensitive)
Allowed characters message/error: (only for free-form fields; e.g. "This field only accepts numbers and dashes")
List items: (only for dropdown and radio fields; enter one per line)
Enabled:
`; finish_html_output(); } } # # Precondition: check_username_for_sql_safeness($input_username). # sub account_exceeds_failed_login_limit { my $input_username = $_[0]; my $increment_failure_count = $_[1] eq 'increment' ? 1 : 0; my $failed_login_limit_exceeded = 0; if($PREF{lock_account_after_N_failed_logins} =~ /^\d+$/) { return unless enc_sql_select("SELECT `id` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username';"); # account DNE. my ($recent_failed_attempts, $recent_failure_count) = (); if($increment_failure_count) { $recent_failed_attempts = offsettime() . ','; $recent_failure_count = 1; } my $failed_attempts = enc_sql_select("SELECT `failed_logins` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username';"); foreach my $failure_time (split(/,/, $failed_attempts)) { if($PREF{failed_logins_within_N_secs_count_towards_lock} =~ /^\d+$/) { if(offsettime() - $failure_time < $PREF{failed_logins_within_N_secs_count_towards_lock}) { $recent_failed_attempts .= $failure_time . ','; $recent_failure_count++; } } else # all failures are "recent", i.e. we don't care how long ago they occurred. { $recent_failed_attempts .= $failure_time . ','; $recent_failure_count++; } } decommaify($recent_failed_attempts); sql_untaint($recent_failed_attempts); my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `failed_logins` = '$recent_failed_attempts' WHERE `username` = '$input_username';"); die_nice("Error: account_exceeds_failed_login_limit(input_username='$input_username'): SQL returned '$success' instead of '1' while updating failed_logins.") unless $success == 1; if($recent_failure_count >= $PREF{lock_account_after_N_failed_logins}) { $failed_login_limit_exceeded = 1; } } return $failed_login_limit_exceeded; } sub interpolate_userbase_variables($) { my $string = shift; printd "interpolate_userbase_variables('$string')"; $string =~ s/%%username%%/$PREF{logged_in_username}/g; $string =~ s/%PREF{(\w+)}/$PREF{$1}/g; $string =~ s/%%(\w+?)%%/enc_sql_select("SELECT `$1` FROM `$PREF{user_table_name}` WHERE `id` = $PREF{logged_in_userid}")/eg; return $string; } sub create_filechucker_userdir($) { return unless $PREF{create_filechucker_userdir_on_account_creation} =~ /yes/i; my $username = shift; my $dir = $PREF{filechucker_userdir_folder}; my $slash = $PREF{DOCROOT} !~ m![/\\]$! && $dir !~ m!^[/\\]! ? '/' : ''; $dir = $PREF{DOCROOT} . $slash . $dir if $PREF{filechucker_userdir_folder_is_in_docroot}; $slash = $dir !~ m![/\\]$! && $username !~ m!^[/\\]! ? '/' : ''; $dir = $dir . $slash . $username; create_dir_if_DNE($dir,0777,'make_parents'); } sub check_login() { sleep $PREF{num_seconds_to_sleep_on_failed_login} unless $PREF{member_is_logged_in}; my $group_memberships = ''; my (@custom_fields, @custom_field_values) = (); if($PREF{member_is_logged_in}) { my $groups = get_groups_hash($PREF{logged_in_userid}); foreach my $group (sort keys %$groups) { $group_memberships .= $group . ',' if $$groups{$group}{is_member}; } $group_memberships =~ s/,+$//; @custom_fields = get_custom_field_names($PREF{user_table_name}); foreach my $customfield (@custom_fields) { my $value = enc_sql_select("SELECT `$customfield` FROM `$PREF{user_table_name}` WHERE `id` = '$PREF{logged_in_userid}'"); push @custom_field_values, $value; } } print_http_headers(); print "admin=$PREF{admin_is_logged_in}:::::member=$PREF{member_is_logged_in}:::::username=$PREF{logged_in_username}:::::group_memberships=${group_memberships}:::::realname=$PREF{logged_in_realname}:::::"; my $i = 0; foreach my $field (@custom_fields) { print "${field}=$custom_field_values[$i]:::::"; $i++; } print "\n"; } ############################################################################################################################################ ### Functions: general. ############################################################################################################################################ sub get_cookies() { use CGI ':standard'; use CGI::Cookie; my %cookies = fetch CGI::Cookie; return %cookies; } sub get_cookie($) { my $which = shift; my %jar = get_cookies(); my $value = ''; if(exists $jar{$which}) { $value = $jar{$which}->value; } elsif($which eq $PREF{site_session_cookie}) { if($qs =~ /(?:^|&)ubsessioncode=(\w+)(?:&|$)/) { my $code = $1; # Accepting the session code from the URL should only be allowed as a last resort. # On decent servers this shouldn't be necessary because we can call UserBase # from PHP using virtual() and/or exec() both of which pass the cookies. Even # on sub-par servers where we have to use include() with the full http:// URL, # we can reduce the security risk by requiring the remote IP to match the server # IP, i.e. ONLY allow the include(http://...) method to work: don't accept URL- # based session codes from any other IP. As a last resort on totally sucky # servers where PHP is crippled and $ENV{SERVER_ADDR} DNE or is variable or # otherwise useless, proceed only by setting a PREF that indicates what a bad # idea it is. if($ENV{REMOTE_ADDR} eq $ENV{SERVER_ADDR}) { $value = $code; } elsif($PREF{my_server_sucks_so_use_less_secure_mode} =~ /yes/i) { sleep $PREF{sleeptime_for_less_secure_mode} || 3; $value = $code; } } } return $value; } sub set_cookie($$$) { my $name = shift; my $value = shift; my $expiry = shift; my $cookie; # This if/else is necessary because setting "expires" to "" isn't # the same as not setting it. Setting it to "" is the same as # setting it to zero, which expires the cookie immediately # (i.e., deletes it). But explicitly *not* setting the expiry # causes the cookie to persist until the end of the session. if($expiry eq "") { $cookie = new CGI::Cookie( -name => $name, -value => $value, -path => '/'); } else { $cookie = new CGI::Cookie( -name => $name, -value => $value, -expires => $expiry, -path => '/'); } if($PREF{output_started}) { print "

$PREF{internal_appname} warning: cannot set cookie '$name' => '$value' because the page output has already been started (perhaps debug is enabled?).

\n"; } elsif($PREF{we_are_virtual}) { print_http_headers(); print "

$PREF{internal_appname} warning: cannot set cookie '$name' => '$value' because we are virtual.

\n"; } else { print "Set-Cookie: $cookie\n"; } } sub expand_custom_vars_in_prefs($) { my $hashref = shift; foreach my $key (keys %$hashref) { # from now on, use %%varname%% instead of $$varname$$, so that it doesn't # matter whether it gets put in double-quotes. next unless $$hashref{$key} && $$hashref{$key} =~ /(\$\$|%%)/; # old way: $$hashref{$key} =~ s/\$\$server_name\$\$/$ENV{'SERVER_NAME'}/g; $$hashref{$key} =~ s/\$\$httphost_withport\$\$/$ENV{'HTTP_HOST'}/g; $$hashref{$key} =~ s/\$\$name_of_site\$\$/$$hashref{'name_of_site'}/g; # new way: $$hashref{$key} =~ s/%%server_name%%/$ENV{SERVER_NAME}/g; $$hashref{$key} =~ s/%%http_host%%/$ENV{HTTP_HOST}/g; $$hashref{$key} =~ s/%%name_of_site%%/$$hashref{name_of_site}/g; } } # pass filename to create and optionally the mode to chmod it to. # the mode must consist of 1-4 octal digits and must NOT be quoted. # see "perldoc -f chmod" and "man chmod". sub create_file_if_DNE { my $file = shift; my $mode = shift; return if -T $file; open(NEW,">$file") or die "$0: couldn't create new file $file: $!\n"; close NEW or die "$0: couldn't close $file after creating it: $!\n"; if($mode) { chmod($mode,$file) or die "$0: couldn't chmod file \"$file\" with mode \"$mode\": $!\n"; } } sub create_dir_if_DNE { my $dir = shift; my $mode = shift; my $make_parents_if_necessary = shift; $make_parents_if_necessary = $make_parents_if_necessary eq 'make_parents' ? 1 : 0; return if -d $dir; $dir =~ s!\\!/!g; if($make_parents_if_necessary) { my $progressively_longer_path = ''; my $ms_windows = 0; if($dir =~ m!^(\w:)/!) { $progressively_longer_path = $1; $ms_windows = 1; } my $i = 0; foreach my $individual_path_element(split(/\//, $dir)) { $i++; next if $i == 1 && $ms_windows; $progressively_longer_path .= '/' . $individual_path_element; unless(-d $progressively_longer_path) { mkdir($progressively_longer_path,$PREF{writable_dir_perms_as_octal}) or die_nice("$PREF{internal_appname}: create_dir_if_DNE(): couldn't create path-portion '$progressively_longer_path' as part of dir '$dir': $!"); if($mode) { chmod($mode,$progressively_longer_path) or die_nice("$PREF{internal_appname}: create_dir_if_DNE(): couldn't chmod path-portion '$progressively_longer_path' as part of dir '$dir' with mode '$mode': $!"); } } } } else { mkdir($dir,$PREF{writable_dir_perms_as_octal}) or die_nice("$PREF{internal_appname}: create_dir_if_DNE(): couldn't create dir $dir: $!"); if($mode) { chmod($mode,$dir) or die_nice("$PREF{internal_appname}: create_dir_if_DNE(): couldn't chmod dir \"$dir\" with mode \"$mode\": $!"); } } } sub send_email { my ($to, $from, $subj, $msg, $mimetype, $die_on_error, $attachment_hashref) = @_; $mimetype = 'text/plain' unless $mimetype; $die_on_error = $die_on_error eq 'die_on_email_error' ? 1 : 0; my $do_fork = !$die_on_error; # if we want to die on error, we can't fork, or the die() will go unreported. $do_fork = 0 if $^O =~ /MSWin32/; # Windows' fork-fu is weak. my ($mail_sent_successfully, $error_msg) = 0; # fork here because sending mail can be slow (and can block) sometimes. # Note: if we don't set $do_fork, perl won't even evaluate the &&'s second # half, so the fork won't happen, and the else{} will. my $forkpid = (); if($do_fork && ($forkpid = fork)) { # parent } else { # child use POSIX; if($do_fork) { defined $forkpid or die "$PREF{internal_appname}: fork error in send_email(): $@\n"; POSIX::setsid() unless $^O =~ /MSWin32/; close STDOUT; close STDIN; } my $msgid = '<' . time . '.' . md5_hex($to . $from . $subj . $msg . $$ . $ENV{REMOTE_PORT}) . '@' . $ENV{HTTP_HOST} . '>'; if($PREF{smtp_server} =~ /\w/) { # Wrap this in an eval{} in case MIME::Lite is missing. # Then we can have the option of setting $PREF{'disable_all_email'} # so that the site still functions, sans email. eval { require MIME::Lite; my $type = (); if($mimetype) { $type = $mimetype; } else { #my $type = $attachment_hashref ? 'multipart/mixed' : 'text/plain'; $type = $attachment_hashref ? 'multipart/mixed' : 'text/plain; charset=ISO-8859-1; format=flowed'; } my $mime_msg = MIME::Lite->new(To => $to, From => $from, Subject => $subj, Type => $type, Data => $msg); unless($mime_msg) { if($die_on_error) { die "$PREF{internal_appname}: error creating MIME body: $!\n"; } else { warn "$PREF{internal_appname}: error creating MIME body: $!\n"; } } if($PREF{generate_message_id_internally} =~ /yes/i) { $mime_msg->add('Message-ID' => $msgid); } if($attachment_hashref) { foreach my $key (keys %$attachment_hashref) { my $mimetype = $$attachment_hashref{$key}{mimetype}; # like 'application/x-gzip' my $filename = $$attachment_hashref{$key}{filename}; my $recommended_filename = $$attachment_hashref{$key}{recommended_filename}; $recommended_filename =~ s!^.*(\\|/)!!; # strip off any preceeding path # Attach the test file $mime_msg->attach( Type => $mimetype, Path => $filename, Filename => $recommended_filename, Disposition => 'attachment' ) or my $foo = sub { if($die_on_error) { die "$PREF{internal_appname}: error attaching file to email: $!\n"; } else { warn "$PREF{internal_appname}: error attaching file to email: $!\n"; } }; } } $PREF{smtp_server} = enc_untaint($PREF{smtp_server}); if($PREF{smtp_auth_username} =~ /\S/ && $PREF{smtp_auth_password} =~ /\S/) { eval { MIME::Lite->send('smtp', $PREF{smtp_server}, Timeout=>30, AuthUser=>$PREF{smtp_auth_username}, AuthPass=>$PREF{smtp_auth_password}, Port=>$PREF{smtp_port}); }; } else { eval { MIME::Lite->send('smtp', $PREF{smtp_server}, Timeout=>30, Port=>$PREF{smtp_port}); }; } if($@) { if($die_on_error) { die "$PREF{internal_appname}: MIME::Lite->send failed: $@\n"; } else { warn "$PREF{internal_appname}: MIME::Lite->send failed: $@\n"; } } eval { $mime_msg->send; }; if($@) { if($die_on_error) { die "$PREF{internal_appname}: \$mime_msg->send failed: $@\n"; } else { warn "$PREF{internal_appname}: \$mime_msg->send failed: $@\n"; } } else { $mail_sent_successfully = 1; } if($attachment_hashref) { foreach my $key (keys %$attachment_hashref) { unlink( $$attachment_hashref{$key}{filename} ) if $$attachment_hashref{$key}{'delete-after-sending'} eq 'yes'; } } }; } my $smtp_error = $@ if $@; if(-e $PREF{path_to_sendmail} && !$mail_sent_successfully) { eval { $PREF{path_to_sendmail} = enc_untaint($PREF{path_to_sendmail}, 'keep_path'); open(SENDMAIL, "|$PREF{path_to_sendmail} -oi -t") or die_nice "$PREF{internal_appname}: Can't fork for sendmail: $!\n"; if($attachment_hashref) { print SENDMAIL qq`MIME-Version: 1.0` . qq`\nFrom: $from` . qq`\nTo: $to` . qq`\nSubject: $subj` . ($PREF{generate_message_id_internally} =~ /yes/i ? "\nMessage-Id: $msgid" : '') . qq`\nContent-Type: multipart/mixed; boundary=encindboundarystring` . qq`\n` . qq`\n--encindboundarystring` . qq`\nContent-Type: ` . ($mimetype ? $mimetype : 'text/plain') . qq`\n` . qq`\n$msg`; foreach my $key (keys %$attachment_hashref) { my $mimetype = $$attachment_hashref{$key}{mimetype}; # like 'application/x-gzip' $mimetype = 'application/octet-stream' unless $mimetype; my $filename = $$attachment_hashref{$key}{filename}; my $recommended_filename = $$attachment_hashref{$key}{recommended_filename}; $recommended_filename =~ s!^.*(\\|/)!!; # strip off any preceeding path my $atch = `uuencode $filename $filename`; # UUencode it so we can send it as an attachment print SENDMAIL qq`\n____________________` . qq`\nAttachment: $filename:` . qq`\n` . qq`\n--encindboundarystring` . qq`\nContent-Type: $mimetype; name="$filename"` . qq`\nContent-Transfer-Encoding: x-uuencode` . qq`\nContent-Disposition: attachment; filename="$recommended_filename"` . qq`\n` . qq`\n$atch` . qq`\n` . qq`\n--encindboundarystring`; } print SENDMAIL qq`\n--encindboundarystring--\n` } else # no attachment. { print SENDMAIL qq`From: $from` . qq`\nTo: $to` . qq`\nSubject: $subj` . ($PREF{generate_message_id_internally} =~ /yes/i ? "\nMessage-Id: $msgid" : '') . qq`\nContent-Type: $mimetype` . qq`\n` . qq`\n$msg`; } close(SENDMAIL) or die_nice "$PREF{internal_appname}: sendmail didn't close nicely: $!\n"; }; if(!$@) { $mail_sent_successfully = 1; } } my $sendmail_error = $@ if $@; unless($mail_sent_successfully) { if($smtp_error) { $error_msg = "$PREF{internal_appname}: couldn't send email: error in send_email() while trying to use MIME::Lite with SMTP server '$PREF{smtp_server}'. Error was: '$smtp_error'\n"; } elsif($sendmail_error) { $error_msg = "$PREF{internal_appname}: couldn't send email: error in send_email() while trying to use sendmail with path '$PREF{path_to_sendmail}'. Error was: '$sendmail_error'\n"; } else { $error_msg = "$PREF{internal_appname}: couldn't send email: error in send_email(): perhaps you need to adjust \$PREF{smtp_server} (currently '$PREF{smtp_server}') or \$PREF{path_to_sendmail} (currently '$PREF{path_to_sendmail}').\n"; } if($die_on_error) { die $error_msg; } else { warn $error_msg; } } if($do_fork) { exit; # exit the child process. } } return ($mail_sent_successfully, $error_msg); } sub enc_untaint { my $item = shift || ''; my $original_item = $item; my $keep_path = shift || ''; #printd "enc_untaint($item)\n"; # Regardless of whether we're keeping the path, dots surrounded by slashes are never allowed. # #$item =~ s!(^|/|\\)\.+(/|\\|$)!$1!g; $item =~ s!\\!/!g; # Need to remove MS garbage beforehand, otherwise an input like .\\StupidCGI.tmp will break this. while($item =~ m!((?:^|/|\\)\.+(?:/|\\|$))!) { $item =~ s!$1!/!; } #printd "removed slashdots: $item\n"; if( $item =~ m!(/|\\)! && !$keep_path) { $item =~ s!^.*[/\\]+([^/\\]+)!$1!; # remove any path from the front. #printd "removed path from front: $item\n"; $item =~ s!^([^/\\]+)[/\\]+!$1!; # ...and the back. } $item =~ s![`\*\?\|<>]!!g; # remove some other potentially-unsafe stuff. my $leading_UNC_slashes = ''; if($item =~ m!^//! && $keep_path) { $leading_UNC_slashes = '//'; $item =~ s!^/+!!; } $item =~ s![/\\]{2,}!/!g; # condense any multiples. $item = $leading_UNC_slashes . $item; # add back any UNC slashes. ($item) = ($item =~ /(.*)/); # untaint. # In case anything slips through, die as a security precaution. # die qq`$0: couldn't untaint "$original_item".\n` if $item =~ m![/\\]! && !$keep_path; die qq`$0: couldn't untaint "$original_item".\n` if $item =~ m!(?:^|/|\\)\.+(?:/|\\|$)!; die qq`$0: couldn't untaint "$original_item".\n` if $item =~ m!^\.+$!; die qq`$0: couldn't untaint "$original_item".\n` if $item =~ m!^\s*$!; #printd "untainted: $item\n\n"; return $item; } sub enc_urlencode { for(@_) { s/([^\w()'*~!.-])/sprintf '%%%02x', ord $1/eg if $_; }; } sub enc_urldecode { # assuming the input really was URL-encoded, then any plus-signs that were originally there # are now in their hex form, so any plus-signs STILL there were converted from spaces by the # browser. so they must be converted back BEFORE restoring any original plus-signs from the # hex codes. convert_plus_signs_back_to_spaces_in_var_from_GET_method(@_); for(@_) { s/%([a-fA-F\d]{2})/chr hex $1/eg if $_; } } sub convert_plus_signs_back_to_spaces_in_var_from_GET_method { for(@_) { s/\+/ /g if $_; } } sub enc_redirect { my $destination = shift; if($destination =~ /^referr?er$/i) { $destination = $ENV{HTTP_REFERER} ? $ENV{HTTP_REFERER} : $PREF{redirection_backup_address}; } unless($destination =~ m!^https?://!) { $destination = $PREF{protoprefix} . $ENV{HTTP_HOST} . $destination; } if($PREF{output_started}) { print qq`

$PREF{internal_appname} warning: cannot redirect because output has already started (perhaps debug is enabled?).  Click here to continue.

\n`; } elsif($PREF{we_are_virtual}) { warn "$0: enc_redirect(): cannot redirect because we are virtual.\n"; print_http_headers(); print qq`

$PREF{internal_appname} warning: cannot redirect because we are virtual.  Click here to continue.

\n`; } else { if($ENV{SERVER_SOFTWARE} =~ /microsoft-iis/i) { # A bug in IIS v5 (and lower, probably) makes cookie-setting fail # when combined with a header-based redirect: # # "BUG: Set-Cookie Is Ignored in CGI When Combined With Location" # http://support.microsoft.com/kb/q176113/ # # So use a meta-redirect instead. # print "Content-type: text/html\n\n"; print qq`\n`; } else { print "Location: $destination\n\n"; } } exit; } # FC, UB, VL sub condense_slashes { s!\\!/!g; my $leave_leading_UNC = 0; for(@_) { if(/^leave_leading_UNC$/) { $leave_leading_UNC = 1; next; } if($leave_leading_UNC) { my $leading_UNC_slashes = ''; if(m!^//!) { $leading_UNC_slashes = '//'; s!^/+!!; } s!/{2,}!/!g; # condense any multiples. $_ = $leading_UNC_slashes . $_; # add back any UNC slashes. } else { s!/{2,}!/!g; } } } # FC, UB, VL sub slashify { # add leading and trailing slashes and condense duplicates. $_ = '/' . $_ . '/' for @_; s!/{2,}!/!g for @_; } # FC, UB, VL sub deslashify { # remove leading and trailing slashes and condense duplicates. s!/{2,}!/!g for @_; s!^/!!g for @_; s!/$!!g for @_; } # FC, UB, VL sub commaify { # add leading and trailing commas and condense duplicates. $_ = ',' . $_ . ',' for @_; s!,{2,}!,!g for @_; } # FC, UB, VL sub decommaify { # remove leading and trailing commas and condense duplicates. s!,{2,}!,!g for @_; s!^,!!g for @_; s!,$!!g for @_; } # FC, UB, VL sub die_unless_numeric($$) { my $number = shift; my $varname = shift; die_nice("$PREF{internal_appname}: non-numeric $varname '$number'...\n") unless $number =~ /^\d+$/; } # FC, UB, VL sub print_http_headers { unless($PREF{output_started} || $PREF{xml_output_started}) { print "Cache-Control: no-store, no-cache\n"; print "Content-type: text/html\n\n"; $PREF{output_started} = 1; } } # FC, UB, VL sub offsettime { return time + $PREF{time_offset}; } # FC, UB, VL sub sql_untaint { s/"/"/g for @_; s/'/'/g for @_; s/`/`/g for @_; s/\\/\/g for @_; } # FC, UB, VL sub sql_un_untaint { s/"/"/g for @_; s/'/'/g for @_; s/`/`/g for @_; s/\/\\/g for @_; } # FC, UB, VL sub enc_hash { return $PREF{use_md5_for_hashes} =~ /yes/i ? md5_hex(@_) : sha1_hex(@_); } # FC, UB, VL sub not_sqlsafe { #print STDERR "not_sqlsafe: got: $_[0]\n"; # Escape any dashes or closing brackets, as per perlre: # # If you want either "-" or "]" itself to be a member of a class, # put it at the start of the list (possibly after a "^"), or escape # it with a backslash. # my $list_of_sql_safe_characters = $PREF{list_of_sql_safe_characters}; $list_of_sql_safe_characters =~ s/\]/\\]/g; $list_of_sql_safe_characters =~ s/-/\\-/g; return $_[0] =~ /[^$list_of_sql_safe_characters]/; } # FC, UB, VL sub oddeven { $_[0] = 0 unless $_[0] && $_[0] =~ /^\d+$/; $_[0]++; return $_[1] && $_[1] eq 'reset' ? 'odd' : $_[0] % 2 == 0 ? 'even' : 'odd'; } # FC, UB, VL sub enc_sql_select($) { my $statement = shift; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL select statement [[$statement]]: $DBI::errstr\n"); return $sth->fetchrow; } sub enc_sql_select_multi($) { my $statement = shift; my $sth = $PREF{dbh}->prepare($statement); $sth->execute() or die_nice("$PREF{internal_appname}: enc_sql_select_multi(): error while executing SQL select statement [[$statement]]: $DBI::errstr\n"); my $i = 1; my %hash = (); my $rowhashref = ''; while($rowhashref = $sth->fetchrow_hashref) { foreach my $field (keys %$rowhashref) { $hash{$i}{$field} = $$rowhashref{$field}; } $i++; } return \%hash; } # FC, UB, VL sub enc_sql_update($) { my $statement = shift; my $sth = $PREF{dbh}->prepare($statement); my $numrows = $sth->execute() or die_nice("$PREF{internal_appname}: error while executing SQL update statement [[$statement]]: $DBI::errstr\n"); return $numrows; } # Success messages that the end-user is supposed to see. # sub exit_with_success { start_html_output('', 'css', 'js'); my $message = join '', @_; $PREF{success_message_template} =~ s/%%message%%/$message/g; print $PREF{success_message_template}; finish_html_output('home', 'pb'); exit; } # Non-error messages that the end-user is supposed to see. # sub exit_with_notice { start_html_output('', 'css', 'js'); my $message = join '', @_; $PREF{notice_message_template} =~ s/%%message%%/$message/g; print $PREF{notice_message_template}; finish_html_output('home', 'pb'); exit; } # FC, PH, UB, VL # Errors that the end-user is supposed to see. # sub exit_with_error { start_html_output('', 'css', 'js'); my $message = join '', @_; $PREF{error_message_template} =~ s/%%message%%/$message/g; print $PREF{error_message_template} =~ /$message/ ? $PREF{error_message_template} : $message; # in case prefs haven't been loaded yet. finish_html_output(); exit; } sub printd { my $msg = shift; chomp $msg; if($PREF{debug} || $PREF{force_debug} =~ /yes/i) { warn "$PREF{internal_appname}-debug: " . (offsettime()) . ": $msg\n"; print $debuglog "$PREF{internal_appname}-debug: " . (offsettime()) . ": $msg\n" if $debuglog; } if($PREF{debug}) { print_http_headers(); print "\n"; } } # Some SQL implementations support other nonsense in the table names; we'll restrict to a sensible set of characters. # sub tablename_is_valid { return ($_[0] =~ /^\w+$/ && length($_[0]) < $PREF{max_tablename_length}); } # FC, UB, VL sub check_tablename_for_sql_safeness { die_nice("Invalid tablename: '$_[0]'") unless tablename_is_valid($_[0]); } # FC, UB, VL sub db_column_exists($$) { my $column_to_find = shift; my $table_name = shift; check_tablename_for_sql_safeness($table_name); my $column_name = (); my $temp = (); my $sth = $PREF{dbh}->prepare("SHOW COLUMNS FROM `$table_name`;"); $sth->execute() or die "$0: Error: db_column_exists(): $DBI::errstr\n"; $sth->bind_columns(\$column_name, \$temp, \$temp, \$temp, \$temp, \$temp); while($sth->fetchrow_arrayref) { return 1 if $column_name eq $column_to_find;; } return 0; } sub get_ip_and_host { my $ip = $ENV{REMOTE_ADDR}; my $host = $ENV{REMOTE_HOST}; if(!($host)) { $host = $ip; } if($host eq $ip) { use Socket; $host = gethostbyaddr(inet_aton($ip), AF_INET); } if(!($host)) { $host = $ip; } return ($ip, $host); } ############################################################################################################################################ ### Functions: login. ############################################################################################################################################ # FC, UB*, VL sub do_login() { $PREF{admin_is_logged_in} = 0; $PREF{member_is_logged_in} = 0; # Get the user's inputted username and password: use CGI ':param'; my $input_username = param($PREF{userbase_user_fieldname}); my $input_password = param($PREF{userbase_pass_fieldname}); my $stay_logged_in = param("stayLoggedIn"); my $ref = param("ref"); my ($expiry) = (); if($stay_logged_in eq "on") { if($PREF{num_days_rememberme_cookie_lasts} !~ /^\d+$/) { $PREF{num_days_rememberme_cookie_lasts} = 7; } $expiry = "+$PREF{num_days_rememberme_cookie_lasts}d"; } else # Log them out as soon as they close the browser: { $expiry = (); } my $restrict_ip = ( ($PREF{enable_ip_address_restriction} =~ /yes/i && param("restrict_ip") =~ /on/i) || ($PREF{force_ip_address_restriction} =~ /yes/i) ) ? 1 : 0; # Get the crypted version of the input password: check_username_for_sql_safeness($input_username); my $salt = enc_sql_select("SELECT `salt` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username';"); # TODO: remove this if/else, and assume that !$salt is an error condition; but # not until around mid-2007 to give clients time to get switched over. # my ($crypted_input_password, $update_this_account_to_new_pw_system) = (); if(!$salt) # old version of UB that's pre-salt, so re-create the password hash and update it in the DB. { $crypted_input_password = md5_hex($input_password); $update_this_account_to_new_pw_system = 1; } else { $crypted_input_password = salt_and_crypt_password($input_password, $salt); } my $account_locked = enc_sql_select("SELECT `acct_locked` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username';"); if($account_locked) { my $lock_expired = ! account_exceeds_failed_login_limit($input_username); if($PREF{lock_lasts_until_admin_removes_it} =~ /no/i && $lock_expired) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_locked` = FALSE WHERE `username` = '$input_username';"); die_nice("Error: do_login(input_username='$input_username'): SQL returned '$success' instead of '1' while updating acct_locked.") unless $success == 1; } else { sleep $PREF{num_seconds_to_sleep_on_failed_login}; enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=eacctlck"); } } my $go = (); if(account_exists($input_username, $crypted_input_password, 'new_login')) { my $account_disabled = enc_sql_select("SELECT `acct_disabled` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username';"); if($account_disabled) { enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=eacctdis"); } $PREF{member_is_logged_in} = 1; # technically true, but can be revoked by check_for_multiple_logins(). $PREF{logged_in_userid} = my $userid = get_user_id($input_username); my $session_id = create_new_session_id($input_username, $crypted_input_password); if(my $shared_session_id = check_for_multiple_logins($userid)) { $session_id = $shared_session_id; } #set_cookie($PREF{site_username_cookie}, $input_username, $expiry); #set_cookie($PREF{site_userid_cookie}, $userid, $expiry); set_cookie($PREF{site_session_cookie}, $session_id, $expiry); if($update_this_account_to_new_pw_system) { my $salt = create_random_salt($PREF{salt_length}); my $new_crypted_password = salt_and_crypt_password($input_password, $salt); my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `password` = '$new_crypted_password', `salt` = '$salt' WHERE `id` = $userid;"); die_nice("Error: do_login(): SQL returned '$success' instead of '1' while updating pw and creating salt.") unless $success == 1; $crypted_input_password = $new_crypted_password; } unless(enc_sql_select("SELECT `failed_logins` FROM `$PREF{user_table_name}` WHERE `id` = $userid;") eq '') { my $statement = "UPDATE `$PREF{user_table_name}` SET `failed_logins` = NULL WHERE `id` = $userid;"; my $success = enc_sql_update($statement); die_nice("Error: do_login(id='$userid'): SQL returned '$success' instead of '1' while updating failed_logins. SQL was: [[$statement]]") unless $success == 1; } #set_cookie($PREF{site_password_cookie}, $crypted_input_password, $expiry); log_user_into_db($userid, $session_id, offsettime(), $restrict_ip); if(force_pw_change($userid)) { $go = $PREF{protoprefix} . $ENV{HTTP_HOST} . $PREF{login_url} . "?action=edituser&id=$userid"; } elsif(is_admin($userid)) { if($PREF{on_admin_login_redirect_to}) { $PREF{on_admin_login_redirect_to} =~ s/%%username%%/$input_username/g; $go = $PREF{on_admin_login_redirect_to}; } else { $go = determine_default_login_destination($ref); } } else { if($PREF{on_member_login_redirect_to}) { $PREF{on_member_login_redirect_to} =~ s/%%username%%/$input_username/g; $go = $PREF{on_member_login_redirect_to}; } else { $go = determine_default_login_destination($ref); } } enc_redirect($go); } # Else they tried to log in but failed. else { # Be sure that we do the sleep before the email, so that any # potential email errors don't cause us to abort early thereby # skipping the sleep and possibly giving away the fact that the # login failed. # sleep $PREF{num_seconds_to_sleep_on_failed_login}; my $account_locked = account_exceeds_failed_login_limit($input_username, 'increment'); if($account_locked) { unless(enc_sql_select("SELECT `acct_locked` FROM `$PREF{user_table_name}` WHERE `username` = '$input_username'")) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `acct_locked` = TRUE WHERE `username` = '$input_username';"); die_nice("Error: do_login(input_username='$input_username'): SQL returned '$success' instead of '1' while updating acct_locked.") unless $success == 1; } } email_failed_logins_to_webmaster($input_username, $input_password); if($PREF{on_failed_login_redirect_to}) { $go = $PREF{on_failed_login_redirect_to}; if($account_locked) { $go .= $go =~ /\?/ ? '&account_locked=1' : '?account_locked=1'; } } else { if($account_locked) { $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=eacctlck"; } else { $go = "$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=ebadauth"; } } enc_redirect($go); } } # FC, UB*, VL sub check_if_logged_in() { ($PREF{admin_is_logged_in}, $PREF{member_is_logged_in}, $PREF{logged_in_username}, $PREF{logged_in_realname}, $PREF{logged_in_email}, $PREF{logged_in_userid}) = (0,0,'','','',''); if(my $session_id = get_cookie($PREF{site_session_cookie})) { check_sessionid_for_sql_safeness($session_id); my ($username,$realname,$email,$id,$ip) = enc_sql_select("SELECT username,name,email,id,ip FROM `$PREF{user_table_name}` WHERE `mrsession` = '$session_id';"); if($username && $id) { if(($PREF{enable_ip_address_restriction} =~ /yes/i && $ip) || ($PREF{force_ip_address_restriction} =~ /yes/i)) { return unless $ip eq $ENV{REMOTE_ADDR}; } if(enc_sql_select("SELECT `acct_disabled` FROM `$PREF{user_table_name}` WHERE `id` = '$id';")) { enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?phase=eacctdis"); } $PREF{logged_in_username} = $username; $PREF{logged_in_realname} = $realname; $PREF{logged_in_email} = $email; $PREF{logged_in_email} = $PREF{logged_in_username} if ($PREF{logged_in_email} !~ /.+\@.+\..+/ && $PREF{logged_in_username} =~ /.+\@.+\..+/); $PREF{logged_in_userid} = $id; $PREF{member_is_logged_in} = 1; if(is_admin($PREF{logged_in_userid})) { $PREF{admin_is_logged_in} = 1; } check_and_update_login_session($PREF{logged_in_userid}); if(force_pw_change($PREF{logged_in_userid}) && $qs !~ /^(logout|logoutall|action=commitedituser)$/) { print_user_form('edit', $PREF{logged_in_userid}); exit; } } } } # FC, UB, VL sub check_and_update_login_session($) { my $userid = shift; if($PREF{idle_timeout} > 0) { my $my_session_id = get_cookie($PREF{site_session_cookie}); my $session_id_in_db = enc_sql_select("SELECT `mrsession` FROM `$PREF{user_table_name}` WHERE `id` = $userid;"); my $login_time = enc_sql_select("SELECT `loggedin` FROM `$PREF{user_table_name}` WHERE `id` = $userid;"); #if( ($my_session_id == $session_id_in_db) && ($login_time =~ /[1-9]/ && !login_session_expired($login_time)) ) if( ($my_session_id == $session_id_in_db) && (!login_session_expired($login_time)) ) { update_loggedin_time($userid, $my_session_id, offsettime()); } else { do_logout(); } } } # FC, UB, VL sub update_loggedin_time { my ($userid, $my_session_id, $newtime) = @_; die_unless_numeric($userid,'userid'); die_unless_numeric($newtime,'newtime'); check_sessionid_for_sql_safeness($my_session_id); my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `loggedin` = $newtime WHERE `id` = $userid AND `mrsession` = '$my_session_id';"); die_nice("Error: update_loggedin_time('$userid', '$my_session_id', '$newtime'): SQL returned '$success' instead of '1' while updating loggedin.") unless $success == 1; } # FC, UB, VL sub login_session_expired($) { my $loggedin_time = shift; return ($PREF{idle_timeout} > 0) && (offsettime() - $loggedin_time > $PREF{idle_timeout}); } # FC*, UB*, VL* sub do_logout { my $force_logout_all = shift; $force_logout_all = $force_logout_all eq 'all' ? 1 : 0; if($PREF{we_are_virtual}) { print_http_headers(); $PREF{forced_logout_link} =~ s/%%logout_url%%/$ENV{SCRIPT_NAME}?logout/g; print $PREF{forced_logout_link}; exit; } else { if($PREF{prevent_multiple_simultaneous_logons_per_username} =~ /yes/i || $force_logout_all) { log_user_out_of_db($PREF{logged_in_username}, get_cookie($PREF{site_session_cookie})); } else { die_unless_numeric($PREF{logged_in_userid}, 'logged_in_userid'); my $numusers = enc_sql_select("SELECT `numusers` FROM `$PREF{user_table_name}` WHERE `id` = '$PREF{logged_in_userid}';"); if($numusers > 1) { my $success = enc_sql_update("UPDATE `$PREF{user_table_name}` SET `numusers`=GREATEST((`numusers`-1),0) WHERE `id` = '$PREF{logged_in_userid}';"); die_nice("Error: do_logout(): SQL returned '$success' instead of '1' while decrementing numusers column.") unless $success == 1; } else { log_user_out_of_db($PREF{logged_in_username}, get_cookie($PREF{site_session_cookie})); } } #set_cookie($PREF{site_username_cookie}, 'Guest', '-1M'); #set_cookie($PREF{site_userid_cookie}, '', '-1M'); #set_cookie($PREF{site_password_cookie}, 'foo', '-1M'); set_cookie($PREF{site_session_cookie}, 0, '-1M'); # Remove the "logout" from the referrer, otherwise we'll get stuck # in an infinite logout loop with this Location: call. $ENV{HTTP_REFERER} =~ s/\?.*logout.*$//; my $whence = (); if($PREF{admin_is_logged_in} && $PREF{on_admin_logout_redirect_to}) { $PREF{on_admin_logout_redirect_to} =~ s/%%username%%/$PREF{logged_in_username}/g; $whence = $PREF{on_admin_logout_redirect_to}; } elsif($PREF{member_is_logged_in} && !$PREF{admin_is_logged_in} && $PREF{on_member_logout_redirect_to}) # need the !admin because admins are members too. { $PREF{on_member_logout_redirect_to} =~ s/%%username%%/$PREF{logged_in_username}/g; $whence = $PREF{on_member_logout_redirect_to}; } else { # After logging out, return to the page we were on. if($ENV{HTTP_REFERER}) { $whence = $ENV{HTTP_REFERER}; my $us1 = $PREF{login_url}; my $us2 = $ENV{SCRIPT_NAME}; if($whence =~ /($us1|$us2)\?.+/) { # If the page we were on before was a login page with some # query-string, then don't go there. $whence = (); } } } enc_urlencode($whence); $whence = undef if $PREF{server_bug_prohibits_use_of_whence} =~ /yes/i; enc_redirect("$PREF{protoprefix}$ENV{HTTP_HOST}$PREF{login_url}?action=loggedout&whence=$whence"); } } # FC*, UB*, VL* sub show_loggedout_page { my $ref = shift; enc_urldecode($ref); my $message = $PREF{loggedout_page_template__no_referer}; if($ref) { $message = $PREF{loggedout_page_template__with_referer}; $message =~ s/%%ref%%/$ref/g; } exit_with_success($message); } # FC, UB, VL # This function must do a case-sensitive lookup (i.e., do NOT use LOWER()) because # FC's userdirs are case-sensitive. So whatever case is used when a username is # created is the case that must always be used when logging in with it. # sub account_exists($$$) { #printd "account_exists('$_[0]', '$_[1]', '$_[2]')\n"; my $user = shift; my $pass = shift; my $third_arg = shift; check_username_for_sql_safeness($user); check_hashedpw_for_sql_safeness($pass); my $count = (); if($third_arg eq 'new_login') { $count = enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE `username` = '$user' AND `password` = '$pass'"); } else { die_unless_numeric($third_arg,'userid'); $count = enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE `username` = '$user' AND `password` = '$pass' AND `id` = $third_arg"); } if($count == 1) { return 1; } elsif($count > 1) { die_nice("$0: account_exists('$user', '$pass', '$third_arg'): error: duplicate records ($count total) for this user!\n"); } else { return 0; } } # FC, UB, VL sub is_admin($) { #printd "is_admin('$_[0]')\n"; my $userid = shift; return 0 unless $userid; return 1 if (!userbase_available() && $userid == -3); # don't bother checking the validity of $userid here, # because user_is_member_of_group() will do it. return user_is_member_of_group($userid,$PREF{admin_group_name}); } # FC, UB, VL sub force_pw_change($) { my $userid = shift; return ( $PREF{enable_forced_password_change} =~ /yes/i && enc_sql_select("SELECT `forcepwchng` FROM `$PREF{user_table_name}` WHERE `id` = '$userid';") && ( !is_admin($userid) || (is_admin($userid) && $PREF{admins_can_be_forced_to_change_their_own_pws} =~ /yes/i) ) ); } # FC, UB, VL sub get_group_id($) { printd "get_group_id($_[0])\n"; my $group = shift; if(userbase_available()) { check_groupname_for_uniqueness($group); # checks for sql safeness too. return enc_sql_select("SELECT `id` FROM `$PREF{group_table_name}` WHERE `group` = '$group'"); } else { if($group =~ /^$PREF{public_group_name}$/i) { return -1; } elsif($group =~ /^$PREF{member_group_name}$/i) { return -2; } elsif($group =~ /^$PREF{admin_group_name}$/i) { return -3; } else { die_nice("$PREF{internal_appname}: get_group_id(): invalid group name '$group'.\n"); } } } # FC, UB, VL sub check_uid_for_uniqueness($) { check_id_for_sql_safeness($_[0]); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE `id` = $_[0]") > 1) { die_nice("$0: error: more than one user record with id=$_[0]!\n"); } } # FC, UB, VL sub check_gid_for_uniqueness($) { return unless userbase_available(); printd "check_gid_for_uniqueness: '$_[0]'\n"; check_id_for_sql_safeness($_[0]); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE `id` = $_[0]") > 1) { die_nice("$0: error: more than one group record with id=$_[0]!\n"); } } # FC, UB, VL sub check_username_for_uniqueness($) { #printd "check_username_for_uniqueness: '$_[0]'\n"; check_username_for_sql_safeness($_[0]); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$_[0]')") > 1) { die_nice("$0: error: more than one user record with username='$_[0]'!\n"); } } # FC, UB, VL sub check_groupname_for_uniqueness { return unless userbase_available(); printd "check_groupname_for_uniqueness($_[0])\n"; check_groupname_for_sql_safeness($_[0]); if(enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE LOWER(`group`) = LOWER('$_[0]')") > 1) { die_nice("$0: error: more than one user record with groupname='$_[0]'!\n"); } } # FC, UB, VL sub user_is_member_of_group { my $userid = shift; my $group = shift; printd "user_is_member_of_group(): userid='$userid', group='$group'\n"; if(userbase_available() && $PREF{member_is_logged_in}) { check_groupname_for_sql_safeness($group); die_unless_numeric($userid,'userid'); return 1 if $group =~ /^$PREF{public_group_name}$/i; return 1 if $group =~ /^$PREF{member_group_name}$/i && enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE `id` = $userid;"); return enc_sql_select("SELECT COUNT(*) FROM `$PREF{group_table_name}` WHERE LOWER(`group`) = LOWER('$group') AND `members` REGEXP '(^|,)$userid(,|\$)'"); } else { return 1 if $group =~ /^$PREF{public_group_name}$/i; return 1 if $group =~ /^$PREF{member_group_name}$/i && $userid =~ /^-(2|3)$/; return 1 if $group =~ /^$PREF{admin_group_name}$/i && $userid == -3; } } # FC, UB, VL sub userbase_available { return ($PREF{internal_appname} eq 'userbase' || $PREF{integrate_with_userbase} =~ /yes/i); } # FC, UB, VL sub get_user_id($) { #printd "get_user_id('$_[0]')\n"; my $username = shift; if(userbase_available() && $username) { die_nice("Error: invalid username '$username'.\n") unless username_is_valid($username); check_username_for_uniqueness($username); # checks for sql safeness too. return enc_sql_select("SELECT `id` FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$username')"); } else { if($PREF{admin_is_logged_in}) { return -3; } elsif($PREF{member_is_logged_in}) { return -2; } else { return -1; } # stranger. } } # FC, UB, VL sub get_member_ids_for_group { printd "get_member_ids_for_group($_[0])\n"; my $group = shift; check_groupname_for_sql_safeness($group); # every account is automatically a member of these groups. if($group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i) { my $statement = "SELECT `id` FROM `$PREF{user_table_name}`"; return $PREF{dbh}->selectall_hashref($statement, 'id'); } else { my $member_ids = enc_sql_select("SELECT `members` FROM `$PREF{group_table_name}` WHERE LOWER(`group`) = LOWER('$group')"); my %member_ids = map { $_ => 1 } split(/,/, $member_ids); return \%member_ids; } } # FC, UB, VL sub get_user_name($) { check_uid_for_uniqueness($_[0]); # checks for sql safeness too. return enc_sql_select("SELECT `username` FROM `$PREF{user_table_name}` WHERE `id` = $_[0]"); } # FC, UB, VL sub get_group_name($) { my $gid = shift; if(userbase_available()) { check_gid_for_uniqueness($gid); # checks for sql safeness too. return enc_sql_select("SELECT `group` FROM `$PREF{group_table_name}` WHERE `id` = $gid"); } else { if($gid == -1) { return $PREF{public_group_name}; } elsif($gid == -2) { return $PREF{member_group_name}; } elsif($gid == -3) { return $PREF{admin_group_name}; } else { die_nice("$PREF{internal_appname}: get_group_name(): invalid group ID '$gid'.\n"); } } } sub hashedpw_is_valid { return $_[0] =~ /^[0-9A-Za-z]+$/ && length($_[0]) < $PREF{max_hashedpw_length}; } # FC, UB, VL sub sessionid_is_valid { return $_[0] =~ /^[0-9A-Za-z]+$/ && length($_[0]) < $PREF{max_hashedpw_length}; } # FC, UB, VL sub username_is_valid { my $space = $PREF{allow_spaces_in_usernames} =~ /yes/i ? ' ' : ''; my $atsign = $PREF{allow_atsigns_in_usernames} =~ /yes/i ? '@' : ''; my $dot = $PREF{allow_dots_in_usernames} =~ /yes/i ? '.' : ''; my $dash = $PREF{allow_dashes_in_usernames} =~ /yes/i ? '-' : ''; return ($_[0] =~ /^[0-9A-Za-z_$space$atsign$dot$dash]+$/ && $_[0] =~ /\w/ && length($_[0]) < $PREF{max_username_length}); } sub groupname_is_valid { my $space = $PREF{allow_spaces_in_usernames} =~ /yes/i ? ' ' : ''; my $atsign = $PREF{allow_atsigns_in_usernames} =~ /yes/i ? '@' : ''; my $dot = $PREF{allow_dots_in_usernames} =~ /yes/i ? '.' : ''; my $dash = $PREF{allow_dashes_in_usernames} =~ /yes/i ? '-' : ''; return ($_[0] =~ /^[0-9A-Za-z_$space$atsign$dot$dash]+$/ && $_[0] =~ /\w/ && length($_[0]) < $PREF{max_groupname_length}); } sub check_hashedpw_for_sql_safeness { die_nice("Invalid hashed password: '$_[0]'") unless hashedpw_is_valid($_[0]); } # FC, UB, VL sub check_username_for_sql_safeness { die_nice("Invalid username: '$_[0]'") unless username_is_valid($_[0]); } # FC, UB, VL sub check_groupname_for_sql_safeness { die_nice("Invalid groupname: '$_[0]'") unless groupname_is_valid($_[0]); } # FC, UB, VL sub check_sessionid_for_sql_safeness { die_nice("Invalid session ID: '$_[0]'") unless sessionid_is_valid($_[0]); } # FC, UB, VL sub check_id_for_sql_safeness { die_nice("Invalid ID: '$_[0]'") unless $_[0] =~ /^(\d+|-[123])$/; } # FC, UB, VL # FC, UB sub get_groups_hash { printd "get_groups_hash('$_[0]')\n"; # If you pass in a uid, then the resulting hash will # also indicate which groups that user is a member of. # my $user_id = shift; my ($id, $group, $members, %groups) = (); if(userbase_available()) { my $sth = $PREF{dbh}->prepare("SELECT `id`, `group`, `members` FROM `$PREF{group_table_name}`"); $sth->execute(); $sth->bind_columns(\$id, \$group, \$members); while($sth->fetchrow_arrayref) { $groups{$group}{name} = $group; $groups{$group}{id} = $id; my $is_member = (); if($group =~ /^($PREF{public_group_name}|$PREF{member_group_name})$/i) { $is_member = 1; } elsif($user_id =~ /^\d+$/) { $is_member = $members =~ /(^|,)$user_id(,|$)/; } $groups{$group}{is_member} = $is_member; } } else { $groups{$PREF{public_group_name}}{name} = $PREF{public_group_name}; $groups{$PREF{public_group_name}}{id} = -1; $groups{$PREF{public_group_name}}{is_member} = 1; # everyone's a member of the public. $groups{$PREF{member_group_name}}{name} = $PREF{member_group_name}; $groups{$PREF{member_group_name}}{id} = -2; $groups{$PREF{member_group_name}}{is_member} = 1 if $user_id =~ /^-(2|3)$/; $groups{$PREF{admin_group_name}}{name} = $PREF{admin_group_name}; $groups{$PREF{admin_group_name}}{id} = -3; $groups{$PREF{admin_group_name}}{is_member} = 1 if $user_id =~ /^-3$/; } return \%groups; } # BL, UB, VL # This function must do a case-insensitive lookup (i.e. use LOWER() on both sides) # so that we never create a username multiple times with different cases. # sub username_is_taken { return 0 unless userbase_available(); my $user = shift; check_username_for_sql_safeness($user); return enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE LOWER(`username`) = LOWER('$user')"); } sub email_address_is_taken { my $address = shift; check_emailaddr_for_sql_safeness($address); return enc_sql_select("SELECT COUNT(*) FROM `$PREF{user_table_name}` WHERE LOWER(`email`) = LOWER('$address')"); } # UB, VL sub salt_and_crypt_password($$) { my $plaintext_password = shift; my $salt = shift; die "$0: salt_and_crypt_password(): no salt?\n" unless $salt; my ($salt1,$salt2) = ($salt =~ /^(.{15})(.{25})$/); my $crypted_password = enc_hash($salt1 . $plaintext_password . $salt2); return $crypted_password; } # FC+VL here. ############################################################################################################################################ ### Begin main block. ############################################################################################################################################ load_prefs(); if($qs eq 'logout') { do_logout(); } elsif($qs eq 'logoutall') { do_logout('all'); } elsif($qs =~ /(?:^|&)action=loggedout&whence=(.*)(?:&|$)/) { show_loggedout_page($1); } # note that the whence regex is .* not .*? because the value will likely contain ampersands that we want to keep. elsif($qs =~ /(?:^|&)action=showusers(?:&|$)/) { showusers(); } elsif($qs =~ /(?:^|&)action=newaccount(?:&|$)/) { print_user_form('user_signup'); } elsif($qs =~ /(?:^|&)action=adduser(?:&|$)/) { print_user_form('added_by_admin'); } elsif($qs =~ /(?:^|&)action=edituser&id=(\d+?)(?:&|$)/) { print_user_form('edit', $1); } elsif($qs =~ /(?:^|&)action=commitadduser(?:&|$)/) { process_new_account(); } elsif($qs =~ /(?:^|&)action=commitedituser(?:&|$)/) { edit_user_account(); } elsif($qs =~ /(?:^|&)action=verify&u=(.+?)&t=(\w+)(?:&|$)/) { do_email_verification($1,$2); } elsif($qs =~ /(?:^|&)action=approve_or_del&u=(.+?)&t=(\w+)(?:&|$)/) { approve_or_delete_pending_account($1,$2); } elsif($qs =~ /(?:^|&)action=approve_pending_acct&u=(.+?)&t=(\w+)(?:&|$)/){ approve_or_delete_pending_account_stage2($1,$2,'approve'); } elsif($qs =~ /(?:^|&)action=delete_pending_acct&u=(.+?)&t=(\w+)(?:&|$)/){ approve_or_delete_pending_account_stage2($1,$2,'delete'); } elsif($qs =~ /(?:^|&)action=deletecustomfield&id=(.+?)(?:&|$)/) { delete_custom_field($1); } elsif($qs =~ /(?:^|&)action=commitdeletecustomfield&id=(.+?)(?:&|$)/) { commit_delete_custom_field($1); } elsif($qs =~ /(?:^|&)action=deleteuser&id=(.+?)(?:&|$)/) { delete_user($1); } elsif($qs =~ /(?:^|&)action=commitdeleteuser&id=(.+?)(?:&|$)/) { commit_delete_user($1); } elsif($qs =~ /(?:^|&)action=showgroups(?:&|$)/) { showgroups(); } elsif($qs =~ /(?:^|&)action=addgroup(?:&|$)/) { print_group_form('add'); } elsif($qs =~ /(?:^|&)action=editgroup&id=(\d+)(?:&|$)/) { print_group_form('edit',$1); } elsif($qs =~ /(?:^|&)action=commitaddgroup(?:&|$)/) { process_new_group(); } elsif($qs =~ /(?:^|&)action=commiteditgroup(?:&|$)/) { edit_group(); } elsif($qs =~ /(?:^|&)action=deletegroup&id=(.+?)(?:&|$)/) { delete_group($1); } elsif($qs =~ /(?:^|&)action=commitdeletegroup&id=(.+?)(?:&|$)/) { commit_delete_group($1); } elsif($qs =~ /(?:^|&)action=validate(?:&|$)/) { do_login(); } elsif($qs =~ /(?:^|&)action=chklogin(?:&|$)/) { check_login(); } #elsif($qs =~ /(?:^|&)action=chpw(?:&|$)/) { chpw(); } #elsif($qs =~ /(?:^|&)action=chpw2(?:&|$)/) { chpw2(); } elsif($qs =~ /(?:^|&)action=addcustomfield(?:&|$)/) { add_or_edit_custom_fields('add'); } elsif($qs =~ /(?:^|&)action=editcustomfield(?:&|$)/) { add_or_edit_custom_fields('edit'); } elsif($qs =~ /(?:^|&)action=import(?:&|$)/) { import_users(); } elsif($qs =~ /(?:^|&)action=pwreset1(?:&|$)/) { print_pwreset_page(); } elsif($qs =~ /(?:^|&)action=pwreset2(?:&|$)/) { send_pwreset_email(); } elsif($qs =~ /(?:^|&)action=pwreset3(?:&|$)/) { process_pwreset(); } elsif($qs =~ /(?:^|&)action=test(?:&|$)/) { test_function(); } elsif($qs =~ /(?:^|&)rslt=\d+(?:&|$)/) { show_results_page(); } elsif($qs =~ /(?:^|&)phase=([es].+?)(?:&|$)/) { show_message($1); } else { if($PREF{admin_is_logged_in} && $PREF{always_redirect_admins_to}) { $PREF{always_redirect_admins_to} =~ s/%%username%%/$PREF{logged_in_username}/g; enc_redirect($PREF{always_redirect_admins_to}); } elsif($PREF{member_is_logged_in} && !$PREF{admin_is_logged_in} && $PREF{always_redirect_members_to}) # need the !admin because admins are members too. { $PREF{always_redirect_members_to} =~ s/%%username%%/$PREF{logged_in_username}/g; enc_redirect($PREF{always_redirect_members_to}); } else { if($PREF{member_is_logged_in}) { $PREF{on_page} = 'mainmenu'; start_html_output("Main Menu"); print_login_landing_page(); finish_html_output(); } else { $PREF{on_page} = 'loginform'; start_html_output(); prompt_for_login(); finish_html_output(); } } }
The Jean Hailes Foundation for Women's Health
Jean Hailes Foundation
Ageing Well
Bone Health for Life
Early Menopause
Health for Women
Managing Menopause
Managing PCOS
Online GP & HP Education

  Privacy Statement

© 2007 The Jean Hailes Foundation for Women`s Health

Website by Impagination