#!/usr/bin/perl package LJ; # # name: LJ::acid_encode # des: Given a decimal number, returns base 30 encoding # using an alphabet of letters & numbers that are # not easily mistaken for each other. # returns: Base 30 encoding, alwyas 7 characters long. # args: number # des-number: Number to encode in base 30. # sub acid_encode { my $num = shift; my $acid = ""; my $digits = "abcdefghjkmnpqrstvwxyz23456789"; while ($num) { my $dig = $num % 30; $acid = substr($digits, $dig, 1) . $acid; $num = ($num - $dig) / 30; } return ("a"x(7-length($acid)) . $acid); } # # name: LJ::acid_decode # des: Given an acid encoding from [func[LJ::acid_encode]], # returns the original decimal number. # returns: Integer. # args: acid # des-acid: base 30 number from [func[LJ::acid_encode]]. # sub acid_decode { my $acid = shift; $acid = lc($acid); my %val; my $digits = "abcdefghjkmnpqrstvwxyz23456789"; for (0..30) { $val{substr($digits,$_,1)} = $_; } my $num = 0; my $place = 0; while ($acid) { return 0 unless ($acid =~ s/[$digits]$//o); $num += $val{$&} * (30 ** $place++); } return $num; } # # name: LJ::acct_code_generate # des: Creates invitation code(s) from an optional userid # for use by anybody. # returns: Code generated (if quantity 1), # number of codes generated (if quantity>1), # or undef on failure. # args: dbarg?, userid?, quantity? # des-userid: Userid to make the invitation code from, # else the code will be from userid 0 (system) # des-quantity: Number of codes to generate (default 1) # sub acct_code_generate { &nodb; my $userid = int(shift); my $quantity = shift || 1; my $dbh = LJ::get_db_writer(); my @authcodes = map {LJ::make_auth_code(5)} 1..$quantity; my @values = map {"(NULL, $userid, 0, '$_')"} @authcodes; my $sql = "INSERT INTO acctcode (acid, userid, rcptid, auth) " . "VALUES " . join(",", @values); my $num_rows = $dbh->do($sql) or return undef; if ($quantity == 1) { my $acid = $dbh->{'mysql_insertid'} or return undef; return acct_code_encode($acid, $authcodes[0]); } else { return $num_rows; } } # # name: LJ::acct_code_encode # des: Given an account ID integer and a 5 digit auth code, returns # a 12 digit account code. # returns: 12 digit account code. # args: acid, auth # des-acid: account ID, a 4 byte unsigned integer # des-auth: 5 random characters from base 30 alphabet. # sub acct_code_encode { my $acid = shift; my $auth = shift; return lc($auth) . acid_encode($acid); } # # name: LJ::acct_code_decode # des: Breaks an account code down into its two parts # returns: list of (account ID, auth code) # args: code # des-code: 12 digit account code # sub acct_code_decode { my $code = shift; return (acid_decode(substr($code, 5, 7)), lc(substr($code, 0, 5))); } # # name: LJ::acct_code_check # des: Checks the validity of a given account code # returns: boolean; 0 on failure, 1 on validity. sets $$err on failure. # args: dbarg?, code, err?, userid? # des-code: account code to check # des-err: optional scalar ref to put error message into on failure # des-userid: optional userid which is allowed in the rcptid field, # to allow for htdocs/create.bml case when people double # click the submit button. # sub acct_code_check { &nodb; my $code = shift; my $err = shift; # optional; scalar ref my $userid = shift; # optional; acceptable userid (double-click proof) my $dbh = LJ::get_db_writer(); unless (length($code) == 12) { $$err = "Malformed code; not 12 characters."; return 0; } my ($acid, $auth) = acct_code_decode($code); my $ac = $dbh->selectrow_hashref("SELECT userid, rcptid, auth ". "FROM acctcode WHERE acid=?", undef, $acid); unless ($ac && $ac->{'auth'} eq $auth) { $$err = "Invalid account code."; return 0; } if ($ac->{'rcptid'} && $ac->{'rcptid'} != $userid) { $$err = "This code has already been used: $code"; return 0; } # is the journal this code came from suspended? my $u = LJ::load_userid($ac->{'userid'}); if ($u && $u->{'statusvis'} eq "S") { $$err = "Code belongs to a suspended account."; return 0; } return 1; } 1;