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
This commit is contained in:
Azareal 2019-04-01 15:44:38 +10:00
parent c8a8de95ae
commit 9d321e9f23
5 changed files with 107 additions and 21 deletions

View File

@ -795,6 +795,7 @@ func parseMediaString(data string) (media MediaEmbed, ok bool) {
port := url.Port() port := url.Port()
query := url.Query() query := url.Query()
// TODO: Treat 127.0.0.1 and [::1] as localhost too
var samesite = hostname == "localhost" || hostname == Site.URL var samesite = hostname == "localhost" || hostname == Site.URL
if samesite { if samesite {
hostname = strings.Split(Site.URL, ":")[0] hostname = strings.Split(Site.URL, ":")[0]

View File

@ -27,7 +27,9 @@ type site struct {
Email string Email string
URL string URL string
Host string Host string
LocalHost bool // Used internally, do not modify as it will be overwritten
Port string Port string
PortInt int // Alias for efficiency, do not modify, will be overwritten
EnableSsl bool EnableSsl bool
EnableEmails bool EnableEmails bool
HasProxy 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? 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 MinifyTemplates bool
BuildSlugs bool // TODO: Make this a setting? BuildSlugs bool // TODO: Make this a setting?
ServerCount int
PrimaryServer bool
ServerCount int
DisableLiveTopicList bool DisableLiveTopicList bool
DisableJSAntispam bool DisableJSAntispam bool
@ -140,7 +144,12 @@ func ProcessConfig() (err error) {
Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1) Config.Noavatar = strings.Replace(Config.Noavatar, "{site_url}", Site.URL, -1)
guestAvatar = GuestAvatar{buildNoavatar(0, 200), buildNoavatar(0, 48)} guestAvatar = GuestAvatar{buildNoavatar(0, 200), buildNoavatar(0, 48)}
Site.Host = Site.URL 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, "\\") Site.URL = strings.TrimSuffix(Site.URL, "\\")
Site.URL = strings.TrimSuffix(Site.URL, ":") Site.URL = strings.TrimSuffix(Site.URL, ":")

View File

@ -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. 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. EnableSsl - Determines whether HTTPS is enabled.

View File

@ -696,19 +696,61 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, prepend string) {
counters.AgentViewCounter.Bump(28) 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: Pass the default path or config struct to the router rather than accessing it via a package global
// TODO: SetDefaultPath // TODO: SetDefaultPath
// TODO: GetDefaultPath // TODO: GetDefaultPath
func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Redirect www. requests to the right place var malformedRequest = func() {
if req.Host == "www." + common.Site.Host { 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? // TODO: Abstract the redirect logic?
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
var s string var s string
if common.Site.EnableSsl { if common.Site.EnableSsl {
s = "s" 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 { if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery dest += "?" + req.URL.RawQuery
} }
@ -717,12 +759,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// Deflect malformed requests // Deflect malformed requests
shost := strings.Split(req.Host,":") if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost != common.Site.Host {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost[0] != common.Site.Host || len(shost) > 2 { malformedRequest()
w.WriteHeader(200) // 400
w.Write([]byte(""))
r.DumpRequest(req,"Malformed Request")
counters.AgentViewCounter.Bump(27)
return return
} }
if common.Dev.FullReqLog { if common.Dev.FullReqLog {

View File

@ -475,19 +475,61 @@ func (r *GenRouter) SuspiciousRequest(req *http.Request, prepend string) {
counters.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) 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: Pass the default path or config struct to the router rather than accessing it via a package global
// TODO: SetDefaultPath // TODO: SetDefaultPath
// TODO: GetDefaultPath // TODO: GetDefaultPath
func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Redirect www. requests to the right place var malformedRequest = func() {
if req.Host == "www." + common.Site.Host { 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? // TODO: Abstract the redirect logic?
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
var s string var s string
if common.Site.EnableSsl { if common.Site.EnableSsl {
s = "s" 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 { if len(req.URL.RawQuery) > 0 {
dest += "?" + req.URL.RawQuery dest += "?" + req.URL.RawQuery
} }
@ -496,12 +538,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// Deflect malformed requests // Deflect malformed requests
shost := strings.Split(req.Host,":") if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost != common.Site.Host {
if len(req.URL.Path) == 0 || req.URL.Path[0] != '/' || shost[0] != common.Site.Host || len(shost) > 2 { malformedRequest()
w.WriteHeader(200) // 400
w.Write([]byte(""))
r.DumpRequest(req,"Malformed Request")
counters.AgentViewCounter.Bump({{.AllAgentMap.malformed}})
return return
} }
if common.Dev.FullReqLog { if common.Dev.FullReqLog {