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