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
 19package config
 20
 21import (
 22	"errors"
 23	"fmt"
 24	"reflect"
 25	"strconv"
 26	"strings"
 27	"time"
 28	"unicode"
 29)
 30
 31type matcher struct {
 32	name          string
 33	required      bool
 34	inheritGlobal bool
 35	defaultVal    func() (interface{}, error)
 36	mapper        func(*Map, Node) (interface{}, error)
 37	store         *reflect.Value
 38
 39	customCallback func(*Map, Node) error
 40}
 41
 42func (m *matcher) assign(val interface{}) {
 43	valRefl := reflect.ValueOf(val)
 44	// Convert untyped nil into typed nil. Otherwise it will panic.
 45	if !valRefl.IsValid() {
 46		valRefl = reflect.Zero(m.store.Type())
 47	}
 48
 49	m.store.Set(valRefl)
 50}
 51
 52// Map structure implements reflection-based conversion between configuration
 53// directives and Go variables.
 54type Map struct {
 55	allowUnknown bool
 56
 57	// All values saved by Map during processing.
 58	Values map[string]interface{}
 59
 60	entries map[string]matcher
 61
 62	// Values used by Process as default values if inheritGlobal is true.
 63	Globals map[string]interface{}
 64	// Config block used by Process.
 65	Block Node
 66}
 67
 68func NewMap(globals map[string]interface{}, block Node) *Map {
 69	return &Map{Globals: globals, Block: block}
 70}
 71
 72// AllowUnknown makes config.Map skip unknown configuration directives instead
 73// of failing.
 74func (m *Map) AllowUnknown() {
 75	m.allowUnknown = true
 76}
 77
 78// EnumList maps a configuration directive to a []string variable.
 79//
 80// Directive must be in form 'name string1 string2' where each string should be from *allowed*
 81// slice. At least one argument should be present.
 82//
 83// See Map.Custom for description of inheritGlobal and required.
 84func (m *Map) EnumList(name string, inheritGlobal, required bool, allowed, defaultVal []string, store *[]string) {
 85	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
 86		return defaultVal, nil
 87	}, func(_ *Map, node Node) (interface{}, error) {
 88		if len(node.Children) != 0 {
 89			return nil, NodeErr(node, "can't declare a block here")
 90		}
 91		if len(node.Args) == 0 {
 92			return nil, NodeErr(node, "expected at least one argument")
 93		}
 94
 95		for _, arg := range node.Args {
 96			isAllowed := false
 97			for _, str := range allowed {
 98				if str == arg {
 99					isAllowed = true
100				}
101			}
102			if !isAllowed {
103				return nil, NodeErr(node, "invalid argument, valid values are: %v", allowed)
104			}
105		}
106
107		return node.Args, nil
108	}, store)
109}
110
111// Enum maps a configuration directive to a string variable.
112//
113// Directive must be in form 'name string' where string should be from *allowed*
114// slice. That string argument will be stored in store variable.
115//
116// See Map.Custom for description of inheritGlobal and required.
117func (m *Map) Enum(name string, inheritGlobal, required bool, allowed []string, defaultVal string, store *string) {
118	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
119		return defaultVal, nil
120	}, func(_ *Map, node Node) (interface{}, error) {
121		if len(node.Children) != 0 {
122			return nil, NodeErr(node, "can't declare a block here")
123		}
124		if len(node.Args) != 1 {
125			return nil, NodeErr(node, "expected exactly one argument")
126		}
127
128		for _, str := range allowed {
129			if str == node.Args[0] {
130				return node.Args[0], nil
131			}
132		}
133
134		return nil, NodeErr(node, "invalid argument, valid values are: %v", allowed)
135	}, store)
136}
137
138// EnumMapped is similar to Map.Enum but maps a stirng to a custom type.
139func EnumMapped[V any](m *Map, name string, inheritGlobal, required bool, mapped map[string]V, defaultVal V, store *V) {
140	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
141		return defaultVal, nil
142	}, func(_ *Map, node Node) (interface{}, error) {
143		if len(node.Children) != 0 {
144			return nil, NodeErr(node, "can't declare a block here")
145		}
146		if len(node.Args) != 1 {
147			return nil, NodeErr(node, "expected exactly one argument")
148		}
149
150		val, ok := mapped[node.Args[0]]
151		if !ok {
152			validValues := make([]string, 0, len(mapped))
153			for k := range mapped {
154				validValues = append(validValues, k)
155			}
156			return nil, NodeErr(node, "invalid argument, valid values are: %v", validValues)
157		}
158
159		return val, nil
160	}, store)
161}
162
163// EnumListMapped is similar to Map.EnumList but maps a stirng to a custom type.
164func EnumListMapped[V any](m *Map, name string, inheritGlobal, required bool, mapped map[string]V, defaultVal []V, store *[]V) {
165	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
166		return defaultVal, nil
167	}, func(_ *Map, node Node) (interface{}, error) {
168		if len(node.Children) != 0 {
169			return nil, NodeErr(node, "can't declare a block here")
170		}
171		if len(node.Args) == 0 {
172			return nil, NodeErr(node, "expected at least one argument")
173		}
174
175		values := make([]V, 0, len(node.Args))
176		for _, arg := range node.Args {
177			val, ok := mapped[arg]
178			if !ok {
179				validValues := make([]string, 0, len(mapped))
180				for k := range mapped {
181					validValues = append(validValues, k)
182				}
183				return nil, NodeErr(node, "invalid argument, valid values are: %v", validValues)
184			}
185			values = append(values, val)
186		}
187		return values, nil
188	}, store)
189}
190
191// Duration maps configuration directive to a time.Duration variable.
192//
193// Directive must be in form 'name duration' where duration is any string accepted by
194// time.ParseDuration. As an additional requirement, result of time.ParseDuration must not
195// be negative.
196//
197// Note that for convenience, if directive does have multiple arguments, they will be joined
198// without separators. E.g. 'name 1h 2m' will become 'name 1h2m' and so '1h2m' will be passed
199// to time.ParseDuration.
200//
201// See Map.Custom for description of arguments.
202func (m *Map) Duration(name string, inheritGlobal, required bool, defaultVal time.Duration, store *time.Duration) {
203	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
204		return defaultVal, nil
205	}, func(_ *Map, node Node) (interface{}, error) {
206		if len(node.Children) != 0 {
207			return nil, NodeErr(node, "can't declare block here")
208		}
209		if len(node.Args) == 0 {
210			return nil, NodeErr(node, "at least one argument is required")
211		}
212
213		durationStr := strings.Join(node.Args, "")
214		dur, err := time.ParseDuration(durationStr)
215		if err != nil {
216			return nil, NodeErr(node, "%v", err)
217		}
218
219		if dur < 0 {
220			return nil, NodeErr(node, "duration must not be negative")
221		}
222
223		return dur, nil
224	}, store)
225}
226
227func ParseDataSize(s string) (int, error) {
228	if len(s) == 0 {
229		return 0, errors.New("missing a number")
230	}
231
232	// ' ' terminates the number+suffix pair.
233	s = s + " "
234
235	var total int
236	currentDigit := ""
237	suffix := ""
238	for _, ch := range s {
239		if unicode.IsDigit(ch) {
240			if suffix != "" {
241				return 0, errors.New("unexpected digit after a suffix")
242			}
243			currentDigit += string(ch)
244			continue
245		}
246		if ch != ' ' {
247			suffix += string(ch)
248			continue
249		}
250
251		num, err := strconv.Atoi(currentDigit)
252		if err != nil {
253			return 0, err
254		}
255
256		if num < 0 {
257			return 0, errors.New("value must not be negative")
258		}
259
260		switch suffix {
261		case "G":
262			total += num * 1024 * 1024 * 1024
263		case "M":
264			total += num * 1024 * 1024
265		case "K":
266			total += num * 1024
267		case "B", "b":
268			total += num
269		default:
270			if num != 0 {
271				return 0, errors.New("unknown unit suffix: " + suffix)
272			}
273		}
274
275		suffix = ""
276		currentDigit = ""
277	}
278
279	return total, nil
280}
281
282// DataSize maps configuration directive to a int variable, representing data size.
283//
284// Syntax requires unit suffix to be added to the end of string to specify
285// data unit and allows multiple arguments (they will be added together).
286//
287// See Map.Custom for description of arguments.
288func (m *Map) DataSize(name string, inheritGlobal, required bool, defaultVal int64, store *int64) {
289	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
290		return defaultVal, nil
291	}, func(_ *Map, node Node) (interface{}, error) {
292		if len(node.Children) != 0 {
293			return nil, NodeErr(node, "can't declare block here")
294		}
295		if len(node.Args) == 0 {
296			return nil, NodeErr(node, "at least one argument is required")
297		}
298
299		durationStr := strings.Join(node.Args, " ")
300		dur, err := ParseDataSize(durationStr)
301		if err != nil {
302			return nil, NodeErr(node, "%v", err)
303		}
304
305		return int64(dur), nil
306	}, store)
307}
308
309func ParseBool(s string) (bool, error) {
310	switch strings.ToLower(s) {
311	case "1", "true", "on", "yes":
312		return true, nil
313	case "0", "false", "off", "no":
314		return false, nil
315	}
316	return false, fmt.Errorf("bool argument should be 'yes' or 'no'")
317}
318
319// Bool maps presence of some configuration directive to a boolean variable.
320// Additionally, 'name yes' and 'name no' are mapped to true and false
321// correspondingly.
322//
323// I.e. if directive 'io_debug' exists in processed configuration block or in
324// the global configuration (if inheritGlobal is true) then Process will store
325// true in target variable.
326func (m *Map) Bool(name string, inheritGlobal, defaultVal bool, store *bool) {
327	m.Custom(name, inheritGlobal, false, func() (interface{}, error) {
328		return defaultVal, nil
329	}, func(_ *Map, node Node) (interface{}, error) {
330		if len(node.Children) != 0 {
331			return nil, NodeErr(node, "can't declare block here")
332		}
333
334		if len(node.Args) == 0 {
335			return true, nil
336		}
337		if len(node.Args) != 1 {
338			return nil, NodeErr(node, "expected exactly 1 argument")
339		}
340
341		b, err := ParseBool(node.Args[0])
342		if err != nil {
343			return nil, NodeErr(node, "bool argument should be 'yes' or 'no'")
344		}
345		return b, nil
346	}, store)
347}
348
349// StringList maps configuration directive with the specified name to variable
350// referenced by 'store' pointer.
351//
352// Configuration directive must be in form 'name arbitrary_string arbitrary_string ...'
353// Where at least one argument must be present.
354//
355// See Custom function for details about inheritGlobal, required and
356// defaultVal.
357func (m *Map) StringList(name string, inheritGlobal, required bool, defaultVal []string, store *[]string) {
358	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
359		return defaultVal, nil
360	}, func(_ *Map, node Node) (interface{}, error) {
361		if len(node.Args) == 0 {
362			return nil, NodeErr(node, "expected at least one argument")
363		}
364		if len(node.Children) != 0 {
365			return nil, NodeErr(node, "can't declare block here")
366		}
367
368		return node.Args, nil
369	}, store)
370}
371
372// String maps configuration directive with the specified name to variable
373// referenced by 'store' pointer.
374//
375// Configuration directive must be in form 'name arbitrary_string'.
376//
377// See Custom function for details about inheritGlobal, required and
378// defaultVal.
379func (m *Map) String(name string, inheritGlobal, required bool, defaultVal string, store *string) {
380	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
381		return defaultVal, nil
382	}, func(_ *Map, node Node) (interface{}, error) {
383		if len(node.Args) != 1 {
384			return nil, NodeErr(node, "expected 1 argument")
385		}
386		if len(node.Children) != 0 {
387			return nil, NodeErr(node, "can't declare block here")
388		}
389
390		return node.Args[0], nil
391	}, store)
392}
393
394// Int maps configuration directive with the specified name to variable
395// referenced by 'store' pointer.
396//
397// Configuration directive must be in form 'name 123'.
398//
399// See Custom function for details about inheritGlobal, required and
400// defaultVal.
401func (m *Map) Int(name string, inheritGlobal, required bool, defaultVal int, store *int) {
402	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
403		return defaultVal, nil
404	}, func(_ *Map, node Node) (interface{}, error) {
405		if len(node.Args) != 1 {
406			return nil, NodeErr(node, "expected 1 argument")
407		}
408		if len(node.Children) != 0 {
409			return nil, NodeErr(node, "can't declare block here")
410		}
411
412		i, err := strconv.Atoi(node.Args[0])
413		if err != nil {
414			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
415		}
416		return i, nil
417	}, store)
418}
419
420// UInt maps configuration directive with the specified name to variable
421// referenced by 'store' pointer.
422//
423// Configuration directive must be in form 'name 123'.
424//
425// See Custom function for details about inheritGlobal, required and
426// defaultVal.
427func (m *Map) UInt(name string, inheritGlobal, required bool, defaultVal uint, store *uint) {
428	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
429		return defaultVal, nil
430	}, func(_ *Map, node Node) (interface{}, error) {
431		if len(node.Args) != 1 {
432			return nil, NodeErr(node, "expected 1 argument")
433		}
434		if len(node.Children) != 0 {
435			return nil, NodeErr(node, "can't declare block here")
436		}
437
438		i, err := strconv.ParseUint(node.Args[0], 10, 32)
439		if err != nil {
440			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
441		}
442		return uint(i), nil
443	}, store)
444}
445
446// Int32 maps configuration directive with the specified name to variable
447// referenced by 'store' pointer.
448//
449// Configuration directive must be in form 'name 123'.
450//
451// See Custom function for details about inheritGlobal, required and
452// defaultVal.
453func (m *Map) Int32(name string, inheritGlobal, required bool, defaultVal int32, store *int32) {
454	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
455		return defaultVal, nil
456	}, func(_ *Map, node Node) (interface{}, error) {
457		if len(node.Args) != 1 {
458			return nil, NodeErr(node, "expected 1 argument")
459		}
460		if len(node.Children) != 0 {
461			return nil, NodeErr(node, "can't declare block here")
462		}
463
464		i, err := strconv.ParseInt(node.Args[0], 10, 32)
465		if err != nil {
466			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
467		}
468		return int32(i), nil
469	}, store)
470}
471
472// UInt32 maps configuration directive with the specified name to variable
473// referenced by 'store' pointer.
474//
475// Configuration directive must be in form 'name 123'.
476//
477// See Custom function for details about inheritGlobal, required and
478// defaultVal.
479func (m *Map) UInt32(name string, inheritGlobal, required bool, defaultVal uint32, store *uint32) {
480	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
481		return defaultVal, nil
482	}, func(_ *Map, node Node) (interface{}, error) {
483		if len(node.Args) != 1 {
484			return nil, NodeErr(node, "expected 1 argument")
485		}
486		if len(node.Children) != 0 {
487			return nil, NodeErr(node, "can't declare block here")
488		}
489
490		i, err := strconv.ParseUint(node.Args[0], 10, 32)
491		if err != nil {
492			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
493		}
494		return uint32(i), nil
495	}, store)
496}
497
498// Int64 maps configuration directive with the specified name to variable
499// referenced by 'store' pointer.
500//
501// Configuration directive must be in form 'name 123'.
502//
503// See Custom function for details about inheritGlobal, required and
504// defaultVal.
505func (m *Map) Int64(name string, inheritGlobal, required bool, defaultVal int64, store *int64) {
506	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
507		return defaultVal, nil
508	}, func(_ *Map, node Node) (interface{}, error) {
509		if len(node.Args) != 1 {
510			return nil, NodeErr(node, "expected 1 argument")
511		}
512		if len(node.Children) != 0 {
513			return nil, NodeErr(node, "can't declare block here")
514		}
515
516		i, err := strconv.ParseInt(node.Args[0], 10, 64)
517		if err != nil {
518			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
519		}
520		return i, nil
521	}, store)
522}
523
524// UInt64 maps configuration directive with the specified name to variable
525// referenced by 'store' pointer.
526//
527// Configuration directive must be in form 'name 123'.
528//
529// See Custom function for details about inheritGlobal, required and
530// defaultVal.
531func (m *Map) UInt64(name string, inheritGlobal, required bool, defaultVal uint64, store *uint64) {
532	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
533		return defaultVal, nil
534	}, func(_ *Map, node Node) (interface{}, error) {
535		if len(node.Args) != 1 {
536			return nil, NodeErr(node, "expected 1 argument")
537		}
538		if len(node.Children) != 0 {
539			return nil, NodeErr(node, "can't declare block here")
540		}
541
542		i, err := strconv.ParseUint(node.Args[0], 10, 64)
543		if err != nil {
544			return nil, NodeErr(node, "invalid integer: %s", node.Args[0])
545		}
546		return i, nil
547	}, store)
548}
549
550// Float maps configuration directive with the specified name to variable
551// referenced by 'store' pointer.
552//
553// Configuration directive must be in form 'name 123.55'.
554//
555// See Custom function for details about inheritGlobal, required and
556// defaultVal.
557func (m *Map) Float(name string, inheritGlobal, required bool, defaultVal float64, store *float64) {
558	m.Custom(name, inheritGlobal, required, func() (interface{}, error) {
559		return defaultVal, nil
560	}, func(_ *Map, node Node) (interface{}, error) {
561		if len(node.Args) != 1 {
562			return nil, NodeErr(node, "expected 1 argument")
563		}
564
565		f, err := strconv.ParseFloat(node.Args[0], 64)
566		if err != nil {
567			return nil, NodeErr(node, "invalid float: %s", node.Args[0])
568		}
569		return f, nil
570	}, store)
571}
572
573// Custom maps configuration directive with the specified name to variable
574// referenced by 'store' pointer.
575//
576// If inheritGlobal is true - Map will try to use a value from globalCfg if
577// none is set in a processed configuration block.
578//
579// If required is true - Map will fail if no value is set in the configuration,
580// both global (if inheritGlobal is true) and in the processed block.
581//
582// defaultVal is a factory function that should return the default value for
583// the variable. It will be used if no value is set in the config. It can be
584// nil if required is true.
585// Note that if inheritGlobal is true, defaultVal of the global directive
586// will be used instead.
587//
588// mapper is a function that should convert configuration directive arguments
589// into variable value.  Both functions may fail with errors, configuration
590// processing will stop immediately then.
591// Note: mapper function should not modify passed values.
592//
593// store is where the value returned by mapper should be stored. Can be nil
594// (value will be saved only in Map.Values).
595func (m *Map) Custom(name string, inheritGlobal, required bool, defaultVal func() (interface{}, error), mapper func(*Map, Node) (interface{}, error), store interface{}) {
596	if m.entries == nil {
597		m.entries = make(map[string]matcher)
598	}
599	if _, ok := m.entries[name]; ok {
600		panic("Map.Custom: duplicate matcher")
601	}
602
603	var target *reflect.Value
604	ptr := reflect.ValueOf(store)
605	if ptr.IsValid() && !ptr.IsNil() {
606		val := ptr.Elem()
607		if !val.CanSet() {
608			panic("Map.Custom: store argument must be settable (a pointer)")
609		}
610		target = &val
611	}
612
613	m.entries[name] = matcher{
614		name:          name,
615		inheritGlobal: inheritGlobal,
616		required:      required,
617		defaultVal:    defaultVal,
618		mapper:        mapper,
619		store:         target,
620	}
621}
622
623// Callback creates mapping that will call mapper() function for each
624// directive with the specified name. No further processing is done.
625//
626// Directives with the specified name will not be returned by Process if
627// AllowUnknown is used.
628//
629// It is intended to permit multiple independent values of directive with
630// implementation-defined handling.
631func (m *Map) Callback(name string, mapper func(*Map, Node) error) {
632	if m.entries == nil {
633		m.entries = make(map[string]matcher)
634	}
635	if _, ok := m.entries[name]; ok {
636		panic("Map.Custom: duplicate matcher")
637	}
638
639	m.entries[name] = matcher{
640		name:           name,
641		customCallback: mapper,
642	}
643}
644
645// Process maps variables from global configuration and block passed in NewMap.
646//
647// If Map instance was not created using NewMap - Process panics.
648func (m *Map) Process() (unknown []Node, err error) {
649	return m.ProcessWith(m.Globals, m.Block)
650}
651
652// Process maps variables from global configuration and block passed in arguments.
653func (m *Map) ProcessWith(globalCfg map[string]interface{}, block Node) (unknown []Node, err error) {
654	unknown = make([]Node, 0, len(block.Children))
655	matched := make(map[string]bool)
656	m.Values = make(map[string]interface{})
657
658	for _, subnode := range block.Children {
659		matcher, ok := m.entries[subnode.Name]
660		if !ok {
661			if !m.allowUnknown {
662				return nil, NodeErr(subnode, "unexpected directive: %s", subnode.Name)
663			}
664			unknown = append(unknown, subnode)
665			continue
666		}
667
668		if matcher.customCallback != nil {
669			if err := matcher.customCallback(m, subnode); err != nil {
670				return nil, err
671			}
672			matched[subnode.Name] = true
673			continue
674		}
675
676		if matched[subnode.Name] {
677			return nil, NodeErr(subnode, "duplicate directive: %s", subnode.Name)
678		}
679		matched[subnode.Name] = true
680
681		val, err := matcher.mapper(m, subnode)
682		if err != nil {
683			return nil, err
684		}
685		m.Values[matcher.name] = val
686		if matcher.store != nil {
687			matcher.assign(val)
688		}
689	}
690
691	for _, matcher := range m.entries {
692		if matched[matcher.name] {
693			continue
694		}
695		if matcher.mapper == nil {
696			continue
697		}
698
699		var val interface{}
700		globalVal, ok := globalCfg[matcher.name]
701		if matcher.inheritGlobal && ok {
702			val = globalVal
703		} else if !matcher.required {
704			if matcher.defaultVal == nil {
705				continue
706			}
707
708			val, err = matcher.defaultVal()
709			if err != nil {
710				return nil, err
711			}
712		} else {
713			return nil, NodeErr(block, "missing required directive: %s", matcher.name)
714		}
715
716		// If we put zero values into map then code that checks globalCfg
717		// above will inherit them for required fields instead of failing.
718		//
719		// This is important for fields that are required to be specified
720		// either globally or on per-block basis (e.g. tls, hostname).
721		// For these directives, global Map does have required = false
722		// so global values are default which is usually zero value.
723		//
724		// This is a temporary solutions, of course, in the long-term
725		// the way global values and "inheritance" is handled should be
726		// revised.
727		store := false
728		valT := reflect.TypeOf(val)
729		if valT != nil {
730			zero := reflect.Zero(valT)
731			store = !reflect.DeepEqual(val, zero.Interface())
732		}
733
734		if store {
735			m.Values[matcher.name] = val
736		}
737		if matcher.store != nil {
738			matcher.assign(val)
739		}
740	}
741
742	return unknown, nil
743}