1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package config2021import (22 "errors"23 "fmt"24 "reflect"25 "strconv"26 "strings"27 "time"28 "unicode"29)3031type matcher struct {32 name string33 required bool34 inheritGlobal bool35 defaultVal func() (interface{}, error)36 mapper func(*Map, Node) (interface{}, error)37 store *reflect.Value3839 customCallback func(*Map, Node) error40}4142func (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 }4849 m.store.Set(valRefl)50}5152// Map structure implements reflection-based conversion between configuration53// directives and Go variables.54type Map struct {55 allowUnknown bool5657 // All values saved by Map during processing.58 Values map[string]interface{}5960 entries map[string]matcher6162 // Values used by Process as default values if inheritGlobal is true.63 Globals map[string]interface{}64 // Config block used by Process.65 Block Node66}6768func NewMap(globals map[string]interface{}, block Node) *Map {69 return &Map{Globals: globals, Block: block}70}7172// AllowUnknown makes config.Map skip unknown configuration directives instead73// of failing.74func (m *Map) AllowUnknown() {75 m.allowUnknown = true76}7778// 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, nil87 }, 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 }9495 for _, arg := range node.Args {96 isAllowed := false97 for _, str := range allowed {98 if str == arg {99 isAllowed = true100 }101 }102 if !isAllowed {103 return nil, NodeErr(node, "invalid argument, valid values are: %v", allowed)104 }105 }106107 return node.Args, nil108 }, store)109}110111// 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, nil120 }, 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 }127128 for _, str := range allowed {129 if str == node.Args[0] {130 return node.Args[0], nil131 }132 }133134 return nil, NodeErr(node, "invalid argument, valid values are: %v", allowed)135 }, store)136}137138// 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, nil142 }, 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 }149150 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 }158159 return val, nil160 }, store)161}162163// 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, nil167 }, 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 }174175 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, nil188 }, store)189}190191// Duration maps configuration directive to a time.Duration variable.192//193// Directive must be in form 'name duration' where duration is any string accepted by194// time.ParseDuration. As an additional requirement, result of time.ParseDuration must not195// be negative.196//197// Note that for convenience, if directive does have multiple arguments, they will be joined198// without separators. E.g. 'name 1h 2m' will become 'name 1h2m' and so '1h2m' will be passed199// 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, nil205 }, 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 }212213 durationStr := strings.Join(node.Args, "")214 dur, err := time.ParseDuration(durationStr)215 if err != nil {216 return nil, NodeErr(node, "%v", err)217 }218219 if dur < 0 {220 return nil, NodeErr(node, "duration must not be negative")221 }222223 return dur, nil224 }, store)225}226227func ParseDataSize(s string) (int, error) {228 if len(s) == 0 {229 return 0, errors.New("missing a number")230 }231232 // ' ' terminates the number+suffix pair.233 s = s + " "234235 var total int236 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 continue245 }246 if ch != ' ' {247 suffix += string(ch)248 continue249 }250251 num, err := strconv.Atoi(currentDigit)252 if err != nil {253 return 0, err254 }255256 if num < 0 {257 return 0, errors.New("value must not be negative")258 }259260 switch suffix {261 case "G":262 total += num * 1024 * 1024 * 1024263 case "M":264 total += num * 1024 * 1024265 case "K":266 total += num * 1024267 case "B", "b":268 total += num269 default:270 if num != 0 {271 return 0, errors.New("unknown unit suffix: " + suffix)272 }273 }274275 suffix = ""276 currentDigit = ""277 }278279 return total, nil280}281282// 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 specify285// 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, nil291 }, 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 }298299 durationStr := strings.Join(node.Args, " ")300 dur, err := ParseDataSize(durationStr)301 if err != nil {302 return nil, NodeErr(node, "%v", err)303 }304305 return int64(dur), nil306 }, store)307}308309func ParseBool(s string) (bool, error) {310 switch strings.ToLower(s) {311 case "1", "true", "on", "yes":312 return true, nil313 case "0", "false", "off", "no":314 return false, nil315 }316 return false, fmt.Errorf("bool argument should be 'yes' or 'no'")317}318319// Bool maps presence of some configuration directive to a boolean variable.320// Additionally, 'name yes' and 'name no' are mapped to true and false321// correspondingly.322//323// I.e. if directive 'io_debug' exists in processed configuration block or in324// the global configuration (if inheritGlobal is true) then Process will store325// 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, nil329 }, func(_ *Map, node Node) (interface{}, error) {330 if len(node.Children) != 0 {331 return nil, NodeErr(node, "can't declare block here")332 }333334 if len(node.Args) == 0 {335 return true, nil336 }337 if len(node.Args) != 1 {338 return nil, NodeErr(node, "expected exactly 1 argument")339 }340341 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, nil346 }, store)347}348349// StringList maps configuration directive with the specified name to variable350// 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 and356// 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, nil360 }, 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 }367368 return node.Args, nil369 }, store)370}371372// String maps configuration directive with the specified name to variable373// 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 and378// 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, nil382 }, 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 }389390 return node.Args[0], nil391 }, store)392}393394// Int maps configuration directive with the specified name to variable395// referenced by 'store' pointer.396//397// Configuration directive must be in form 'name 123'.398//399// See Custom function for details about inheritGlobal, required and400// 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, nil404 }, 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 }411412 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, nil417 }, store)418}419420// UInt maps configuration directive with the specified name to variable421// referenced by 'store' pointer.422//423// Configuration directive must be in form 'name 123'.424//425// See Custom function for details about inheritGlobal, required and426// 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, nil430 }, 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 }437438 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), nil443 }, store)444}445446// Int32 maps configuration directive with the specified name to variable447// referenced by 'store' pointer.448//449// Configuration directive must be in form 'name 123'.450//451// See Custom function for details about inheritGlobal, required and452// 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, nil456 }, 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 }463464 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), nil469 }, store)470}471472// UInt32 maps configuration directive with the specified name to variable473// referenced by 'store' pointer.474//475// Configuration directive must be in form 'name 123'.476//477// See Custom function for details about inheritGlobal, required and478// 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, nil482 }, 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 }489490 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), nil495 }, store)496}497498// Int64 maps configuration directive with the specified name to variable499// referenced by 'store' pointer.500//501// Configuration directive must be in form 'name 123'.502//503// See Custom function for details about inheritGlobal, required and504// 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, nil508 }, 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 }515516 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, nil521 }, store)522}523524// UInt64 maps configuration directive with the specified name to variable525// referenced by 'store' pointer.526//527// Configuration directive must be in form 'name 123'.528//529// See Custom function for details about inheritGlobal, required and530// 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, nil534 }, 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 }541542 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, nil547 }, store)548}549550// Float maps configuration directive with the specified name to variable551// 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 and556// 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, nil560 }, func(_ *Map, node Node) (interface{}, error) {561 if len(node.Args) != 1 {562 return nil, NodeErr(node, "expected 1 argument")563 }564565 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, nil570 }, store)571}572573// Custom maps configuration directive with the specified name to variable574// referenced by 'store' pointer.575//576// If inheritGlobal is true - Map will try to use a value from globalCfg if577// 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 for583// the variable. It will be used if no value is set in the config. It can be584// nil if required is true.585// Note that if inheritGlobal is true, defaultVal of the global directive586// will be used instead.587//588// mapper is a function that should convert configuration directive arguments589// into variable value. Both functions may fail with errors, configuration590// 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 nil594// (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 }602603 var target *reflect.Value604 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 = &val611 }612613 m.entries[name] = matcher{614 name: name,615 inheritGlobal: inheritGlobal,616 required: required,617 defaultVal: defaultVal,618 mapper: mapper,619 store: target,620 }621}622623// Callback creates mapping that will call mapper() function for each624// directive with the specified name. No further processing is done.625//626// Directives with the specified name will not be returned by Process if627// AllowUnknown is used.628//629// It is intended to permit multiple independent values of directive with630// 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 }638639 m.entries[name] = matcher{640 name: name,641 customCallback: mapper,642 }643}644645// 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}651652// 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{})657658 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 continue666 }667668 if matcher.customCallback != nil {669 if err := matcher.customCallback(m, subnode); err != nil {670 return nil, err671 }672 matched[subnode.Name] = true673 continue674 }675676 if matched[subnode.Name] {677 return nil, NodeErr(subnode, "duplicate directive: %s", subnode.Name)678 }679 matched[subnode.Name] = true680681 val, err := matcher.mapper(m, subnode)682 if err != nil {683 return nil, err684 }685 m.Values[matcher.name] = val686 if matcher.store != nil {687 matcher.assign(val)688 }689 }690691 for _, matcher := range m.entries {692 if matched[matcher.name] {693 continue694 }695 if matcher.mapper == nil {696 continue697 }698699 var val interface{}700 globalVal, ok := globalCfg[matcher.name]701 if matcher.inheritGlobal && ok {702 val = globalVal703 } else if !matcher.required {704 if matcher.defaultVal == nil {705 continue706 }707708 val, err = matcher.defaultVal()709 if err != nil {710 return nil, err711 }712 } else {713 return nil, NodeErr(block, "missing required directive: %s", matcher.name)714 }715716 // If we put zero values into map then code that checks globalCfg717 // above will inherit them for required fields instead of failing.718 //719 // This is important for fields that are required to be specified720 // either globally or on per-block basis (e.g. tls, hostname).721 // For these directives, global Map does have required = false722 // so global values are default which is usually zero value.723 //724 // This is a temporary solutions, of course, in the long-term725 // the way global values and "inheritance" is handled should be726 // revised.727 store := false728 valT := reflect.TypeOf(val)729 if valT != nil {730 zero := reflect.Zero(valT)731 store = !reflect.DeepEqual(val, zero.Interface())732 }733734 if store {735 m.Values[matcher.name] = val736 }737 if matcher.store != nil {738 matcher.assign(val)739 }740 }741742 return unknown, nil743}