1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package dovecotsasl2021import (22 "fmt"23 "net"2425 "github.com/emersion/go-sasl"26 dovecotsasl "github.com/foxcpp/go-dovecot-sasl"27 "github.com/foxcpp/maddy/framework/config"28 "github.com/foxcpp/maddy/framework/exterrors"29 "github.com/foxcpp/maddy/framework/log"30 "github.com/foxcpp/maddy/framework/module"31 "github.com/foxcpp/maddy/internal/auth"32)3334type Auth struct {35 instName string36 serverEndpoint string37 log log.Logger3839 network string40 addr string4142 mechanisms map[string]dovecotsasl.Mechanism43}4445const modName = "dovecot_sasl"4647func New(_, instName string, _, inlineArgs []string) (module.Module, error) {48 a := &Auth{49 instName: instName,50 log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug},51 }5253 switch len(inlineArgs) {54 case 0:55 case 1:56 a.serverEndpoint = inlineArgs[0]57 default:58 return nil, fmt.Errorf("%s: one or none arguments needed", modName)59 }6061 return a, nil62}6364func (a *Auth) Name() string {65 return modName66}6768func (a *Auth) InstanceName() string {69 return a.instName70}7172func (a *Auth) getConn() (*dovecotsasl.Client, error) {73 // TODO: Connection pooling74 conn, err := net.Dial(a.network, a.addr)75 if err != nil {76 return nil, fmt.Errorf("%s: unable to contact server: %v", modName, err)77 }7879 cl, err := dovecotsasl.NewClient(conn)80 if err != nil {81 return nil, fmt.Errorf("%s: unable to contact server: %v", modName, err)82 }8384 return cl, nil85}8687func (a *Auth) returnConn(cl *dovecotsasl.Client) {88 cl.Close()89}9091func (a *Auth) Init(cfg *config.Map) error {92 cfg.String("endpoint", false, false, a.serverEndpoint, &a.serverEndpoint)93 if _, err := cfg.Process(); err != nil {94 return err95 }96 if a.serverEndpoint == "" {97 return fmt.Errorf("%s: missing server endpoint", modName)98 }99100 endp, err := config.ParseEndpoint(a.serverEndpoint)101 if err != nil {102 return fmt.Errorf("%s: invalid server endpoint: %v", modName, err)103 }104105 // Dial once to check usability and also to get list of mechanisms.106 conn, err := net.Dial(endp.Scheme, endp.Address())107 if err != nil {108 return fmt.Errorf("%s: unable to contact server: %v", modName, err)109 }110111 cl, err := dovecotsasl.NewClient(conn)112 if err != nil {113 return fmt.Errorf("%s: unable to contact server: %v", modName, err)114 }115116 defer cl.Close()117 a.mechanisms = make(map[string]dovecotsasl.Mechanism, len(cl.ConnInfo().Mechs))118 for name, mech := range cl.ConnInfo().Mechs {119 if mech.Private {120 continue121 }122 a.mechanisms[name] = mech123 }124125 a.network = endp.Scheme126 a.addr = endp.Address()127128 return nil129}130131func (a *Auth) AuthPlain(username, password string) error {132 if _, ok := a.mechanisms[sasl.Plain]; ok {133 cl, err := a.getConn()134 if err != nil {135 return exterrors.WithTemporary(err, true)136 }137 defer a.returnConn(cl)138139 // Pretend it is SMTPS even though we really don't know.140 // We also have no connection information to pass to the server...141 return cl.Do("SMTP", sasl.NewPlainClient("", username, password),142 dovecotsasl.Secured, dovecotsasl.NoPenalty)143 }144 if _, ok := a.mechanisms[sasl.Login]; ok {145 cl, err := a.getConn()146 if err != nil {147 return err148 }149 defer a.returnConn(cl)150151 return cl.Do("SMTP", sasl.NewLoginClient(username, password),152 dovecotsasl.Secured, dovecotsasl.NoPenalty)153 }154155 return auth.ErrUnsupportedMech156}157158func init() {159 module.Register(modName, New)160}