maddy

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

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

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19// Package modconfig provides matchers for config.Map that query
 20// modules registry and parse inline module definitions.
 21//
 22// They should be used instead of manual querying when there is need to
 23// reference a module instance in the configuration.
 24//
 25// See ModuleFromNode documentation for explanation of what is 'args'
 26// for some functions (DeliveryTarget).
 27package modconfig
 28
 29import (
 30	"fmt"
 31	"io"
 32	"reflect"
 33	"strings"
 34
 35	parser "github.com/foxcpp/maddy/framework/cfgparser"
 36	"github.com/foxcpp/maddy/framework/config"
 37	"github.com/foxcpp/maddy/framework/hooks"
 38	"github.com/foxcpp/maddy/framework/log"
 39	"github.com/foxcpp/maddy/framework/module"
 40)
 41
 42// createInlineModule is a helper function for config matchers that can create inline modules.
 43func createInlineModule(preferredNamespace, modName string, args []string) (module.Module, error) {
 44	var newMod module.FuncNewModule
 45	originalModName := modName
 46
 47	// First try to extend the name with preferred namespace unless the name
 48	// already contains it.
 49	if !strings.Contains(modName, ".") && preferredNamespace != "" {
 50		modName = preferredNamespace + "." + modName
 51		newMod = module.Get(modName)
 52	}
 53
 54	// Then try global namespace for compatibility and complex modules.
 55	if newMod == nil {
 56		newMod = module.Get(originalModName)
 57	}
 58
 59	// Bail if both failed.
 60	if newMod == nil {
 61		return nil, fmt.Errorf("unknown module: %s (namespace: %s)", originalModName, preferredNamespace)
 62	}
 63
 64	return newMod(modName, "", nil, args)
 65}
 66
 67// initInlineModule constructs "faked" config tree and passes it to module
 68// Init function to make it look like it is defined at top-level.
 69//
 70// args must contain at least one argument, otherwise initInlineModule panics.
 71func initInlineModule(modObj module.Module, globals map[string]interface{}, block config.Node) error {
 72	err := modObj.Init(config.NewMap(globals, block))
 73	if err != nil {
 74		return err
 75	}
 76
 77	if closer, ok := modObj.(io.Closer); ok {
 78		hooks.AddHook(hooks.EventShutdown, func() {
 79			log.Debugf("close %s (%s)", modObj.Name(), modObj.InstanceName())
 80			if err := closer.Close(); err != nil {
 81				log.Printf("module %s (%s) close failed: %v", modObj.Name(), modObj.InstanceName(), err)
 82			}
 83		})
 84	}
 85
 86	return nil
 87}
 88
 89// ModuleFromNode does all work to create or get existing module object with a certain type.
 90// It is not used by top-level module definitions, only for references from other
 91// modules configuration blocks.
 92//
 93// inlineCfg should contain configuration directives for inline declarations.
 94// args should contain values that are used to create module.
 95// It should be either module name + instance name or just module name. Further extensions
 96// may add other string arguments (currently, they can be accessed by module instances
 97// as inlineArgs argument to constructor).
 98//
 99// It checks using reflection whether it is possible to store a module object into modObj
100// pointer (e.g. it implements all necessary interfaces) and stores it if everything is fine.
101// If module object doesn't implement necessary module interfaces - error is returned.
102// If modObj is not a pointer, ModuleFromNode panics.
103//
104// preferredNamespace is used as an implicit prefix for module name lookups.
105// Module with name preferredNamespace + "." + args[0] will be preferred over just args[0].
106// It can be omitted.
107func ModuleFromNode(preferredNamespace string, args []string, inlineCfg config.Node, globals map[string]interface{}, moduleIface interface{}) error {
108	if len(args) == 0 {
109		return parser.NodeErr(inlineCfg, "at least one argument is required")
110	}
111
112	referenceExisting := strings.HasPrefix(args[0], "&")
113
114	var modObj module.Module
115	var err error
116	if referenceExisting {
117		if len(args) != 1 || inlineCfg.Children != nil {
118			return parser.NodeErr(inlineCfg, "exactly one argument is required to use existing config block")
119		}
120		modObj, err = module.GetInstance(args[0][1:])
121		log.Debugf("%s:%d: reference %s", inlineCfg.File, inlineCfg.Line, args[0])
122	} else {
123		log.Debugf("%s:%d: new module %s %v", inlineCfg.File, inlineCfg.Line, args[0], args[1:])
124		modObj, err = createInlineModule(preferredNamespace, args[0], args[1:])
125	}
126	if err != nil {
127		return err
128	}
129
130	// NOTE: This will panic if moduleIface is not a pointer.
131	modIfaceType := reflect.TypeOf(moduleIface).Elem()
132	modObjType := reflect.TypeOf(modObj)
133
134	if modIfaceType.Kind() == reflect.Interface {
135		// Case for assignment to module interface type.
136		if !modObjType.Implements(modIfaceType) && !modObjType.AssignableTo(modIfaceType) {
137			return parser.NodeErr(inlineCfg, "module %s (%s) doesn't implement %v interface", modObj.Name(), modObj.InstanceName(), modIfaceType)
138		}
139	} else if !modObjType.AssignableTo(modIfaceType) {
140		// Case for assignment to concrete module type. Used in "module groups".
141		return parser.NodeErr(inlineCfg, "module %s (%s) is not %v", modObj.Name(), modObj.InstanceName(), modIfaceType)
142	}
143
144	reflect.ValueOf(moduleIface).Elem().Set(reflect.ValueOf(modObj))
145
146	if !referenceExisting {
147		if err := initInlineModule(modObj, globals, inlineCfg); err != nil {
148			return err
149		}
150	}
151
152	return nil
153}
154
155// GroupFromNode provides a special kind of ModuleFromNode syntax that allows
156// to omit the module name when defining inine configuration.  If it is not
157// present, name in defaultModule is used.
158func GroupFromNode(defaultModule string, args []string, inlineCfg config.Node, globals map[string]interface{}, moduleIface interface{}) error {
159	if len(args) == 0 {
160		args = append(args, defaultModule)
161	}
162	return ModuleFromNode("", args, inlineCfg, globals, moduleIface)
163}