3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-22 20:09:41 +01:00

add support for tor (#369)

This commit is contained in:
Shivaram Lingamneni 2019-02-25 21:50:43 -05:00
parent d43ce07b66
commit b0f89062fa
10 changed files with 307 additions and 77 deletions

View File

@ -34,6 +34,8 @@ _Copyright © 2018 Daniel Oaks <daniel@danieloaks.net>_
- Commands - Commands
- Integrating with other software - Integrating with other software
- HOPM - HOPM
- ZNC
- Tor
- Acknowledgements - 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. 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).
-------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------

View File

@ -65,6 +65,7 @@ type Client struct {
idletimer *IdleTimer idletimer *IdleTimer
invitedTo map[string]bool invitedTo map[string]bool
isDestroyed bool isDestroyed bool
isTor bool
isQuitting bool isQuitting bool
languages []string languages []string
loginThrottle connection_limits.GenericThrottle loginThrottle connection_limits.GenericThrottle
@ -117,12 +118,12 @@ type ClientDetails struct {
accountName string accountName string
} }
// NewClient sets up a new client and starts its goroutine. // NewClient sets up a new client and runs its goroutine.
func NewClient(server *Server, conn net.Conn, isTLS bool) { func RunNewClient(server *Server, conn clientConn) {
now := time.Now() now := time.Now()
config := server.Config() config := server.Config()
fullLineLenLimit := config.Limits.LineLen.Tags + config.Limits.LineLen.Rest 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{ client := &Client{
atime: now, atime: now,
capabilities: caps.NewSet(), capabilities: caps.NewSet(),
@ -131,6 +132,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
channels: make(ChannelSet), channels: make(ChannelSet),
ctime: now, ctime: now,
flags: modes.NewModeSet(), flags: modes.NewModeSet(),
isTor: conn.IsTor,
loginThrottle: connection_limits.GenericThrottle{ loginThrottle: connection_limits.GenericThrottle{
Duration: config.Accounts.LoginThrottling.Duration, Duration: config.Accounts.LoginThrottling.Duration,
Limit: config.Accounts.LoginThrottling.MaxAttempts, Limit: config.Accounts.LoginThrottling.MaxAttempts,
@ -145,29 +147,42 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
} }
client.languages = server.languages.Default() 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() 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 // error is not useful to us here anyways so we can ignore it
client.certfp, _ = client.socket.CertFP() client.certfp, _ = client.socket.CertFP()
} }
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) { if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
client.doIdentLookup(conn.Conn)
}
}
client.run()
}
func (client *Client) doIdentLookup(conn net.Conn) {
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil { if err != nil {
server.logger.Error("internal", "bad server address", err.Error()) client.server.logger.Error("internal", "bad server address", err.Error())
return return
} }
serverPort, _ := strconv.Atoi(serverPortString) serverPort, _ := strconv.Atoi(serverPortString)
clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String()) clientHost, clientPortString, err := net.SplitHostPort(conn.RemoteAddr().String())
if err != nil { if err != nil {
server.logger.Error("internal", "bad client address", err.Error()) client.server.logger.Error("internal", "bad client address", err.Error())
return return
} }
clientPort, _ := strconv.Atoi(clientPortString) clientPort, _ := strconv.Atoi(clientPortString)
@ -185,18 +200,20 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
} else { } else {
client.Notice(client.t("*** Could not find your username")) client.Notice(client.t("*** Could not find your username"))
} }
}
go client.run()
} }
func (client *Client) isAuthorized(config *Config) bool { func (client *Client) isAuthorized(config *Config) bool {
saslSent := client.account != "" saslSent := client.account != ""
passRequirementMet := (config.Server.passwordBytes == nil) || client.sentPassCommand || (config.Accounts.SkipServerPassword && saslSent) // PASS requirement
if !passRequirementMet { if !((config.Server.passwordBytes == nil) || client.sentPassCommand || (config.Accounts.SkipServerPassword && saslSent)) {
return false return false
} }
saslRequirementMet := !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) // Tor connections may be required to authenticate with SASL
return saslRequirementMet 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() { func (client *Client) resetFakelag() {
@ -296,10 +313,6 @@ func (client *Client) run() {
client.resetFakelag() 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 firstLine := true
for { for {
@ -884,10 +897,10 @@ func (client *Client) destroy(beingResumed bool) {
} }
// remove from connection limits // remove from connection limits
ipaddr := client.IP() if client.isTor {
// this check shouldn't be required but eh client.server.torLimiter.RemoveClient()
if ipaddr != nil { } else {
client.server.connectionLimiter.RemoveClient(ipaddr) client.server.connectionLimiter.RemoveClient(client.IP())
} }
client.server.resumeManager.Delete(client) client.server.resumeManager.Delete(client)

View File

@ -251,6 +251,15 @@ type FakelagConfig struct {
Cooldown time.Duration 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. // Config defines the overall configuration.
type Config struct { type Config struct {
Network struct { Network struct {
@ -265,6 +274,7 @@ type Config struct {
Listen []string Listen []string
UnixBindMode os.FileMode `yaml:"unix-bind-mode"` UnixBindMode os.FileMode `yaml:"unix-bind-mode"`
TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"`
TorListeners TorListenersConfig `yaml:"tor-listeners"`
STS STSConfig STS STSConfig
CheckIdent bool `yaml:"check-ident"` CheckIdent bool `yaml:"check-ident"`
MOTD string MOTD string
@ -806,5 +816,18 @@ func LoadConfig(filename string) (config *Config, err error) {
config.History.ClientLength = 0 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 return config, nil
} }

View File

@ -0,0 +1,55 @@
// Copyright (c) 2019 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// 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()
}

View File

@ -47,6 +47,12 @@ func (wc *webircConfig) Populate() (err error) {
// ApplyProxiedIP applies the given IP to the client. // ApplyProxiedIP applies the given IP to the client.
func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool) { 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 // ensure IP is sane
parsedProxiedIP := net.ParseIP(proxiedIP).To16() parsedProxiedIP := net.ParseIP(proxiedIP).To16()
if parsedProxiedIP == nil { if parsedProxiedIP == nil {

View File

@ -143,6 +143,13 @@ func (client *Client) SetRegistered() {
client.stateMutex.Unlock() 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) { func (client *Client) AwayMessage() (result string) {
client.stateMutex.RLock() client.stateMutex.RLock()
result = client.awayMessage result = client.awayMessage

View File

@ -1884,6 +1884,11 @@ func noticeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] 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 // split privmsg
splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine)) 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() msgid := server.generateMessageID()
// restrict messages appropriately when +R is set // restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine // 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) user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "NOTICE", user.nick, splitMsg)
} }
nickMaskString := client.NickMaskString() nickMaskString := client.NickMaskString()
@ -2087,12 +2094,23 @@ func pongHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
return false 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 <target>{,<target>} <message> // PRIVMSG <target>{,<target>} <message>
func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func privmsgHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
clientOnlyTags := utils.GetClientOnlyTags(msg.Tags) clientOnlyTags := utils.GetClientOnlyTags(msg.Tags)
targets := strings.Split(msg.Params[0], ",") targets := strings.Split(msg.Params[0], ",")
message := msg.Params[1] 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 // split privmsg
splitMsg := utils.MakeSplitMessage(message, !client.capabilities.Has(caps.MaxLine)) 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() msgid := server.generateMessageID()
// restrict messages appropriately when +R is set // restrict messages appropriately when +R is set
// intentionally make the sending user think the message went through fine // 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) user.SendSplitMsgFromClient(msgid, client, clientOnlyTags, "PRIVMSG", user.nick, splitMsg)
} }
nickMaskString := client.NickMaskString() nickMaskString := client.NickMaskString()

View File

@ -19,6 +19,11 @@ func sendRoleplayMessage(server *Server, client *Client, source string, targetSt
if isAction { if isAction {
message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick) message = fmt.Sprintf("\x01ACTION %s (%s)\x01", message, client.nick)
} else { } 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) message = fmt.Sprintf("%s (%s)", message, client.nick)
} }

View File

@ -21,7 +21,6 @@ import (
"time" "time"
"github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircfmt"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/caps" "github.com/oragono/oragono/irc/caps"
"github.com/oragono/oragono/irc/connection_limits" "github.com/oragono/oragono/irc/connection_limits"
"github.com/oragono/oragono/irc/isupport" "github.com/oragono/oragono/irc/isupport"
@ -35,10 +34,7 @@ import (
var ( var (
// common error line to sub values into // common error line to sub values into
errorMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "%s ")}[0]).Line() errorMsg = "ERROR :%s\r\n"
// common error responses
couldNotParseIPMsg, _ = (&[]ircmsg.IrcMessage{ircmsg.MakeMessage(nil, "", "ERROR", "Unable to parse your IP address")}[0]).Line()
// supportedUserModesString acts as a cache for when we introduce users // supportedUserModesString acts as a cache for when we introduce users
supportedUserModesString = modes.SupportedUserModes.String() supportedUserModesString = modes.SupportedUserModes.String()
@ -58,6 +54,7 @@ var (
type ListenerWrapper struct { type ListenerWrapper struct {
listener net.Listener listener net.Listener
tlsConfig *tls.Config tlsConfig *tls.Config
isTor bool
shouldStop bool shouldStop bool
// protects atomic update of tlsConfig and shouldStop: // protects atomic update of tlsConfig and shouldStop:
configMutex sync.Mutex // tier 1 configMutex sync.Mutex // tier 1
@ -92,6 +89,7 @@ type Server struct {
signals chan os.Signal signals chan os.Signal
snomasks *SnoManager snomasks *SnoManager
store *buntdb.DB store *buntdb.DB
torLimiter connection_limits.TorLimiter
whoWas *WhoWasList whoWas *WhoWasList
stats *Stats stats *Stats
semaphores *ServerSemaphores semaphores *ServerSemaphores
@ -109,6 +107,7 @@ var (
type clientConn struct { type clientConn struct {
Conn net.Conn Conn net.Conn
IsTLS bool IsTLS bool
IsTor bool
} }
// NewServer returns a new Oragono server. // NewServer returns a new Oragono server.
@ -252,22 +251,27 @@ func (server *Server) Run() {
} }
func (server *Server) acceptClient(conn clientConn) { func (server *Server) acceptClient(conn clientConn) {
// check IP address var isBanned bool
ipaddr := utils.AddrToIP(conn.Conn.RemoteAddr()) var banMsg string
if ipaddr != nil { var ipaddr net.IP
isBanned, banMsg := server.checkBans(ipaddr) if conn.IsTor {
ipaddr = utils.IPv4LoopbackAddress
isBanned, banMsg = server.checkTorLimits()
} else {
ipaddr = utils.AddrToIP(conn.Conn.RemoteAddr())
isBanned, banMsg = server.checkBans(ipaddr)
}
if isBanned { 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 // 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.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
conn.Conn.Close() conn.Conn.Close()
return return
} }
}
server.logger.Info("localconnect-ip", fmt.Sprintf("Client connecting from %v", ipaddr)) 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) { 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, "" 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 // IRC protocol listeners
// //
// createListener starts a given listener. // 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 // make listener
var listener net.Listener var listener net.Listener
var err error var err error
@ -338,6 +353,7 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod
wrapper := ListenerWrapper{ wrapper := ListenerWrapper{
listener: listener, listener: listener,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
isTor: isTor,
shouldStop: false, shouldStop: false,
} }
@ -349,10 +365,10 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod
conn, err := listener.Accept() conn, err := listener.Accept()
// synchronously access config data: // synchronously access config data:
// whether TLS is enabled and whether we should stop listening
wrapper.configMutex.Lock() wrapper.configMutex.Lock()
shouldStop = wrapper.shouldStop shouldStop = wrapper.shouldStop
tlsConfig = wrapper.tlsConfig tlsConfig = wrapper.tlsConfig
isTor = wrapper.isTor
wrapper.configMutex.Unlock() wrapper.configMutex.Unlock()
if err == nil { if err == nil {
@ -362,6 +378,7 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod
newConn := clientConn{ newConn := clientConn{
Conn: conn, Conn: conn,
IsTLS: tlsConfig != nil, IsTLS: tlsConfig != nil,
IsTor: isTor,
} }
// hand off the connection // hand off the connection
go server.acceptClient(newConn) 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) rb.Add(nil, client.server.name, RPL_WHOISOPERATOR, cnick, tnick, tOper.WhoisLine)
} }
if client.HasMode(modes.Operator) || client == target { 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) { if target.HasMode(modes.TLS) {
rb.Add(nil, client.server.name, RPL_WHOISSECURE, cnick, tnick, client.t("is using a secure connection")) 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 return err
} }
tlConf := &config.Server.TorListeners
server.torLimiter.Configure(tlConf.MaxConnections, tlConf.ThrottleDuration, tlConf.MaxConnectionsPerDuration)
// reload logging config // reload logging config
wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO() wasLoggingRawIO := !initial && server.logger.IsLoggingRawIO()
err = server.logger.ApplyConfig(config.Logging) 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) { 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", 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 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 // update or destroy all existing listeners
for addr := range server.listeners { for addr := range server.listeners {
currentListener := server.listeners[addr] 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 // its next Accept(). this is like sending over a buffered channel of
// size 1, but where sending a second item overwrites the buffered item // size 1, but where sending a second item overwrites the buffered item
// instead of blocking. // instead of blocking.
tlsConfig := tlsListeners[addr]
isTor := isTorListener(addr)
currentListener.configMutex.Lock() currentListener.configMutex.Lock()
currentListener.shouldStop = !stillConfigured currentListener.shouldStop = !stillConfigured
currentListener.tlsConfig = tlsListeners[addr] currentListener.tlsConfig = tlsConfig
currentListener.isTor = isTor
currentListener.configMutex.Unlock() currentListener.configMutex.Unlock()
if stillConfigured { if stillConfigured {
logListener(addr, currentListener.tlsConfig) logListener(addr, tlsConfig, isTor)
} else { } else {
// tell the listener it should stop by interrupting its Accept() call: // tell the listener it should stop by interrupting its Accept() call:
currentListener.listener.Close() currentListener.listener.Close()
@ -978,15 +1010,16 @@ func (server *Server) setupListeners(config *Config) (err error) {
_, exists := server.listeners[newaddr] _, exists := server.listeners[newaddr]
if !exists { if !exists {
// make new listener // make new listener
isTor := isTorListener(newaddr)
tlsConfig := tlsListeners[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 { if listenerErr != nil {
server.logger.Error("server", "couldn't listen on", newaddr, listenerErr.Error()) server.logger.Error("server", "couldn't listen on", newaddr, listenerErr.Error())
err = listenerErr err = listenerErr
continue continue
} }
server.listeners[newaddr] = listener server.listeners[newaddr] = listener
logListener(newaddr, tlsConfig) logListener(newaddr, tlsConfig, isTor)
} }
} }

View File

@ -33,6 +33,30 @@ server:
key: tls.key key: tls.key
cert: tls.crt 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 # strict transport security, to get clients to automagically use TLS
sts: sts:
# whether to advertise STS # whether to advertise STS