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:
commit
e18bb864e2
@ -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).
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
120
irc/client.go
120
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,
|
||||
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)
|
||||
|
@ -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
|
||||
}
|
||||
|
55
irc/connection_limits/tor.go
Normal file
55
irc/connection_limits/tor.go
Normal 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()
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
|
24
oragono.yaml
24
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
|
||||
|
Loading…
Reference in New Issue
Block a user