Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions conventional.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ server:
# "/hidden_service_sockets/oragono_tor_sock":
# tor: true

# Example of a WebSocket listener:
# ":4430":
# websocket: true
# tls:
# key: tls.key
# cert: tls.crt

# sets the permissions for Unix listen sockets. on a typical Linux system,
# the default is 0775 or 0755, which prevents other users/groups from connecting
# to the socket. With 0777, it behaves like a normal TCP socket
Expand Down Expand Up @@ -81,6 +88,15 @@ server:
# should clients include this STS policy when they ship their inbuilt preload lists?
preload: false

websockets:
# sets the Origin headers that will be accepted for websocket connections.
# an empty list means any value (or no value) is allowed. the main use of this
# is to prevent malicious third-party Javascript from co-opting non-malicious
# clients (i.e., mainstream browsers) to DDoS your server.
allowed-origins:
# - "https://oragono.io"
# - "https://*.oragono.io"

# casemapping controls what kinds of strings are permitted as identifiers (nicknames,
# channel names, account names, etc.), and how they are normalized for case.
# with the recommended default of 'precis', utf-8 identifiers that are "sane"
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-ldap/ldap/v3 v3.1.7
github.com/go-sql-driver/mysql v1.5.0
github.com/gorilla/websocket v1.4.2
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 // indirect
github.com/goshuirc/irc-go v0.0.0-20200311142257-57fd157327ac
github.com/onsi/ginkgo v1.12.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940 h1:KmRLPRstEJiE/9OjumKqI8Rccip8Qmyw2FwyTFxtVqs=
github.com/goshuirc/e-nfa v0.0.0-20160917075329-7071788e3940/go.mod h1:VOmrX6cmj7zwUeexC9HzznUdTIObHqIXUrWNYS+Ik7w=
github.com/goshuirc/irc-go v0.0.0-20190713001546-05ecc95249a0 h1:unxsR0de0MIS708eZI7lKa6HiP8FS0PhGCWwwEt9+vQ=
Expand Down
81 changes: 37 additions & 44 deletions irc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,26 +254,31 @@ type ClientDetails struct {
}

