#! /usr/bin/perl require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl"; use strict; use POSIX qw ( ceil ); my $start = 1; # What userid should we start on? my $chunk = 1000; # How many user rows should we pull at a time to check my $flush_chunk = 100; # How many should we load / update at a time my $dbh = LJ::get_db_writer() or die("No database writer"); my @bad_userids = (); # List of userids that have bad passwords my @common_passwords; if (-e "$ENV{'LJHOME'}/cgi-bin/common-passwords.pl") { require "$ENV{'LJHOME'}/cgi-bin/common-passwords.pl"; my $leetify = sub { my $pass = shift; # Change letters into a letter and number # character class $pass =~ s/a/\[a4\]/g; $pass =~ s/b/\[b8\]/g; $pass =~ s/e/\[e3\]/g; $pass =~ s/g/\[g6\]/g; $pass =~ s/i/\[i1\]/g; $pass =~ s/l/\[l1\]/g; $pass =~ s/o/\[o0\]/g; $pass =~ s/s/\[s5\]/g; $pass =~ s/z/\[z2\]/g; # Change numbers, not created by above, # into a number and letter character class $pass =~ s/(\w)0(\w)/$1\[0o\]$2/g; $pass =~ s/(\w)1(\w)/$1\[1il\]$2/g; $pass =~ s/(\w)2(\w)/$1\[2z\]$2/g; $pass =~ s/(\w)3(\w)/$1\[3e\]$2/g; $pass =~ s/(\w)4(\w)/$1\[4a\]$2/g; $pass =~ s/(\w)5(\w)/$1\[5s\]$2/g; $pass =~ s/(\w)6(\w)/$1\[6g\]$2/g; $pass =~ s/(\w)7(\w)/$1\[7t\]$2/g; $pass =~ s/(\w)8(\w)/$1\[8b\]$2/g; return $pass; }; foreach my $cp (@LJ::COMMON_PASSWORDS) { my $com_len = length($cp); my $cpr = $leetify->($cp); push @common_passwords, $cpr; } } # Start this bad boy up runner(); sub runner { # If we got users this time, assume there are more to get my $more_users = get_users(); if (scalar @bad_userids >= $flush_chunk || !$more_users) { flush(); runner() unless !$more_users; } else { runner(); } } sub get_users { # Grab some users to look at print STDERR "Selecting users from table starting at $start\n"; my $end = $start + $chunk; my $sth = $dbh->prepare("SELECT userid, password FROM user WHERE userid BETWEEN $start AND $end AND password <> ''"); $sth->execute() or die("Unable to select users starting at $start"); my $distinct_chars = sub { my %seen = map { $_, 1 } split//, shift; return scalar keys %seen; }; # Loop over result rows and run their password through cracklib my $users = 0; while (my $row = $sth->fetchrow_hashref()) { $users++; my $bad = 0; # only ASCII if (!LJ::is_ascii($row->{'password'})) { $bad = 1; } # at least 6 chars elsif (length($row->{'password'}) < 6) { $bad = 1; } # must have 4 distinct characters elsif ($distinct_chars->($row->{'password'}) < 4) { $bad = 1; } else { foreach my $cp (@common_passwords) { # See if their password contains the common password if ($row->{'password'} =~ /$cp/i) { $bad = 1; } } } push @bad_userids, $row->{'userid'} if $bad; } # Update our start value for next run $start += $chunk + 1; print STDERR "Done selecting and checking this chunk\n"; return $users; } sub flush { if (scalar @bad_userids < 1) { print STDERR "No users to flush\n"; return 1; } print STDERR "Flushing " . scalar @bad_userids . " users\n"; # Load all the users we need to flush at once by id my $users = LJ::load_userids(@bad_userids); # Did we get them? die("Unable to load users") unless ref $users eq 'HASH'; # Loop over each user and set their prop foreach my $u (values %$users) { print STDERR "$u->{userid} $u->{user}"; unless (LJ::isu($u)) { print STDERR " ERROR - Unable to load user"; } print STDERR "\n"; $u->set_prop('badpassword', 1) or die("Unable to set prop for $u->{userid} $u->{user}"); } # Empty it out for the next list to flush @bad_userids = (); # Since LJ::load_userids_multiple caches each user it # loaded, and we only load each once, destroy the cache # so we don't eat a shitload of memory $LJ::REQ_CACHE_USER_ID = (); print STDERR "Flushed\n"; return 1; } 1;