#!/usr/bin/perl -T -w # # Good Sex For Mutants CGI # # Copyright 2003-2005 Deekoo L., all rights lefts and other directions reserved. # This thing is licensed under the GNU GPL, version 2. # use GDBM_File; use Fcntl ':flock'; use Math::Trig qw(great_circle_distance deg2rad pi); # $distance = great_circle_distance($theta0, $phi0, $theta1, $phi1, [, $rho]); my $DATAPATH = '/home/deekoo/var/gsfm'; my $STATICDATAPATH = '/home/deekoo/lib/gsfm'; my $BODYATTRIBS = 'bgcolor="#000000" text="#9966cc" link="#ffffff" '. 'vlink="#cccccc" alink="ff0000" style="background-repeat: no-repeat; '. 'background: url(../../images/eye_of_rlyeh.jpg) right bottom;"'; my $STYLESHEET=''; my (@MINOR_ERRORS, $PASSWORD, $USER_ID, @attribute_descs, @attribute_headers, $query); use CGI; $query = new CGI; $action = $query->param('action'); # DEBUG STUFF - figure out why latlongs are changing. $ENV{PATH} = '/bin:/usr/bin'; my $time = localtime(time()); $time=~s/ /_/gs; $time=~s/:/./gs; $thingy=`tar cfz /home/deekoo/var/gsfm-backup/$time.tar.gz $DATAPATH 2>/dev/null`; $thingy.=''; # And silence a warning. # END DEBUG STUFF if (!defined($action)) { &fatalerror("Cannot build fnordage. No action specified.\n"); } elsif ($action eq 'create') { &do_create(); } elsif ($action eq 'login') { &do_login(); } elsif ($action eq 'search') { &search(); } elsif ($action eq 'set_other_attributes') { &set_other_attributes(); } elsif ($action eq 'show_other_attributes') { &show_other_attributes(); } elsif ($action eq 'set_your_attributes') { &set_your_attributes(); } elsif ($action eq 'set_your_profile') { &fatalerror("XXX set_profile unwritten.\n"); } elsif ($action eq 'show_your_attributes') { &show_your_attributes(); #} elsif ($action eq 'show_your_profile') { # &show_your_profile(); #} elsif ($action eq 'show_your_location') { # &show_your_location(); } elsif ($action eq 'view_profile') { &view_profile(); } else { &fatalerror("No wife, no horse, no moustache, no code for action $action.\n"); } exit(0); #sub show_your_location() #{ # print "Content-type: text/html\012\015\012\015\n"; # print "Show/set your location\n"; # print ""; #} sub show_integer_select() { # Args: Name of select, max, selected. my $sel = $_[2]; print "\n"; } #sub show_your_profile() #{ # &check_login(); # &load_your_attributes(); # &open_profile_db(); # print "Content-type: text/html\015\012\015\012 # Editing your profile # \n"; # print '
'; # print "

Editing profile for $USER_ID

\n"; # &show_integer_select('minopt', &option_requirement_count, $PROFILES{"minopt_$USER_ID"}); # print ''; # print ''; # print "
\n"; #} sub htmlescape() { my $ret = $_[0]; $ret =~ s/&/&/gs; $ret =~ s//>/gs; $ret =~ s/"/"/gs; return $ret; } sub open_profile_db() { &read_lock_data_file('profiles.db'); dbmopen(%PROFILES, "$DATAPATH/profiles.db", 0666) or &fatalerror("Unable to profile dbmopen.\n"); } sub view_profile() { &check_login(); my $other = $query->param('o'); &unblank($other) or &fatalerror("Viewing the profile of that which does not exist.\n"); &open_profile_db() or &fatalerror("Could not open profile DB.\n"); &read_lock_data_file('other-attributes.db'); dbmopen(%OTHER_ATTRIBUTES_DB, "$DATAPATH/other-attributes.db", 0666) or &fatalerror("Cannot open other-attributes.db\n"); &read_lock_data_file('attributes.db'); dbmopen(%ATTRIBUTES_DB,"$DATAPATH/attributes.db", 0666) or &fatalerror("Cannot open attributes.db\n"); &consider_match($other); if (exists($PROFILES{"maxunc_$other"}) and ($YOUR_MANDATORY_SCORE{$other} > $PROFILES{"maxunc_$other"})) { &fatalerror("Profile restriction.\n"); } if (exists($PROFILES{"minopt_$other"}) and ($YOUR_DESIRABILITY{$other} < $PROFILES{"minopt_$other"})) { &fatalerror("Restriction Profilakteka.\n"); } print "Content-type: text/html\015\012\015\012Viewing profile for \n"; print &htmlescape($other); print "$STYLESHEET\n"; print "

