+ $r->log->warn("Authentication failed for $user/$takenby");
+ output_401($r);
+ return undef;
+ }
+ $r->log->info("Authentication succeeded for $user/$takenby");
+
+ # Make sure we can use Digest authentication in the future with this password.
+ my $ha1 = Digest::MD5::md5_hex($user . ':pr0n.sesse.net:' . $pass);
+ if (!defined($ref->{'digest_ha1_hex'}) || $ref->{'digest_ha1_hex'} ne $ha1) {
+ $dbh->do('UPDATE users SET digest_ha1_hex=? WHERE username=? AND vhost=?',
+ undef, $ha1, $user, $r->get_server_name)
+ or die "Couldn't update: " . $dbh->errstr;
+ $r->log->info("Updated Digest auth hash for for $user");
+ }
+
+ # Make sure we can use bcrypt authentication in the future with this password.
+ # Also remove old-style SHA1 password when we migrate.
+ if (!$bcrypt_matches) {
+ my $salt = get_pseudorandom_bytes(16); # Doesn't need to be cryptographically secur.
+ my $hash = "\$2a\$07\$" . Crypt::Eksblowfish::Bcrypt::en_base64($salt);
+ my $cryptpassword = Crypt::Eksblowfish::Bcrypt::bcrypt($pass, $hash);
+ $dbh->do('UPDATE users SET sha1password=NULL,cryptpassword=? WHERE username=? AND vhost=?',
+ undef, $cryptpassword, $user, $r->get_server_name)
+ or die "Couldn't update: " . $dbh->errstr;
+ $r->log->info("Updated bcrypt hash for $user");
+ }
+
+ return ($user, $takenby);
+}
+
+sub get_pseudorandom_bytes {
+ my $num_left = shift;
+ my $bytes = "";
+ open my $randfh, "<", "/dev/urandom"
+ or die "/dev/urandom: $!";
+ binmode $randfh;
+ while ($num_left > 0) {
+ my $tmp;
+ if (sysread($randfh, $tmp, $num_left) == -1) {
+ die "sysread(/dev/urandom): $!";
+ }
+ $bytes .= $tmp;
+ $num_left -= length($bytes);
+ }
+ close $randfh;
+ return $bytes;
+}
+
+sub check_digest_auth {
+ my ($r, $auth) = @_;
+
+ # We're a bit more liberal than RFC2069 in the parsing here, allowing
+ # quoted strings everywhere.
+ my %auth = ();
+ while ($auth =~ s/^ ([a-zA-Z]+) # key
+ =
+ (
+ [^",]* # either something that doesn't contain comma or quotes
+ |
+ " ( [^"\\] | \\ . ) * " # or a full quoted string
+ )
+ (?: (?: , \s* ) + | $ ) # delimiter(s), or end of string
+ //x) {
+ my ($key, $value) = ($1, $2);
+ if ($value =~ /^"(.*)"$/) {
+ $value = $1;
+ $value =~ s/\\(.)/$1/g;
+ }
+ $auth{$key} = $value;
+ }
+ unless (exists($auth{'username'}) &&
+ exists($auth{'uri'}) &&
+ exists($auth{'nonce'}) &&
+ exists($auth{'opaque'}) &&
+ exists($auth{'response'})) {
+ output_401($r);
+ return undef;
+ }
+ if ($r->uri ne $auth{'uri'}) {
+ output_401($r);