package main import ( "errors" "io/ioutil" "os/user" "path" "strings" "time" "dim13.org/acme" "gopkg.in/yaml.v2" ) const ( defKeySize = 2048 defGrace = time.Hour * 24 * 7 // one week keyPath = "private" crtPath = "certs" ) type config struct { Gracetime time.Duration Listen string ListenTLS string BaseDir string KeySize int Directory string Desire []desire Hook map[string]string } type desire struct { Mail string Phone string KeySize int KeyFile string Domain []domain } type domain struct { Gracetime time.Duration Altnames []string KeySize int KeyFile string CrtFile string Webroot string Hook []string } var ( errNoAltNames = errors.New("no altnames specified") errNoMail = errors.New("no mail specified") ) func expandHome(p string) (string, error) { if strings.HasPrefix(p, "~") { usr, err := user.Current() if err != nil { return "", err } return path.Join(usr.HomeDir, p[1:]), nil } return p, nil } func LoadConfig(fname string) (*config, error) { conf, err := ioutil.ReadFile(fname) if err != nil { return nil, err } c := new(config) err = yaml.Unmarshal(conf, c) if err != nil { return nil, err } c.BaseDir, err = expandHome(c.BaseDir) if err != nil { return nil, err } // apply defaults if c.Gracetime == 0 { c.Gracetime = defGrace } if c.KeySize == 0 { c.KeySize = defKeySize } if c.Directory == "" { c.Directory = acme.LE1 } for i, des := range c.Desire { if des.KeySize == 0 { des.KeySize = c.KeySize } if des.Mail == "" { return nil, errNoMail } if des.KeyFile == "" { des.KeyFile = des.Mail + ".key" } des.KeyFile = path.Join(c.BaseDir, keyPath, des.KeyFile) c.Desire[i] = des for i, dom := range des.Domain { if dom.Gracetime == 0 { dom.Gracetime = c.Gracetime } if dom.KeySize == 0 { dom.KeySize = c.KeySize } if len(dom.Altnames) == 0 { return nil, errNoAltNames } dom.Altnames = checkWWW(dom.Altnames) if dom.KeyFile == "" { dom.KeyFile = dom.Altnames[0] + ".key" } if dom.CrtFile == "" { dom.CrtFile = dom.Altnames[0] + ".pem" } dom.KeyFile = path.Join(c.BaseDir, keyPath, dom.KeyFile) dom.CrtFile = path.Join(c.BaseDir, crtPath, dom.CrtFile) des.Domain[i] = dom } } return c, nil } func checkWWW(altnames []string) []string { ch := make(chan string) go func(ch chan string, s []string) { for _, an := range s { if strings.HasPrefix(an, "www.") { ch <- an[4:] } } close(ch) }(ch, altnames) has := func(s string) bool { for _, an := range altnames { if an == s { return true } } return false } for d := range ch { if !has(d) { altnames = append(altnames, d) } } return altnames } func (c config) dump() ([]byte, error) { return yaml.Marshal(c) }