Viewing profile of ",&htmlescape($other),"

\n"; if (exists($PROFILES{"desc_$other"})) { print "
",$PROFILES{"desc_$other"},"
\n"; } else { print "

[No profile set]

\n"; } if (exists($PROFILES{"long_$USER_ID"}) && exists($PROFILES{"long_$other"})) { my $distrad = great_circle_distance( deg2rad($PROFILES{"long_$USER_ID"}), pi/2-deg2rad($PROFILES{"lat_$USER_ID"}), deg2rad($PROFILES{"long_$other"}), pi/2-deg2rad($PROFILES{"lat_$other"})); my $distnm = $distrad/0.00029088820866572159; my $distsm = $distnm*1.15077945; my $distkm = $distsm*1.609344; print "Distance: $distsm miles, or $distkm kilometers, or ", $distkm*1000/1024, " kibometers. (A kibometer is 1024 meters.)

"; } else { print "

Cannot calculate distance - you and/or $other don't have a location specified.

\n"; } &show_command_footer(); print "\n"; } sub consider_match() { # No need to consider people with no self-description whatsoever. exists($ATTRIBUTES_DB{$_[0]}) or return; my @their_attributes = split(/,/s, $ATTRIBUTES_DB{$_[0]}); my @their_other_attributes = split(/,/s, $OTHER_ATTRIBUTES_DB{"_$_[0]"}); my @their_other_attributes_forbidden = split(/,/s, $OTHER_ATTRIBUTES_DB{"f_$_[0]"}); my @their_other_attributes_required = split(/,/s, $OTHER_ATTRIBUTES_DB{"r_$_[0]"}); my $x = 0; my $their_forbidden_unclear = 0; my $forbidden_unclear = 0; my $forbidden_absent = 0; my $required_unclear = 0; my $required_present = 0; # my $desirable_absent = 0; # my $desirable_present = 0; # my $undesirable_absent = 0; # my $undesirable_present = 0; my $desirability = 0; my $your_mandatory_score = 0; my $your_mandatory_uncertainty = 0; my $your_desirability = 0; while ($x < $#attribute_descs) { $their_attributes[$x] = 0 if !defined($their_attributes[$x]); $their_other_attributes[$x] = 0 if !defined($their_other_attributes[$x]); $your_attributes[$x] = 0 if !defined($your_attributes[$x]); $other_attributes[$x] = 0 if !defined($other_attributes[$x]); if ($other_attributes_forbidden[$x]) { if ($their_attributes[$x]==1) { # They have something the user doesn't want. return; } elsif ($their_attributes[$x] == 0) { $forbidden_unclear++; } else { # It's a trinary; 0, 1, and -1 should be the only allowed # entries. # Assume they don't have it. $forbidden_absent++; } } elsif ($other_attributes_required[$x]) { # We'll assume that the user doesn't both require and forbid something. # Users who find a way to deserve unreliability. if ($their_attributes[$x] == -1) { return; } elsif ($their_attributes[$x] == 1) { $required_present++; } else { $required_unclear++; } } else { # It's only desirable/non. $desirability += $their_attributes[$x] * $other_attributes[$x]; # if ($other_attributes[$x] == 1) { # if ($their_attributes[$x]==1) { # $desirable_present++; # } elsif ($their_attributes[$x]==-1 { # $desirable_absent++; # } # } elsif ($other_attributes[$x] == -1) { # if ($their_attributes[$x]==1) { # $undesirable_present++; # } elsif ($their_attributes[$x]==-1) { # $undesirable_absent++; # } # } } if ($their_other_attributes_forbidden[$x]) { if ($your_attributes[$x]==1) { return; # They don't want you. } elsif ($your_attributes[$x]==0) { $your_mandatory_uncertainty++; } else { $your_mandatory_score++; } } elsif ($their_other_attributes_required[$x]) { if ($your_attributes[$x]==-1) { return; } elsif ($your_attributes[$x]==0) { $your_mandatory_uncertainty++; } else { $your_mandatory_score++; } } else { $your_desirability += $your_attributes[$x] * $their_other_attributes[$x]; } $x++; } $MANDATORY_SCORE{$_[0]} = $forbidden_absent + $required_present; $YOUR_MANDATORY_SCORE{$_[0]} = $your_mandatory_score; $MANDATORY_UNCERTAINTY{$_[0]} = $forbidden_unclear+$required_unclear; $YOUR_MANDATORY_UNCERTAINTY{$_[0]} = $your_mandatory_uncertainty; $DESIRABILITY{$_[0]} = $desirability; $YOUR_DESIRABILITY{$_[0]} = $your_desirability; } sub score($) { # Returns the score # XXX does not yet factor in distance. return ( $MANDATORY_SCORE{$_[0]}**2 - $MANDATORY_UNCERTAINTY{$_[0]}**2) + $DESIRABILITY{$_[0]} + ( $YOUR_MANDATORY_SCORE{$_[0]}**2 - $YOUR_MANDATORY_UNCERTAINTY{$_[0]}**2) + $YOUR_DESIRABILITY{$_[0]}; } #sub lat($) #{ # if ($_[0] < 0) { # return (0-$_[0]).'S'; # } # return "$_[0]N"; #} # #sub long($) #{ # if ($_[0] < 0) { # return (0-$_[0]).'W'; # } # return "$_[0]E"; #} sub arc_string($$$) { my ($deg,$pos,$neg) = @_; my $ideg = int($deg); my $ret = abs($ideg); if ($deg != $ideg) { my $minsec = abs($deg-$ideg); my $mins = int(sprintf("%.6g",$minsec*60)); $ret .= " ".$mins."'"; if ($mins!=($minsec*60)) { my $secs = int(sprintf("%.1g",(($minsec*60)-$mins)*60)); $ret .= " ".$secs."''" if $secs; } } if ($deg>0) { $ret .= $_[1]; } elsif ($deg<0) { $ret .= $_[2]; } return $ret; } sub long_string($) { return arc_string($_[0],'E','W'); } sub lat_string($) { return arc_string($_[0],'N','S'); } sub latlong_and_distance($) { my $other = $_[0]; my $ret=''; if (exists($PROFILES{"lat_$other"})) { $ret .= lat_string($PROFILES{"lat_$other"}). ' '. long_string($PROFILES{"long_$other"}); } else { return '[unknown]'; } if (exists($PROFILES{"lat_$USER_ID"})) { my $distrad = great_circle_distance( deg2rad($PROFILES{"long_$USER_ID"}), pi/2-deg2rad($PROFILES{"lat_$USER_ID"}), deg2rad($PROFILES{"long_$other"}), pi/2-deg2rad($PROFILES{"lat_$other"})); my $distnm = $distrad/0.00029088820866572159; my $distsm = int($distnm*1.15077945); $ret .= "
(~$distsm miles away)"; } else { $ret .= ' [immeasurable]'; } # my $distkm = $distsm*1.609344; # return &lat($PROFILES{"lat_$other"}).'/'.&long($PROFILES{"long_$other"}). # "($distsm miles away)"; return $ret; } sub sort_and_show_matches() { my @unsorted = keys(%MANDATORY_SCORE); my @matches = sort { &score($b) <=> &score($a); } @unsorted; print "
\n"; print ''; print '\n"; print '\n"; print ""; print "\n"; print "\n"; print ""; print ""; print "\n"; print "\n"; print "\n"; foreach (@matches) { print ""; print ""; print "\n"; print "'; print "\n"; print "'; print "\n"; } print "
UsernameTheir suitability to you.Your suitability to them.Their locale
(Distance)
 RequiredOptionalRequiredOptional
