1package netauth23import (4 "context"5 "fmt"67 "github.com/foxcpp/maddy/framework/config"8 "github.com/foxcpp/maddy/framework/log"9 "github.com/foxcpp/maddy/framework/module"10 "github.com/hashicorp/go-hclog"11 "github.com/netauth/netauth/pkg/netauth"12)1314const modName = "auth.netauth"1516func init() {17 var _ module.PlainAuth = &Auth{}18 var _ module.Table = &Auth{}19 module.Register(modName, New)20 module.Register("table.netauth", New)21}2223// Auth binds all methods related to the NetAuth client library.24type Auth struct {25 instName string26 mustGroup string2728 nacl *netauth.Client2930 log log.Logger31}3233// New creates a new instance of the NetAuth module.34func New(modName, instName string, _, inlineArgs []string) (module.Module, error) {35 return &Auth{36 instName: instName,37 log: log.Logger{Name: modName},38 }, nil39}4041// Init performs deferred initialization actions.42func (a *Auth) Init(cfg *config.Map) error {43 l := hclog.New(&hclog.LoggerOptions{Output: a.log})44 n, err := netauth.NewWithLog(l)45 if err != nil {46 return err47 }48 a.nacl = n49 a.nacl.SetServiceName("maddy")50 cfg.String("require_group", false, false, "", &a.mustGroup)51 cfg.Bool("debug", true, false, &a.log.Debug)52 if _, err := cfg.Process(); err != nil {53 return err54 }5556 a.log.Debugln("Debug logging enabled")57 a.log.Debugf("mustGroups status: %s", a.mustGroup)58 return nil59}6061// Name returns "auth.netauth" as the fixed module name.62func (a *Auth) Name() string {63 return modName64}6566// InstanceName returns the configured name for this instance of the67// plugin. Given the way that NetAuth works it doesn't really make68// sense to have more than one instance, but this is part of the API.69func (a *Auth) InstanceName() string {70 return a.instName71}7273// Lookup requests the entity from the remote NetAuth server,74// potentially returning that the user does not exist at all.75func (a *Auth) Lookup(ctx context.Context, username string) (string, bool, error) {76 e, err := a.nacl.EntityInfo(ctx, username)77 if err != nil {78 return "", false, fmt.Errorf("%s: search: %w", modName, err)79 }8081 if a.mustGroup != "" {82 if err := a.checkMustGroup(username); err != nil {83 return "", false, err84 }85 }86 return e.GetID(), true, nil87}8889// AuthPlain attempts straightforward authentication of the entity on90// the remote NetAuth server.91func (a *Auth) AuthPlain(username, password string) error {92 a.log.Debugf("attempting to auth user: %s", username)93 if err := a.nacl.AuthEntity(context.Background(), username, password); err != nil {94 return module.ErrUnknownCredentials95 }96 a.log.Debugln("netauth returns successful auth")97 if a.mustGroup != "" {98 if err := a.checkMustGroup(username); err != nil {99 return err100 }101 }102 return nil103}104105func (a *Auth) checkMustGroup(username string) error {106 a.log.Debugf("Performing require_group check: must=%s", a.mustGroup)107 groups, err := a.nacl.EntityGroups(context.Background(), username)108 if err != nil {109 return fmt.Errorf("%s: groups: %w", modName, err)110 }111 for _, g := range groups {112 if g.GetName() == a.mustGroup {113 return nil114 }115 }116 return fmt.Errorf("%s: missing required group (%s not in %s)", modName, username, a.mustGroup)117}