diff --git a/irc/commands.go b/irc/commands.go index 429bd6f0..e2fdc5b6 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -276,6 +276,11 @@ var Commands = map[string]Command{ handler: versionHandler, minParams: 0, }, + "WEBIRC": { + handler: webircHandler, + usablePreReg: true, + minParams: 4, + }, "WHO": { handler: whoHandler, minParams: 0, diff --git a/irc/config.go b/irc/config.go index 6eaa2be4..905ff432 100644 --- a/irc/config.go +++ b/irc/config.go @@ -158,9 +158,10 @@ type Config struct { STS STSConfig CheckIdent bool `yaml:"check-ident"` MOTD string - MOTDFormatting bool `yaml:"motd-formatting"` - ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` - MaxSendQString string `yaml:"max-sendq"` + MOTDFormatting bool `yaml:"motd-formatting"` + ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + WebIRC []webircConfig `yaml:"webirc"` + MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes uint64 ConnectionLimiter connection_limits.LimiterConfig `yaml:"connection-limits"` ConnectionThrottler connection_limits.ThrottlerConfig `yaml:"connection-throttling"` @@ -393,6 +394,22 @@ func LoadConfig(filename string) (config *Config, err error) { return nil, fmt.Errorf("Could not parse connection-throttle ban-duration: %s", err.Error()) } } + // process webirc blocks + var newWebIRC []webircConfig + for _, webirc := range config.Server.WebIRC { + // skip webirc blocks with no hosts (such as the example one) + if len(webirc.hosts) == 0 { + continue + } + + err = webirc.ProcessPassword() + if err != nil { + return nil, fmt.Errorf("Could not parse WebIRC config: %s", err.Error()) + } + newWebIRC = append(newWebIRC, webirc) + } + config.Server.WebIRC = newWebIRC + // process limits if config.Limits.LineLen.Tags < 512 || config.Limits.LineLen.Rest < 512 { return nil, errors.New("Line lengths must be 512 or greater (check the linelen section under server->limits)") } diff --git a/irc/gateways.go b/irc/gateways.go new file mode 100644 index 00000000..03cd3a0e --- /dev/null +++ b/irc/gateways.go @@ -0,0 +1,103 @@ +// Copyright (c) 2012-2014 Jeremy Latt +// Copyright (c) 2014-2015 Edmund Huber +// Copyright (c) 2017 Daniel Oaks +// released under the MIT license + +package irc + +import ( + "fmt" + "net" + + "github.com/oragono/oragono/irc/passwd" + + "github.com/goshuirc/irc-go/ircmsg" + "github.com/oragono/oragono/irc/utils" +) + +type webircConfig struct { + passwordString string `yaml:"password"` + password []byte `yaml:"password-bytes"` + hosts []string +} + +// ProcessPassword populates our password. +func (wc *webircConfig) ProcessPassword() error { + password, error := passwd.DecodePasswordHash(wc.passwordString) + wc.password = password + return error +} + +// WEBIRC password gateway hostname ip +func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { + // only allow unregistered clients to use this command + if client.registered { + return false + } + + clientAddress := utils.IPString(client.socket.conn.RemoteAddr()) + clientHostname := client.hostname + server.configurableStateMutex.RLock() + defer server.configurableStateMutex.RUnlock() + for _, info := range server.webirc { + for _, address := range info.hosts { + if clientHostname == address || clientAddress == address { + // confirm password + givenPassword := msg.Params[0] + if passwd.ComparePasswordString(info.password, givenPassword) == nil { + proxiedIP := msg.Params[3] + + return client.ApplyProxiedIP(proxiedIP) + } + } + } + } + + client.Quit("WEBIRC command is not usable from your address or incorrect password given") + return true +} + +// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT +// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt +func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { + // only allow unregistered clients to use this command + if client.registered { + return false + } + + clientAddress := utils.IPString(client.socket.conn.RemoteAddr()) + clientHostname := client.hostname + server.configurableStateMutex.RLock() + defer server.configurableStateMutex.RUnlock() + for _, address := range server.proxyAllowedFrom { + if clientHostname == address || clientAddress == address { + proxiedIP := msg.Params[1] + + return client.ApplyProxiedIP(proxiedIP) + } + } + client.Quit("PROXY command is not usable from your address") + return true +} + +// ApplyProxiedIP applies the given IP to the client. +func (client *Client) ApplyProxiedIP(proxiedIP string) (exiting bool) { + // ensure IP is sane + parsedProxiedIP := net.ParseIP(proxiedIP) + if parsedProxiedIP == nil { + client.Quit(fmt.Sprintf("Proxied IP address is not valid: [%s]", proxiedIP)) + return true + } + + isBanned, banMsg := client.server.checkBans(parsedProxiedIP) + if isBanned { + client.Quit(banMsg) + return true + } + + // given IP is sane! override the client's current IP + client.proxiedIP = proxiedIP + client.rawHostname = utils.LookupHostname(proxiedIP) + client.hostname = client.rawHostname + return false +} diff --git a/irc/help.go b/irc/help.go index a83b1711..148d07a9 100644 --- a/irc/help.go +++ b/irc/help.go @@ -460,6 +460,14 @@ Shows information about the given users. Takes up to 10 nicknames.`, text: `VERSION [server] Views the version of software and the RPL_ISUPPORT tokens for the given server.`, + }, + "webirc": { + oper: true, // not really, but it's restricted anyways + text: `WEBIRC + +Used by web<->IRC gateways and bouncers, the WEBIRC command allows gateways to +pass-through the real IP addresses of clients: +ircv3.net/specs/extensions/webirc.html`, }, "who": { text: `WHO [o] diff --git a/irc/passwd/unsalted.go b/irc/passwd/unsalted.go index bedb2228..0c8478e1 100644 --- a/irc/passwd/unsalted.go +++ b/irc/passwd/unsalted.go @@ -43,3 +43,8 @@ func DecodePasswordHash(encoded string) (decoded []byte, err error) { func ComparePassword(hash, password []byte) error { return bcrypt.CompareHashAndPassword(hash, password) } + +// ComparePasswordString compares a given password string with the given hash. +func ComparePasswordString(hash []byte, password string) error { + return ComparePassword(hash, []byte(password)) +} diff --git a/irc/server.go b/irc/server.go index 5e92034f..d4eff747 100644 --- a/irc/server.go +++ b/irc/server.go @@ -118,6 +118,7 @@ type Server struct { snomasks *SnoManager store *buntdb.DB stsEnabled bool + webirc []webircConfig whoWas *WhoWasList } @@ -1260,6 +1261,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error { } server.configurableStateMutex.Unlock() + // apply new WebIRC command restrictions + server.webirc = config.Server.WebIRC + // apply new PROXY command restrictions server.proxyAllowedFrom = config.Server.ProxyAllowedFrom @@ -2158,41 +2162,3 @@ func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool return false } - -// PROXY TCP4/6 SOURCEIP DESTIP SOURCEPORT DESTPORT -// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt -func proxyHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool { - // only allow unregistered clients to use this command - if client.registered { - return false - } - - clientAddress := utils.IPString(client.socket.conn.RemoteAddr()) - clientHostname := client.hostname - for _, address := range server.proxyAllowedFrom { - if clientHostname == address || clientAddress == address { - proxiedIP := msg.Params[1] - - // ensure IP is sane - parsedProxiedIP := net.ParseIP(proxiedIP) - if parsedProxiedIP == nil { - client.Quit(fmt.Sprintf("Proxied IP address is not valid: [%s]", proxiedIP)) - return true - } - - isBanned, banMsg := server.checkBans(parsedProxiedIP) - if isBanned { - client.Quit(banMsg) - return true - } - - // override the client's regular IP - client.proxiedIP = msg.Params[1] - client.rawHostname = utils.LookupHostname(msg.Params[1]) - client.hostname = client.rawHostname - return false - } - } - client.Quit("PROXY command is not usable from your address") - return true -} diff --git a/oragono.yaml b/oragono.yaml index b2a35817..1ab2c636 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -65,6 +65,19 @@ server: # - localhost # - "127.0.0.1" + # controls the use of the WEBIRC command (by IRC<->web interfaces, bouncers and similar) + webirc: + # one webirc block -- should correspond to one set of gateways + - + # password the gateway uses to connect, made with oragono genpasswd + password: JDJhJDA0JG9rTTVERlNRa0hpOEZpNkhjZE95SU9Da1BseFdlcWtOTEQxNEFERVlqbEZNTkdhOVlYUkMu + + # hosts that can use this webirc command + hosts: + # - localhost + # - "127.0.0.1" + # - "0::1" + # maximum length of clients' sendQ in bytes # this should be big enough to hold /LIST and HELP replies max-sendq: 16k