diff --git a/irc/client.go b/irc/client.go index 1e0b5515..12777737 100644 --- a/irc/client.go +++ b/irc/client.go @@ -33,6 +33,7 @@ const ( var ( // ErrNickAlreadySet is a weird error that's sent when the server's consistency has been compromised. ErrNickAlreadySet = errors.New("Nickname is already set") + LoopbackIP = net.ParseIP("127.0.0.1") ) // Client is an IRC client. @@ -64,7 +65,7 @@ type Client struct { nickMaskCasefolded string nickMaskString string // cache for nickmask string since it's used with lots of replies operName string - proxiedIP string // actual remote IP if using the PROXY protocol + proxiedIP net.IP // actual remote IP if using the PROXY protocol quitMessage string rawHostname string realname string @@ -111,7 +112,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { // error is not useful to us here anyways so we can ignore it client.certfp, _ = client.socket.CertFP() } - if server.checkIdent { + if server.checkIdent && !utils.AddrIsUnix(conn.RemoteAddr()) { _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) serverPort, _ := strconv.Atoi(serverPortString) if err != nil { @@ -146,19 +147,18 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) *Client { // IP returns the IP address of this client. func (client *Client) IP() net.IP { - if client.proxiedIP != "" { - return net.ParseIP(client.proxiedIP) + if client.proxiedIP != nil { + return client.proxiedIP } - - return net.ParseIP(utils.IPString(client.socket.conn.RemoteAddr())) + if ip := utils.AddrToIP(client.socket.conn.RemoteAddr()); ip != nil { + return ip + } + // unix domain socket that hasn't issued PROXY/WEBIRC yet. YOLO + return LoopbackIP } // IPString returns the IP address of this client as a string. func (client *Client) IPString() string { - if client.proxiedIP != "" { - return client.proxiedIP - } - ip := client.IP().String() if 0 < len(ip) && ip[0] == ':' { ip = "0" + ip @@ -581,7 +581,7 @@ func (client *Client) AllNickmasks() []string { masks = append(masks, mask) } - mask2, err := Casefold(fmt.Sprintf("%s!%s@%s", client.nick, client.username, utils.IPString(client.socket.conn.RemoteAddr()))) + mask2, err := Casefold(fmt.Sprintf("%s!%s@%s", client.nick, client.username, client.IPString())) if err == nil && mask2 != mask { masks = append(masks, mask2) } diff --git a/irc/gateways.go b/irc/gateways.go index 7eb84161..354650e6 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -38,10 +38,34 @@ func (wc *webircConfig) Populate() (err error) { return err } +func isGatewayAllowed(addr net.Addr, gatewaySpec string) bool { + // "localhost" includes any loopback IP or unix domain socket + if gatewaySpec == "localhost" { + return utils.AddrIsLocal(addr) + } + + ip := utils.AddrToIP(addr) + if ip == nil { + return false + } + + // exact IP match + if ip.String() == gatewaySpec { + return true + } + + // CIDR match + _, gatewayNet, err := net.ParseCIDR(gatewaySpec) + if err != nil { + return false + } + return gatewayNet.Contains(ip) +} + // WEBIRC [:flag1 flag2=x flag3] func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // only allow unregistered clients to use this command - if client.registered || client.proxiedIP != "" { + if client.registered || client.proxiedIP != nil { return false } @@ -68,11 +92,9 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { } } - clientAddress := utils.IPString(client.socket.conn.RemoteAddr()) - clientHostname := client.hostname for _, info := range server.WebIRCConfig() { - for _, address := range info.Hosts { - if clientHostname == address || clientAddress == address { + for _, gateway := range info.Hosts { + if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { // confirm password and/or fingerprint givenPassword := msg.Params[0] if 0 < len(info.Password) && passwd.ComparePasswordString(info.Password, givenPassword) != nil { @@ -96,14 +118,12 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { // only allow unregistered clients to use this command - if client.registered || client.proxiedIP != "" { + if client.registered || client.proxiedIP != nil { return false } - clientAddress := utils.IPString(client.socket.conn.RemoteAddr()) - clientHostname := client.hostname - for _, address := range server.ProxyAllowedFrom() { - if clientHostname == address || clientAddress == address { + for _, gateway := range server.ProxyAllowedFrom() { + if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { proxiedIP := msg.Params[1] // assume PROXY connections are always secure @@ -130,7 +150,7 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (exiting bool) } // given IP is sane! override the client's current IP - client.proxiedIP = proxiedIP + client.proxiedIP = parsedProxiedIP client.rawHostname = utils.LookupHostname(proxiedIP) client.hostname = client.rawHostname diff --git a/irc/server.go b/irc/server.go index 6f46cb44..228d8f50 100644 --- a/irc/server.go +++ b/irc/server.go @@ -267,19 +267,15 @@ func (server *Server) Run() { func (server *Server) acceptClient(conn clientConn) { // check IP address - ipaddr := net.ParseIP(utils.IPString(conn.Conn.RemoteAddr())) - if ipaddr == nil { - conn.Conn.Write([]byte(couldNotParseIPMsg)) - conn.Conn.Close() - return - } - - isBanned, banMsg := server.checkBans(ipaddr) - if isBanned { - // this might not show up properly on some clients, but our objective here is just to close the connection out before it has a load impact on us - conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg))) - conn.Conn.Close() - return + ipaddr := utils.AddrToIP(conn.Conn.RemoteAddr()) + if ipaddr != nil { + isBanned, banMsg := server.checkBans(ipaddr) + if isBanned { + // this might not show up properly on some clients, but our objective here is just to close the connection out before it has a load impact on us + conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg))) + conn.Conn.Close() + return + } } server.logger.Debug("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr)) @@ -336,7 +332,23 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { // createListener starts the given listeners. func (server *Server) createListener(addr string, tlsConfig *tls.Config) *ListenerWrapper { // make listener - listener, err := net.Listen("tcp", addr) + var listener net.Listener + var err error + optional_unix_prefix := "unix:" + optional_prefix_len := len(optional_unix_prefix) + if len(addr) >= optional_prefix_len && strings.ToLower(addr[0:optional_prefix_len]) == optional_unix_prefix { + addr = addr[optional_prefix_len:] + if len(addr) == 0 || addr[0] != '/' { + log.Fatal("Bad unix socket address", addr) + } + } + if len(addr) > 0 && addr[0] == '/' { + // https://stackoverflow.com/a/34881585 + os.Remove(addr) + listener, err = net.Listen("unix", addr) + } else { + listener, err = net.Listen("tcp", addr) + } if err != nil { log.Fatal(server, "listen error: ", err) } diff --git a/irc/utils/net.go b/irc/utils/net.go index bad537c2..bbc19898 100644 --- a/irc/utils/net.go +++ b/irc/utils/net.go @@ -22,6 +22,9 @@ func IPString(addr net.Addr) string { // AddrLookupHostname returns the hostname (if possible) or address for the given `net.Addr`. func AddrLookupHostname(addr net.Addr) string { + if AddrIsUnix(addr) { + return "localhost" + } return LookupHostname(IPString(addr)) } @@ -30,10 +33,22 @@ func AddrIsLocal(addr net.Addr) bool { if tcpaddr, ok := addr.(*net.TCPAddr); ok { return tcpaddr.IP.IsLoopback() } - if _, ok := addr.(*net.UnixAddr); ok { - return true + _, ok := addr.(*net.UnixAddr) + return ok +} + +// AddrToIP returns the IP address for a net.Addr, or nil if it's a unix domain socket. +func AddrToIP(addr net.Addr) net.IP { + if tcpaddr, ok := addr.(*net.TCPAddr); ok { + return tcpaddr.IP } - return false + return nil +} + +// AddrIsUnix returns whether the address is a unix domain socket. +func AddrIsUnix(addr net.Addr) bool { + _, ok := addr.(*net.UnixAddr) + return ok } // LookupHostname returns the hostname for `addr` if it has one. Otherwise, just returns `addr`. diff --git a/oragono.yaml b/oragono.yaml index 9ad2e468..bebdce35 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -16,6 +16,8 @@ server: - "127.0.0.1:6668" - "[::1]:6668" - ":6697" # ssl port + # unix domain socket for proxying: + # - "/tmp/oragono_sock" # tls listeners tls-listeners: @@ -59,11 +61,12 @@ server: #motd-formatting: true # addresses/hostnames the PROXY command can be used from - # this should be restricted to 127.0.0.1 and localhost at most + # this should be restricted to 127.0.0.1/8 and localhost at most # you should also add these addresses to the connection limits and throttling exemption lists proxy-allowed-from: # - localhost # - "127.0.0.1" + # - "127.0.0.1/8" # controls the use of the WEBIRC command (by IRC<->web interfaces, bouncers and similar) webirc: @@ -79,6 +82,7 @@ server: hosts: # - localhost # - "127.0.0.1" + # - "127.0.0.1/8" # - "0::1" # maximum length of clients' sendQ in bytes