storage/sql: Remove support for the _perdomain directives

Domain is now always required. See #172.
This commit is contained in:
fox.cpp 2019-12-02 20:26:17 +03:00
parent 3217b872fa
commit 196755bc11
No known key found for this signature in database
GPG key ID: E76D97CCEDE90B6C
5 changed files with 93 additions and 97 deletions

View file

@ -33,10 +33,6 @@ func findBlockInCfg(path, cfgBlock string) (root, block *config.Node, err error)
}
for _, node := range nodes {
if node.Name != "sql" {
continue
}
if len(node.Args) == 0 && cfgBlock == node.Name {
return &config.Node{Children: nodes}, &node, nil
}

View file

@ -1,7 +1,6 @@
package main
import (
imapsql "github.com/foxcpp/go-imap-sql"
"github.com/foxcpp/maddy/config"
"github.com/foxcpp/maddy/storage/sql"
@ -9,11 +8,10 @@ import (
_ "github.com/lib/pq"
)
func sqlFromCfgBlock(root, node *config.Node) (*imapsql.Backend, error) {
func sqlFromCfgBlock(root, node *config.Node) (*sql.Storage, error) {
// Global variables relevant for sql module.
globals := config.NewMap(nil, root)
globals.Bool("auth_perdomain", false, false, nil)
globals.StringList("auth_domains", false, false, nil, nil)
// None now...
globals.AllowUnknown()
_, err := globals.Process()
if err != nil {
@ -33,5 +31,5 @@ func sqlFromCfgBlock(root, node *config.Node) (*imapsql.Backend, error) {
return nil, err
}
return mod.(*sql.Storage).Back, nil
return mod.(*sql.Storage), nil
}

View file

@ -28,6 +28,12 @@ Supported RDBMS:
Can be used as a storage backend (for IMAP), authentication backend (IMAP &
SMTP) or delivery target (SMTP).
Account names are required to have the form of a email address and are
case-insensitive. UTF-8 names are supported with restrictions defined in the
PRECIS UsernameCaseMapped profile.
The database can be managed using the maddyctl utility.
## Arguments
Specify the driver and DSN.
@ -108,38 +114,3 @@ of cache size in KiB.
SQLite-specific performance tuning option. Amount of milliseconds to wait
before giving up on DB lock.
*Syntax*: storage_perdomain _boolean_ ++
*Default*: no
Don't remove domain part of username when accessing the underlying storage and
require it to be present. Can be used if you want user@domain1 and user@domain2
to be different accounts at the storage level.
Typically used with 'auth_domain'.
*Note*: when storage_perdomain is disabled, local-part quoting in addresses is
undone. That is for a recipient "test test"@example.org the used account is
test test (two 'test' words with a space in between). However, when
storage_perdomain is on, no unquoting is done, since the account name is a
complete RFC 5321 address and hence should be encoded.
*Syntax*: auth_perdomain _boolean_ ++
*Default*: global directive value
Don't remove domain part of username when authenticating and require it to be
present. Can be used if you want user@domain1 and user@domain2 to be different
accounts.
*Syntax*: auth_domains _domains..._ ++
*Default*: not specified
Domains that should be allowed in username during authentication.
For example, if 'auth_domains' is set to "domain1 domain2", then
username, username@domain1 and username@domain2 will be accepted as valid login
name in addition to just username.
If used without 'auth_perdomain', domain part will be removed from login before
check with underlying auth. mechanism. If 'auth_perdomain' is set, then
auth_domains must be also set and domain part WILL NOT be removed before check.

56
storage/sql/maddyctl.go Normal file
View file

@ -0,0 +1,56 @@
package sql
import "github.com/emersion/go-imap/backend"
// These methods wrap corresponding go-imap-sql methods, but also apply
// maddy-specific credentials rules.
func (store *Storage) ListUsers() ([]string, error) {
return store.Back.ListUsers()
}
func (store *Storage) CreateUser(username, password string) error {
accountName, err := prepareUsername(username)
if err != nil {
return err
}
// TODO: PRECIS OpaqueString for password.
return store.Back.CreateUser(accountName, password)
}
func (store *Storage) CreateUserNoPass(username string) error {
accountName, err := prepareUsername(username)
if err != nil {
return err
}
return store.Back.CreateUserNoPass(accountName)
}
func (store *Storage) DeleteUser(username string) error {
accountName, err := prepareUsername(username)
if err != nil {
return err
}
return store.Back.DeleteUser(accountName)
}
func (store *Storage) SetUserPassword(username, newPassword string) error {
accountName, err := prepareUsername(username)
if err != nil {
return err
}
return store.Back.SetUserPassword(accountName, newPassword)
}
func (store *Storage) GetUser(username string) (backend.User, error) {
accountName, err := prepareUsername(username)
if err != nil {
return nil, err
}
return store.Back.GetUser(accountName)
}

View file

@ -20,7 +20,6 @@ import (
"github.com/emersion/go-message/textproto"
imapsql "github.com/foxcpp/go-imap-sql"
"github.com/foxcpp/maddy/address"
"github.com/foxcpp/maddy/auth"
"github.com/foxcpp/maddy/buffer"
"github.com/foxcpp/maddy/config"
"github.com/foxcpp/maddy/dns"
@ -38,10 +37,7 @@ type Storage struct {
instName string
Log log.Logger
storagePerDomain bool
authPerDomain bool
authDomains []string
junkMbox string
junkMbox string
inlineDriver string
inlineDsn []string
@ -59,33 +55,14 @@ type delivery struct {
}
func (d *delivery) AddRcpt(rcptTo string) error {
var accountName string
// Side note: <postmaster> address will be always accepted
// and delivered to "postmaster" account for both cases.
if d.store.storagePerDomain {
accountName = rcptTo
} else {
var err error
accountName, _, err = address.Split(rcptTo)
if err != nil {
return &exterrors.SMTPError{
Code: 501,
EnhancedCode: exterrors.EnhancedCode{5, 1, 3},
Message: "Invalid recipient address",
TargetName: "sql",
Err: err,
}
}
accountName, err = address.UnquoteMbox(accountName)
if err != nil {
return &exterrors.SMTPError{
Code: 501,
EnhancedCode: exterrors.EnhancedCode{5, 1, 3},
Message: "Invalid recipient address",
TargetName: "sql",
Err: err,
}
accountName, err := prepareUsername(rcptTo)
if err != nil {
return &exterrors.SMTPError{
Code: 501,
EnhancedCode: exterrors.EnhancedCode{5, 1, 1},
Message: "User does not exist",
TargetName: "sql",
Err: err,
}
}
@ -98,14 +75,14 @@ func (d *delivery) AddRcpt(rcptTo string) error {
// go-imap-sql does certain optimizations to store the message
// with small amount of per-recipient data in a efficient way.
userHeader := textproto.Header{}
userHeader.Add("Delivered-To", rcptTo)
userHeader.Add("Delivered-To", accountName)
if err := d.d.AddRcpt(strings.ToLower(accountName), userHeader); err != nil {
if err := d.d.AddRcpt(accountName, userHeader); err != nil {
if err == imapsql.ErrUserDoesntExists || err == backend.ErrNoSuchMailbox {
return &exterrors.SMTPError{
Code: 550,
EnhancedCode: exterrors.EnhancedCode{5, 1, 1},
Message: "User doesn't exist",
Message: "User does not exist",
TargetName: "sql",
Err: err,
}
@ -199,9 +176,6 @@ func (store *Storage) Init(cfg *config.Map) error {
cfg.StringList("compression", false, false, []string{"off"}, &compression)
cfg.DataSize("appendlimit", false, false, 32*1024*1024, &appendlimitVal)
cfg.Bool("debug", true, false, &store.Log.Debug)
cfg.Bool("storage_perdomain", false, false, &store.storagePerDomain)
cfg.Bool("auth_perdomain", false, false, &store.authPerDomain)
cfg.StringList("auth_domains", true, false, nil, &store.authDomains)
cfg.Int("sqlite3_cache_size", false, false, 0, &opts.CacheSize)
cfg.Int("sqlite3_busy_timeout", false, false, 0, &opts.BusyTimeout)
cfg.Bool("sqlite3_exclusive_lock", false, false, &opts.ExclusiveLock)
@ -220,10 +194,6 @@ func (store *Storage) Init(cfg *config.Map) error {
opts.Log = &store.Log
if store.authPerDomain && store.authDomains == nil {
return errors.New("sql: auth_domains must be set if auth_perdomain is used")
}
if appendlimitVal == -1 {
opts.MaxMsgBytes = nil
} else {
@ -292,9 +262,20 @@ func (store *Storage) EnableChildrenExt() bool {
return store.Back.EnableChildrenExt()
}
func prepareUsername(username string) (string, error) {
mbox, domain, err := address.Split(username)
if err != nil {
return "", fmt.Errorf("sql: username prepare: %w", err)
}
// TODO: Unicode normalization and other shenanigans.
return mbox + "@" + domain, nil
}
func (store *Storage) CheckPlain(username, password string) bool {
accountName, ok := auth.CheckDomainAuth(username, store.authPerDomain, store.authDomains)
if !ok {
accountName, err := prepareUsername(username)
if err != nil {
return false
}
@ -302,15 +283,9 @@ func (store *Storage) CheckPlain(username, password string) bool {
}
func (store *Storage) GetOrCreateUser(username string) (backend.User, error) {
var accountName string
if store.storagePerDomain {
if !strings.Contains(username, "@") {
return nil, errors.New("GetOrCreateUser: username@domain required")
}
accountName = username
} else {
parts := strings.Split(username, "@")
accountName = parts[0]
accountName, err := prepareUsername(username)
if err != nil {
return nil, backend.ErrInvalidCredentials
}
return store.Back.GetOrCreateUser(accountName)