From b0f89062fab24c727fe961d3d8b6125d63062c1c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Mon, 25 Feb 2019 21:50:43 -0500 Subject: [PATCH] add support for tor (#369) --- docs/MANUAL.md | 44 ++++++++++++++ irc/client.go | 111 +++++++++++++++++++---------------- irc/config.go | 23 ++++++++ irc/connection_limits/tor.go | 55 +++++++++++++++++ irc/gateways.go | 6 ++ irc/getters.go | 7 +++ irc/handlers.go | 24 +++++++- irc/roleplay.go | 5 ++ irc/server.go | 85 +++++++++++++++++++-------- oragono.yaml | 24 ++++++++ 10 files changed, 307 insertions(+), 77 deletions(-) create mode 100644 irc/connection_limits/tor.go diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 5d9c957e..a05666ba 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -34,6 +34,8 @@ _Copyright © 2018 Daniel Oaks _ - Commands - Integrating with other software - HOPM + - ZNC + - Tor - Acknowledgements @@ -601,6 +603,48 @@ kline = "DLINE ANDKILL 2h %i :Open proxy found on your host."; Versions of ZNC prior to 1.7 have a [bug](https://github.com/znc/znc/issues/1212) in their SASL implementation that renders them incompatible with Oragono. However, you should be able to authenticate from ZNC using its [NickServ](https://wiki.znc.in/Nickserv) module. +## Tor + +Oragono has code support for adding an .onion address to an IRC server, or operating an IRC server as a Tor hidden service. This is subtle, so you should be familiar with the [Tor Project](https://www.torproject.org/) and the concept of a [hidden service](https://www.torproject.org/docs/tor-onion-service.html.en). + +There are two possible ways to serve Oragono over Tor. One is to add a .onion address to a server that also serves non-Tor clients, and whose IP address is public information. This is relatively straightforward. Add a separate listener, for example `127.0.0.2:6668`, to Oragono's `server.listen`, then add it to `server.tor-listeners.listeners` Configure Tor like this: + +```` +HiddenServiceDir /var/lib/tor/oragono_hidden_service +HiddenServicePort 6667 127.0.0.2:6668 + +# these are optional, but can be used to speed up the circuits in the case +# where the server's own IP is public information (clients will remain anonymous): +HiddenServiceNonAnonymousMode 1 +HiddenServiceSingleHopMode 1 +```` + +The second way is to run Oragono as a true hidden service, where the server's actual IP address is a secret. This requires hardening measures on the Oragono side: + +* Oragono should not accept any connections on its public interfaces. You should remove any listener that starts with the address of a public interface, or with `:`, which means "listen on all available interfaces". You should listen only on `127.0.0.1:6667` and a Unix domain socket such as `/hidden_service_sockets/oragono.sock`. +* In this mode, it is especially important that all operator passwords are strong and all operators are trusted (operators have a larger attack surface to deanonymize the server). +* Tor hidden services are at risk of being deanonymized if a client can trick the server into performing a non-Tor network request. Oragono should not perform any such requests (such as hostname resolution or ident lookups) in response to input received over a correctly configured Tor listener. However, Oragono has not been thoroughly audited against such deanonymization attacks --- therefore, Oragono should be deployed with additional sandboxing to protect against this: + * Oragono should run with no direct network connectivity, e.g., by running in its own Linux network namespace. systemd implements this with the [PrivateNetwork](https://www.freedesktop.org/software/systemd/man/systemd.exec.html) configuration option: add `PrivateNetwork=true` to Oragono's systemd unit file. + * Since the loopback adapters are local to a specific network namespace, Oragono must be configured to listen on a Unix domain socket that the Tor daemon can connect to. However, distributions typically package Tor with its own hardening profiles, which will restrict which sockets it can connect to. Below is a recipe for configuring this with the official Tor packages for Debian: + +1. Create a directory with `0777` permissions such as `/hidden_service_sockets`. +1. Configure Oragono to listen on `/hidden_service_sockets/oragono.sock`, and add this socket to `server.tor-listeners.listeners`. +1. Ensure that Oragono has no direct network access as described above, e.g., with `PrivateNetwork=true`. +1. Next, modify Tor's apparmor profile so that it can connect to this socket, by adding the line ` /hidden_service_sockets/** rw,` to `/etc/apparmor.d/local/system_tor`. +1. Finally, configure Tor with: + +```` +HiddenServiceDir /var/lib/tor/oragono_hidden_service +HiddenServicePort 6667 unix:/hidden_service_sockets/oragono.sock +# DO NOT enable HiddenServiceNonAnonymousMode +```` + +Instructions on how client software should connect to an .onion address are outside the scope of this manual. However: + +1. [Hexchat](https://hexchat.github.io/) is known to support .onion addresses, once it has been configured to use a local Tor daemon as a SOCKS proxy (Settings -> Preferences -> Network Setup -> Proxy Server). +1. Pidgin should work with [torsocks](https://trac.torproject.org/projects/tor/wiki/doc/torsocks). + + -------------------------------------------------------------------------------------------- diff --git a/irc/client.go b/irc/client.go index a4f76339..2752ea76 100644 --- a/irc/client.go +++ b/irc/client.go @@ -65,6 +65,7 @@ type Client struct { idletimer *IdleTimer invitedTo map[string]bool isDestroyed bool + isTor bool isQuitting bool languages []string loginThrottle connection_limits.GenericThrottle @@ -117,12 +118,12 @@ type ClientDetails struct { accountName string } -// NewClient sets up a new client and starts its goroutine. -func NewClient(server *Server, conn net.Conn, isTLS bool) { +// NewClient sets up a new client and runs its goroutine. +func RunNewClient(server *Server, conn clientConn) { now := time.Now() config := server.Config() fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest - socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes) + socket := NewSocket(conn.Conn, fullLineLenLimit*2, config.Server.MaxSendQBytes) client := &Client{ atime: now, capabilities: caps.NewSet(), @@ -131,6 +132,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { channels: make(ChannelSet), ctime: now, flags: modes.NewModeSet(), + isTor: conn.IsTor, loginThrottle: connection_limits.GenericThrottle{ Duration: config.Accounts.LoginThrottling.Duration, Limit: config.Accounts.LoginThrottling.MaxAttempts, @@ -145,58 +147,73 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { } client.languages = server.languages.Default() - remoteAddr := conn.RemoteAddr() - client.realIP = utils.AddrToIP(remoteAddr) - if client.realIP == nil { - server.logger.Error("internal", "bad remote address", remoteAddr.String()) - return - } client.recomputeMaxlens() - if isTLS { - client.SetMode(modes.TLS, true) + if conn.IsTLS { + client.SetMode(modes.TLS, true) // error is not useful to us here anyways so we can ignore it client.certfp, _ = client.socket.CertFP() } - if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) { - _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) - if err != nil { - server.logger.Error("internal", "bad server address", err.Error()) - return - } - serverPort, _ := strconv.Atoi(serverPortString) - clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String()) - if err != nil { - server.logger.Error("internal", "bad client address", err.Error()) - return - } - clientPort, _ := strconv.Atoi(clientPortString) - client.Notice(client.t("*** Looking up your username")) - resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds) - if err == nil { - err := client.SetNames(resp.Identifier, "", true) - if err == nil { - client.Notice(client.t("*** Found your username")) - // we don't need to updateNickMask here since nickMask is not used for anything yet - } else { - client.Notice(client.t("*** Got a malformed username, ignoring")) - } - } else { - client.Notice(client.t("*** Could not find your username")) + if conn.IsTor { + client.SetMode(modes.TLS, true) + client.realIP = utils.IPv4LoopbackAddress + client.rawHostname = config.Server.TorListeners.Vhost + } else { + remoteAddr := conn.Conn.RemoteAddr() + client.realIP = utils.AddrToIP(remoteAddr) + // Set the hostname for this client + // (may be overridden by a later PROXY command from stunnel) + client.rawHostname = utils.LookupHostname(client.realIP.String()) + if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) { + client.doIdentLookup(conn.Conn) } } - go client.run() + + client.run() +} + +func (client *Client) doIdentLookup(conn net.Conn) { + _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) + if err != nil { + client.server.logger.Error("internal", "bad server address", err.Error()) + return + } + serverPort, _ := strconv.Atoi(serverPortString) + clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String()) + if err != nil { + client.server.logger.Error("internal", "bad client address", err.Error()) + return + } + clientPort, _ := strconv.Atoi(clientPortString) + + client.Notice(client.t("*** Looking up your username")) + resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds) + if err == nil { + err := client.SetNames(resp.Identifier, "", true) + if err == nil { + client.Notice(client.t("*** Found your username")) + // we don't need to updateNickMask here since nickMask is not used for anything yet + } else { + client.Notice(client.t("*** Got a malformed username, ignoring")) + } + } else { + client.Notice(client.t("*** Could not find your username")) + } } func (client *Client) isAuthorized(config *Config) bool { saslSent := client.account != "" - passRequirementMet := (config.Server.passwordBytes == nil) || client.sentPassCommand || (config.Accounts.SkipServerPassword && saslSent) - if !passRequirementMet { + // PASS requirement + if !((config.Server.passwordBytes == nil) || client.sentPassCommand || (config.Accounts.SkipServerPassword && saslSent)) { return false } - saslRequirementMet := !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) - return saslRequirementMet + // Tor connections may be required to authenticate with SASL + if config.Server.TorListeners.RequireSasl && !saslSent { + return false + } + // finally, enforce require-sasl + return !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) } func (client *Client) resetFakelag() { @@ -296,10 +313,6 @@ func (client *Client) run() { client.resetFakelag() - // Set the hostname for this client - // (may be overridden by a later PROXY command from stunnel) - client.rawHostname = utils.LookupHostname(client.realIP.String()) - firstLine := true for { @@ -884,10 +897,10 @@ func (client *Client) destroy(beingResumed bool) { } // remove from connection limits - ipaddr := client.IP() - // this check shouldn't be required but eh - if ipaddr != nil { - client.server.connectionLimiter.RemoveClient(ipaddr) + if client.isTor { + client.server.torLimiter.RemoveClient() + } else { + client.server.connectionLimiter.RemoveClient(client.IP()) } client.server.resumeManager.Delete(client) diff --git a/irc/config.go b/irc/config.go index 112a5387..4fd71faa 100644 --- a/irc/config.go +++ b/irc/config.go @@ -251,6 +251,15 @@ type FakelagConfig struct { Cooldown time.Duration } +type TorListenersConfig struct { + Listeners []string + RequireSasl bool `yaml:"require-sasl"` + Vhost string + MaxConnections int `yaml:"max-connections"` + ThrottleDuration time.Duration `yaml:"throttle-duration"` + MaxConnectionsPerDuration int `yaml:"max-connections-per-duration"` +} + // Config defines the overall configuration. type Config struct { Network struct { @@ -265,6 +274,7 @@ type Config struct { Listen []string UnixBindMode os.FileMode `yaml:"unix-bind-mode"` TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` + TorListeners TorListenersConfig `yaml:"tor-listeners"` STS STSConfig CheckIdent bool `yaml:"check-ident"` MOTD string @@ -806,5 +816,18 @@ func LoadConfig(filename string) (config *Config, err error) { config.History.ClientLength = 0 } + for _, listenAddress := range config.Server.TorListeners.Listeners { + found := false + for _, configuredListener := range config.Server.Listen { + if listenAddress == configuredListener { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("%s is configured as a Tor listener, but is not in server.listen", listenAddress) + } + } + return config, nil } diff --git a/irc/connection_limits/tor.go b/irc/connection_limits/tor.go new file mode 100644 index 00000000..87081465 --- /dev/null +++ b/irc/connection_limits/tor.go @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Shivaram Lingamneni +// released under the MIT license + +package connection_limits + +import ( + "errors" + "sync" + "time" +) + +var ( + ErrLimitExceeded = errors.New("too many concurrent connections") + ErrThrottleExceeded = errors.New("too many recent connection attempts") +) + +// TorLimiter is a combined limiter and throttler for use on connections +// proxied from a Tor hidden service (so we don't have meaningful IPs, +// a notion of CIDR width, etc.) +type TorLimiter struct { + sync.Mutex + + numConnections int + maxConnections int + throttle GenericThrottle +} + +func (tl *TorLimiter) Configure(maxConnections int, duration time.Duration, maxConnectionsPerDuration int) { + tl.Lock() + defer tl.Unlock() + tl.maxConnections = maxConnections + tl.throttle.Duration = duration + tl.throttle.Limit = maxConnectionsPerDuration +} + +func (tl *TorLimiter) AddClient() error { + tl.Lock() + defer tl.Unlock() + + if tl.maxConnections != 0 && tl.maxConnections <= tl.numConnections { + return ErrLimitExceeded + } + throttled, _ := tl.throttle.Touch() + if throttled { + return ErrThrottleExceeded + } + tl.numConnections += 1 + return nil +} + +func (tl *TorLimiter) RemoveClient() { + tl.Lock() + tl.numConnections -= 1 + tl.Unlock() +} diff --git a/irc/gateways.go b/irc/gateways.go index f40fdf71..ca42cc79 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -47,6 +47,12 @@ func (wc *webircConfig) Populate() (err error) { // ApplyProxiedIP applies the given IP to the client. func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool) { + // PROXY and WEBIRC are never accepted from a Tor listener, even if the address itself + // is whitelisted: + if client.isTor { + return false + } + // ensure IP is sane parsedProxiedIP := net.ParseIP(proxiedIP).To16() if parsedProxiedIP == nil { diff --git a/irc/getters.go b/irc/getters.go index 1f6ff9cb..6fef1fd8 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -143,6 +143,13 @@ func (client *Client) SetRegistered() { client.stateMutex.Unlock() } +func (client *Client) RawHostname() (result string) { + client.stateMutex.Lock() + result = client.rawHostname + client.stateMutex.Unlock() + return +} + func (client *Client) AwayMessage() (result string) { client.stateMutex.RLock() result = client.awayMessage diff --git a/irc/handlers.go b/irc/handlers.go index dfbf0ac5..ce3fb292 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1884,6 +1884,11 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] + if client.isTor && isRestrictedCTCPMessage(message) { + rb.Add(nil, server.name, "NOTICE", client.t("CTCP messages are disabled over Tor")) + return false + } + // split privmsg splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine)) @@ -1930,7 +1935,9 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re msgid := server.generateMessageID() // restrict messages appropriately when +R is set // intentionally make the sending user think the message went through fine - if !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() { + allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() + allowedTor := !user.isTor || !isRestrictedCTCPMessage(message) + if allowedPlusR && allowedTor { user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg) } nickMaskString := client.NickMaskString() @@ -2087,12 +2094,23 @@ func pongHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp return false } +func isRestrictedCTCPMessage(message string) bool { + // block all CTCP privmsgs to Tor clients except for ACTION + // DCC can potentially be used for deanonymization, the others for fingerprinting + return strings.HasPrefix(message, "\x01") && !strings.HasPrefix(message, "\x01ACTION") +} + // PRIVMSG {,} func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { clientOnlyTags := utils.GetClientOnlyTags(msg.Tags) targets := strings.Split(msg.Params[0], ",") message := msg.Params[1] + if client.isTor && isRestrictedCTCPMessage(message) { + rb.Add(nil, server.name, "NOTICE", client.t("CTCP messages are disabled over Tor")) + return false + } + // split privmsg splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine)) @@ -2142,7 +2160,9 @@ func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R msgid := server.generateMessageID() // restrict messages appropriately when +R is set // intentionally make the sending user think the message went through fine - if !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() { + allowedPlusR := !user.HasMode(modes.RegisteredOnly) || client.LoggedIntoAccount() + allowedTor := !user.isTor || !isRestrictedCTCPMessage(message) + if allowedPlusR && allowedTor { user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg) } nickMaskString := client.NickMaskString() diff --git a/irc/roleplay.go b/irc/roleplay.go index a2cc376d..2991414d 100644 --- a/irc/roleplay.go +++ b/irc/roleplay.go @@ -19,6 +19,11 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt if isAction { message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick) } else { + // block attempts to send CTCP messages to Tor clients + // TODO(#395) clean this up + if len(message) != 0 && message[0] == '\x01' { + return + } message = fmt.Sprintf("%s (%s)", message, client.nick) } diff --git a/irc/server.go b/irc/server.go index fdf4f01e..23b71614 100644 --- a/irc/server.go +++ b/irc/server.go @@ -21,7 +21,6 @@ import ( "time" "github.com/goshuirc/irc-go/ircfmt" - "github.com/goshuirc/irc-go/ircmsg" "github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/isupport" @@ -35,10 +34,7 @@ import ( var ( // common error line to sub values into - errorMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "%s ")}[0]).Line() - - // common error responses - couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line() + errorMsg = "ERROR :%s\r\n" // supportedUserModesString acts as a cache for when we introduce users supportedUserModesString = modes.SupportedUserModes.String() @@ -58,6 +54,7 @@ var ( type ListenerWrapper struct { listener net.Listener tlsConfig *tls.Config + isTor bool shouldStop bool // protects atomic update of tlsConfig and shouldStop: configMutex sync.Mutex // tier 1 @@ -92,6 +89,7 @@ type Server struct { signals chan os.Signal snomasks *SnoManager store *buntdb.DB + torLimiter connection_limits.TorLimiter whoWas *WhoWasList stats *Stats semaphores *ServerSemaphores @@ -109,6 +107,7 @@ var ( type clientConn struct { Conn net.Conn IsTLS bool + IsTor bool } // NewServer returns a new Oragono server. @@ -252,22 +251,27 @@ func (server *Server) Run() { } func (server *Server) acceptClient(conn clientConn) { - // check IP address - 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 - } + var isBanned bool + var banMsg string + var ipaddr net.IP + if conn.IsTor { + ipaddr = utils.IPv4LoopbackAddress + isBanned, banMsg = server.checkTorLimits() + } else { + ipaddr = utils.AddrToIP(conn.Conn.RemoteAddr()) + 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.Info("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr)) - // prolly don't need to alert snomasks on this, only on connection reg - NewClient(server, conn.Conn, conn.IsTLS) + go RunNewClient(server, conn) } func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { @@ -310,12 +314,23 @@ func (server *Server) checkBans(ipaddr net.IP) (banned bool, message string) { return false, "" } +func (server *Server) checkTorLimits() (banned bool, message string) { + switch server.torLimiter.AddClient() { + case connection_limits.ErrLimitExceeded: + return true, "Too many clients from the Tor network" + case connection_limits.ErrThrottleExceeded: + return true, "Exceeded connection throttle for the Tor network" + default: + return false, "" + } +} + // // IRC protocol listeners // // createListener starts a given listener. -func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMode os.FileMode) (*ListenerWrapper, error) { +func (server *Server) createListener(addr string, tlsConfig *tls.Config, isTor bool, bindMode os.FileMode) (*ListenerWrapper, error) { // make listener var listener net.Listener var err error @@ -338,6 +353,7 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod wrapper := ListenerWrapper{ listener: listener, tlsConfig: tlsConfig, + isTor: isTor, shouldStop: false, } @@ -349,10 +365,10 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod conn, err := listener.Accept() // synchronously access config data: - // whether TLS is enabled and whether we should stop listening wrapper.configMutex.Lock() shouldStop = wrapper.shouldStop tlsConfig = wrapper.tlsConfig + isTor = wrapper.isTor wrapper.configMutex.Unlock() if err == nil { @@ -362,6 +378,7 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod newConn := clientConn{ Conn: conn, IsTLS: tlsConfig != nil, + IsTor: isTor, } // hand off the connection go server.acceptClient(newConn) @@ -524,7 +541,7 @@ func (client *Client) getWhoisOf(target *Client, rb *ResponseBuffer) { rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine) } if client.HasMode(modes.Operator) || client == target { - rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", target.username, utils.LookupHostname(target.IPString())), target.IPString(), client.t("Actual user@host, Actual IP")) + rb.Add(nil, client.server.name, RPL_WHOISACTUALLY, cnick, tnick, fmt.Sprintf("%s@%s", targetInfo.username, target.RawHostname()), target.IPString(), client.t("Actual user@host, Actual IP")) } if target.HasMode(modes.TLS) { rb.Add(nil, client.server.name, RPL_WHOISSECURE, cnick, tnick, client.t("is using a secure connection")) @@ -639,6 +656,9 @@ func (server *Server) applyConfig(config *Config, initial bool) (err error) { return err } + tlConf := &config.Server.TorListeners + server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration) + // reload logging config wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO() err = server.logger.ApplyConfig(config.Logging) @@ -931,9 +951,9 @@ func (server *Server) loadDatastore(config *Config) error { } func (server *Server) setupListeners(config *Config) (err error) { - logListener := func(addr string, tlsconfig *tls.Config) { + logListener := func(addr string, tlsconfig *tls.Config, isTor bool) { server.logger.Info("listeners", - fmt.Sprintf("now listening on %s, tls=%t.", addr, (tlsconfig != nil)), + fmt.Sprintf("now listening on %s, tls=%t, tor=%t.", addr, (tlsconfig != nil), isTor), ) } @@ -943,6 +963,15 @@ func (server *Server) setupListeners(config *Config) (err error) { return } + isTorListener := func(listener string) bool { + for _, torListener := range config.Server.TorListeners.Listeners { + if listener == torListener { + return true + } + } + return false + } + // update or destroy all existing listeners for addr := range server.listeners { currentListener := server.listeners[addr] @@ -958,13 +987,16 @@ func (server *Server) setupListeners(config *Config) (err error) { // its next Accept(). this is like sending over a buffered channel of // size 1, but where sending a second item overwrites the buffered item // instead of blocking. + tlsConfig := tlsListeners[addr] + isTor := isTorListener(addr) currentListener.configMutex.Lock() currentListener.shouldStop = !stillConfigured - currentListener.tlsConfig = tlsListeners[addr] + currentListener.tlsConfig = tlsConfig + currentListener.isTor = isTor currentListener.configMutex.Unlock() if stillConfigured { - logListener(addr, currentListener.tlsConfig) + logListener(addr, tlsConfig, isTor) } else { // tell the listener it should stop by interrupting its Accept() call: currentListener.listener.Close() @@ -978,15 +1010,16 @@ func (server *Server) setupListeners(config *Config) (err error) { _, exists := server.listeners[newaddr] if !exists { // make new listener + isTor := isTorListener(newaddr) tlsConfig := tlsListeners[newaddr] - listener, listenerErr := server.createListener(newaddr, tlsConfig, config.Server.UnixBindMode) + listener, listenerErr := server.createListener(newaddr, tlsConfig, isTor, config.Server.UnixBindMode) if listenerErr != nil { server.logger.Error("server", "couldn't listen on", newaddr, listenerErr.Error()) err = listenerErr continue } server.listeners[newaddr] = listener - logListener(newaddr, tlsConfig) + logListener(newaddr, tlsConfig, isTor) } } diff --git a/oragono.yaml b/oragono.yaml index 8181f729..a1a9790f 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -33,6 +33,30 @@ server: key: tls.key cert: tls.crt + # tor listeners: designate listeners for use by a tor hidden service / .onion address + # WARNING: if you are running oragono as a pure hidden service, see the + # anonymization / hardening recommendations in docs/MANUAL.md + tor-listeners: + # any connections that come in on these listeners will be considered + # Tor connections. it is strongly recommended that these listeners *not* + # be on public interfaces: they should be on 127.0.0.0/8 or unix domain + listeners: + # - "/tmp/oragono_tor_sock" + + # if this is true, connections from Tor must authenticate with SASL + require-sasl: false + + # what hostname should be displayed for Tor connections? + vhost: "tor-network.onion" + + # allow at most this many connections at once (0 for no limit): + max-connections: 64 + + # connection throttling (limit how many connection attempts are allowed at once): + throttle-duration: 10m + # set to 0 to disable throttling: + max-connections-per-duration: 64 + # strict transport security, to get clients to automagically use TLS sts: # whether to advertise STS