package main import ( "errors" "io/ioutil" "os" "os/user" "path" "strings" "time" "dim13.org/acme" "gopkg.in/yaml.v2" ) const ( defKeySize = 2048 defGrace = time.Hour * 24 * 7 keyPath = "private" crtPath = "certs" ) type Config struct { Gracetime time.Duration Listen string ListenTLS string BaseDir string KeySize int Provider []provider Hook map[string]string } type provider struct { Directory string Account []account } type account struct { Mail string Phone string KeySize int KeyFile string Domain []domain } type domain struct { Altnames []string KeySize int KeyFile string CrtFile string Webroot string Hook []string } var ( errNoProvider = errors.New("no provider specified") errNoAccount = errors.New("no account specified") errNoKey = errors.New("no key file specified") errNoCrt = errors.New("no crt file specified") errNoAltNames = errors.New("no altnames specified") errNoMail = errors.New("no mail specified") ) func DumpYAML(c *Config) (string, error) { out, err := yaml.Marshal(c) return string(out), err } /* func DumpTOML(c *Config) (string, error) { buf := &bytes.Buffer{} err := toml.NewEncoder(buf).Encode(c) return buf.String(), err } */ func LoadConfig(fname string) (*Config, error) { conf, err := ioutil.ReadFile(fname) if err != nil { return nil, err } c := &Config{} err = yaml.Unmarshal(conf, c) if err != nil { return nil, err } if strings.HasPrefix(c.BaseDir, "~") { usr, err := user.Current() if err != nil { return nil, err } c.BaseDir = path.Join(usr.HomeDir, c.BaseDir[1:]) } // apply defaults if c.Gracetime == 0 { c.Gracetime = defGrace } if c.KeySize == 0 { c.KeySize = defKeySize } for i, pro := range c.Provider { if pro.Directory == "" { pro.Directory = acme.LE1 } c.Provider[i] = pro for i, acc := range pro.Account { if acc.KeySize == 0 { acc.KeySize = c.KeySize } if acc.Mail == "" { return nil, errNoMail } if acc.KeyFile == "" { return nil, errNoKey } if c.BaseDir != "" { acc.KeyFile = path.Join(c.BaseDir, acc.KeyFile) } pro.Account[i] = acc for i, dom := range acc.Domain { if dom.KeySize == 0 { dom.KeySize = c.KeySize } if len(dom.Altnames) == 0 { return nil, errNoAltNames } dom.Altnames = checkWWW(dom.Altnames) d := dom.Altnames[0] if dom.KeyFile == "" { dom.KeyFile = path.Join(keyPath, replace(d)+".key") } if dom.CrtFile == "" { dom.CrtFile = path.Join(crtPath, replace(d)+".crt") } if c.BaseDir != "" { dom.KeyFile = path.Join(c.BaseDir, dom.KeyFile) dom.CrtFile = path.Join(c.BaseDir, dom.CrtFile) } acc.Domain[i] = dom } } } return c, nil } func mkdirs(c *Config) error { key := keyPath crt := crtPath if c.BaseDir != "" { key = path.Join(c.BaseDir, keyPath) crt = path.Join(c.BaseDir, crtPath) } err := os.MkdirAll(key, 0700) if err != nil { return err } return os.MkdirAll(crt, 0755) } func replace(s string) string { return strings.Replace(s, ".", "_", -1) } 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 }