$MANDATORY_SCORE{$_} ", $MANDATORY_UNCERTAINTY{$_} ? "(Uncertainty $MANDATORY_UNCERTAINTY{$_})" : "", "$DESIRABILITY{$_}$YOUR_MANDATORY_SCORE{$_}", $YOUR_MANDATORY_UNCERTAINTY{$_} ? " (Uncertainty $YOUR_MANDATORY_UNCERTAINTY{$_})" : "", '$YOUR_DESIRABILITY{$_}",&latlong_and_distance($_),'
\n"; return; } sub search() { &check_login(); &read_lock_data_file('other-attributes.db'); &load_attribute_descs(); &open_profile_db(); dbmopen(%OTHER_ATTRIBUTES_DB, "$DATAPATH/other-attributes.db", 0666) or &fatalerror("Cannot open other-attributes.db\n"); exists($OTHER_ATTRIBUTES_DB{"_$USER_ID"}) or &fatalerror("Searching for anything that moves... Bugger all, can't use the motion detector 'cause everyone and everything in my databanks is too paranoid to turn their webcam on for you. Go tell us what you want to fuck and maybe the search'll work better, 'k?"); @other_attributes = split(/,/s, $OTHER_ATTRIBUTES_DB{"_$USER_ID"}); @other_attributes_forbidden = split(/,/s, $OTHER_ATTRIBUTES_DB{"f_$USER_ID"}); @other_attributes_required = split(/,/s, $OTHER_ATTRIBUTES_DB{"r_$USER_ID"}); &read_lock_data_file('attributes.db'); dbmopen(%ATTRIBUTES_DB,"$DATAPATH/attributes.db", 0666) or &fatalerror("Cannot open attributes.db\n"); exists($ATTRIBUTES_DB{$USER_ID}) or &fatalerror("You need to describe yourself before you can search.\n"); @your_attributes = split(/,/s, $ATTRIBUTES_DB{$USER_ID}); print "Content-type: text/html\015\012\015\012"; print "Searching...$STYLESHEET\n"; print "\n"; print "

