1/*
2** Zabbix
3** Copyright (C) 2001-2021 Zabbix SIA
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18**/
19
20// Package conf provides .conf file loading and unmarshalling
21package conf
22
23import (
24	"bytes"
25	"errors"
26	"fmt"
27	"os"
28	"path/filepath"
29	"reflect"
30	"runtime"
31	"strconv"
32	"strings"
33	"unicode/utf8"
34
35	"zabbix.com/pkg/std"
36)
37
38// Meta structure is used to store the 'conf' tag metadata.
39type Meta struct {
40	name         string
41	defaultValue *string
42	optional     bool
43	min          int64
44	max          int64
45}
46type Suffix struct {
47	suffix string
48	factor int
49}
50
51func validateParameterName(key []byte) (err error) {
52	for i, b := range key {
53		if ('A' > b || b > 'Z') && ('a' > b || b > 'z') && ('0' > b || b > '9') && b != '_' && b != '.' {
54			return fmt.Errorf("invalid character '%c' at position %d", b, i+1)
55		}
56	}
57	return
58}
59
60// parseLine parses parameter configuration line and returns key,value pair.
61// The line must have format: <key>[ ]=[ ]<value> where whitespace surrounding
62// '=' is optional.
63func parseLine(line []byte) (key []byte, value []byte, err error) {
64	valueStart := bytes.IndexByte(line, '=')
65	if valueStart == -1 {
66		return nil, nil, errors.New("missing assignment operator")
67	}
68
69	if key = bytes.TrimSpace(line[:valueStart]); len(key) == 0 {
70		return nil, nil, errors.New("missing variable name")
71	}
72
73	if err = validateParameterName(key); err != nil {
74		return
75	}
76
77	return key, bytes.TrimSpace(line[valueStart+1:]), nil
78}
79
80// getMeta returns 'conf' tag metadata.
81// The metadata has format [name=<name>,][optional,][range=<range>,][default=<default value>]
82//   where:
83//   <name> - the parameter name,
84//   optional - set if the value is optional,
85//   <range> - the allowed range <min>:<max>, where <min>, <max> values are optional,
86//   <default value> - the default value. If specified it must always be the last tag.
87func getMeta(field reflect.StructField) (meta *Meta, err error) {
88	m := Meta{name: "", optional: false, min: -1, max: -1}
89	conf := field.Tag.Get("conf")
90
91loop:
92	for conf != "" {
93		tags := strings.SplitN(conf, ",", 2)
94		fields := strings.SplitN(tags[0], "=", 2)
95		tag := strings.TrimSpace(fields[0])
96		if len(fields) == 1 {
97			// boolean tag
98			switch tag {
99			case "optional":
100				m.optional = true
101			default:
102				return nil, fmt.Errorf("invalid conf tag '%s'", tag)
103			}
104		} else {
105			// value tag
106			switch tag {
107			case "default":
108				value := fields[1]
109				if len(tags) == 2 {
110					value += "," + tags[1]
111				}
112				m.defaultValue = &value
113				break loop
114			case "name":
115				m.name = strings.TrimSpace(fields[1])
116			case "range":
117				limits := strings.Split(fields[1], ":")
118				if len(limits) != 2 {
119					return nil, errors.New("invalid range format")
120				}
121				if limits[0] != "" {
122					m.min, _ = strconv.ParseInt(limits[0], 10, 64)
123				}
124				if limits[1] != "" {
125					m.max, _ = strconv.ParseInt(limits[1], 10, 64)
126				}
127			default:
128				return nil, fmt.Errorf("invalid conf tag '%s'", tag)
129			}
130		}
131
132		if len(tags) == 1 {
133			break
134		}
135		conf = tags[1]
136	}
137
138	if m.name == "" {
139		m.name = field.Name
140	}
141	return &m, nil
142}
143
144func getTimeSuffix(str string) (string, int) {
145	suffixes := []Suffix{
146		Suffix{
147			suffix: "s",
148			factor: 1,
149		},
150		Suffix{
151			suffix: "m",
152			factor: 60,
153		},
154		Suffix{
155			suffix: "h",
156			factor: 3600,
157		},
158		Suffix{
159			suffix: "d",
160			factor: 86400,
161		},
162		Suffix{
163			suffix: "w",
164			factor: (7 * 86400),
165		},
166	}
167
168	for _, s := range suffixes {
169		if strings.HasSuffix(str, s.suffix) == true {
170			str = strings.TrimSuffix(str, s.suffix)
171			return str, s.factor
172		}
173	}
174	return str, 1
175
176}
177
178func setBasicValue(value reflect.Value, meta *Meta, str *string) (err error) {
179	if str == nil {
180		return nil
181	}
182	switch value.Type().Kind() {
183	case reflect.String:
184		value.SetString(*str)
185	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
186		var v int64
187		var r int
188		*str, r = getTimeSuffix(*str)
189		if v, err = strconv.ParseInt(*str, 10, 64); err == nil {
190			v = v * int64(r)
191			if meta != nil {
192				if meta.min != -1 && v < meta.min || meta.max != -1 && v > meta.max {
193					return errors.New("value out of range")
194				}
195			}
196			value.SetInt(v)
197		}
198	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
199		var v uint64
200		var r int
201		*str, r = getTimeSuffix(*str)
202		if v, err = strconv.ParseUint(*str, 10, 64); err == nil {
203			v = v * uint64(r)
204			if meta != nil {
205				if meta.min != -1 && v < uint64(meta.min) || meta.max != -1 && v > uint64(meta.max) {
206					return errors.New("value out of range")
207				}
208			}
209			value.SetUint(v)
210		}
211	case reflect.Float32, reflect.Float64:
212		var v float64
213		if v, err = strconv.ParseFloat(*str, 64); err == nil {
214			if meta != nil {
215				if meta.min != -1 && v < float64(meta.min) || meta.max != -1 && v > float64(meta.max) {
216					return errors.New("value out of range")
217				}
218			}
219			value.SetFloat(v)
220		}
221	case reflect.Bool:
222		var v bool
223		switch *str {
224		case "true":
225			v = true
226		case "false":
227			v = false
228		default:
229			return errors.New("invalid boolean value")
230		}
231		value.SetBool(v)
232	case reflect.Ptr:
233		v := reflect.New(value.Type().Elem())
234		value.Set(v)
235		return setBasicValue(v.Elem(), meta, str)
236	default:
237		err = fmt.Errorf("unsupported variable type %v", value.Type().Kind())
238	}
239	return err
240}
241
242func setStructValue(value reflect.Value, node *Node) (err error) {
243	rt := value.Type()
244	for i := 0; i < rt.NumField(); i++ {
245		var meta *Meta
246		if meta, err = getMeta(rt.Field(i)); err != nil {
247			return
248		}
249		child := node.get(meta.name)
250		if child != nil || meta.defaultValue != nil {
251			if err = setValue(value.Field(i), meta, child); err != nil {
252				return
253			}
254		} else if !meta.optional {
255			return fmt.Errorf("cannot find mandatory parameter %s", meta.name)
256		}
257	}
258	return
259}
260
261func setMapValue(value reflect.Value, node *Node) (err error) {
262	m := reflect.MakeMap(reflect.MapOf(value.Type().Key(), value.Type().Elem()))
263	for _, v := range node.Nodes {
264		if child, ok := v.(*Node); ok {
265			k := reflect.New(value.Type().Key())
266			if err = setBasicValue(k.Elem(), nil, &child.Name); err != nil {
267				return
268			}
269			v := reflect.New(value.Type().Elem())
270			if err = setValue(v.Elem(), nil, child); err != nil {
271				return
272			}
273			m.SetMapIndex(k.Elem(), v.Elem())
274		}
275	}
276	value.Set(m)
277	return
278}
279
280func setSliceValue(value reflect.Value, node *Node) (err error) {
281	tmpValues := make([][]byte, 0)
282	for _, v := range node.Nodes {
283		if val, ok := v.(*Value); ok {
284			tmpValues = append(tmpValues, val.Value)
285		}
286	}
287	size := len(tmpValues)
288	values := reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), 0, size)
289
290	if len(tmpValues) > 0 {
291		for _, data := range tmpValues {
292			v := reflect.New(value.Type().Elem())
293			str := string(data)
294			if err = setBasicValue(v.Elem(), nil, &str); err != nil {
295				return
296			}
297			values = reflect.Append(values, v.Elem())
298		}
299	} else {
300		for _, n := range node.Nodes {
301			if child, ok := n.(*Node); ok {
302				v := reflect.New(value.Type().Elem())
303				if err = setValue(v.Elem(), nil, child); err != nil {
304					return
305				}
306				values = reflect.Append(values, v.Elem())
307			}
308		}
309	}
310	value.Set(values)
311	return
312}
313
314func setValue(value reflect.Value, meta *Meta, node *Node) (err error) {
315	var str *string
316	if node != nil {
317		node.used = true
318	}
319	switch value.Type().Kind() {
320	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
321		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
322		reflect.Float32, reflect.Float64, reflect.Bool, reflect.String:
323		if str, err = node.getValue(meta); err == nil {
324			if err = setBasicValue(value, meta, str); err != nil {
325				return node.newError("%s", err.Error())
326			}
327		}
328	case reflect.Struct:
329		if node != nil {
330			return setStructValue(value, node)
331		}
332	case reflect.Map:
333		if node != nil {
334			return setMapValue(value, node)
335		}
336	case reflect.Slice:
337		if node != nil {
338			return setSliceValue(value, node)
339		}
340	case reflect.Ptr:
341		v := reflect.New(value.Type().Elem())
342		value.Set(v)
343		return setValue(v.Elem(), meta, node)
344	case reflect.Interface:
345		value.Set(reflect.ValueOf(node))
346		node.markUsed(true)
347	}
348
349	return nil
350}
351
352// assignValues assigns parsed nodes to the specified structure
353func assignValues(v interface{}, root *Node) (err error) {
354	rv := reflect.ValueOf(v)
355
356	switch rv.Type().Kind() {
357	case reflect.Ptr:
358		rv = rv.Elem()
359	default:
360		return errors.New("output variable must be a pointer to a structure")
361	}
362
363	switch rv.Type().Kind() {
364	case reflect.Struct:
365		if err = setStructValue(rv, root); err != nil {
366			return err
367		}
368	default:
369		return errors.New("output variable must be a pointer to a structure")
370	}
371	return root.checkUsage()
372}
373
374func newIncludeError(root *Node, filename *string, errmsg string) (err error) {
375	if root.includeFail {
376		return errors.New(errmsg)
377	}
378	root.includeFail = true
379	if filename != nil {
380		return fmt.Errorf(`cannot include "%s": %s`, *filename, errmsg)
381	}
382	return fmt.Errorf(`cannot load file: %s`, errmsg)
383}
384
385func hasMeta(path string) bool {
386	var metaChars string
387	if runtime.GOOS != "windows" {
388		metaChars = `*?[\`
389	} else {
390		metaChars = `*?[`
391	}
392	return strings.ContainsAny(path, metaChars)
393}
394
395func loadInclude(root *Node, path string) (err error) {
396	if hasMeta(filepath.Dir(path)) {
397		return newIncludeError(root, &path, "glob pattern is supported only in file names")
398	}
399	if !hasMeta(path) {
400		var fi os.FileInfo
401		if fi, err = stdOs.Stat(path); err != nil {
402			return newIncludeError(root, &path, err.Error())
403		}
404		if fi.IsDir() {
405			path = filepath.Join(path, "*")
406		}
407	} else {
408		var fi os.FileInfo
409		if fi, err = stdOs.Stat(filepath.Dir(path)); err != nil {
410			return newIncludeError(root, &path, err.Error())
411		}
412		if !fi.IsDir() {
413			return newIncludeError(root, &path, "base path is not a directory")
414		}
415	}
416
417	var paths []string
418	if hasMeta(path) {
419		if paths, err = filepath.Glob(path); err != nil {
420			return newIncludeError(root, nil, err.Error())
421		}
422	} else {
423		paths = append(paths, path)
424	}
425
426	for _, path := range paths {
427		// skip directories
428		var fi os.FileInfo
429		if fi, err = stdOs.Stat(path); err != nil {
430			return newIncludeError(root, &path, err.Error())
431		}
432		if fi.IsDir() {
433			continue
434		}
435		if !filepath.IsAbs(path) {
436			return newIncludeError(root, &path, "relative paths are not supported")
437		}
438
439		var file std.File
440		if file, err = stdOs.Open(path); err != nil {
441			return newIncludeError(root, &path, err.Error())
442		}
443		defer file.Close()
444
445		buf := bytes.Buffer{}
446		if _, err = buf.ReadFrom(file); err != nil {
447			return newIncludeError(root, &path, err.Error())
448		}
449
450		if err = parseConfig(root, buf.Bytes()); err != nil {
451			return newIncludeError(root, &path, err.Error())
452		}
453	}
454	return
455}
456
457func parseConfig(root *Node, data []byte) (err error) {
458	const maxStringLen = 2048
459	var line []byte
460
461	root.level++
462
463	for offset, end, num := 0, 0, 1; end != -1; offset, num = offset+end+1, num+1 {
464		if end = bytes.IndexByte(data[offset:], '\n'); end != -1 {
465			line = bytes.TrimSpace(data[offset : offset+end])
466		} else {
467			line = bytes.TrimSpace(data[offset:])
468		}
469
470		if len(line) > maxStringLen {
471			return fmt.Errorf("cannot parse configuration at line %d: limit of %d bytes is exceeded", num, maxStringLen)
472		}
473
474		if len(line) == 0 || line[0] == '#' {
475			continue
476		}
477
478		if !utf8.ValidString(string(line)) {
479			return fmt.Errorf("cannot parse configuration at line %d: not a valid UTF-8 character found", num)
480		}
481
482		var key, value []byte
483		if key, value, err = parseLine(line); err != nil {
484			return fmt.Errorf("cannot parse configuration at line %d: %s", num, err.Error())
485		}
486		if string(key) == "Include" {
487			if root.level == 10 {
488				return fmt.Errorf("include depth exceeded limits")
489			}
490
491			if err = loadInclude(root, string(value)); err != nil {
492				return
493			}
494		} else {
495			root.add(key, value, num)
496		}
497	}
498	root.level--
499	return nil
500}
501
502// Unmarshal unmarshals input data into specified structure. The input data can be either
503// a byte array ([]byte) with configuration file or interface{} either returned by Marshal
504// or a configuration file Unmarshaled into interface{} variable before.
505// The third is optional 'strict' parameter that forces strict validation of configuration
506// and structure fields (enabled by default). When disabled it will unmarshal part of
507// configuration into incomplete target structures.
508func Unmarshal(data interface{}, v interface{}, args ...interface{}) (err error) {
509	rv := reflect.ValueOf(v)
510	if rv.Kind() != reflect.Ptr || rv.IsNil() {
511		return errors.New("Invalid output parameter")
512	}
513
514	strict := true
515	if len(args) > 0 {
516		var ok bool
517		if strict, ok = args[0].(bool); !ok {
518			return errors.New("Invalid mode parameter")
519		}
520	}
521
522	var root *Node
523	switch u := data.(type) {
524	case nil:
525		root = &Node{
526			Name:   "",
527			used:   false,
528			Nodes:  make([]interface{}, 0),
529			parent: nil,
530			Line:   0}
531	case []byte:
532		root = &Node{
533			Name:   "",
534			used:   false,
535			Nodes:  make([]interface{}, 0),
536			parent: nil,
537			Line:   0}
538
539		if err = parseConfig(root, u); err != nil {
540			return fmt.Errorf("Cannot read configuration: %s", err.Error())
541		}
542	case *Node:
543		root = u
544		root.markUsed(false)
545	default:
546		return errors.New("Invalid input parameter")
547	}
548
549	if !strict {
550		root.markUsed(true)
551	}
552
553	if err = assignValues(v, root); err != nil {
554		return fmt.Errorf("Cannot assign configuration: %s", err.Error())
555	}
556
557	return nil
558}
559
560func Load(filename string, v interface{}) (err error) {
561	var file std.File
562
563	if file, err = stdOs.Open(filename); err != nil {
564		return fmt.Errorf(`cannot open configuration file: %s`, err.Error())
565	}
566	defer file.Close()
567
568	buf := bytes.Buffer{}
569	if _, err = buf.ReadFrom(file); err != nil {
570		return fmt.Errorf("cannot load configuration: %s", err.Error())
571	}
572
573	return Unmarshal(buf.Bytes(), v)
574}
575
576var stdOs std.Os
577
578func init() {
579	stdOs = std.NewOs()
580}
581