From 3480f124cd8f4068613389cf19cd59400b658689 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Tue, 17 Dec 2019 15:10:23 -0500 Subject: [PATCH] fix #688 --- irc/client.go | 77 ++++++++++++++++++++++++++++++++++++++++++++---- irc/config.go | 36 ++++++++++++++-------- irc/gateways.go | 8 +---- irc/getters.go | 8 ----- irc/server.go | 16 ++++++---- irc/utils/net.go | 19 ++++-------- oragono.yaml | 8 +++++ 7 files changed, 118 insertions(+), 54 deletions(-) diff --git a/irc/client.go b/irc/client.go index b67e78b1..30d8c22c 100644 --- a/irc/client.go +++ b/irc/client.go @@ -259,11 +259,10 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) { // cover up details of the tor proxying infrastructure (not a user privacy concern, // but a hardening measure): session.proxiedIP = utils.IPv4LoopbackAddress + client.proxiedIP = session.proxiedIP session.rawHostname = config.Server.TorListeners.Vhost + client.rawHostname = session.rawHostname } else { - // set the hostname for this client (may be overridden later by PROXY or WEBIRC) - session.rawHostname = utils.LookupHostname(session.realIP.String()) - client.cloakedHostname = config.Server.Cloaks.ComputeCloak(session.realIP) remoteAddr := conn.Conn.RemoteAddr() if utils.AddrIsLocal(remoteAddr) { // treat local connections as secure (may be overridden later by WEBIRC) @@ -274,13 +273,71 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) { } } client.realIP = session.realIP - client.rawHostname = session.rawHostname - client.proxiedIP = session.proxiedIP server.stats.Add() client.run(session, proxyLine) } +// resolve an IP to an IRC-ready hostname, using reverse DNS, forward-confirming if necessary, +// and sending appropriate notices to the client +func (client *Client) lookupHostname(session *Session, overwrite bool) { + if client.isTor { + return + } // else: even if cloaking is enabled, look up the real hostname to show to operators + + config := client.server.Config() + ip := session.realIP + if session.proxiedIP != nil { + ip = session.proxiedIP + } + ipString := ip.String() + + var hostname, candidate string + if config.Server.lookupHostnames { + session.Notice("*** Looking up your hostname...") + + names, err := net.LookupAddr(ipString) + if err == nil && 0 < len(names) { + candidate = strings.TrimSuffix(names[0], ".") + } + if utils.IsHostname(candidate) { + if config.Server.ForwardConfirmHostnames { + addrs, err := net.LookupHost(candidate) + if err == nil { + for _, addr := range addrs { + if addr == ipString { + hostname = candidate // successful forward confirmation + break + } + } + } + } else { + hostname = candidate + } + } + } + + if hostname != "" { + session.Notice("*** Found your hostname") + } else { + if config.Server.lookupHostnames { + session.Notice("*** Couldn't look up your hostname") + } + hostname = utils.IPStringToHostname(ipString) + } + + session.rawHostname = hostname + // update the hostname if this is a new connection or a resume, but not if it's a reattach + if overwrite || client.rawHostname == "" { + cloakedHostname := config.Server.Cloaks.ComputeCloak(ip) + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + client.rawHostname = hostname + client.cloakedHostname = cloakedHostname + client.updateNickMaskNoMutex() + } +} + func (client *Client) doIdentLookup(conn net.Conn) { _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) if err != nil { @@ -617,7 +674,7 @@ func (session *Session) playResume() { details := client.Details() oldNickmask := details.nickMask - client.SetRawHostname(session.rawHostname) + client.lookupHostname(session, true) hostname := client.Hostname() // may be a vhost timestampString := timestamp.Format(IRCv3TimestampFormat) @@ -887,6 +944,10 @@ func (client *Client) updateNickMaskNoMutex() { } } + if client.hostname == "" { + return // pre-registration, don't bother generating the hostname + } + cfhostname, err := Casefold(client.hostname) if err != nil { client.server.logger.Error("internal", "hostname couldn't be casefolded", client.hostname, err.Error()) @@ -1255,6 +1316,10 @@ func (client *Client) Notice(text string) { client.Send(nil, client.server.name, "NOTICE", client.Nick(), text) } +func (session *Session) Notice(text string) { + session.Send(nil, session.client.server.name, "NOTICE", session.client.Nick(), text) +} + func (client *Client) addChannel(channel *Channel) { client.stateMutex.Lock() client.channels[channel] = true diff --git a/irc/config.go b/irc/config.go index c1d27983..09f1d588 100644 --- a/irc/config.go +++ b/irc/config.go @@ -293,19 +293,22 @@ type Config struct { Listen []string TLSListeners map[string]TLSListenConfig `yaml:"tls-listeners"` // either way, the result is this: - trueListeners map[string]listenerConfig - STS STSConfig - CheckIdent bool `yaml:"check-ident"` - MOTD string - motdLines []string - MOTDFormatting bool `yaml:"motd-formatting"` - ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` - proxyAllowedFromNets []net.IPNet - WebIRC []webircConfig `yaml:"webirc"` - MaxSendQString string `yaml:"max-sendq"` - MaxSendQBytes int - AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` - Compatibility struct { + trueListeners map[string]listenerConfig + STS STSConfig + LookupHostnames *bool `yaml:"lookup-hostnames"` + lookupHostnames bool + ForwardConfirmHostnames bool `yaml:"forward-confirm-hostnames"` + CheckIdent bool `yaml:"check-ident"` + MOTD string + motdLines []string + MOTDFormatting bool `yaml:"motd-formatting"` + ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + proxyAllowedFromNets []net.IPNet + WebIRC []webircConfig `yaml:"webirc"` + MaxSendQString string `yaml:"max-sendq"` + MaxSendQBytes int + AllowPlaintextResume bool `yaml:"allow-plaintext-resume"` + Compatibility struct { ForceTrailing *bool `yaml:"force-trailing"` forceTrailing bool SendUnprefixedSasl bool `yaml:"send-unprefixed-sasl"` @@ -635,6 +638,13 @@ func LoadConfig(filename string) (config *Config, err error) { // set this even if STS is disabled config.Server.capValues[caps.STS] = config.Server.STS.Value() + // lookup-hostnames defaults to true if unset + if config.Server.LookupHostnames != nil { + config.Server.lookupHostnames = *config.Server.LookupHostnames + } else { + config.Server.lookupHostnames = true + } + // process webirc blocks var newWebIRC []webircConfig for _, webirc := range config.Server.WebIRC { diff --git a/irc/gateways.go b/irc/gateways.go index 5d1ddff6..ac94c654 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -76,18 +76,12 @@ func (client *Client) ApplyProxiedIP(session *Session, proxiedIP string, tls boo client.server.connectionLimiter.RemoveClient(session.realIP) // given IP is sane! override the client's current IP - ipstring := parsedProxiedIP.String() - client.server.logger.Info("localconnect-ip", "Accepted proxy IP for client", ipstring) - rawHostname := utils.LookupHostname(ipstring) - cloakedHostname := client.server.Config().Server.Cloaks.ComputeCloak(parsedProxiedIP) + client.server.logger.Info("localconnect-ip", "Accepted proxy IP for client", parsedProxiedIP.String()) client.stateMutex.Lock() defer client.stateMutex.Unlock() client.proxiedIP = parsedProxiedIP - client.rawHostname = rawHostname session.proxiedIP = parsedProxiedIP - session.rawHostname = rawHostname - client.cloakedHostname = cloakedHostname // nickmask will be updated when the client completes registration // set tls info client.certfp = "" diff --git a/irc/getters.go b/irc/getters.go index 7fe7d08a..6bbd6ed8 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -239,14 +239,6 @@ func (client *Client) RawHostname() (result string) { return } -func (client *Client) SetRawHostname(rawHostname string) { - client.stateMutex.Lock() - defer client.stateMutex.Unlock() - - client.rawHostname = rawHostname - client.updateNickMaskNoMutex() -} - func (client *Client) AwayMessage() (result string) { client.stateMutex.RLock() result = client.awayMessage diff --git a/irc/server.go b/irc/server.go index 9724632f..5c56ac4d 100644 --- a/irc/server.go +++ b/irc/server.go @@ -377,12 +377,9 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { return } - // check KLINEs - isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...) - if isBanned { - c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil) - return true - } + // we have nickname, username, and the final value of the IP address: + // do the hostname lookup and set the nickmask + session.client.lookupHostname(session, false) if session.client != c { // reattached, bail out. @@ -392,6 +389,13 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { return } + // check KLINEs + isBanned, info := server.klines.CheckMasks(c.AllNickmasks()...) + if isBanned { + c.Quit(info.BanMessage(c.t("You are banned from this server (%s)")), nil) + return true + } + // registration has succeeded: c.SetRegistered() diff --git a/irc/utils/net.go b/irc/utils/net.go index d3a4de2e..c5c98153 100644 --- a/irc/utils/net.go +++ b/irc/utils/net.go @@ -40,22 +40,13 @@ func AddrIsUnix(addr net.Addr) bool { return ok } -// LookupHostname returns the hostname for `addr` if it has one. Otherwise, just returns `addr`. -func LookupHostname(addr string) string { - names, err := net.LookupAddr(addr) - if err == nil && len(names) > 0 { - candidate := strings.TrimSuffix(names[0], ".") - if IsHostname(candidate) { - return candidate - } - } - - // return original address if no hostname found - if len(addr) > 0 && addr[0] == ':' { +// IPStringToHostname converts a string representation of an IP address to an IRC-ready hostname +func IPStringToHostname(ipStr string) string { + if 0 < len(ipStr) && ipStr[0] == ':' { // fix for IPv6 hostnames (so they don't start with a colon), same as all other IRCds - addr = "0" + addr + ipStr = "0" + ipStr } - return addr + return ipStr } var allowedHostnameChars = "abcdefghijklmnopqrstuvwxyz1234567890-." diff --git a/oragono.yaml b/oragono.yaml index 0f9fe891..1f2d9ef4 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -85,6 +85,14 @@ server: # should clients include this STS policy when they ship their inbuilt preload lists? preload: false + # whether to look up user hostnames with reverse DNS + # (to suppress this for privacy purposes, use the ip-cloaking options below) + lookup-hostnames: true + # whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for + # any hostname returned from reverse DNS, resolve it back to an IP address and reject it + # unless it matches the connecting IP + forward-confirm-hostnames: true + # use ident protocol to get usernames check-ident: false