// RunClient sets up a new client and runs its goroutine.
func (server *Server) RunClient(conn clientConn, proxyLine string) {
func (server *Server) RunClient(conn IRCConn) {
proxiedConn := conn.UnderlyingConn()
var isBanned bool
var banMsg string
var realIP net.IP
if conn.Config.Tor {
realIP = utils.IPv4LoopbackAddress
realIP := utils.AddrToIP(proxiedConn.RemoteAddr())
var proxiedIP net.IP
if proxiedConn.Config.Tor {
// cover up details of the tor proxying infrastructure (not a user privacy concern,
// but a hardening measure):
proxiedIP = utils.IPv4LoopbackAddress
isBanned, banMsg = server.checkTorLimits()
} else {
realIP = utils.AddrToIP(conn.Conn.RemoteAddr())
// skip the ban check for k8s-style proxy-before-TLS
if proxyLine == "" {
isBanned, banMsg = server.checkBans(realIP)
ipToCheck := realIP
if proxiedConn.ProxiedIP != nil {
proxiedIP = proxiedConn.ProxiedIP
ipToCheck = proxiedIP
}
isBanned, banMsg = server.checkBans(ipToCheck)
}

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()
conn.Write([]byte(fmt.Sprintf(errorMsg, banMsg)))
conn.Close()
return
}

Expand All @@ -282,13 +287,13 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
now := time.Now().UTC()
config := server.Config()
// give them 1k of grace over the limit:
socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes)
socket := NewSocket(conn, config.Server.MaxSendQBytes)
client := &Client{
lastSeen: now,
lastActive: now,
channels: make(ChannelSet),
ctime: now,
isSTSOnly: conn.Config.STSOnly,
isSTSOnly: proxiedConn.Config.STSOnly,
languages: server.Languages().Default(),
loginThrottle: connection_limits.GenericThrottle{
Duration: config.Accounts.LoginThrottling.Duration,
Expand All @@ -299,6 +304,8 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
nick: "*", // * is used until actual nick is given
nickCasefolded: "*",
nickMaskString: "*", // * is used until actual nick is given
realIP: realIP,
proxiedIP: proxiedIP,
}
client.writerSemaphore.Initialize(1)
client.history.Initialize(config.History.ClientLength, config.History.AutoresizeWindow)
Expand All @@ -311,7 +318,8 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
ctime: now,
lastActive: now,
realIP: realIP,
isTor: conn.Config.Tor,
proxiedIP: proxiedIP,
isTor: proxiedConn.Config.Tor,
}
client.sessions = []*Session{session}

Expand All @@ -322,34 +330,28 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
client.SetMode(defaultMode, true)
}

if conn.Config.TLSConfig != nil {
if proxiedConn.Config.TLSConfig != nil {
client.SetMode(modes.TLS, true)
// error is not useful to us here anyways so we can ignore it
session.certfp, _ = socket.CertFP()
session.certfp, _ = utils.GetCertFP(proxiedConn.Conn, RegisterTimeout)
}

if conn.Config.Tor {
if session.isTor {
client.SetMode(modes.TLS, true)
// cover up details of the tor proxying infrastructure (not a user privacy concern,
// but a hardening measure):
session.proxiedIP = utils.IPv4LoopbackAddress
client.proxiedIP = session.proxiedIP
session.rawHostname = config.Server.TorListeners.Vhost
client.rawHostname = session.rawHostname
} else {
remoteAddr := conn.Conn.RemoteAddr()
if realIP.IsLoopback() || utils.IPInNets(realIP, config.Server.secureNets) {
// treat local connections as secure (may be overridden later by WEBIRC)
client.SetMode(modes.TLS, true)
}
if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
client.doIdentLookup(conn.Conn)
if config.Server.CheckIdent {
client.doIdentLookup(proxiedConn.Conn)
}
}
client.realIP = session.realIP

server.stats.Add()
client.run(session, proxyLine)
client.run(session)
}

func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
Expand Down Expand Up @@ -476,21 +478,19 @@ func (client *Client) lookupHostname(session *Session, overwrite bool) {
}

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())
localTCPAddr, ok := conn.LocalAddr().(*net.TCPAddr)
if !ok {
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())
serverPort := localTCPAddr.Port
remoteTCPAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
if !ok {
return
}
clientPort, _ := strconv.Atoi(clientPortString)
clientPort := remoteTCPAddr.Port

client.Notice(client.t("*** Looking up your username"))
resp, err := ident.Query(clientHost, serverPort, clientPort, IdentTimeoutSeconds)
resp, err := ident.Query(remoteTCPAddr.IP.String(), serverPort, clientPort, IdentTimeoutSeconds)
if err == nil {
err := client.SetNames(resp.Identifier, "", true)
if err == nil {
Expand Down Expand Up @@ -567,7 +567,7 @@ func (client *Client) t(originalString string) string {

// main client goroutine: read lines and execute the corresponding commands
// `proxyLine` is the PROXY-before-TLS line, if there was one
func (client *Client) run(session *Session, proxyLine string) {
func (client *Client) run(session *Session) {

defer func() {
if r := recover(); r != nil {
Expand Down Expand Up @@ -601,14 +601,7 @@ func (client *Client) run(session *Session, proxyLine string) {
firstLine := !isReattach

for {
var line string
var err error
if proxyLine == "" {
line, err = session.socket.Read()
} else {
line = proxyLine // pretend we're just now receiving the proxy-before-TLS line
proxyLine = ""
}
line, err := session.socket.Read()
if err != nil {
quitMessage := "connection closed"
if err == errReadQ {
Expand Down Expand Up @@ -681,7 +674,7 @@ func (client *Client) run(session *Session, proxyLine string) {
break
} else if session.client != client {
// bouncer reattach
go session.client.run(session, "")
go session.client.run(session)
break
}
}
Expand Down
63 changes: 39 additions & 24 deletions irc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,10 @@ type TLSListenConfig struct {

// This is the YAML-deserializable type of the value of the `Server.Listeners` map
type listenerConfigBlock struct {
TLS TLSListenConfig
Tor bool
STSOnly bool `yaml:"sts-only"`
}

// listenerConfig is the config governing a particular listener (bound address),
// in particular whether it has TLS or Tor (or both) enabled.
type listenerConfig struct {
TLSConfig *tls.Config
Tor bool
STSOnly bool
ProxyBeforeTLS bool
TLS TLSListenConfig
Tor bool
STSOnly bool `yaml:"sts-only"`
WebSocket bool
}

type PersistentStatus uint
Expand Down Expand Up @@ -486,8 +478,12 @@ type Config struct {
Listeners map[string]listenerConfigBlock
UnixBindMode os.FileMode `yaml:"unix-bind-mode"`
TorListeners TorListenersConfig `yaml:"tor-listeners"`
WebSockets struct {
AllowedOrigins []string `yaml:"allowed-origins"`
allowedOriginRegexps []*regexp.Regexp
}
// they get parsed into this internal representation:
trueListeners map[string]listenerConfig
trueListeners map[string]utils.ListenerConfig
STS STSConfig
LookupHostnames *bool `yaml:"lookup-hostnames"`
lookupHostnames bool
Expand Down Expand Up @@ -747,14 +743,22 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error
return operators, nil
}

func loadTlsConfig(config TLSListenConfig) (tlsConfig *tls.Config, err error) {
func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Config, err error) {
cert, err := tls.LoadX509KeyPair(config.Cert, config.Key)
if err != nil {
return nil, ErrInvalidCertKeyPair
}
clientAuth := tls.RequestClientCert
if webSocket {
// if Chrome receives a server request for a client certificate
// on a websocket connection, it will immediately disconnect:
// https://bugs.chromium.org/p/chromium/issues/detail?id=329884
// work around this behavior:
clientAuth = tls.NoClientCert
}
result := tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert,
ClientAuth: clientAuth,
}
return &result, nil
}
Expand All @@ -765,22 +769,24 @@ func (conf *Config) prepareListeners() (err error) {
return fmt.Errorf("No listeners were configured")
}

conf.Server.trueListeners = make(map[string]listenerConfig)
conf.Server.trueListeners = make(map[string]utils.ListenerConfig)
for addr, block := range conf.Server.Listeners {
var lconf listenerConfig
var lconf utils.ListenerConfig
lconf.ProxyDeadline = time.Minute
lconf.Tor = block.Tor
lconf.STSOnly = block.STSOnly
if lconf.STSOnly && !conf.Server.STS.Enabled {
return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr)
}
if block.TLS.Cert != "" {
tlsConfig, err := loadTlsConfig(block.TLS)
tlsConfig, err := loadTlsConfig(block.TLS, block.WebSocket)
if err != nil {
return err
}
lconf.TLSConfig = tlsConfig
lconf.ProxyBeforeTLS = block.TLS.Proxy
lconf.RequireProxy = block.TLS.Proxy
}
lconf.WebSocket = block.WebSocket
conf.Server.trueListeners[addr] = lconf
}
return nil
Expand Down Expand Up @@ -846,6 +852,14 @@ func LoadConfig(filename string) (config *Config, err error) {
return nil, fmt.Errorf("failed to prepare listeners: %v", err)
}

for _, glob := range config.Server.WebSockets.AllowedOrigins {
globre, err := utils.CompileGlob(glob)
if err != nil {
return nil, fmt.Errorf("invalid websocket allowed-origin expression: %s", glob)
}
config.Server.WebSockets.allowedOriginRegexps = append(config.Server.WebSockets.allowedOriginRegexps, globre)
}

if config.Server.STS.Enabled {
if config.Server.STS.Port < 0 || config.Server.STS.Port > 65535 {
return nil, fmt.Errorf("STS port is incorrect, should be 0 if disabled: %d", config.Server.STS.Port)
Expand Down Expand Up @@ -1203,6 +1217,11 @@ func (config *Config) Diff(oldConfig *Config) (addedCaps, removedCaps *caps.Set)
}

func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard, folded *regexp.Regexp, err error) {
standard, err = utils.CompileGlob(guestFormat)
if err != nil {
return
}

starIndex := strings.IndexByte(guestFormat, '*')
if starIndex == -1 {
return nil, nil, errors.New("guest format must contain exactly one *")
Expand All @@ -1212,10 +1231,6 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
if strings.IndexByte(final, '*') != -1 {
return nil, nil, errors.New("guest format must contain exactly one *")
}
standard, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initial, final))
if err != nil {
return
}
initialFolded, err := casefoldWithSetting(initial, casemapping)
if err != nil {
return
Expand All @@ -1224,6 +1239,6 @@ func compileGuestRegexp(guestFormat string, casemapping Casemapping) (standard,
if err != nil {
return
}
folded, err = regexp.Compile(fmt.Sprintf("^%s(.*)%s$", initialFolded, finalFolded))
folded, err = utils.CompileGlob(fmt.Sprintf("%s*%s", initialFolded, finalFolded))
return
}
Loading