(Searching using your existing preference settings.)

\n"; my @keys = keys(%ATTRIBUTES_DB); my $key; my $x; for $key (@keys) { &consider_match($key); } &sort_and_show_matches(); print "\n"; dbmclose(%ATTRIBUTES_DB); &unlock_data_file("attributes.db"); dbmclose(%OTHER_ATTRIBUTES_DB); &unlock_data_file("other-attributes.db"); } sub option_requirement_count() { my $x=0; my $ret = 0; while ($x<$#other_attributes) { if ($other_attributes[$x]) { $ret++; } $x++; } return $ret; } sub sanity_check_profile() { my $opt = &option_requirement_count(); $your_profile{'minopt'} = 0 if !defined($your_profile{'minopt'}); if ($your_profile{'minopt'}>$opt) { push(@MINOR_ERRORS, "Minimum optional desirability exceeds your optional desirability range.\n Fixing."); $your_profile{'minopt'} = $opt; } if ($your_profile{'desc'}=~s/20000) { push(@MINOR_ERRORS, "Your profile was too long. It has been truncated. (Removed section:
". substr($your_profile{'desc'},20000)."
)
\n");
    substr($your_profile{'desc'}, 20000) = '';
  }
}

sub save_your_attributes($)
{
  &write_lock_data_file('attributes.db');
  dbmopen(%YOUR_ATTRIBUTES_DB, "$DATAPATH/attributes.db", 0666) or
    &fatalerror("Unable to dbmopen(attributes.db)\n");
  $YOUR_ATTRIBUTES_DB{$USER_ID} = join(',',@your_attributes);
  dbmclose(%YOUR_ATTRIBUTES_DB);
  &unlock_data_file('attributes.db');
  &save_your_profile();
}

sub save_your_profile()
{
  &write_lock_data_file('profiles.db');
  dbmopen(%PROFILES, "$DATAPATH/profiles.db", 0666) or
    &fatalerror("Unable to dbmopen(profiles.db)\n");
  &sanity_check_profile();
  $PROFILES{"maxunc_$USER_ID"} = $your_profile{'maxunc'} if
    defined($your_profile{'maxunc'});
  $PROFILES{"minopt_$USER_ID"} = $your_profile{'minopt'} if
    defined($your_profile{'minopt'});
  $PROFILES{"lat_$USER_ID"} = $your_profile{'lat'} if
    (defined($your_profile{'lat'}) && $your_profile{'lat'} ne '');
  $PROFILES{"long_$USER_ID"} = $your_profile{'long'} if
    (defined($your_profile{'long'}) && $your_profile{'long'} ne '');
  $PROFILES{"desc_$USER_ID"} = $your_profile{'desc'} if
    defined($your_profile{'desc'});
  $PROFILES{"maxdist_$USER_ID"} = $your_profile{'maxdist'} if
    (defined($your_profile{'maxdist'}) && $your_profile{'maxdist'} ne '');
  dbmclose(%PROFILES);
  &unlock_data_file('profiles.db');
}

