3
0
mirror of https://github.com/pragma-/pbot.git synced 2024-11-21 19:39:44 +01:00

Store user passwords as salted hash digests

This was way overdue. User passwords are no longer stored as cleartext.

When PBot is restarted after applying this commit, all stored passwords will
be converted to salted hash digests.

The `useradd`, `userset` and `my` commands will now hash passwords.

Why did it take me so long to finally get around to hashing passwords
properly, you might ask. The reason why this wasn't done sooner is because
all of my users used hostmask-based `autologin`. The passwords that PBot
randomly generated were ignored and never used.

I do regret that it took me so long to get around to this, for those of you
who might be using custom passwords instead of hostmask-based `autologin`.
This commit is contained in:
Pragmatic Software 2024-06-22 22:38:15 -07:00
parent 784c2508e5
commit 6722fd7f8d
No known key found for this signature in database
GPG Key ID: CC916B6E3C84ECCE
8 changed files with 134 additions and 19 deletions

View File

@ -12,6 +12,7 @@ requires 'perl' => '5.020000';
# PBot core # PBot core
requires 'Cache::FileCache'; requires 'Cache::FileCache';
requires 'Carp'; requires 'Carp';
requires 'Crypt::SaltedHash';
requires 'DateTime'; requires 'DateTime';
requires 'DateTime::Format::Duration'; requires 'DateTime::Format::Duration';
requires 'DateTime::Format::Flexible'; requires 'DateTime::Format::Flexible';

View File

