Skip to content

Commit

Permalink
userdb: Add support for receive-only users
Browse files Browse the repository at this point in the history
Some use cases, like receive-only MTAs, need domain users for receiving
emails, but have no real need for passwords since they will never use
submission.

Today, that is not supported, and those use-cases require the
administrator to come up with a password unnecessarily, adding
complexity and possibly risk.

This patch implements "receive-only users", which don't have a valid
password, thus exist for the purposes of delivering mail, but always
fail authentication.

See #44 for more details and
rationale.

Thanks to xavierg who suggested this feature on IRC.
  • Loading branch information
albertito committed Dec 3, 2023
1 parent dbff2f0 commit 83ae4c3
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 47 deletions.
21 changes: 17 additions & 4 deletions cmd/chasquid-util/chasquid-util.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
// Usage to show users on --help or invocation errors.
const usage = `
Usage:
chasquid-util [options] user-add <user@domain> [--password=<password>]
chasquid-util [options] user-add <user@domain> [--password=<password>] [--receive_only]
chasquid-util [options] user-remove <user@domain>
chasquid-util [options] authenticate <user@domain> [--password=<password>]
chasquid-util [options] check-userdb <domain>
Expand Down Expand Up @@ -140,12 +140,25 @@ func checkUserDB() {
fmt.Println("Database loaded")
}

// chasquid-util user-add <user@domain> [--password=<password>]
// chasquid-util user-add <user@domain> [--password=<password>] [--receive_only]
func userAdd() {
user, _, db := userDBFromArgs(true)
password := getPassword()

err := db.AddUser(user, password)
_, recvOnly := args["--receive_only"]
_, hasPassword := args["--password"]

if recvOnly && hasPassword {
Fatalf("Cannot specify both --receive_only and --password")
}

var err error
if recvOnly {
err = db.AddDeniedUser(user)
} else {
password := getPassword()
err = db.AddUser(user, password)
}

if err != nil {
Fatalf("Error adding user: %v", err)
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/chasquid-util/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ if ! r user-add user@domain --password=passwd > /dev/null; then
fi
check_userdb

if ! r user-add denied@domain --receive_only > /dev/null; then
echo user-add --receive_only failed
exit 1
fi
check_userdb

if r user-add xxx@domain --password=passwd --receive_only > /dev/null; then
echo user-add --password --receive_only worked
exit 1
fi

if ! r authenticate user@domain --password=passwd > /dev/null; then
echo authenticate failed
exit 1
Expand All @@ -44,6 +55,11 @@ if r authenticate user@domain --password=abcd > /dev/null; then
exit 1
fi

if r authenticate denied@domain --password=abcd > /dev/null; then
echo authenticate on a no-submission user worked
exit 1
fi

# Interactive authentication.
# Need to wrap the execution under "script" since the interaction requires an
# actual TTY, and that's a fairly portable way to do that.
Expand Down
11 changes: 7 additions & 4 deletions docs/man/chasquid-util.1
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "chasquid-util 1"
.TH chasquid-util 1 "2023-07-29" "" ""
.TH chasquid-util 1 "2023-12-03" "" ""
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
Expand All @@ -142,7 +142,7 @@
chasquid\-util \- chasquid management tool
.SH "SYNOPSIS"
.IX Header "SYNOPSIS"
\&\fBchasquid-util\fR [\fIoptions\fR] user-add \fIuser@domain\fR [\-\-password=\fIpassword\fR]
\&\fBchasquid-util\fR [\fIoptions\fR] user-add \fIuser@domain\fR [\-\-password=\fIpassword\fR] [\-\-receive_only]
.PP
\&\fBchasquid-util\fR [\fIoptions\fR] user-remove \fIuser@domain\fR
.PP
Expand All @@ -160,9 +160,12 @@ chasquid\-util \- chasquid management tool
chasquid-util is a command-line utility for \fBchasquid\fR\|(1) operations.
.SH "OPTIONS"
.IX Header "OPTIONS"
.IP "\fBuser-add\fR \fIuser@domain\fR [\-\-password=\fIpassword\fR]" 8
.IX Item "user-add user@domain [--password=password]"
.IP "\fBuser-add\fR \fIuser@domain\fR [\-\-password=\fIpassword\fR] [\-\-receive_only]" 8
.IX Item "user-add user@domain [--password=password] [--receive_only]"
Add a new user to the domain.
.Sp
If \fI\-\-receive_only\fR is given, then the user will never successfully
authenticate. This is useful when creating receive-only users.
.IP "\fBuser-remove\fR \fIuser@domain\fR" 8
.IX Item "user-remove user@domain"
Remove the user from the domain.
Expand Down
7 changes: 5 additions & 2 deletions docs/man/chasquid-util.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ chasquid-util - chasquid management tool

# SYNOPSIS

**chasquid-util** \[_options_\] user-add _user@domain_ \[--password=_password_\]
**chasquid-util** \[_options_\] user-add _user@domain_ \[--password=_password_\] \[--receive\_only\]

**chasquid-util** \[_options_\] user-remove _user@domain_

Expand All @@ -24,10 +24,13 @@ chasquid-util is a command-line utility for [chasquid(1)](chasquid.1.md) operati

# OPTIONS

- **user-add** _user@domain_ \[--password=_password_\]
- **user-add** _user@domain_ \[--password=_password_\] \[--receive\_only\]

Add a new user to the domain.

If _--receive\_only_ is given, then the user will never successfully
authenticate. This is useful when creating receive-only users.

- **user-remove** _user@domain_

Remove the user from the domain.
Expand Down
7 changes: 5 additions & 2 deletions docs/man/chasquid-util.1.pod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ chasquid-util - chasquid management tool

=head1 SYNOPSIS

B<chasquid-util> [I<options>] user-add I<user@domain> [--password=I<password>]
B<chasquid-util> [I<options>] user-add I<user@domain> [--password=I<password>] [--receive_only]

B<chasquid-util> [I<options>] user-remove I<user@domain>

Expand All @@ -28,10 +28,13 @@ chasquid-util is a command-line utility for chasquid(1) operations.

=over 8

=item B<user-add> I<user@domain> [--password=I<password>]
=item B<user-add> I<user@domain> [--password=I<password>] [--receive_only]

Add a new user to the domain.

If I<--receive_only> is given, then the user will never successfully
authenticate. This is useful when creating receive-only users.

=item B<user-remove> I<user@domain>

Remove the user from the domain.
Expand Down
18 changes: 18 additions & 0 deletions internal/userdb/userdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ func (p *Password) PasswordMatches(plain string) bool {
return s.Scrypt.PasswordMatches(plain)
case *Password_Plain:
return s.Plain.PasswordMatches(plain)
case *Password_Denied:
return false
default:
return false
}
Expand Down Expand Up @@ -164,6 +166,22 @@ func (db *DB) AddUser(name, plainPassword string) error {
return nil
}

// AddDenied to the database. If the user is already present, override it.
// Note we enforce that the name has been normalized previously.
func (db *DB) AddDeniedUser(name string) error {
if norm, err := normalize.User(name); err != nil || name != norm {
return errors.New("invalid username")
}

db.mu.Lock()
db.db.Users[name] = &Password{
Scheme: &Password_Denied{&Denied{}},
}
db.mu.Unlock()

return nil
}

// RemoveUser from the database. Returns True if the user was there, False
// otherwise.
func (db *DB) RemoveUser(name string) bool {
Expand Down
135 changes: 103 additions & 32 deletions internal/userdb/userdb.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 83ae4c3

Please sign in to comment.