package service import ( "encoding/json" "errors" "fmt" "log" "net" "os" "path" "github.com/spf13/viper" "golang.org/x/crypto/bcrypt" ) type UnauthorizedError string func (uae UnauthorizedError) Error() string { return "Invalid user or password" } type UserConfig struct { db *viper.Viper UserName string PassWord string Domain string Router UserConfigRouter Others []UserConfigOther } type UserConfigRouter struct { Hostname string NFT UserConfigNFT } type UserConfigOther struct { Hostname string V6IID string RegisterV4 bool NFT UserConfigNFT } type UserConfigNFT struct { Table string Set4 string Set6 string } func LoadConfigFile(configFile string) (*UserConfig, error) { configFile = path.Clean(configFile) log.Printf("Trying to load config file %s", configFile) if _, err := os.Stat(configFile); err != nil { log.Printf("cannot stat the file %s: %v", configFile, err) return nil, UnauthorizedError("cannot stat") } v := viper.New() v.SetConfigFile(configFile) err := v.ReadInConfig() if err != nil { return nil, fmt.Errorf("failed to parse config file %s: %v", configFile, err) } result := &UserConfig{db: v} err = result.db.Unmarshal(result) if err != nil { return nil, fmt.Errorf("failed to unmarshal config file %s: %v", configFile, err) } err = result.Validate() if err != nil { return nil, fmt.Errorf("failed to parse config: %v", err) } return result, nil } func LoadConfigForUser(username string, password string) (*UserConfig, error) { configFile := fmt.Sprintf("%s/%s.yml", C.Users.ConfigDir, username) result, err := LoadConfigFile(configFile) if err != nil { return nil, err } err = result.Authenticate(password) if err != nil { log.Printf("Failed to check password") return nil, UnauthorizedError("pwcheck failed") } return result, nil } func HashPassword(pw []byte) (string, error) { hash, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost) if err != nil { return "", fmt.Errorf("failed to create password hash: %v", err) } return string(hash), nil } func (uc *UserConfig) Authenticate(pwToCheck string) error { hashedPassword := []byte(uc.PassWord) bytePwToCheck := []byte(pwToCheck) err := bcrypt.CompareHashAndPassword(hashedPassword, bytePwToCheck) return err } func (uco *UserConfigOther) ConvertIIDToAddress(localNet *net.IPNet) net.IP { if localNet == nil { return nil } out := make(net.IP, net.IPv6len) ipiid := net.ParseIP(uco.V6IID) for i := 0; i < net.IPv6len; i++ { // We take the corresponding byte from the IID and mask it out with the // inversed Mask of the network we got (in essence a Host Mask). This // leaves us those bits, that are not taken by the netmask, so that we // can OR all this together. maskedIID := ipiid[i] &^ localNet.Mask[i] out[i] = localNet.IP[i] | maskedIID } return out } func (ucn *UserConfigNFT) ValidateSetNames() error { if ucn.Set4 == "" && ucn.Set6 == "" { return nil } if ucn.Table == "" { return errors.New("NFT table name undefined") } if ucn.Set4 == ucn.Set6 { return errors.New("set4 and set6 are identical") } return nil } func (uc *UserConfig) Validate() error { if err := uc.Router.NFT.ValidateSetNames(); err != nil { return fmt.Errorf("router NFT validation failed: %v", err) } if uc.Router.Hostname == "" { return errors.New("router record has no Hostname") } dnsnames := make(map[string]bool) dnsnames[uc.Router.Hostname] = true for i, other := range uc.Others { if other.Hostname == "" { return fmt.Errorf("other record #%d has no Hostname", i) } if dnsnames[other.Hostname] { return fmt.Errorf("the Hostname %s is used twice", other.Hostname) } dnsnames[other.Hostname] = true if err := other.NFT.ValidateSetNames(); err != nil { return fmt.Errorf("other %s NFT validation failed: %v", other.Hostname, err) } if other.V6IID == "" { return fmt.Errorf("other record %s has no V6IID", other.Hostname) } iidIP := net.ParseIP(other.V6IID) if iidIP == nil { return fmt.Errorf("other record %s has invalid V6IID %s", other.Hostname, other.V6IID) } if iidIP.To4() != nil { return fmt.Errorf("other record %s IID looks like an IPv4 Address", other.Hostname) } } return nil } func (uc *UserConfig) GetStateFileName() string { return fmt.Sprintf("%s/%s.yml", C.Users.StateDir, uc.UserName) } func (uc *UserConfig) PrettyPrint() string { s, err := json.MarshalIndent(uc, "", " ") if err != nil { log.Fatalf("Failed to pretty print UpdateConfig via JSON: %v", err) } return string(s) }