sub parse_arc($$$)
{
  return undef unless defined $_[0];
  my ($latin,$posch,$negch) = @_;
  my $sign = 1;
  if ($latin=~s/$negch//sig) {
    return undef if $latin=~/$posch/is;
    $sign = -1;
  } else {
    $latin=~s/$posch//sig;
  }
  if ($latin=~/^([0-9]+)$/s) {
    return $latin*$sign;
  } elsif ($latin =~/^(-[0-9]+)$/s) {
    return $latin*$sign;
  }
  if ($latin=~/^([0-9]+)([^0-9]+)(.*?)$/s) {
    my $degrees = $1;
    my $tail=$3;
    $tail=~s/^\s+//si;
    $tail=~s/\s+$//si;
    if ($tail=~/([0-9]+)'$/s) {
      $degrees += $1*(1/60);
    } elsif ($tail=~/([0-9]+)'[^0-9]*([0-9]+)(''|")/si) {
      my $minutes = $1;
      my $seconds = $2;
      return ($degrees + ($minutes*(1/60)) + ($seconds*(1/3600))) * $sign;
    }
    return $degrees * $sign;
  }
  return undef;
}

sub parse_latitude($)
{
  return parse_arc($_[0],'N','S');
}

sub parse_longitude($)
{
  return parse_arc($_[0],'E','W');
}

sub set_your_attributes($)
{
  &check_login();
  my @params = $query->param();
  my $param;
  for $param (@params) {
    if ($param=~/^option_([0-9]+)$/s) {
      my $num = $1;
      $your_attributes[$num] = $query->param($param);
    }
  }
  $your_profile{'maxunc'} = $query->param('profile_maxunc');
  $your_profile{'minopt'} = $query->param('profile_minopt');
  $your_profile{'desc'} = $query->param('profile_desc');
  $your_profile{'lat'} = parse_latitude($query->param('profile_lat'));
  $your_profile{'long'} = parse_longitude($query->param('profile_long'));
  $your_profile{'maxdist'} = $query->param('profile_maxdist');
  &save_your_attributes();
  &success("Your attributes have been updated.\n");
}