@ -109,7 +109,7 @@ Parameter | Description
`hostmasks` | The hostmasks from which this user is recognized/allowed to login from (e.g., `somenick!*@*.somedomain.com` or `*!*@unaffiliated/someuser`). Can be a comma-separated list of values. `hostmasks` | The hostmasks from which this user is recognized/allowed to login from (e.g., `somenick!*@*.somedomain.com` or `*!*@unaffiliated/someuser`). Can be a comma-separated list of values.
`channels` | The channels this user belongs to; use `global` for all channels. Can be a comma-separated list of values. `channels` | The channels this user belongs to; use `global` for all channels. Can be a comma-separated list of values.
`capabilities` | A comma-separated list of [user-capabilities](#user-capabilities) for this user. `capabilities` | A comma-separated list of [user-capabilities](#user-capabilities) for this user.
`password` | The password the user will use to login (from `/msg`, obviously). Generates a random password if omitted. Users may view and set their password by using the [`my`](Commands.md#my) command. `password` | The password the user will use to login (from `/msg`, obviously). Users may update their password by using the [`my`](Commands.md#my) command once logged in.
### userdel ### userdel
Removes a user from PBot. Removes a user from PBot.

View File

@ -101,13 +101,19 @@ to set the `preserve_whitespace` [factoid metadata](Factoids.md#factoid-metadata
## How do I change my password? ## How do I change my password?
If you have a NickServ account or a unique hostmask, you don't need a PBot password. If you have a NickServ account or a unique hostmask, you don't need a PBot password.
The `stayloggedin` metadata on your user account can be set instead. The `autologin` and `stayloggedin` metadata on your user account can be set instead.
But if you prefer to be safe instead of sorry, use the [`my`](Commands.md#my) command But if you prefer to be safe instead of sorry, use the [`my`](Commands.md#my) command
to set the `password` user metadata for your user account. Your hostmask must match the to set the `password` and unset the `autologin` and `stayloggedin` metadata for your
user account. user account. Your hostmask must match the user account and you must be logged in.
my password <your password> my password <your password>
my autologin 0
my stayloggedin 0
If you are unable to log in, ask an admin to set a temporary password for you
with the [`userset`](Admin.md#userset) command. Log in with the temporary
password and then use the above commands to update your password.
## How do I make PBot remember my `date` timezone? ## How do I make PBot remember my `date` timezone?
Use the [`my`](Commands.md#my) command to set the `timezone` user metadata for your Use the [`my`](Commands.md#my) command to set the `timezone` user metadata for your

View File

@ -362,7 +362,7 @@ command in the PBot terminal console. Its usage is:
Suppose your nick is `Bob` and your hostmask is `Bob!~user@some.domain.com`. Use the following command: Suppose your nick is `Bob` and your hostmask is `Bob!~user@some.domain.com`. Use the following command:
useradd Bob Bob!~user@*.domain.com global botowner useradd Bob Bob!~user@*.domain.com global botowner <password>
This will create a user account named `Bob` with the `botowner` [user-capability](Admin.md#user-capabilities) that can administrate This will create a user account named `Bob` with the `botowner` [user-capability](Admin.md#user-capabilities) that can administrate
all channels. Note the wildcard replacing `some` in `some.domain.com`. Now as long as all channels. Note the wildcard replacing `some` in `some.domain.com`. Now as long as
@ -372,16 +372,14 @@ It is very important that user account hostmasks are defined as strictly or as n
as possible to match only the person it is intended for. Ideally, the user would have a as possible to match only the person it is intended for. Ideally, the user would have a
NickServ account, a user-cloak given by the staff of the IRC server or a unique DNS name. NickServ account, a user-cloak given by the staff of the IRC server or a unique DNS name.
In your own IRC client, connected using the hostmask we just added, type the You can change your password with:
following command, in a private `/query` or `/msg`:
my password
This will show you the randomly generated password that was assigned to your
user account. You can change it -- if you want to -- with:
my password <new password> my password <new password>
or
userset Bob password <new password>
Then you can login with: Then you can login with:
login <password> login <password>
@ -389,8 +387,30 @@ Then you can login with:
Now you can use `/msg` in your own IRC client to administrate PBot, instead of Now you can use `/msg` in your own IRC client to administrate PBot, instead of
the terminal console. the terminal console.
If you want to autologin without typing a password, first ensure your hostmask is unique -- preferably
by using a NickServ vhost/cloak or a reverse-DNS name. Then set the following metadata on your account:
userset Bob autologin 1
If you want to remain permanently logged in, ensure your hostmask is unique and set the following metadata on your account:
userset Bob stayloggedin 1
### Adding other users and admins ### Adding other users and admins
To add users to PBot, use the [`useradd`](Admin.md#useradd) command. Its usage is: Users may create their own unprivileged accounts by using the [`my`](Commands.md#my) command. It will automatically
set their username, hostmask, channel and log them into it.
Users added this way will have `autologin` and `stayloggedin` enabled. If they feel their hostmask is insecure, they
can disable `autologin` and `stayloggedin` with:
my autologin 0
my stayloggedin 0
And then update their login password with:
my password <new password>
Alternatively, you can manually add users to PBot with the [`useradd`](Admin.md#useradd) command. Its usage is:
useradd <username> <hostmasks> [channels [capabilities [password]]] useradd <username> <hostmasks> [channels [capabilities [password]]]
@ -399,14 +419,25 @@ The `hostmasks` and `channels` arguments can be a comma-separated list of values
If you omit the `capabilities` argument, the user will be a normal unprivileged user. See [user-capabilities](Admin.md#user-capabilities) If you omit the `capabilities` argument, the user will be a normal unprivileged user. See [user-capabilities](Admin.md#user-capabilities)
for more information about user-capabilities. for more information about user-capabilities.
If you omit the `password` argument, a random password will be generated. The user
can use the [`my`](Commands.md#my) command to view or change it.
Users may view and change their own metadata by using the [`my`](Commands.md#my) command, Users may view and change their own metadata by using the [`my`](Commands.md#my) command,
provided their hostmask matches the user account. provided their hostmask matches the user account.
my [key [value]] my [key [value]]
Admins may change a user's password with:
userset <username> password <new password>
If the user has a unique hostmask, preferable via a NickServ vhost/cloak or a reverse-DNS name, they
may prefer to use passwordless autologin via:
userset <username> autologin 1
If the user has a unique hostmask, preferable via a NickServ vhost/cloak or a reverse-DNS name, they
may prefer to remain permanently logged in via:
userset <username> stayloggedin 1
For more information, see the [Admin documentation](Admin.md). For more information, see the [Admin documentation](Admin.md).
### Adding channels ### Adding channels

View File

@ -237,6 +237,10 @@ sub cmd_userset($self, $context) {
return "To set the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner'); return "To set the $key capability your user account must also have it." unless $self->{pbot}->{capabilities}->userhas($u, 'botowner');
} }
if ($key eq 'password' and defined $value) {
$value = $self->{pbot}->{users}->digest_password($value);
}
my $result = $self->{pbot}->{users}->{storage}->set($name, $key, $value); my $result = $self->{pbot}->{users}->{storage}->set($name, $key, $value);
$result =~ s/^password: .*;?$/password: <private>;/m; $result =~ s/^password: .*;?$/password: <private>;/m;
@ -351,6 +355,10 @@ sub cmd_my($self, $context) {
$result = "Usage: my <key> [value]; "; $result = "Usage: my <key> [value]; ";
} }
if ($key eq 'password' and defined $value) {
$value = $self->{pbot}->{users}->digest_password($value);
}
$result .= $self->{pbot}->{users}->{storage}->set($name, $key, $value); $result .= $self->{pbot}->{users}->{storage}->set($name, $key, $value);
$result =~ s/^password: .*;?$/password: <private>;/m; $result =~ s/^password: .*;?$/password: <private>;/m;
return $result; return $result;

View File

@ -10,6 +10,8 @@ use parent 'PBot::Core::Class';
use PBot::Imports; use PBot::Imports;
use Crypt::SaltedHash;
sub initialize($self, %conf) { sub initialize($self, %conf) {
$self->{storage} = PBot::Core::Storage::HashObject->new( $self->{storage} = PBot::Core::Storage::HashObject->new(
pbot => $conf{pbot}, pbot => $conf{pbot},
@ -27,6 +29,7 @@ sub add_user($self, $name, $channels, $hostmasks, $capabilities = 'none', $passw
$channels = 'global' if $channels !~ m/^#/; $channels = 'global' if $channels !~ m/^#/;
$password //= $self->{pbot}->random_nick(16); $password //= $self->{pbot}->random_nick(16);
$password = $self->digest_password($password);
my $data = { my $data = {
channels => $channels, channels => $channels,
@ -177,7 +180,7 @@ sub login($self, $channel, $hostmask, $password = undef) {
return "You do not have a user account$channel_text."; return "You do not have a user account$channel_text.";
} }
if (defined $password and $user->{password} ne $password) { if (defined $password and !Crypt::SaltedHash->validate($user->{password}, $password)) {
$self->{pbot}->{logger}->log("Bad login password for $channel $hostmask\n"); $self->{pbot}->{logger}->log("Bad login password for $channel $hostmask\n");
return "I don't think so."; return "I don't think so.";
} }
@ -218,4 +221,10 @@ sub get_loggedin_user_metadata($self, $channel, $hostmask, $key) {
return undef; return undef;
} }
sub digest_password($self, $password) {
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-512');
$csh->add($password);
return $csh->generate;
}
1; 1;

View File

@ -25,8 +25,8 @@ use PBot::Imports;
# These are set by the /misc/update_version script # These are set by the /misc/update_version script
use constant { use constant {
BUILD_NAME => "PBot", BUILD_NAME => "PBot",
BUILD_REVISION => 4761, BUILD_REVISION => 4762,
BUILD_DATE => "2024-06-12", BUILD_DATE => "2024-06-22",
}; };
sub initialize {} sub initialize {}

60
updates/4762_hash_passwords.pl Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env perl
# Replaces user cleartext passwords with salted hashes.
#
# This was way overdue. User passwords are no longer stored as cleartext.
#
# Why did it take me so long to finally get around to hashing passwords
# properly, you might ask. The reason why this wasn't done sooner is because
# all of my users used hostmask-based `autologin`. The passwords that PBot
# randomly generated were ignored and never used.
#
# I do regret that it took me so long to get around to this, for those of you
# who might be using custom passwords instead of hostmask-based `autologin`.
use warnings;
use strict;
BEGIN {
use File::Basename;
my $location = -l __FILE__ ? dirname readlink __FILE__ : dirname __FILE__;
unshift @INC, $location;
}
use lib4422::HashObject;
use lib3503::PBot;
use Crypt::SaltedHash;
my ($data_dir, $version, $last_update) = @ARGV;
print "Hashing passwords ... version: $version, last_update: $last_update, data_dir: $data_dir\n";
my $pbot = lib3503::PBot->new();
my $users = lib4422::HashObject->new(name => 'Users', filename => "$data_dir/users", pbot => $pbot);
$users->load;
if (not keys $users->{hash}->%*) {
die "No users loaded";
}
print "Updating users:\n";
foreach my $user (keys %{$users->{hash}}) {
if ($user eq '$metadata$') {
$users->{hash}->{$user}->{update_version} = 4762;
next;
}
print " $user ...";
my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-512');
$csh->add($users->{hash}->{$user}->{password});
$users->{hash}->{$user}->{password} = $csh->generate;
print " done\n";
}
$users->save;
print "Done.\n";
exit 0;