maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1//go:build !windows
  2// +build !windows
  3
  4/*
  5Maddy Mail Server - Composable all-in-one email server.
  6Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  7
  8This program is free software: you can redistribute it and/or modify
  9it under the terms of the GNU General Public License as published by
 10the Free Software Foundation, either version 3 of the License, or
 11(at your option) any later version.
 12
 13This program is distributed in the hope that it will be useful,
 14but WITHOUT ANY WARRANTY; without even the implied warranty of
 15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16GNU General Public License for more details.
 17
 18You should have received a copy of the GNU General Public License
 19along with this program.  If not, see <https://www.gnu.org/licenses/>.
 20*/
 21
 22package shadow
 23
 24import (
 25	"errors"
 26	"fmt"
 27	"os"
 28	"path/filepath"
 29
 30	"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)
 35
 36type Auth struct {
 37	instName   string
 38	useHelper  bool
 39	helperPath string
 40
 41	Log log.Logger
 42}
 43
 44func 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	}, nil
 52}
 53
 54func (a *Auth) Name() string {
 55	return "shadow"
 56}
 57
 58func (a *Auth) InstanceName() string {
 59	return a.instName
 60}
 61
 62func (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 err
 67	}
 68
 69	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	}
 84
 85	return nil
 86}
 87
 88func (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	}
 92
 93	ent, err := Lookup(username)
 94	if err != nil {
 95		if errors.Is(err, ErrNoSuchUser) {
 96			return "", false, nil
 97		}
 98		return "", false, err
 99	}
100
101	if !ent.IsAccountValid() {
102		return "", false, nil
103	}
104
105	return "", true, nil
106}
107
108func (a *Auth) AuthPlain(username, password string) error {
109	if a.useHelper {
110		return external.AuthUsingHelper(a.helperPath, username, password)
111	}
112
113	ent, err := Lookup(username)
114	if err != nil {
115		return err
116	}
117
118	if !ent.IsAccountValid() {
119		return fmt.Errorf("shadow: account is expired")
120	}
121
122	if !ent.IsPasswordValid() {
123		return fmt.Errorf("shadow: password is expired")
124	}
125
126	if err := ent.VerifyPassword(password); err != nil {
127		if errors.Is(err, ErrWrongPassword) {
128			return module.ErrUnknownCredentials
129		}
130		return err
131	}
132
133	return nil
134}
135
136func init() {
137	module.Register("auth.shadow", New)
138}