diff --git a/irc/accounts.go b/irc/accounts.go index 48c64445..061caeec 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -456,7 +456,7 @@ func (am *AccountManager) setPassword(account string, password string) (err erro } func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) { - if callbackNamespace == "*" || callbackNamespace == "none" { + if callbackNamespace == "*" || callbackNamespace == "none" || callbackNamespace == "admin" { return "", nil } else if callbackNamespace == "mailto" { return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue) @@ -590,7 +590,9 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er if err != nil { return err } - am.Login(client, clientAccount) + if client != nil { + am.Login(client, clientAccount) + } return nil } diff --git a/irc/client.go b/irc/client.go index ccc9562b..19068fec 100644 --- a/irc/client.go +++ b/irc/client.go @@ -32,10 +32,6 @@ const ( IRCv3TimestampFormat = "2006-01-02T15:04:05.000Z" ) -var ( - LoopbackIP = net.ParseIP("127.0.0.1") -) - // ResumeDetails is a place to stash data at various stages of // the resume process: when handling the RESUME command itself, // when completing the registration, and when rejoining channels. @@ -55,7 +51,6 @@ type Client struct { account string accountName string // display name of the account: uncasefolded, '*' if not logged in atime time.Time - authorized bool awayMessage string capabilities *caps.Set capState caps.State @@ -88,12 +83,14 @@ type Client struct { quitMessage string rawHostname string realname string + realIP net.IP registered bool resumeDetails *ResumeDetails resumeToken string saslInProgress bool saslMechanism string saslValue string + sentPassCommand bool server *Server skeleton string socket *Socket @@ -130,7 +127,6 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes) client := &Client{ atime: now, - authorized: server.Password() == nil, capabilities: caps.NewSet(), capState: caps.NoneState, capVersion: caps.Cap301, @@ -151,6 +147,12 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { } 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() if isTLS { client.SetMode(modes.TLS, true) @@ -158,7 +160,7 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { // error is not useful to us here anyways so we can ignore it client.certfp, _ = client.socket.CertFP() } - if config.Server.CheckIdent && !utils.AddrIsUnix(conn.RemoteAddr()) { + 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()) @@ -189,6 +191,16 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) { go client.run() } +func (client *Client) isAuthorized(config *Config) bool { + saslSent := client.account != "" + passRequirementMet := (config.Server.passwordBytes == nil) || client.sentPassCommand || (config.Accounts.SkipServerPassword && saslSent) + if !passRequirementMet { + return false + } + saslRequirementMet := !config.Accounts.RequireSasl.Enabled || saslSent || utils.IPInNets(client.IP(), config.Accounts.RequireSasl.exemptedNets) + return saslRequirementMet +} + func (client *Client) resetFakelag() { fakelag := func() *Fakelag { if client.HasRoleCapabs("nofakelag") { @@ -211,14 +223,13 @@ func (client *Client) resetFakelag() { // IP returns the IP address of this client. func (client *Client) IP() net.IP { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + if client.proxiedIP != nil { return client.proxiedIP } - if ip := utils.AddrToIP(client.socket.conn.RemoteAddr()); ip != nil { - return ip - } - // unix domain socket that hasn't issued PROXY/WEBIRC yet. YOLO - return LoopbackIP + return client.realIP } // IPString returns the IP address of this client as a string. @@ -289,7 +300,7 @@ func (client *Client) run() { // Set the hostname for this client // (may be overridden by a later PROXY command from stunnel) - client.rawHostname = utils.AddrLookupHostname(client.socket.conn.RemoteAddr()) + client.rawHostname = utils.LookupHostname(client.realIP.String()) firstLine := true diff --git a/irc/commands.go b/irc/commands.go index af2ee92b..ec098f0d 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -54,7 +54,9 @@ func (cmd *Command) Run(server *Server, client *Client, msg ircmsg.IrcMessage) b } // most servers do this only for PING/PONG, but we'll do it for any command: - client.idletimer.Touch() + if client.registered { + client.idletimer.Touch() + } if !cmd.leaveClientIdle { client.Active() diff --git a/irc/config.go b/irc/config.go index bf7db64c..60870a9d 100644 --- a/irc/config.go +++ b/irc/config.go @@ -11,6 +11,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "os" "path/filepath" "regexp" @@ -54,7 +55,12 @@ func (conf *TLSListenConfig) Config() (*tls.Config, error) { type AccountConfig struct { Registration AccountRegistrationConfig AuthenticationEnabled bool `yaml:"authentication-enabled"` - LoginThrottling struct { + RequireSasl struct { + Enabled bool + Exempted []string + exemptedNets []net.IPNet + } `yaml:"require-sasl"` + LoginThrottling struct { Enabled bool Duration time.Duration MaxAttempts int `yaml:"max-attempts"` @@ -261,8 +267,9 @@ type Config struct { STS STSConfig CheckIdent bool `yaml:"check-ident"` MOTD string - MOTDFormatting bool `yaml:"motd-formatting"` - ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + MOTDFormatting bool `yaml:"motd-formatting"` + ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` + proxyAllowedFromNets []net.IPNet WebIRC []webircConfig `yaml:"webirc"` MaxSendQString string `yaml:"max-sendq"` MaxSendQBytes int @@ -595,6 +602,16 @@ func LoadConfig(filename string) (config *Config, err error) { } } + config.Accounts.RequireSasl.exemptedNets, err = utils.ParseNetList(config.Accounts.RequireSasl.Exempted) + if err != nil { + return nil, fmt.Errorf("Could not parse require-sasl exempted nets: %v", err.Error()) + } + + config.Server.proxyAllowedFromNets, err = utils.ParseNetList(config.Server.ProxyAllowedFrom) + if err != nil { + return nil, fmt.Errorf("Could not parse proxy-allowed-from nets: %v", err.Error()) + } + rawRegexp := config.Accounts.VHosts.ValidRegexpRaw if rawRegexp != "" { regexp, err := regexp.Compile(rawRegexp) diff --git a/irc/connection_limits/limiter.go b/irc/connection_limits/limiter.go index 0a4e3813..d7572a94 100644 --- a/irc/connection_limits/limiter.go +++ b/irc/connection_limits/limiter.go @@ -8,6 +8,8 @@ import ( "fmt" "net" "sync" + + "github.com/oragono/oragono/irc/utils" ) // LimiterConfig controls the automated connection limits. @@ -36,23 +38,18 @@ type Limiter struct { // population holds IP -> count of clients connected from there population map[string]int - // exemptedIPs holds IPs that are exempt from limits - exemptedIPs map[string]bool // exemptedNets holds networks that are exempt from limits exemptedNets []net.IPNet } -// maskAddr masks the given IPv4/6 address with our cidr limit masks. -func (cl *Limiter) maskAddr(addr net.IP) net.IP { - if addr.To4() == nil { - // IPv6 addr - addr = addr.Mask(cl.ipv6Mask) +// addrToKey canonicalizes `addr` to a string key. +func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string { + if addr.To4() != nil { + addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us } else { - // IPv4 addr - addr = addr.Mask(cl.ipv4Mask) + addr = addr.Mask(v6Mask) } - - return addr + return addr.String() } // AddClient adds a client to our population if possible. If we can't, throws an error instead. @@ -67,18 +64,12 @@ func (cl *Limiter) AddClient(addr net.IP, force bool) error { // check exempted lists // we don't track populations for exempted addresses or nets - this is by design - if cl.exemptedIPs[addr.String()] { + if utils.IPInNets(addr, cl.exemptedNets) { return nil } - for _, ex := range cl.exemptedNets { - if ex.Contains(addr) { - return nil - } - } // check population - cl.maskAddr(addr) - addrString := addr.String() + addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask) if cl.population[addrString]+1 > cl.subnetLimit && !force { return errTooManyClients @@ -98,7 +89,7 @@ func (cl *Limiter) RemoveClient(addr net.IP) { return } - addrString := addr.String() + addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask) cl.population[addrString] = cl.population[addrString] - 1 // safety limiter @@ -121,21 +112,9 @@ func NewLimiter() *Limiter { // ApplyConfig atomically applies a config update to a connection limit handler func (cl *Limiter) ApplyConfig(config LimiterConfig) error { // assemble exempted nets - exemptedIPs := make(map[string]bool) - var exemptedNets []net.IPNet - for _, cidr := range config.Exempted { - ipaddr := net.ParseIP(cidr) - _, netaddr, err := net.ParseCIDR(cidr) - - if ipaddr == nil && err != nil { - return fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) - } - - if ipaddr != nil { - exemptedIPs[ipaddr.String()] = true - } else { - exemptedNets = append(exemptedNets, *netaddr) - } + exemptedNets, err := utils.ParseNetList(config.Exempted) + if err != nil { + return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error()) } cl.Lock() @@ -151,7 +130,6 @@ func (cl *Limiter) ApplyConfig(config LimiterConfig) error { if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 { cl.subnetLimit = config.IPsPerSubnet } - cl.exemptedIPs = exemptedIPs cl.exemptedNets = exemptedNets return nil diff --git a/irc/connection_limits/throttler.go b/irc/connection_limits/throttler.go index 505f08a5..e45aabc4 100644 --- a/irc/connection_limits/throttler.go +++ b/irc/connection_limits/throttler.go @@ -8,6 +8,8 @@ import ( "net" "sync" "time" + + "github.com/oragono/oragono/irc/utils" ) // ThrottlerConfig controls the automated connection throttling. @@ -82,25 +84,10 @@ type Throttler struct { banDuration time.Duration banMessage string - // exemptedIPs holds IPs that are exempt from limits - exemptedIPs map[string]bool // exemptedNets holds networks that are exempt from limits exemptedNets []net.IPNet } -// maskAddr masks the given IPv4/6 address with our cidr limit masks. -func (ct *Throttler) maskAddr(addr net.IP) net.IP { - if addr.To4() == nil { - // IPv6 addr - addr = addr.Mask(ct.ipv6Mask) - } else { - // IPv4 addr - addr = addr.Mask(ct.ipv4Mask) - } - - return addr -} - // ResetFor removes any existing count for the given address. func (ct *Throttler) ResetFor(addr net.IP) { ct.Lock() @@ -111,8 +98,7 @@ func (ct *Throttler) ResetFor(addr net.IP) { } // remove - ct.maskAddr(addr) - addrString := addr.String() + addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask) delete(ct.population, addrString) } @@ -126,18 +112,12 @@ func (ct *Throttler) AddClient(addr net.IP) error { } // check exempted lists - if ct.exemptedIPs[addr.String()] { + if utils.IPInNets(addr, ct.exemptedNets) { return nil } - for _, ex := range ct.exemptedNets { - if ex.Contains(addr) { - return nil - } - } // check throttle - ct.maskAddr(addr) - addrString := addr.String() + addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask) details := ct.population[addrString] // retrieve mutable throttle state from the map // add in constant state to process the limiting operation @@ -184,21 +164,9 @@ func NewThrottler() *Throttler { // ApplyConfig atomically applies a config update to a throttler func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error { // assemble exempted nets - exemptedIPs := make(map[string]bool) - var exemptedNets []net.IPNet - for _, cidr := range config.Exempted { - ipaddr := net.ParseIP(cidr) - _, netaddr, err := net.ParseCIDR(cidr) - - if ipaddr == nil && err != nil { - return fmt.Errorf("Could not parse exempted IP/network [%s]", cidr) - } - - if ipaddr != nil { - exemptedIPs[ipaddr.String()] = true - } else { - exemptedNets = append(exemptedNets, *netaddr) - } + exemptedNets, err := utils.ParseNetList(config.Exempted) + if err != nil { + return fmt.Errorf("Could not parse throttle exemption list: %v", err.Error()) } ct.Lock() @@ -211,7 +179,6 @@ func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error { ct.duration = config.Duration ct.banDuration = config.BanDuration ct.banMessage = config.BanMessage - ct.exemptedIPs = exemptedIPs ct.exemptedNets = exemptedNets return nil diff --git a/irc/connection_limits/throttler_test.go b/irc/connection_limits/throttler_test.go index b7862375..c31c4276 100644 --- a/irc/connection_limits/throttler_test.go +++ b/irc/connection_limits/throttler_test.go @@ -62,25 +62,59 @@ func TestGenericThrottleDisabled(t *testing.T) { } } -func TestConnectionThrottle(t *testing.T) { +func makeTestThrottler(v4len, v6len int) *Throttler { minute, _ := time.ParseDuration("1m") maxConnections := 3 config := ThrottlerConfig{ Enabled: true, - CidrLenIPv4: 32, - CidrLenIPv6: 64, + CidrLenIPv4: v4len, + CidrLenIPv6: v6len, ConnectionsPerCidr: maxConnections, Duration: minute, } throttler := NewThrottler() throttler.ApplyConfig(config) + return throttler +} +func TestConnectionThrottle(t *testing.T) { + throttler := makeTestThrottler(32, 64) addr := net.ParseIP("8.8.8.8") - for i := 0; i < maxConnections; i += 1 { + for i := 0; i < 3; i += 1 { err := throttler.AddClient(addr) assertEqual(err, nil, t) } err := throttler.AddClient(addr) assertEqual(err, errTooManyClients, t) } + +func TestConnectionThrottleIPv6(t *testing.T) { + throttler := makeTestThrottler(32, 64) + + var err error + err = throttler.AddClient(net.ParseIP("2001:0db8::1")) + assertEqual(err, nil, t) + err = throttler.AddClient(net.ParseIP("2001:0db8::2")) + assertEqual(err, nil, t) + err = throttler.AddClient(net.ParseIP("2001:0db8::3")) + assertEqual(err, nil, t) + + err = throttler.AddClient(net.ParseIP("2001:0db8::4")) + assertEqual(err, errTooManyClients, t) +} + +func TestConnectionThrottleIPv4(t *testing.T) { + throttler := makeTestThrottler(24, 64) + + var err error + err = throttler.AddClient(net.ParseIP("192.168.1.101")) + assertEqual(err, nil, t) + err = throttler.AddClient(net.ParseIP("192.168.1.102")) + assertEqual(err, nil, t) + err = throttler.AddClient(net.ParseIP("192.168.1.103")) + assertEqual(err, nil, t) + + err = throttler.AddClient(net.ParseIP("192.168.1.104")) + assertEqual(err, errTooManyClients, t) +} diff --git a/irc/gateways.go b/irc/gateways.go index 10ec5f89..719ae364 100644 --- a/irc/gateways.go +++ b/irc/gateways.go @@ -25,6 +25,7 @@ type webircConfig struct { Password []byte `yaml:"password-bytes"` Fingerprint string Hosts []string + allowedNets []net.IPNet } // Populate fills out our password or fingerprint. @@ -36,49 +37,23 @@ func (wc *webircConfig) Populate() (err error) { if wc.PasswordString != "" { wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString) } + + if err == nil { + wc.allowedNets, err = utils.ParseNetList(wc.Hosts) + } + return err } -func isGatewayAllowed(addr net.Addr, gatewaySpec string) bool { - // "localhost" includes any loopback IP or unix domain socket - if gatewaySpec == "localhost" { - return utils.AddrIsLocal(addr) - } - - ip := utils.AddrToIP(addr) - if ip == nil { - return false - } - - // exact IP match - if ip.String() == gatewaySpec { - return true - } - - // CIDR match - _, gatewayNet, err := net.ParseCIDR(gatewaySpec) - if err != nil { - return false - } - return gatewayNet.Contains(ip) -} - // ApplyProxiedIP applies the given IP to the client. func (client *Client) ApplyProxiedIP(proxiedIP string, tls bool) (success bool) { // ensure IP is sane - parsedProxiedIP := net.ParseIP(proxiedIP) + parsedProxiedIP := net.ParseIP(proxiedIP).To16() if parsedProxiedIP == nil { client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP)) return false } - // undo any mapping of v4 addresses into the v6 space: https://stackoverflow.com/a/1618259 - // this is how a typical stunnel4 deployment on Linux will handle dual-stack - unmappedIP := parsedProxiedIP.To4() - if unmappedIP != nil { - parsedProxiedIP = unmappedIP - } - isBanned, banMsg := client.server.checkBans(parsedProxiedIP) if isBanned { client.Quit(banMsg) @@ -117,14 +92,12 @@ func handleProxyCommand(server *Server, client *Client, line string) (err error) return errBadProxyLine } - for _, gateway := range server.ProxyAllowedFrom() { - if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { - // assume PROXY connections are always secure - if client.ApplyProxiedIP(params[2], true) { - return nil - } else { - return errBadProxyLine - } + if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) { + // assume PROXY connections are always secure + if client.ApplyProxiedIP(params[2], true) { + return nil + } else { + return errBadProxyLine } } diff --git a/irc/getters.go b/irc/getters.go index 99878a26..ff3f8c85 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -32,14 +32,6 @@ func (server *Server) RecoverFromErrors() bool { return *server.Config().Debug.RecoverFromErrors } -func (server *Server) ProxyAllowedFrom() []string { - return server.Config().Server.ProxyAllowedFrom -} - -func (server *Server) WebIRCConfig() []webircConfig { - return server.Config().Server.WebIRC -} - func (server *Server) DefaultChannelModes() modes.Modes { return server.Config().Channels.defaultModes } @@ -170,18 +162,6 @@ func (client *Client) SetAccountName(account string) (changed bool) { return } -func (client *Client) Authorized() bool { - client.stateMutex.RLock() - defer client.stateMutex.RUnlock() - return client.authorized -} - -func (client *Client) SetAuthorized(authorized bool) { - client.stateMutex.Lock() - defer client.stateMutex.Unlock() - client.authorized = authorized -} - func (client *Client) HasMode(mode modes.Mode) bool { // client.flags has its own synch return client.flags.HasMode(mode) diff --git a/irc/handlers.go b/irc/handlers.go index 0dfa2cb3..d78efff4 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -323,10 +323,6 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, // let the SASL handler do its thing exiting := handler(server, client, client.saslMechanism, data, rb) - if client.LoggedIntoAccount() && server.AccountConfig().SkipServerPassword { - client.SetAuthorized(true) - } - // wait 'til SASL is done before emptying the sasl vars client.saslInProgress = false client.saslMechanism = "" @@ -516,7 +512,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo } case "END": - if !client.Registered() { + if !client.registered { client.capState = caps.NegotiatedState } @@ -1577,7 +1573,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res // NICK func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if client.Registered() { + if client.registered { performNickChange(server, client, client, msg.Params[0], rb) } else { client.preregNick = msg.Params[0] @@ -1758,7 +1754,7 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp // PASS func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if client.Registered() { + if client.registered { rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister")) return false } @@ -1766,7 +1762,6 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp // if no password exists, skip checking serverPassword := server.Password() if serverPassword == nil { - client.SetAuthorized(true) return false } @@ -1778,7 +1773,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp return true } - client.SetAuthorized(true) + client.sentPassCommand = true return false } @@ -1986,7 +1981,7 @@ func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re oldnick := msg.Params[0] token := msg.Params[1] - if client.Registered() { + if client.registered { rb.Add(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, connection registration has already been completed")) return false } @@ -2188,7 +2183,7 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R // USER * 0 func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - if client.Registered() { + if client.registered { rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister")) return false } @@ -2249,7 +2244,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R // WEBIRC [:flag1 flag2=x flag3] func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { // only allow unregistered clients to use this command - if client.Registered() || client.proxiedIP != nil { + if client.registered || client.proxiedIP != nil { return false } @@ -2276,26 +2271,24 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re } } - for _, info := range server.WebIRCConfig() { - for _, gateway := range info.Hosts { - if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { - // confirm password and/or fingerprint - givenPassword := msg.Params[0] - if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, []byte(givenPassword)) != nil { - continue - } - if 0 < len(info.Fingerprint) && client.certfp != info.Fingerprint { - continue - } - - proxiedIP := msg.Params[3] - // see #211; websocket gateways will wrap ipv6 addresses in square brackets - // because IRC parameters can't start with : - if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") { - proxiedIP = proxiedIP[1 : len(proxiedIP)-1] - } - return !client.ApplyProxiedIP(proxiedIP, secure) + givenPassword := []byte(msg.Params[0]) + for _, info := range server.Config().Server.WebIRC { + if utils.IPInNets(client.realIP, info.allowedNets) { + // confirm password and/or fingerprint + if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil { + continue } + if 0 < len(info.Fingerprint) && client.certfp != info.Fingerprint { + continue + } + + proxiedIP := msg.Params[3] + // see #211; websocket gateways will wrap ipv6 addresses in square brackets + // because IRC parameters can't start with : + if strings.HasPrefix(proxiedIP, "[") && strings.HasSuffix(proxiedIP, "]") { + proxiedIP = proxiedIP[1 : len(proxiedIP)-1] + } + return !client.ApplyProxiedIP(proxiedIP, secure) } } diff --git a/irc/idletimer.go b/irc/idletimer.go index 64b22494..e548d413 100644 --- a/irc/idletimer.go +++ b/irc/idletimer.go @@ -83,11 +83,6 @@ func (it *IdleTimer) Start() { } func (it *IdleTimer) Touch() { - // ignore touches from unregistered clients - if !it.client.Registered() { - return - } - it.updateIdleDuration() it.Lock() diff --git a/irc/nickserv.go b/irc/nickserv.go index 16d486c1..8ebe0b52 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -5,7 +5,6 @@ package irc import ( "fmt" - "strings" "github.com/goshuirc/irc-go/ircfmt" ) @@ -123,6 +122,18 @@ SADROP forcibly de-links the given nickname from the attached user account.`, enabled: servCmdRequiresAccreg, minParams: 1, }, + "saregister": { + handler: nsSaregisterHandler, + help: `Syntax: $bSAREGISTER $b + +SAREGISTER registers an account on someone else's behalf. +This is for use in configurations that require SASL for all connections; +an administrator can set use this command to set up user accounts.`, + helpShort: `$bSAREGISTER$b registers an account on someone else's behalf.`, + enabled: servCmdRequiresAccreg, + capabs: []string{"accreg"}, + minParams: 2, + }, "unregister": { handler: nsUnregisterHandler, help: `Syntax: $bUNREGISTER [code]$b @@ -311,22 +322,12 @@ func nsInfoHandler(server *Server, client *Client, command string, params []stri func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { // get params - username, email := params[0], params[1] + account, email := params[0], params[1] var passphrase string if len(params) > 2 { passphrase = params[2] } - if !server.AccountConfig().Registration.Enabled { - nsNotice(rb, client.t("Account registration has been disabled")) - return - } - - if username == "" { - nsNotice(rb, client.t("No username supplied")) - return - } - certfp := client.certfp if passphrase == "" && certfp == "" { nsNotice(rb, client.t("You need to either supply a passphrase or be connected via TLS with a client cert")) @@ -359,9 +360,6 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] } } - // get and sanitise account name - account := strings.TrimSpace(username) - err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, client.certfp) if err == nil { if callbackNamespace == "*" { @@ -381,7 +379,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] errMsg := client.t("Could not register") if err == errCertfpAlreadyExists { errMsg = client.t("An account already exists for your certificate fingerprint") - } else if err == errAccountAlreadyRegistered { + } else if err == errAccountAlreadyRegistered || err == errAccountAlreadyVerified { errMsg = client.t("Account already exists") } else if err == errAccountBadPassphrase { errMsg = client.t("Passphrase contains forbidden characters or is otherwise invalid") @@ -391,6 +389,29 @@ func nsRegisterHandler(server *Server, client *Client, command string, params [] } } +func nsSaregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { + account, passphrase := params[0], params[1] + err := server.accounts.Register(nil, account, "admin", "", passphrase, "") + if err == nil { + err = server.accounts.Verify(nil, account, "") + } + + if err != nil { + var errMsg string + if err == errAccountAlreadyRegistered || err == errAccountAlreadyVerified { + errMsg = client.t("Account already exists") + } else if err == errAccountBadPassphrase { + errMsg = client.t("Passphrase contains forbidden characters or is otherwise invalid") + } else { + server.logger.Error("services", "unknown error from saregister", err.Error()) + errMsg = client.t("Could not register") + } + nsNotice(rb, errMsg) + } else { + nsNotice(rb, fmt.Sprintf(client.t("Successfully registered account %s"), account)) + } +} + func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { username := params[0] var verificationCode string diff --git a/irc/server.go b/irc/server.go index 15b5c1ee..b447a257 100644 --- a/irc/server.go +++ b/irc/server.go @@ -381,7 +381,7 @@ func (server *Server) generateMessageID() string { // func (server *Server) tryRegister(c *Client) { - if c.Registered() { + if c.registered { return } @@ -389,9 +389,10 @@ func (server *Server) tryRegister(c *Client) { return } - // client MUST send PASS (or AUTHENTICATE, if skip-server-password is set) + // client MUST send PASS if necessary, or authenticate with SASL if necessary, // before completing the other registration commands - if !c.Authorized() { + config := server.Config() + if !c.isAuthorized(config) { c.Quit(c.t("Bad password")) c.destroy(false) return diff --git a/irc/utils/net.go b/irc/utils/net.go index c24f7057..d3a4de2e 100644 --- a/irc/utils/net.go +++ b/irc/utils/net.go @@ -11,43 +11,27 @@ import ( var ( // subnet mask for an ipv6 /128: - mask128 = net.CIDRMask(128, 128) + mask128 = net.CIDRMask(128, 128) + IPv4LoopbackAddress = net.ParseIP("127.0.0.1").To16() ) -// IPString returns a simple IP string from the given net.Addr. -func IPString(addr net.Addr) string { - addrStr := addr.String() - ipaddr, _, err := net.SplitHostPort(addrStr) - //TODO(dan): Why is this needed, does this happen? - if err != nil { - return addrStr - } - return ipaddr -} - -// AddrLookupHostname returns the hostname (if possible) or address for the given `net.Addr`. -func AddrLookupHostname(addr net.Addr) string { - if AddrIsUnix(addr) { - return "localhost" - } - return LookupHostname(IPString(addr)) -} - // AddrIsLocal returns whether the address is from a trusted local connection (loopback or unix). func AddrIsLocal(addr net.Addr) bool { if tcpaddr, ok := addr.(*net.TCPAddr); ok { return tcpaddr.IP.IsLoopback() } - _, ok := addr.(*net.UnixAddr) - return ok + return AddrIsUnix(addr) } -// AddrToIP returns the IP address for a net.Addr, or nil if it's a unix domain socket. +// AddrToIP returns the IP address for a net.Addr; unix domain sockets are treated as IPv4 loopback func AddrToIP(addr net.Addr) net.IP { if tcpaddr, ok := addr.(*net.TCPAddr); ok { - return tcpaddr.IP + return tcpaddr.IP.To16() + } else if AddrIsUnix(addr) { + return IPv4LoopbackAddress + } else { + return nil } - return nil } // AddrIsUnix returns whether the address is a unix domain socket. @@ -100,6 +84,16 @@ func IsHostname(name string) bool { return true } +// Convenience to test whether `ip` is contained in any of `nets`. +func IPInNets(ip net.IP, nets []net.IPNet) bool { + for _, network := range nets { + if network.Contains(ip) { + return true + } + } + return false +} + // NormalizeIPToNet represents an address (v4 or v6) as the v6 /128 CIDR // containing only it. func NormalizeIPToNet(addr net.IP) (network net.IPNet) { @@ -156,3 +150,25 @@ func NormalizedNetFromString(str string) (result net.IPNet, err error) { } return NormalizeIPToNet(ip), nil } + +// Parse a list of IPs and nets as they would appear in one of our config +// files, e.g., proxy-allowed-from or a throttling exemption list. +func ParseNetList(netList []string) (nets []net.IPNet, err error) { + var network net.IPNet + for _, netStr := range netList { + if netStr == "localhost" { + ipv4Loopback, _ := NormalizedNetFromString("127.0.0.0/8") + ipv6Loopback, _ := NormalizedNetFromString("::1/128") + nets = append(nets, ipv4Loopback) + nets = append(nets, ipv6Loopback) + continue + } + network, err = NormalizedNetFromString(netStr) + if err != nil { + return + } else { + nets = append(nets, network) + } + } + return +} diff --git a/oragono.yaml b/oragono.yaml index 45ca1748..4646c412 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -67,8 +67,8 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true - # addresses/hostnames the PROXY command can be used from - # this should be restricted to 127.0.0.1/8 and localhost at most + # addresses/CIDRs the PROXY command can be used from + # this should be restricted to 127.0.0.1/8 and ::1/128 (unless you have a good reason) # you should also add these addresses to the connection limits and throttling exemption lists proxy-allowed-from: # - localhost @@ -85,7 +85,7 @@ server: # password the gateway uses to connect, made with oragono genpasswd password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee" - # hosts that can use this webirc command + # addresses/CIDRs that can use this webirc command # you should also add these addresses to the connection limits and throttling exemption lists hosts: # - localhost @@ -117,9 +117,9 @@ server: # IPs/networks which are exempted from connection limits exempted: - - "127.0.0.1" - - "127.0.0.1/8" - - "::1/128" + - "localhost" + # - "192.168.1.1" + # - "2001:0db8::/32" # automated connection throttling connection-throttling: @@ -145,9 +145,9 @@ server: # IPs/networks which are exempted from connection limits exempted: - - "127.0.0.1" - - "127.0.0.1/8" - - "::1/128" + - "localhost" + # - "192.168.1.1" + # - "2001:0db8::/32" # account options accounts: @@ -198,6 +198,18 @@ accounts: # PASS as well, so it can be configured to authenticate with SASL only. skip-server-password: false + # require-sasl controls whether clients are required to have accounts + # (and sign into them using SASL) to connect to the server + require-sasl: + # if this is enabled, all clients must authenticate with SASL while connecting + enabled: false + + # IPs/CIDRs which are exempted from the account requirement + exempted: + - "localhost" + # - '127.0.0.2' + # - '10.10.0.0/16' + # nick-reservation controls how, and whether, nicknames are linked to accounts nick-reservation: # is there any enforcement of reserved nicknames?