1//go:build !windows2// +build !windows34/*5Maddy Mail Server - Composable all-in-one email server.6Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors78This program is free software: you can redistribute it and/or modify9it under the terms of the GNU General Public License as published by10the Free Software Foundation, either version 3 of the License, or11(at your option) any later version.1213This program is distributed in the hope that it will be useful,14but WITHOUT ANY WARRANTY; without even the implied warranty of15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16GNU General Public License for more details.1718You should have received a copy of the GNU General Public License19along with this program. If not, see <https://www.gnu.org/licenses/>.20*/2122package shadow2324import (25 "errors"26 "fmt"27 "os"28 "path/filepath"2930 "github.com/foxcpp/maddy/framework/config"31 "github.com/foxcpp/maddy/framework/log"32 "github.com/foxcpp/maddy/framework/module"33 "github.com/foxcpp/maddy/internal/auth/external"34)3536type Auth struct {37 instName string38 useHelper bool39 helperPath string4041 Log log.Logger42}4344func New(modName, instName string, _, inlineArgs []string) (module.Module, error) {45 if len(inlineArgs) != 0 {46 return nil, errors.New("shadow: inline arguments are not used")47 }48 return &Auth{49 instName: instName,50 Log: log.Logger{Name: modName},51 }, nil52}5354func (a *Auth) Name() string {55 return "shadow"56}5758func (a *Auth) InstanceName() string {59 return a.instName60}6162func (a *Auth) Init(cfg *config.Map) error {63 cfg.Bool("debug", true, false, &a.Log.Debug)64 cfg.Bool("use_helper", false, false, &a.useHelper)65 if _, err := cfg.Process(); err != nil {66 return err67 }6869 if a.useHelper {70 a.helperPath = filepath.Join(config.LibexecDirectory, "maddy-shadow-helper")71 if _, err := os.Stat(a.helperPath); err != nil {72 return fmt.Errorf("shadow: no helper binary (maddy-shadow-helper) found in %s", config.LibexecDirectory)73 }74 } else {75 f, err := os.Open("/etc/shadow")76 if err != nil {77 if os.IsPermission(err) {78 return fmt.Errorf("shadow: can't read /etc/shadow due to permission error, use helper binary or run maddy as a privileged user")79 }80 return fmt.Errorf("shadow: can't read /etc/shadow: %v", err)81 }82 f.Close()83 }8485 return nil86}8788func (a *Auth) Lookup(username string) (string, bool, error) {89 if a.useHelper {90 return "", false, fmt.Errorf("shadow: table lookup are not possible when using a helper")91 }9293 ent, err := Lookup(username)94 if err != nil {95 if errors.Is(err, ErrNoSuchUser) {96 return "", false, nil97 }98 return "", false, err99 }100101 if !ent.IsAccountValid() {102 return "", false, nil103 }104105 return "", true, nil106}107108func (a *Auth) AuthPlain(username, password string) error {109 if a.useHelper {110 return external.AuthUsingHelper(a.helperPath, username, password)111 }112113 ent, err := Lookup(username)114 if err != nil {115 return err116 }117118 if !ent.IsAccountValid() {119 return fmt.Errorf("shadow: account is expired")120 }121122 if !ent.IsPasswordValid() {123 return fmt.Errorf("shadow: password is expired")124 }125126 if err := ent.VerifyPassword(password); err != nil {127 if errors.Is(err, ErrWrongPassword) {128 return module.ErrUnknownCredentials129 }130 return err131 }132133 return nil134}135136func init() {137 module.Register("auth.shadow", New)138}