sub show_your_attribute($)
{
  print "$attribute_descs[$_[0]]: 
"; } sub write_lock_data_file() { open("LOCK_$_[0]","<$DATAPATH/$_[0]") or &fatalerror("Could not open $_[0] for locking\n"); flock("LOCK_$_[0]", &LOCK_EX) or &fatalerror("Could not lock $_[0]\n"); } sub read_lock_data_file() { open("LOCK_$_[0]","<$DATAPATH/$_[0]") or &fatalerror("Could not open $_[0] for locking\n"); flock("LOCK_$_[0]", &LOCK_SH) or &fatalerror("Could not lock $_[0]\n"); } sub unlock_data_file($) { flock("LOCK_$_[0]", &LOCK_UN) or &fatalerror("Silly error unlocking $_[0]\n"); } sub load_your_attributes() { &read_lock_data_file('attributes.db'); dbmopen(%YOUR_ATTRIBUTE_DB,"$DATAPATH/attributes.db", 0666) or &fatalerror("Could not open attributes.db\n"); if (exists($YOUR_ATTRIBUTE_DB{$USER_ID})) { @your_attributes = split(/,/, $YOUR_ATTRIBUTE_DB{$USER_ID}); } dbmclose(%YOUR_ATTRIBUTE_DB); &unlock_data_file('attributes.db'); } sub textarea_clean() { my $ret = $_[0]; $ret=~s/Good Sex For Mutants: You $STYLESHEET
"; print "

About You

\n"; print "

Put anything that you can't describe using the trinary pulldowns", " here. Including your contact information; if you don't put some form ", " of contact information here, no prospective match who comes along ", " later will be able to find you. HTML is not allowed in this ", "field.

\n"; print "
\n"; print "Minimum desirability to see your profile:"; &show_integer_select('profile_minopt', $#attribute_descs, $PROFILES{"minopt_$USER_ID"}, [ "Doesn't matter", -1000 ]); print "
(You will only show up in searches by users who are at least this desirable to you, as near as the program can tell.)
"; print "

Maximum uncertainty to see your profile:"; &show_integer_select('profile_maxunc', $#attribute_descs, $PROFILES{"maxunc_$USER_ID"}, [ "Doesn't matter", 1000 ]); print "
(this determines how many of your required attributes the person searching can have answered with 'decline to state' and still see you in search responses.)

"; print "

Latitude: / "; print "Longitude: (Note: Lat and long should be numbers only. West longitude and south latitude are negative. Any good atlas should have a \"You are Here\" mark to find the correct locale...)
\n"; print "Maximum distance away to show matches (in miles): (Not yet used.)

\n"; my $x = 0; while ($x < $#attribute_descs) { &show_attribute_header($x); &show_your_attribute($x); $x++; } print "\n"; print "\n"; print "\n"; print "\n"; print ""; } sub get_user_id() { $USER_ID = $query->param('user_id'); &unblank($USER_ID) or &fatalerror("user_id left blank.\n"); } sub set_other_attributes($) { &check_login(); &load_other_attributes(); my @params = $query->param(); my $p; for $p (@params) { if ($p=~/^opinion_([0-9]+)$/s) { my $num = $1; my $val = $query->param($p); if ($val eq 'forbidden') { $other_attributes_forbidden[$num] = 1; $other_attributes_required[$num] = 0; $other_attributes[$num] = -20000; } elsif ($val eq 'required') { $other_attributes_required[$num] = 1; $other_attributes_forbidden[$num] = 0; $other_attributes[$num] = 20000; } else { $other_attributes[$num] = $val; $other_attributes_required[$num] = 0; $other_attributes_forbidden[$num] = 0; } } } &save_other_attributes(); &success("Partner's desired attributes updated.\n"); } sub save_other_attributes() { dbmopen(%OTHER_ATTRIBUTES_DB, "$DATAPATH/other-attributes.db", 0666) or &fatalerror("Could not dbopen other-attributes.db\n"); $OTHER_ATTRIBUTES_DB{"_$USER_ID"} = join(',',@other_attributes); $OTHER_ATTRIBUTES_DB{"r_$USER_ID"} = join(',',@other_attributes_required); $OTHER_ATTRIBUTES_DB{"f_$USER_ID"} = join(',',@other_attributes_forbidden); dbmclose(%OTHER_ATTRIBUTES_DB); } sub print_selector($) { print ''; defined($other_attributes[$_[0]]) or $other_attributes[$_[0]] = 0; print ''; print '"; print ''; print '\n"; } sub load_attribute_descs() { local *FILE; open(FILE,"<$STATICDATAPATH/attribute-descriptions") or &fatalerror("Cannot load attribute descriptions ($!).\n"); local $/ = '%%'; @attribute_descs = ; close(FILE); my $x = 0; @attribute_headers = (''); while ($x<=$#attribute_descs) { if ($attribute_descs[$x]=~/^::(.*)/s) { $attribute_headers[$x]=$1; splice(@attribute_descs, $x, 1); chomp($attribute_headers[$x]); } else { chomp($attribute_descs[$x++]); $attribute_headers[$x] = ''; } } } sub load_other_attributes() { dbmopen(%OTHER_ATTRIBUTE_DB,"$DATAPATH/other-attributes.db", 0644) or &fatalerror("Error opening other-attributes.db\n"); if (exists($OTHER_ATTRIBUTE_DB{"_$USER_ID"})) { @other_attributes = split(/,/,$OTHER_ATTRIBUTE_DB{"_$USER_ID"}); } if (exists($OTHER_ATTRIBUTE_DB{"r_$USER_ID"})) { @other_attributes_required = split(/,/, $OTHER_ATTRIBUTE_DB{"r_$USER_ID"}); } if (exists($OTHER_ATTRIBUTE_DB{"f_$USER_ID"})) { @other_attributes_forbidden = split(/,/, $OTHER_ATTRIBUTE_DB{"f_$USER_ID"}); } dbmclose(%OTHER_ATTRIBUTE_DB); } =head2 sub uriescape($) { my $ret = $_[0]; $ret =~ s/\%/%25/gs; while ($ret =~ /([^-a-zA-Z0-9\%\. ])/s) { my $rep = $1; my $enc = sprintf("%02x",ord($rep)); $ret =~ s/\Q$rep\E/\%$enc/gs; } return $ret; } =cut sub show_attribute_header($) { return if !$attribute_headers[$_[0]]; print "\"(*)\"\n"; print "\"\n"; print "

$attribute_headers[$_[0]]

\n"; } else { print ' width=100>
$1
\n"; } print "
"; } sub show_other_attributes() { &check_login(); &load_attribute_descs(); &load_other_attributes(); print "Content-type: text/html\015\012\015\012 Good Sex For Mutants: Desire $STYLESHEET

Input your opinion of the following attributes in a sex partner:

    "; my $x = 0; while ($x<$#attribute_descs) { &show_attribute_header($x); print "
  • $attribute_descs[$x]:
  • "; $x++; } print "\n"; print "\n"; print "\n"; print "
    \n"; print "
    \n"; print "
\n"; exit(0); } sub unblank() { return (defined($_[0]) && ($_[0] ne '')); } sub check_login() { &open_userdb(); my $user = $query->param('user_id'); &unblank($user) or &fatalerror("No user ID specified.\n"); exists($USER_DB{$user}) or &fatalerror("No such user $user in database. Note that your user ID is case-sensitive: Stang, STANG, and stang are really *three different people*, and sTANG doesn't exist."); my $pass = $query->param('password'); defined($pass) or &fatalerror("No password specified.\n"); if ($USER_DB{$user} ne $pass) { &fatalerror("The password you typed isn't $user\'s password.\n"); } $USER_ID = $user; $PASSWORD = $pass; } sub do_login() { &check_login(); &success("Login OK.\n"); } sub open_userdb() { if (!dbmopen(%USER_DB, "$DATAPATH/users.db", 0644)) { print STDERR "Couldn't open $DATAPATH/users.db\n"; &fatalerror("User database\nCannot be opene'd now\nperhaps it is gone?\n\n\n($!)\n"); } } sub close_userdb() { dbmclose(%USER_DB); } sub do_create() { &open_userdb(); my $user = $query->param('user_id'); if (!defined($user)) { &fatalerror("How can you create what you have not defined? You must define the momentum, then, and 6D coordinates are too annoying to parse right now!\n"); } if (exists($USER_DB{"$user"})) { &fatalerror("Khepera created Itself. Sie nicht Khepera.\nAnd it appears someone has created an account as $user already.\n Maybe it's you."); } &fatalerror("Sorry, all usernames with underscores are reserved for\n use after X-day 8661.\n") if $user=~/^_/s; $USER_ID = $user; my $password = $query->param('password'); defined($password) or &fatalerror("You need a password, Silly One. Unless you are many, in which case you still need a password, Silly Many.\n"); my $password2 = $query->param('password2'); defined ($password2) or &fatalerror("Only one password per account. Only one account per password. Only one assword per fusion.\n"); if ($password2 ne $password) { &fatalerror("Password does not match password. Are you sure you are authorized to be yourself?\n"); } &check_password($password); $USER_DB{$user} = $password; $PASSWORD = $password; &close_userdb(); &success('Your account has been created.'); } sub fatalerror($) { print "Content-type: text/html\015\012\015\012 Good Sex For Mutants: Error $STYLESHEET
\n";
  print "Fatal Error: $_[0]\n
\n"; &show_command_footer() if (defined($USER_ID) && defined($PASSWORD)); print "\n"; exit(0); } sub success() { if (!defined($USER_ID)) { &fatalerror("Internal error: You aren't using this, it seems.\n"); } if (!defined($PASSWORD)) { &fatalerror("Internal error while succeeding: I forgot your password.\nPlease email your credit card number to our staff and we'll fix it.\nOr something like that.\nIf this success had been successful, the message would have been\n$_[0]"); } print "Content-type: text/html\015\012\015\012 Success$STYLESHEET
";
  if (@MINOR_ERRORS) {
    print "Minor errors occurred:\n";
    for (@MINOR_ERRORS) {
      print "$_\n";
    }
  }
  print
    "$_[0]
"; &show_command_footer(); print "\n"; } sub show_command_footer() { my $uid = &htmlescape($USER_ID); my $pw = &htmlescape($PASSWORD); print "
"; print "
"; } sub check_password($) { my $password = $_[0]; if (($password =~ /\Q$USER_ID\E/s) || ($USER_ID=~/\Q$password\E/s) || ($password =~/^fuck$/i) || ($password eq '') || ($password=~/^password$/i)) { &fatalerror("Stupid password.\n"); } }