From 9d321e9f235b671d3176529452bede8c17d48a24 Mon Sep 17 00:00:00 2001 From: Azareal Date: Mon, 1 Apr 2019 15:44:38 +1000 Subject: [PATCH] The router now redirects requests to localhost domains with localhost equivalents in the host header which don't quite match the destination to the proper domain. The router now rejects host headers with the wrong port for non-standard ports. The www. redirect now handles non-standard ports properly. The Site.Port configuration setting is now validated on start-up to ensure it's a valid integer. Quickly fixed up the grammar of the Port block in configuration.md --- common/parser.go | 1 + common/site.go | 13 ++++++++-- docs/configuration.md | 2 +- gen_router.go | 56 ++++++++++++++++++++++++++++++++++++------- router_gen/main.go | 56 ++++++++++++++++++++++++++++++++++++------- 5 files changed, 107 insertions(+), 21 deletions(-) diff --git a/common/parser.go b/common/parser.go index 69b5f9e9..2e978e6b 100644 --- a/common/parser.go +++ b/common/parser.go @@ -795,6 +795,7 @@ func parseMediaString(data string) (media MediaEmbed, ok bool) { port := url.Port() query := url.Query() + // TODO: Treat 127.0.0.1 and [::1] as localhost too var samesite = hostname == "localhost" || hostname == Site.URL if samesite { hostname = strings.Split(Site.URL, ":")[0] diff --git a/common/site.go b/common/site.go index 87724994..8e95f7bb 100644 --- a/common/site.go +++ b/common/site.go @@ -27,7 +27,9 @@ type site struct { Email string URL string Host string + LocalHost bool // Used internally, do not modify as it will be overwritten Port string + PortInt int // Alias for efficiency, do not modify, will be overwritten EnableSsl bool EnableEmails bool HasProxy bool @@ -81,7 +83,9 @@ type config struct { DefaultForum int // The forum posts go in by default, this used to be covered by the Uncategorised Forum, but we want to replace it with a more robust solution. Make this a setting? MinifyTemplates bool BuildSlugs bool // TODO: Make this a setting? - ServerCount int + + PrimaryServer bool + ServerCount int DisableLiveTopicList bool DisableJSAntispam bool @@ -140,7 +144,12 @@ func ProcessConfig() (err error) { Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1) guestAvatar = GuestAvatar{buildNoavatar(0, 200), buildNoavatar(0, 48)} Site.Host = Site.URL - if Site.Port != "80" && Site.Port != "443" { + Site.LocalHost = Site.Host == "localhost" || Site.Host == "127.0.0.1" || Site.Host == "::1" + Site.PortInt, err = strconv.Atoi(Site.Port) + if err != nil { + return errors.New("The port must be a valid integer") + } + if Site.PortInt != 80 && Site.PortInt != 443 { Site.URL = strings.TrimSuffix(Site.URL, "/") Site.URL = strings.TrimSuffix(Site.URL, "\\") Site.URL = strings.TrimSuffix(Site.URL, ":") diff --git a/docs/configuration.md b/docs/configuration.md index bce91e23..a2efc9bb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -18,7 +18,7 @@ Email - The email address you want to show up in the From: field when Gosora sen URL - The URL for your site. Please leave out the `http://` or `https://` and the `/` at the end. -Port - The port you want Gosora to listen on. This will usually be 443 for HTTPS and 80 for HTTP. Gosora usually try to bind to both, if you're on HTTPS to redirect users from the HTTP site to the HTTPS one. +Port - The port you want Gosora to listen on. This will usually be 443 for HTTPS and 80 for HTTP. Gosora will try to bind to both, if you're on HTTPS to redirect users from the HTTP site to the HTTPS one. EnableSsl - Determines whether HTTPS is enabled. diff --git a/gen_router.go b/gen_router.go index 68c98afa..30977b31 100644 --- a/gen_router.go +++ b/gen_router.go @@ -696,19 +696,61 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, prepend string) { counters.AgentViewCounter.Bump(28) } +func isLocalHost(host string) bool { + return host=="localhost" || host=="127.0.0.1" || host=="::1" +} + // TODO: Pass the default path or config struct to the router rather than accessing it via a package global // TODO: SetDefaultPath // TODO: GetDefaultPath func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Redirect www. requests to the right place - if req.Host == "www." + common.Site.Host { + var malformedRequest = func() { + w.WriteHeader(200) // 400 + w.Write([]byte("")) + r.DumpRequest(req,"Malformed Request") + counters.AgentViewCounter.Bump(27) + } + + // Split the Host and Port string + var shost, sport string + if req.Host[0]=='[' { + spl := strings.Split(req.Host,"]") + if len(spl) > 2 { + malformedRequest() + return + } + shost = strings.TrimPrefix(spl[0],"[") + sport = strings.TrimPrefix(spl[1],":") + } else { + spl := strings.Split(req.Host,":") + if len(spl) > 2 { + malformedRequest() + return + } + shost = spl[0] + if len(shost)==2 { + sport = spl[1] + } + } + // TODO: Reject requests from non-local IPs, if the site host is set to localhost or a localhost IP + if common.Site.PortInt != 80 && common.Site.PortInt != 443 && sport != common.Site.Port { + malformedRequest() + return + } + + // Redirect www. and local IP requests to the right place + if shost == "www." + common.Site.Host || (common.Site.LocalHost && shost != common.Site.Host && isLocalHost(shost)) { // TODO: Abstract the redirect logic? w.Header().Set("Connection", "close") var s string if common.Site.EnableSsl { s = "s" } - dest := "http"+s+"://" + common.Site.Host + req.URL.Path + var p string + if common.Site.PortInt != 80 && common.Site.PortInt != 443 { + p = ":"+common.Site.Port + } + dest := "http"+s+"://" + common.Site.Host+p + req.URL.Path if len(req.URL.RawQuery) > 0 { dest += "?" + req.URL.RawQuery } @@ -717,12 +759,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // Deflect malformed requests - shost := strings.Split(req.Host,":") - if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost[0] != common.Site.Host || len(shost) > 2 { - w.WriteHeader(200) // 400 - w.Write([]byte("")) - r.DumpRequest(req,"Malformed Request") - counters.AgentViewCounter.Bump(27) + if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost != common.Site.Host { + malformedRequest() return } if common.Dev.FullReqLog { diff --git a/router_gen/main.go b/router_gen/main.go index 23c42dd9..c9517788 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -475,19 +475,61 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, prepend string) { counters.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) } +func isLocalHost(host string) bool { + return host=="localhost" || host=="127.0.0.1" || host=="::1" +} + // TODO: Pass the default path or config struct to the router rather than accessing it via a package global // TODO: SetDefaultPath // TODO: GetDefaultPath func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Redirect www. requests to the right place - if req.Host == "www." + common.Site.Host { + var malformedRequest = func() { + w.WriteHeader(200) // 400 + w.Write([]byte("")) + r.DumpRequest(req,"Malformed Request") + counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}}) + } + + // Split the Host and Port string + var shost, sport string + if req.Host[0]=='[' { + spl := strings.Split(req.Host,"]") + if len(spl) > 2 { + malformedRequest() + return + } + shost = strings.TrimPrefix(spl[0],"[") + sport = strings.TrimPrefix(spl[1],":") + } else { + spl := strings.Split(req.Host,":") + if len(spl) > 2 { + malformedRequest() + return + } + shost = spl[0] + if len(shost)==2 { + sport = spl[1] + } + } + // TODO: Reject requests from non-local IPs, if the site host is set to localhost or a localhost IP + if common.Site.PortInt != 80 && common.Site.PortInt != 443 && sport != common.Site.Port { + malformedRequest() + return + } + + // Redirect www. and local IP requests to the right place + if shost == "www." + common.Site.Host || (common.Site.LocalHost && shost != common.Site.Host && isLocalHost(shost)) { // TODO: Abstract the redirect logic? w.Header().Set("Connection", "close") var s string if common.Site.EnableSsl { s = "s" } - dest := "http"+s+"://" + common.Site.Host + req.URL.Path + var p string + if common.Site.PortInt != 80 && common.Site.PortInt != 443 { + p = ":"+common.Site.Port + } + dest := "http"+s+"://" + common.Site.Host+p + req.URL.Path if len(req.URL.RawQuery) > 0 { dest += "?" + req.URL.RawQuery } @@ -496,12 +538,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } // Deflect malformed requests - shost := strings.Split(req.Host,":") - if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost[0] != common.Site.Host || len(shost) > 2 { - w.WriteHeader(200) // 400 - w.Write([]byte("")) - r.DumpRequest(req,"Malformed Request") - counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}}) + if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost != common.Site.Host { + malformedRequest() return } if common.Dev.FullReqLog {