From 77a5027292eb23c7576be22bc510f9883d8e2e25 Mon Sep 17 00:00:00 2001 From: Martin Bock Date: Fri, 13 Mar 2020 20:54:48 +0100 Subject: [PATCH 1/2] Implement MigratePasswords command --- app/Commands/MigratePasswords.php | 88 +++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/Commands/MigratePasswords.php diff --git a/app/Commands/MigratePasswords.php b/app/Commands/MigratePasswords.php new file mode 100644 index 0000000..6e1a846 --- /dev/null +++ b/app/Commands/MigratePasswords.php @@ -0,0 +1,88 @@ +option('csv-file')) { + $this->warn('php migrator ' . $this->getSynopsis(false)); + $this->error('Please specify the CSV file with the new password hashes.'); + return; + } + + $hashes = $this->readHashes(); + + $this->table(['Username', 'Domain', 'Hash'], $hashes->toArray()); + + $confirmed = + $this->confirm('Do you want to write these hashes to your database, thus overwriting the existing ones?'); + if (!$confirmed) { + $this->error('You said no, aborting...'); + return; + } + + $this->task('Migrating Passwords', function () use ($hashes) { + DB::connection('mysql_mum')->beginTransaction(); + $hashes->each(function (array $row) { + /** @var Mailbox $mailbox */ + $mailbox = Mailbox::whereAddress($row[0] . '@' . $row[1])->firstOrFail(); + $mailbox->password = $row[2]; + $mailbox->saveOrFail(); + }); + DB::connection('mysql_mum')->commit(); + }); + } + + /** + * Read the supplied CSV file with password hashes. + * We assume it has the following layout and is separated by double quotes. + * | username | domain | hash | + * | -------- | ------ | ------- | + * | joe | doe.co | $2y$... | + * + * @return Collection + */ + private function readHashes(): Collection + { + $file = fopen($this->option('csv-file'), 'r'); + $hashes = Collection::make(); + while ($row = fgetcsv($file, 1000, ',')) { + $hashes->add([$row[0], $row[1], $row[2]]); + } + return $hashes; + } +} From 4af94e17e1b72e2760279376f9150ff6c5fd7c0e Mon Sep 17 00:00:00 2001 From: Martin Bock Date: Fri, 13 Mar 2020 22:32:05 +0100 Subject: [PATCH 2/2] Support direct output from Dovecot password migration script --- app/Commands/MigratePasswords.php | 62 +++++++++++++++++++------------ 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/app/Commands/MigratePasswords.php b/app/Commands/MigratePasswords.php index 6e1a846..d22c57b 100644 --- a/app/Commands/MigratePasswords.php +++ b/app/Commands/MigratePasswords.php @@ -3,16 +3,14 @@ namespace App\Commands; use App\Models\Mum\Mailbox; -use App\Models\PasswordHash\PasswordHashInput; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; -use function app; -use App\Migrators\VimbAdmin\AliasMigrator; -use App\Migrators\VimbAdmin\DomainMigrator; -use App\Migrators\VimbAdmin\MailboxMigrator; +use Illuminate\Support\Facades\Log; +use Throwable; use LaravelZero\Framework\Commands\Command; +use function array_push; use function fgetcsv; use function fopen; +use function preg_replace; class MigratePasswords extends Command { @@ -46,7 +44,7 @@ public function handle(): void $hashes = $this->readHashes(); - $this->table(['Username', 'Domain', 'Hash'], $hashes->toArray()); + $this->table(['Mailbox', 'Hash'], $hashes); $confirmed = $this->confirm('Do you want to write these hashes to your database, thus overwriting the existing ones?'); @@ -55,33 +53,51 @@ public function handle(): void return; } - $this->task('Migrating Passwords', function () use ($hashes) { - DB::connection('mysql_mum')->beginTransaction(); - $hashes->each(function (array $row) { - /** @var Mailbox $mailbox */ - $mailbox = Mailbox::whereAddress($row[0] . '@' . $row[1])->firstOrFail(); - $mailbox->password = $row[2]; - $mailbox->saveOrFail(); - }); - DB::connection('mysql_mum')->commit(); + $output = $this->task('Migrating Passwords', function () use ($hashes) { + try { + DB::connection('mysql_mum')->beginTransaction(); + foreach ($hashes as $row) { + /** @var Mailbox $mailbox */ + $mailbox = Mailbox::whereAddress($row['mailbox'])->firstOrFail(); + $mailbox->password = $row['hash']; + $mailbox->saveOrFail(); + Log::debug('Overwritten password hash for ' . $row['mailbox']); + } + DB::connection('mysql_mum')->commit(); + } catch (Throwable $exception) { + DB::connection('mysql_mum')->rollBack(); + Log::error('Failed to migrate passwords: ' . $exception); + return 1; + } }); + + if (!$output) { + $this->comment('Have a look in the log file for more details.'); + } } /** * Read the supplied CSV file with password hashes. * We assume it has the following layout and is separated by double quotes. - * | username | domain | hash | - * | -------- | ------ | ------- | - * | joe | doe.co | $2y$... | * - * @return Collection + * | mailbox | hash | + * | -------------- | ------------------ | + * | jon @ doe.com | $2y$... | + * | jane @ doe.com | {BLF-CRYPT}$2y$... | + * + * If your hash has a prefix appended by Dovecot's `doveadm`, + * this method will remove it for you. + * + * @return array */ - private function readHashes(): Collection + private function readHashes(): array { $file = fopen($this->option('csv-file'), 'r'); - $hashes = Collection::make(); + $hashes = []; while ($row = fgetcsv($file, 1000, ',')) { - $hashes->add([$row[0], $row[1], $row[2]]); + $mailbox = $row[0]; + $hash = preg_replace('/({\S+})?(\$\S{1,2}\$.+$)/', '$2', $row[1]); + array_push($hashes, ['mailbox' => $mailbox, 'hash' => $hash]); } return $hashes; }