mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-18 14:47:03 +00:00
auth: Allow users without a domain
Some deployments already have users that authenticate without a domain. Today, we refuse to even consider those, and reject them at parsing time. However, it is a use-case worth supporting, at least with some restrictions that make the complexity manageable. This patch changes the auth package to support authenticating users without an "@domain" part. Those requests will always be directly passed on to the fallback authenticator, if available. The dovecot fallback authenticator can already handle this case just fine.
This commit is contained in:
@@ -31,8 +31,9 @@ accordingly.
|
||||
|
||||
This lets chasquid issue authentication requests to dovecot.
|
||||
|
||||
Authentication requests sent by chasquid to dovecot will use the
|
||||
fully-qualified user form, `user@domain`.
|
||||
Authentication requests sent by chasquid to dovecot will pass on the username
|
||||
as specified by the client. This will usually be either `user@domain`, or just
|
||||
`user`.
|
||||
|
||||
|
||||
## Configuring chasquid
|
||||
|
||||
@@ -42,7 +42,7 @@ type Authenticator struct {
|
||||
// Fallback backend, to use when backends[domain] (which may not exist)
|
||||
// did not yield a positive result.
|
||||
// Note that this backend gets the user with the domain included, of the
|
||||
// form "user@domain".
|
||||
// form "user@domain" (if available).
|
||||
Fallback Backend
|
||||
|
||||
// How long Authenticate calls should last, approximately.
|
||||
@@ -90,7 +90,11 @@ func (a *Authenticator) Authenticate(user, domain, password string) (bool, error
|
||||
}
|
||||
|
||||
if a.Fallback != nil {
|
||||
ok, err := a.Fallback.Authenticate(user+"@"+domain, password)
|
||||
id := user
|
||||
if domain != "" {
|
||||
id = user + "@" + domain
|
||||
}
|
||||
ok, err := a.Fallback.Authenticate(id, password)
|
||||
tr.Debugf("Fallback: %v %v", ok, err)
|
||||
return ok, err
|
||||
}
|
||||
@@ -113,7 +117,11 @@ func (a *Authenticator) Exists(user, domain string) (bool, error) {
|
||||
}
|
||||
|
||||
if a.Fallback != nil {
|
||||
ok, err := a.Fallback.Exists(user + "@" + domain)
|
||||
id := user
|
||||
if domain != "" {
|
||||
id = user + "@" + domain
|
||||
}
|
||||
ok, err := a.Fallback.Exists(id)
|
||||
tr.Debugf("Fallback: %v %v", ok, err)
|
||||
return ok, err
|
||||
}
|
||||
@@ -158,9 +166,11 @@ func (a *Authenticator) Reload() error {
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc4954#section-4.1.
|
||||
//
|
||||
// Either both ID match, or one of them is empty.
|
||||
// We expect the ID to be "user@domain", which is NOT an RFC requirement but
|
||||
// our own.
|
||||
// Either both IDs match, or one of them is empty.
|
||||
//
|
||||
// We split the id into user@domain, since in most cases we expect that to be
|
||||
// the used form, and normalize them. If there is no domain, we just return
|
||||
// "" for it. The rest of the stack will know how to handle it.
|
||||
func DecodeResponse(response string) (user, domain, passwd string, err error) {
|
||||
buf, err := base64.StdEncoding.DecodeString(response)
|
||||
if err != nil {
|
||||
@@ -201,16 +211,13 @@ func DecodeResponse(response string) (user, domain, passwd string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Identity must be in the form "user@domain".
|
||||
// This is NOT an RFC requirement, it's our own.
|
||||
// Split identity into "user@domain", if possible.
|
||||
user = identity
|
||||
idsp := strings.SplitN(identity, "@", 2)
|
||||
if len(idsp) != 2 {
|
||||
err = fmt.Errorf("identity must be in the form user@domain")
|
||||
return
|
||||
}
|
||||
|
||||
if len(idsp) >= 2 {
|
||||
user = idsp[0]
|
||||
domain = idsp[1]
|
||||
}
|
||||
|
||||
// Normalize the user and domain. This is so users can write the username
|
||||
// in their own style and still can log in. For the domain, we use IDNA
|
||||
|
||||
@@ -19,6 +19,7 @@ func TestDecodeResponse(t *testing.T) {
|
||||
{"dUBkAABwYXNz", "u", "d", "pass"}, // u@d\0\0pass
|
||||
{"AHVAZABwYXNz", "u", "d", "pass"}, // \0u@d\0pass
|
||||
{"dUBkAABwYXNz/w==", "u", "d", "pass\xff"}, // u@d\0\0pass\xff
|
||||
{"dQB1AHBhc3M=", "u", "", "pass"}, // u\0u\0pass
|
||||
|
||||
// "ñaca@ñeque\0\0clavaré"
|
||||
{"w7FhY2FAw7FlcXVlAABjbGF2YXLDqQ==", "ñaca", "ñeque", "clavaré"},
|
||||
@@ -42,7 +43,7 @@ func TestDecodeResponse(t *testing.T) {
|
||||
|
||||
failedCases := []string{
|
||||
"", "\x00", "\x00\x00", "\x00\x00\x00", "\x00\x00\x00\x00",
|
||||
"a\x00b", "a\x00b\x00c", "a@a\x00b@b\x00pass", "a\x00a\x00pass",
|
||||
"a\x00b", "a\x00b\x00c", "a@a\x00b@b\x00pass",
|
||||
"\xffa@b\x00\xffa@b\x00pass",
|
||||
}
|
||||
for _, c := range failedCases {
|
||||
|
||||
@@ -5,10 +5,11 @@ ssl = no
|
||||
default_internal_user = $USER
|
||||
default_login_user = $USER
|
||||
|
||||
# Before auth checks, rename "u@d" to "u-AT-d". This exercises that chasquid
|
||||
# Before auth checks, rename "u@d" to "u-x". This exercises that chasquid
|
||||
# handles well the case where the returned user information does not match the
|
||||
# requested user.
|
||||
auth_username_format = "%n-AT-%d"
|
||||
# We drop the domain, to exercise "naked" auth handling.
|
||||
auth_username_format = "%n-x"
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
user-AT-srv:{plain}password:1000:1000::/home/user
|
||||
user-x:{plain}password:1000:1000::/home/user
|
||||
naked-x:{plain}gun:1001:1001::/home/naked
|
||||
|
||||
@@ -26,3 +26,8 @@ password secretpassword
|
||||
account badpasswd : default
|
||||
user user@srv
|
||||
password badsecretpassword
|
||||
|
||||
account naked : default
|
||||
from naked@srv
|
||||
user naked
|
||||
password gun
|
||||
|
||||
@@ -51,11 +51,22 @@ mkdir -p .logs
|
||||
chasquid -v=2 --logfile=.logs/chasquid.log --config_dir=config &
|
||||
wait_until_ready 1025
|
||||
|
||||
# Send an email as user@srv successfully.
|
||||
# Send an email as "user@srv" successfully.
|
||||
run_msmtp user@srv < content
|
||||
wait_for_file .mail/user@srv
|
||||
mail_diff content .mail/user@srv
|
||||
|
||||
# Send an email as "naked" successfully.
|
||||
rm .mail/user@srv
|
||||
run_msmtp -a naked user@srv < content
|
||||
wait_for_file .mail/user@srv
|
||||
mail_diff content .mail/user@srv
|
||||
|
||||
# Send an email to the "naked" user successfully.
|
||||
run_msmtp naked@srv < content
|
||||
wait_for_file .mail/naked@srv
|
||||
mail_diff content .mail/naked@srv
|
||||
|
||||
# Fail to send to nobody@srv (user does not exist).
|
||||
if run_msmtp nobody@srv < content 2> /dev/null; then
|
||||
fail "successfuly sent an email to a non-existent user"
|
||||
|
||||
Reference in New Issue
Block a user