diff --git a/irc/client.go b/irc/client.go index 245e64cd..d3ce14bc 100644 --- a/irc/client.go +++ b/irc/client.go @@ -246,6 +246,8 @@ func (client *Client) run() { // (may be overridden by a later PROXY command from stunnel) client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr()) + firstLine := true + for { maxlenTags, maxlenRest := client.recomputeMaxlens() @@ -259,7 +261,20 @@ func (client *Client) run() { break } - client.server.logger.Debug("userinput ", client.nick, "<- ", line) + client.server.logger.Debug("userinput", client.nick, "<- ", line) + + // special-cased handling of PROXY protocol, see `handleProxyCommand` for details: + if firstLine { + firstLine = false + if strings.HasPrefix(line, "PROXY") { + err = handleProxyCommand(client.server, client, line) + if err != nil { + break + } else { + continue + } + } + } msg, err = ircmsg.ParseLineMaxLen(line, maxlenTags, maxlenRest) if err == ircmsg.ErrorLineIsEmpty { diff --git a/irc/commands.go b/irc/commands.go index 02e969e1..f31b5c54 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -215,11 +215,6 @@ func init() { handler: privmsgHandler, minParams: 2, }, - "PROXY": { - handler: proxyHandler, - usablePreReg: true, - minParams: 5, - }, "RENAME": { handler: renameHandler, minParams: 2, diff --git a/irc/gateways.go b/irc/gateways.go index 3870ef53..10ec5f89 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -6,13 +6,20 @@ package irc import ( + "errors" "fmt" "net" + "strings" "github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/utils" ) +var ( + errBadGatewayAddress = errors.New("PROXY/WEBIRC commands are not accepted from this IP address") + errBadProxyLine = errors.New("Invalid PROXY/WEBIRC command") +) + type webircConfig struct { PasswordString string `yaml:"password"` Password []byte `yaml:"password-bytes"` @@ -57,22 +64,29 @@ func isGatewayAllowed(addr net.Addr, gatewaySpec string) bool { } // ApplyProxiedIP applies the given IP to the client. -func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool) { +func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool) { // ensure IP is sane parsedProxiedIP := net.ParseIP(proxiedIP) if parsedProxiedIP == nil { client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP)) - return true + return false + } + + // undo any mapping of v4 addresses into the v6 space: https://stackoverflow.com/a/1618259 + // this is how a typical stunnel4 deployment on Linux will handle dual-stack + unmappedIP := parsedProxiedIP.To4() + if unmappedIP != nil { + parsedProxiedIP = unmappedIP } isBanned, banMsg := client.server.checkBans(parsedProxiedIP) if isBanned { client.Quit(banMsg) - return true + return false } // given IP is sane! override the client's current IP - rawHostname := utils.LookupHostname(proxiedIP) + rawHostname := utils.LookupHostname(parsedProxiedIP.String()) client.stateMutex.Lock() client.proxiedIP = parsedProxiedIP client.rawHostname = rawHostname @@ -83,5 +97,37 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool) client.certfp = "" client.SetMode(modes.TLS, tls) - return false + return true +} + +// handle the PROXY command: http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt +// PROXY must be sent as the first message in the session and has the syntax: +// PROXY TCP[46] SOURCEIP DESTIP SOURCEPORT DESTPORT\r\n +// unfortunately, an ipv6 SOURCEIP can start with a double colon; in this case, +// the message is invalid IRC and can't be parsed normally, hence the special handling. +func handleProxyCommand(server *Server, client *Client, line string) (err error) { + defer func() { + if err != nil { + client.Quit(client.t("Bad or unauthorized PROXY command")) + } + }() + + params := strings.Fields(line) + if len(params) != 6 { + return errBadProxyLine + } + + for _, gateway := range server.ProxyAllowedFrom() { + if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { + // assume PROXY connections are always secure + if client.ApplyProxiedIP(params[2], true) { + return nil + } else { + return errBadProxyLine + } + } + } + + // real source IP is not authorized to issue PROXY: + return errBadGatewayAddress } diff --git a/irc/handlers.go b/irc/handlers.go index 1444ef47..0cffa2a2 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1910,26 +1910,6 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R return false } -// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT -// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt -func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - // only allow unregistered clients to use this command - if client.Registered() || client.proxiedIP != nil { - return false - } - - for _, gateway := range server.ProxyAllowedFrom() { - if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { - proxiedIP := msg.Params[1] - - // assume PROXY connections are always secure - return client.ApplyProxiedIP(proxiedIP, true) - } - } - client.Quit(client.t("PROXY command is not usable from your address")) - return true -} - // QUIT [] func quitHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { reason := "Quit" @@ -2416,7 +2396,7 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") { proxiedIP = proxiedIP[1 : len(proxiedIP)-1] } - return client.ApplyProxiedIP(proxiedIP, secure) + return !client.ApplyProxiedIP(proxiedIP, secure) } } } diff --git a/irc/help.go b/irc/help.go index 7ec87912..be487cb4 100644 --- a/irc/help.go +++ b/irc/help.go @@ -385,13 +385,6 @@ Replies to a PING. Used to check link connectivity.`, text: `PRIVMSG {,} Sends the text to the given targets as a PRIVMSG.`, - }, - "proxy": { - oper: true, // not really, but it's restricted anyways - text: `PROXY TCP4/6 - -Used by haproxy's PROXY v1 protocol, to allow for alternate TLS support: -http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt`, }, "rename": { text: `RENAME []