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 dovecotsasld2021import (22 "fmt"23 stdlog "log"24 "net"25 "strings"26 "sync"2728 "github.com/emersion/go-sasl"29 dovecotsasl "github.com/foxcpp/go-dovecot-sasl"30 "github.com/foxcpp/maddy/framework/config"31 modconfig "github.com/foxcpp/maddy/framework/config/module"32 "github.com/foxcpp/maddy/framework/log"33 "github.com/foxcpp/maddy/framework/module"34 "github.com/foxcpp/maddy/internal/auth"35 "github.com/foxcpp/maddy/internal/authz"36)3738const modName = "dovecot_sasld"3940type Endpoint struct {41 addrs []string42 log log.Logger43 saslAuth auth.SASLAuth4445 listenersWg sync.WaitGroup4647 srv *dovecotsasl.Server48}4950func New(_ string, addrs []string) (module.Module, error) {51 return &Endpoint{52 addrs: addrs,53 saslAuth: auth.SASLAuth{54 Log: log.Logger{Name: modName + "/saslauth"},55 },56 log: log.Logger{Name: modName, Debug: log.DefaultLogger.Debug},57 }, nil58}5960func (endp *Endpoint) Name() string {61 return modName62}6364func (endp *Endpoint) InstanceName() string {65 return modName66}6768func (endp *Endpoint) Init(cfg *config.Map) error {69 cfg.Callback("auth", func(m *config.Map, node config.Node) error {70 return endp.saslAuth.AddProvider(m, node)71 })72 cfg.Bool("sasl_login", false, false, &endp.saslAuth.EnableLogin)73 config.EnumMapped(cfg, "auth_map_normalize", true, false, authz.NormalizeFuncs, authz.NormalizeAuto,74 &endp.saslAuth.AuthNormalize)75 modconfig.Table(cfg, "auth_map", true, false, nil, &endp.saslAuth.AuthMap)76 if _, err := cfg.Process(); err != nil {77 return err78 }7980 endp.srv = dovecotsasl.NewServer()81 endp.srv.Log = stdlog.New(endp.log, "", 0)82 endp.saslAuth.Log.Debug = endp.log.Debug8384 for _, mech := range endp.saslAuth.SASLMechanisms() {85 endp.srv.AddMechanism(mech, mechInfo[mech], func(req *dovecotsasl.AuthReq) sasl.Server {86 var remoteAddr net.Addr87 if req.RemoteIP != nil && req.RemotePort != 0 {88 remoteAddr = &net.TCPAddr{IP: req.RemoteIP, Port: int(req.RemotePort)}89 }9091 return endp.saslAuth.CreateSASL(mech, remoteAddr, func(_ string, _ auth.ContextData) error { return nil })92 })93 }9495 for _, addr := range endp.addrs {96 parsed, err := config.ParseEndpoint(addr)97 if err != nil {98 return fmt.Errorf("%s: %v", modName, err)99 }100101 l, err := net.Listen(parsed.Network(), parsed.Address())102 if err != nil {103 return fmt.Errorf("%s: %v", modName, err)104 }105 endp.log.Printf("listening on %v", l.Addr())106107 endp.listenersWg.Add(1)108 go func() {109 defer endp.listenersWg.Done()110 if err := endp.srv.Serve(l); err != nil {111 if !strings.HasSuffix(err.Error(), "use of closed network connection") {112 endp.log.Printf("failed to serve %v: %v", l.Addr(), err)113 }114 }115 }()116 }117118 return nil119}120121func (endp *Endpoint) Close() error {122 return endp.srv.Close()123}124125func init() {126 module.RegisterEndpoint(modName, New)127}