package conf import ( `encoding/json` `encoding/xml` `hash` `os` `github.com/BurntSushi/toml` `github.com/creasty/defaults` `github.com/davecgh/go-spew/spew` `github.com/goccy/go-yaml` `github.com/zeebo/blake3` `r00t2.io/gobroke/tplCmd` `r00t2.io/goutils/logging` `r00t2.io/sysutils/paths` ) /* Checksum guarantees a standard checksum of a configuration. If you are comparing new vs. old configs, be sure to use this function and compare against a Config.Checksum() to ensure consistent hashing algorithms etc. */ func Checksum(confBytes []byte) (cksum []byte, err error) { // If built with CGO enabled, this'll take advantage of SIMD. // Should be fast enough without it, though. var h hash.Hash = blake3.New() if _, err = h.Write(confBytes); err != nil { return } cksum = h.Sum(nil) return } /* ChecksumPath is a convenience wrapper around Checksum, operating on a filepath instead of bytes. */ func ChecksumPath(confPath string) (cksum []byte, err error) { var b []byte if err = paths.RealPath(&confPath); err != nil { return } if b, err = os.ReadFile(confPath); err != nil { return } if cksum, err = Checksum(b); err != nil { return } return } // NewConfig returns a conf.Config from filepath path. func NewConfig(path string, debug bool, log logging.Logger) (cfg *Config, err error) { var b []byte if log == nil { log = &logging.NullLogger{} } log.Debug("conf.NewConfig: New config from path '%s'", path) if err = paths.RealPath(&path); err != nil { log.Err("conf.NewConfig: Received error canonizing config path '%s': %s", path, err) return } log.Debug("conf.NewConfig: Canonized configuration path to '%s'", path) if cfg, err = NewConfigFromBytes(b, debug, log); err != nil { log.Err("conf.NewConfig: Received error parsing config '%s': %s", path, err) return } cfg.confPath = new(string) *cfg.confPath = path log.Debug("conf.NewConfig: Successfully parsed and loaded configuration '%s'", path) return } // NewConfigFromBytes returns a conf.Config from bytes b. b may be a JSON, TOML, XML, or YAML representation. func NewConfigFromBytes(b []byte, debug bool, log logging.Logger) (cfg *Config, err error) { var tplBytes []byte if b == nil || len(b) == 0 { return } if log == nil { log = &logging.NullLogger{} } log.Debug("conf.NewConfigFromBytes: New config from %d bytes.", len(b)) log.Debug("conf.NewConfigFromBytes: Config before parsing:\n%s", string(b)) if err = json.Unmarshal(b, &cfg); err != nil { if err = xml.Unmarshal(b, &cfg); err != nil { if err = yaml.Unmarshal(b, &cfg); err != nil { if err = toml.Unmarshal(b, &cfg); err != nil { log.Err("conf.NewConfigFromBytes: Unable to parse config as JSON, XML, YAML, or TOML; config invalid.") err = ErrUnkownSyntax return } else { log.Debug("conf.NewConfigFromBytes: Config parsed as TOML.") } } else { log.Debug("conf.NewConfigFromBytes: Config parsed as YAML.") } } else { log.Debug("conf.NewConfigFromBytes: Config parsed as XML.") } } else { log.Debug("conf.NewConfigFromBytes: Config parsed as JSON.") } if err = defaults.Set(cfg); err != nil { return } log.Debug("conf.NewConfigFromBytes: Configuration after parsing and defaults:\n%s", spew.Sdump(cfg)) if err = validate.Struct(cfg); err != nil { log.Err("conf.NewConfigFromBytes: Config validation failed: %v", err) return } cfg.log = log cfg.debug = debug for _, t := range cfg.Tunnels { if t == nil { continue } t.cfg = cfg if t.Username == nil { if cfg.Username == nil { log.Err( "conf.NewConfigFromBytes: Username is not provided for tunnel %d and no default username was provided.", t.TunnelID, ) err = ErrMissingUser return } else { t.Username = cfg.Username } } if t.TemplateConfigs != nil && len(t.TemplateConfigs) > 0 { for _, tpl := range t.TemplateConfigs { if tpl == nil { continue } if err = paths.RealPath(&tpl.Template); err != nil { log.Err("conf.NewConfigFromBytes: Unable to canonize path to template '%s': %s", tpl.Template, err) return } if err = paths.RealPath(&tpl.Dest); err != nil { log.Err("conf.NewConfigFromBytes: Unable to canonize path to destination '%s': %s", tpl.Dest, err) return } if tplBytes, err = os.ReadFile(tpl.Template); err != nil { log.Err("conf.NewConfigFromBytes: Unable to read template file '%s': %s", tpl.Template, err) return } tpl.Tpl = tplCmd.GetTpl() if _, err = tpl.Tpl.Parse(string(tplBytes)); err != nil { log.Err("conf.NewConfigFromBytes: Unable to parse template file '%s': %s", tpl.Template, err) return } if tpl.Perms != nil { if err = tpl.Perms.SetMissing(); err != nil { log.Err("conf.NewConfigFromBytes: Unable to enrich permissions for template '%s': %s", tpl.Template, err) return } } } } } if cfg.cksum, err = Checksum(b); err != nil { log.Err("conf.NewConfigFromBytes: Error calculating checksum: %s", err) return } log.Debug("conf.NewConfigFromBytes: Successfully parsed and loaded configuration.") return }