diff --git a/irc/client.go b/irc/client.go index a290adf4..6a342a51 100644 --- a/irc/client.go +++ b/irc/client.go @@ -329,22 +329,19 @@ func (server *Server) RunClient(conn IRCConn) { for _, defaultMode := range config.Accounts.defaultUserModes { client.SetMode(defaultMode, true) } + if proxiedConn.Secure { + client.SetMode(modes.TLS, true) + } if proxiedConn.Config.TLSConfig != nil { - client.SetMode(modes.TLS, true) // error is not useful to us here anyways so we can ignore it session.certfp, _ = utils.GetCertFP(proxiedConn.Conn, RegisterTimeout) } if session.isTor { - client.SetMode(modes.TLS, true) session.rawHostname = config.Server.TorListeners.Vhost client.rawHostname = session.rawHostname } else { - if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) { - // treat local connections as secure (may be overridden later by WEBIRC) - client.SetMode(modes.TLS, true) - } if config.Server.CheckIdent { client.doIdentLookup(proxiedConn.Conn) } diff --git a/irc/listeners.go b/irc/listeners.go index c6e6054f..bc01c0aa 100644 --- a/irc/listeners.go +++ b/irc/listeners.go @@ -88,13 +88,6 @@ func (nl *NetListener) Stop() error { return nl.listener.Close() } -// ensure that any IP we got from the PROXY line is trustworthy (otherwise, clear it) -func validateProxiedIP(conn *utils.WrappedConn, config *Config) { - if !utils.IPInNets(utils.AddrToIP(conn.RemoteAddr()), config.Server.proxyAllowedFromNets) { - conn.ProxiedIP = nil - } -} - func (nl *NetListener) serve() { for { conn, err := nl.listener.Accept() @@ -103,9 +96,7 @@ func (nl *NetListener) serve() { // hand off the connection wConn, ok := conn.(*utils.WrappedConn) if ok { - if wConn.ProxiedIP != nil { - validateProxiedIP(wConn, nl.server.Config()) - } + confirmProxyData(wConn, "", "", "", nl.server.Config()) go nl.server.RunClient(NewIRCStreamConn(wConn)) } else { nl.server.logger.Error("internal", "invalid connection type", nl.addr) @@ -159,8 +150,9 @@ func (wl *WSListener) Stop() error { func (wl *WSListener) handle(w http.ResponseWriter, r *http.Request) { config := wl.server.Config() - proxyAllowedFrom := config.Server.proxyAllowedFromNets - proxiedIP := utils.HandleXForwardedFor(r.RemoteAddr, r.Header.Get("X-Forwarded-For"), proxyAllowedFrom) + remoteAddr := r.RemoteAddr + xff := r.Header.Get("X-Forwarded-For") + xfp := r.Header.Get("X-Forwarded-Proto") wsUpgrader := websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { @@ -192,18 +184,39 @@ func (wl *WSListener) handle(w http.ResponseWriter, r *http.Request) { conn.Close() return } - if wConn.ProxiedIP != nil { - validateProxiedIP(wConn, config) - } else { - // if there was no PROXY protocol IP, use the validated X-Forwarded-For IP instead, - // unless it is redundant - if proxiedIP != nil && !proxiedIP.Equal(utils.AddrToIP(wConn.RemoteAddr())) { - wConn.ProxiedIP = proxiedIP - } - } + + confirmProxyData(wConn, remoteAddr, xff, xfp, config) // avoid a DoS attack from buffering excessively large messages: conn.SetReadLimit(maxReadQBytes) go wl.server.RunClient(NewIRCWSConn(conn)) } + +// validate conn.ProxiedIP and conn.Secure against config, HTTP headers, etc. +func confirmProxyData(conn *utils.WrappedConn, remoteAddr, xForwardedFor, xForwardedProto string, config *Config) { + if conn.ProxiedIP != nil { + if !utils.IPInNets(utils.AddrToIP(conn.RemoteAddr()), config.Server.proxyAllowedFromNets) { + conn.ProxiedIP = nil + } + } else if xForwardedFor != "" { + proxiedIP := utils.HandleXForwardedFor(remoteAddr, xForwardedFor, config.Server.proxyAllowedFromNets) + // don't set proxied IP if it is redundant with the actual IP + if proxiedIP != nil && !proxiedIP.Equal(utils.AddrToIP(conn.RemoteAddr())) { + conn.ProxiedIP = proxiedIP + } + } + + if conn.Config.TLSConfig != nil || conn.Config.Tor { + // we terminated our own encryption: + conn.Secure = true + } else if !conn.Config.WebSocket { + // plaintext normal connection: loopback and secureNets are secure + realIP := utils.AddrToIP(conn.RemoteAddr()) + conn.Secure = realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) + } else { + // plaintext websocket: trust X-Forwarded-Proto from a trusted source + conn.Secure = utils.IPInNets(utils.AddrToIP(conn.RemoteAddr()), config.Server.proxyAllowedFromNets) && + xForwardedProto == "https" + } +} diff --git a/irc/utils/proxy.go b/irc/utils/proxy.go index 805d06a8..1214530d 100644 --- a/irc/utils/proxy.go +++ b/irc/utils/proxy.go @@ -93,6 +93,9 @@ type WrappedConn struct { net.Conn ProxiedIP net.IP Config ListenerConfig + // Secure indicates whether we believe the connection between us and the client + // was secure against interception and modification (including all proxies): + Secure bool } // ReloadableListener is a wrapper for net.Listener that allows reloading