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:
parent
d43ce07b66
commit
b0f89062fa
@ -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).
|
||||||
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
111
irc/client.go
111
irc/client.go
@ -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,58 +147,73 @@ 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 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"))
|
if conn.IsTor {
|
||||||
resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
|
client.SetMode(modes.TLS, true)
|
||||||
if err == nil {
|
client.realIP = utils.IPv4LoopbackAddress
|
||||||
err := client.SetNames(resp.Identifier, "", true)
|
client.rawHostname = config.Server.TorListeners.Vhost
|
||||||
if err == nil {
|
} else {
|
||||||
client.Notice(client.t("*** Found your username"))
|
remoteAddr := conn.Conn.RemoteAddr()
|
||||||
// we don't need to updateNickMask here since nickMask is not used for anything yet
|
client.realIP = utils.AddrToIP(remoteAddr)
|
||||||
} else {
|
// Set the hostname for this client
|
||||||
client.Notice(client.t("*** Got a malformed username, ignoring"))
|
// (may be overridden by a later PROXY command from stunnel)
|
||||||
}
|
client.rawHostname = utils.LookupHostname(client.realIP.String())
|
||||||
} else {
|
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
|
||||||
client.Notice(client.t("*** Could not find your username"))
|
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 {
|
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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
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.
|
// 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 {
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
if isBanned {
|
ipaddr = utils.IPv4LoopbackAddress
|
||||||
// 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
|
isBanned, banMsg = server.checkTorLimits()
|
||||||
conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
|
} else {
|
||||||
conn.Conn.Close()
|
ipaddr = utils.AddrToIP(conn.Conn.RemoteAddr())
|
||||||
return
|
isBanned, banMsg = server.checkBans(ipaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isBanned {
|
||||||
|
// this might not show up properly on some clients, but our objective here is just to close the connection out before it has a load impact on us
|
||||||
|
conn.Conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
|
||||||
|
conn.Conn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
oragono.yaml
24
oragono.yaml
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user