197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
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
|
|
}
|