3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-10 22:19:31 +01:00

Merge pull request #430 from slingamn/tor.2

add Tor support (plus a few random changes)
This commit is contained in:
Daniel Oaks 2019-03-05 08:49:14 +00:00 committed by GitHub
commit e18bb864e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 330 additions and 86 deletions

View File

@ -32,8 +32,10 @@ _Copyright © 2018 Daniel Oaks <daniel@danieloaks.net>_
- Channel Modes
- Channel Prefixes
- Commands
- Integrating with other software
- Working with other software
- HOPM
- ZNC
- Tor
- Acknowledgements
@ -541,7 +543,7 @@ We may add some additional notes here for specific commands down the line, but r
--------------------------------------------------------------------------------------------
# Integrating with other software
# Working with other software
Oragono should interoperate with most IRC-based software, including bots. If you have problems getting your preferred software to work with Oragono, feel free to report it to us. If the root cause is a bug in Oragono, we'll fix it.
@ -601,6 +603,50 @@ 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`. Then 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
````
Tor provides end-to-end encryption for hidden services, so there's no need to enable TLS in Oragono for the listener (`127.0.0.2:6668` in this example). Doing so is not recommended, given the difficulty in obtaining a TLS certificate valid for an .onion address.
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, and the Tor daemon will run in the root namespace, Tor will be unable to connect to Oragono over loopback TCP. Instead, Oragono must listen on a named Unix domain socket that the Tor daemon can connect to. However, distributions typically package Tor with its own hardening profiles, which restrict which sockets it can access. 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
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,
languages: server.Languages().Default(),
loginThrottle: connection_limits.GenericThrottle{
Duration: config.Accounts.LoginThrottling.Duration,
@ -145,58 +147,73 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
history: history.NewHistoryBuffer(config.History.ClientLength),
}
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 client.isTor && 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 {
@ -315,7 +328,9 @@ func (client *Client) run() {
break
}
client.server.logger.Debug("userinput", client.nick, "<- ", line)
if client.server.logger.IsLoggingRawIO() {
client.server.logger.Debug("userinput", client.nick, "<- ", line)
}
// special-cased handling of PROXY protocol, see `handleProxyCommand` for details:
if firstLine {
@ -403,6 +418,11 @@ func (client *Client) tryResume() (success bool) {
return
}
if oldClient.isTor != client.isTor {
client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection from Tor to non-Tor or vice versa"))
return
}
err := server.clients.Resume(client, oldClient)
if err != nil {
client.Send(nil, server.name, "RESUME", "ERR", client.t("Cannot resume connection"))
@ -882,10 +902,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)

View File

@ -249,6 +249,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 {
@ -263,6 +272,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
@ -694,5 +704,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
}

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.
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 {
@ -61,13 +67,15 @@ func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool)
}
// given IP is sane! override the client's current IP
rawHostname := utils.LookupHostname(parsedProxiedIP.String())
ipstring := parsedProxiedIP.String()
client.server.logger.Info("localconnect-ip", "Accepted proxy IP for client", ipstring)
rawHostname := utils.LookupHostname(ipstring)
client.stateMutex.Lock()
defer client.stateMutex.Unlock()
client.proxiedIP = parsedProxiedIP
client.rawHostname = rawHostname
client.stateMutex.Unlock()
// nickmask will be updated when the client completes registration
// set tls info
client.certfp = ""
client.SetMode(modes.TLS, tls)

View File

@ -149,6 +149,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

View File

@ -1878,6 +1878,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))
@ -1924,7 +1929,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()
@ -2081,12 +2088,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 <target>{,<target>} <message>
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))
@ -2136,7 +2154,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()

View File

@ -52,7 +52,8 @@ func (rm *ResumeManager) GenerateToken(client *Client) (token string) {
}
// VerifyToken looks up the client corresponding to a resume token, returning
// nil if there is no such client or the token is invalid.
// nil if there is no such client or the token is invalid. If successful,
// the token is consumed and cannot be used to resume again.
func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
if len(token) != 2*utils.SecretTokenLength {
return
@ -68,6 +69,8 @@ func (rm *ResumeManager) VerifyToken(token string) (client *Client) {
// disallow resume of an unregistered client; this prevents the use of
// resume as an auth bypass
if pair.client.Registered() {
// consume the token, ensuring that at most one resume can succeed
delete(rm.resumeIDtoCreds, id)
return pair.client
}
}

View File

@ -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)
}

View File

@ -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"
@ -34,10 +33,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()
@ -57,6 +53,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
@ -91,6 +88,7 @@ type Server struct {
signals chan os.Signal
snomasks *SnoManager
store *buntdb.DB
torLimiter connection_limits.TorLimiter
whoWas *WhoWasList
stats *Stats
semaphores *ServerSemaphores
@ -108,6 +106,7 @@ var (
type clientConn struct {
Conn net.Conn
IsTLS bool
IsTor bool
}
// NewServer returns a new Oragono server.
@ -245,22 +244,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) {
@ -303,12 +307,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
@ -331,6 +346,7 @@ func (server *Server) createListener(addr string, tlsConfig *tls.Config, bindMod
wrapper := ListenerWrapper{
listener: listener,
tlsConfig: tlsConfig,
isTor: isTor,
shouldStop: false,
}
@ -342,10 +358,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 {
@ -355,6 +371,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)
@ -515,7 +532,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"))
@ -631,6 +648,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)
@ -908,9 +928,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),
)
}
@ -920,6 +940,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]
@ -935,13 +964,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()
@ -955,15 +987,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)
}
}

View File

@ -10,8 +10,8 @@ import (
)
var (
// standard b32 alphabet, but in lowercase for silly aesthetic reasons
b32encoder = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding)
// slingamn's own private b32 alphabet, removing 1, l, o, and 0
b32encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
)
const (

View File

@ -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