From 93942d6e52324af4da06d6c2c2ceeca3724f8e00 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Tue, 19 Nov 2024 13:43:30 -0600 Subject: [PATCH] Fixed broken fqdn validation when adding a domain. --- api/graph/schema.resolvers.go | 22 ++--------- cmd/admin/commands.go | 3 ++ values.go | 69 ++++++++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index d576c09..726e558 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -39,7 +39,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" - "netlandish.com/x/gobwebs-oauth2" + oauth2 "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" "netlandish.com/x/gobwebs/crypto" "netlandish.com/x/gobwebs/database" @@ -1723,10 +1723,6 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu lang := links.GetLangFromRequest(server.EchoForContext(ctx).Request(), user) lt := localizer.GetLocalizer(lang) - domRegex, err := regexp.Compile(links.DomainPattern) - if err != nil { - return nil, fmt.Errorf(lt.Translate("Domain pattern regexp compile error")) - } validator := valid.New(ctx) host, err := idna.Lookup.ToASCII(input.LookupName) @@ -1747,7 +1743,7 @@ func (r *mutationResolver) AddDomain(ctx context.Context, input model.DomainInpu lt.Translate("Name may not exceed 500 characters")). WithField("lookupName"). WithCode(valid.ErrValidationCode) - validator.Expect(domRegex.MatchString(host), + validator.Expect(links.IsDomainName(host), lt.Translate("Invalid FQDN format")). WithField("lookupName"). WithCode(valid.ErrValidationCode) @@ -3694,11 +3690,6 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin return nil, nil } - domRegex, err := regexp.Compile(links.DomainPattern) - if err != nil { - return nil, fmt.Errorf(lt.Translate("Domain pattern regexp compile error")) - } - host, err := idna.Lookup.ToASCII(input.LookupName) validator.Expect(err == nil, @@ -3728,7 +3719,7 @@ func (r *mutationResolver) AddAdminDomain(ctx context.Context, input model.Admin lt.Translate("Name may not exceed 500 characters")). WithField("lookupName"). WithCode(valid.ErrValidationCode) - validator.Expect(domRegex.MatchString(host), + validator.Expect(links.IsDomainName(host), lt.Translate("Invalid FQDN format")). WithField("lookupName"). WithCode(valid.ErrValidationCode) @@ -3871,11 +3862,6 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up return nil, nil } - domRegex, err := regexp.Compile(links.DomainPattern) - if err != nil { - return nil, fmt.Errorf(lt.Translate("Domain pattern regexp compile error")) - } - host, err := idna.Lookup.ToASCII(input.LookupName) validator.Expect(err == nil, @@ -3909,7 +3895,7 @@ func (r *mutationResolver) UpdateAdminDomain(ctx context.Context, input model.Up lt.Translate("Name may not exceed 500 characters")). WithField("lookupName"). WithCode(valid.ErrValidationCode) - validator.Expect(domRegex.MatchString(input.LookupName), + validator.Expect(links.IsDomainName(input.LookupName), lt.Translate("Invalid FQDN format")). WithField("lookupName"). WithCode(valid.ErrValidationCode) diff --git a/cmd/admin/commands.go b/cmd/admin/commands.go index 217eb52..674679a 100644 --- a/cmd/admin/commands.go +++ b/cmd/admin/commands.go @@ -52,6 +52,9 @@ func domainValidation(dom, ser string, config *config.Config, domRegex *regexp.R if err != nil { return "", fmt.Errorf("Invalid %s domain", ser) } + // Leaving the regexp check because custom deployments may use internal name scheming. + // Otherwise we'd use links.IsDomainName. In this case, for admin setup script, + // using the regexp is the best choice. if !domRegex.MatchString(host) { return "", fmt.Errorf("Invalid %s FQDN format", ser) diff --git a/values.go b/values.go index b16c2c1..97a2b96 100644 --- a/values.go +++ b/values.go @@ -31,7 +31,7 @@ const ( EmailPattern string = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` // DomainPattern ... - DomainPattern string = `^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$` + DomainPattern string = `^((([a-z0-9][a-z0-9\-]*[a-z0-9])|[a-z0-9])\.?)+$` ) const ( @@ -102,3 +102,70 @@ const ( FilterYearToDay FilterLast12Months ) + +// Taken from https://go.dev/src/net/dnsclient.go to avoid using +// linkname hack. That is obviously discouraged as noted in the source +// comments. Also for our case, we want to ensure at least one dot ('.') is +// included in the given domain name. +func IsDomainName(s string) bool { + // The root domain name is valid. See golang.org/issue/45715. + if s == "." { + return true + } + + // See RFC 1035, RFC 3696. + // Presentation format has dots before every label except the first, and the + // terminal empty label is optional here because we assume fully-qualified + // (absolute) input. We must therefore reserve space for the first and last + // labels' length octets in wire format, where they are necessary and the + // maximum total length is 255. + // So our _effective_ maximum is 253, but 254 is not rejected if the last + // character is a dot. + + l := len(s) + if l == 0 || l > 254 || l == 254 && s[l-1] != '.' { + return false + } + + last := byte('.') + nonNumeric := false // true once we've seen a letter or hyphen + hasDot := false + partlen := 0 + for i := 0; i < len(s); i++ { + c := s[i] + switch { + default: + return false + case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_': + nonNumeric = true + partlen++ + case '0' <= c && c <= '9': + // fine + partlen++ + case c == '-': + // Byte before dash cannot be dot. + if last == '.' { + return false + } + partlen++ + nonNumeric = true + case c == '.': + // Byte before dot cannot be dot, dash. + if last == '.' || last == '-' { + return false + } + if partlen > 63 || partlen == 0 { + return false + } + partlen = 0 + hasDot = true + } + last = c + } + + if last == '-' || partlen > 63 || !hasDot { + return false + } + + return nonNumeric +} -- 2.45.2