Merge pull request #345 from slingamn/saslonly.3

add sasl-only config option
This commit is contained in:
Shivaram Lingamneni 2019-02-05 04:10:35 -05:00 committed by GitHub
commit 928caba922
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 251 additions and 249 deletions

View File

@ -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) { 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 return "", nil
} else if callbackNamespace == "mailto" { } else if callbackNamespace == "mailto" {
return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue) return am.dispatchMailtoCallback(client, casefoldedAccount, callbackValue)
@ -590,7 +590,9 @@ func (am *AccountManager) Verify(client *Client, account string, code string) er
if err != nil { if err != nil {
return err return err
} }
am.Login(client, clientAccount) if client != nil {
am.Login(client, clientAccount)
}
return nil return nil
} }

View File

@ -32,10 +32,6 @@ const (
IRCv3TimestampFormat = "2006-01-02T15:04:05.000Z" 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 // ResumeDetails is a place to stash data at various stages of
// the resume process: when handling the RESUME command itself, // the resume process: when handling the RESUME command itself,
// when completing the registration, and when rejoining channels. // when completing the registration, and when rejoining channels.
@ -55,7 +51,6 @@ type Client struct {
account string account string
accountName string // display name of the account: uncasefolded, '*' if not logged in accountName string // display name of the account: uncasefolded, '*' if not logged in
atime time.Time atime time.Time
authorized bool
awayMessage string awayMessage string
capabilities *caps.Set capabilities *caps.Set
capState caps.State capState caps.State
@ -88,12 +83,14 @@ type Client struct {
quitMessage string quitMessage string
rawHostname string rawHostname string
realname string realname string
realIP net.IP
registered bool registered bool
resumeDetails *ResumeDetails resumeDetails *ResumeDetails
resumeToken string resumeToken string
saslInProgress bool saslInProgress bool
saslMechanism string saslMechanism string
saslValue string saslValue string
sentPassCommand bool
server *Server server *Server
skeleton string skeleton string
socket *Socket socket *Socket
@ -130,7 +127,6 @@ func NewClient(server *Server, conn net.Conn, isTLS bool) {
socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes) socket := NewSocket(conn, fullLineLenLimit*2, config.Server.MaxSendQBytes)
client := &Client{ client := &Client{
atime: now, atime: now,
authorized: server.Password() == nil,
capabilities: caps.NewSet(), capabilities: caps.NewSet(),
capState: caps.NoneState, capState: caps.NoneState,
capVersion: caps.Cap301, capVersion: caps.Cap301,
@ -151,6 +147,12 @@ 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 { if isTLS {
client.SetMode(modes.TLS, true) 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 // 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(conn.RemoteAddr()) { if config.Server.CheckIdent && !utils.AddrIsUnix(remoteAddr) {
_, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String()) _, serverPortString, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil { if err != nil {
server.logger.Error("internal", "bad server address", err.Error()) 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() 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() { func (client *Client) resetFakelag() {
fakelag := func() *Fakelag { fakelag := func() *Fakelag {
if client.HasRoleCapabs("nofakelag") { if client.HasRoleCapabs("nofakelag") {
@ -211,14 +223,13 @@ func (client *Client) resetFakelag() {
// IP returns the IP address of this client. // IP returns the IP address of this client.
func (client *Client) IP() net.IP { func (client *Client) IP() net.IP {
client.stateMutex.RLock()
defer client.stateMutex.RUnlock()
if client.proxiedIP != nil { if client.proxiedIP != nil {
return client.proxiedIP return client.proxiedIP
} }
if ip := utils.AddrToIP(client.socket.conn.RemoteAddr()); ip != nil { return client.realIP
return ip
}
// unix domain socket that hasn't issued PROXY/WEBIRC yet. YOLO
return LoopbackIP
} }
// IPString returns the IP address of this client as a string. // 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 // Set the hostname for this client
// (may be overridden by a later PROXY command from stunnel) // (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 firstLine := true

View File

@ -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: // 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 { if !cmd.leaveClientIdle {
client.Active() client.Active()

View File

@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -54,7 +55,12 @@ func (conf *TLSListenConfig) Config() (*tls.Config, error) {
type AccountConfig struct { type AccountConfig struct {
Registration AccountRegistrationConfig Registration AccountRegistrationConfig
AuthenticationEnabled bool `yaml:"authentication-enabled"` AuthenticationEnabled bool `yaml:"authentication-enabled"`
LoginThrottling struct { RequireSasl struct {
Enabled bool
Exempted []string
exemptedNets []net.IPNet
} `yaml:"require-sasl"`
LoginThrottling struct {
Enabled bool Enabled bool
Duration time.Duration Duration time.Duration
MaxAttempts int `yaml:"max-attempts"` MaxAttempts int `yaml:"max-attempts"`
@ -261,8 +267,9 @@ type Config struct {
STS STSConfig STS STSConfig
CheckIdent bool `yaml:"check-ident"` CheckIdent bool `yaml:"check-ident"`
MOTD string MOTD string
MOTDFormatting bool `yaml:"motd-formatting"` MOTDFormatting bool `yaml:"motd-formatting"`
ProxyAllowedFrom []string `yaml:"proxy-allowed-from"` ProxyAllowedFrom []string `yaml:"proxy-allowed-from"`
proxyAllowedFromNets []net.IPNet
WebIRC []webircConfig `yaml:"webirc"` WebIRC []webircConfig `yaml:"webirc"`
MaxSendQString string `yaml:"max-sendq"` MaxSendQString string `yaml:"max-sendq"`
MaxSendQBytes int 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 rawRegexp := config.Accounts.VHosts.ValidRegexpRaw
if rawRegexp != "" { if rawRegexp != "" {
regexp, err := regexp.Compile(rawRegexp) regexp, err := regexp.Compile(rawRegexp)

View File

@ -8,6 +8,8 @@ import (
"fmt" "fmt"
"net" "net"
"sync" "sync"
"github.com/oragono/oragono/irc/utils"
) )
// LimiterConfig controls the automated connection limits. // LimiterConfig controls the automated connection limits.
@ -36,23 +38,18 @@ type Limiter struct {
// population holds IP -> count of clients connected from there // population holds IP -> count of clients connected from there
population map[string]int 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 holds networks that are exempt from limits
exemptedNets []net.IPNet exemptedNets []net.IPNet
} }
// maskAddr masks the given IPv4/6 address with our cidr limit masks. // addrToKey canonicalizes `addr` to a string key.
func (cl *Limiter) maskAddr(addr net.IP) net.IP { func addrToKey(addr net.IP, v4Mask net.IPMask, v6Mask net.IPMask) string {
if addr.To4() == nil { if addr.To4() != nil {
// IPv6 addr addr = addr.Mask(v4Mask) // IP.Mask() handles the 4-in-6 mapping for us
addr = addr.Mask(cl.ipv6Mask)
} else { } else {
// IPv4 addr addr = addr.Mask(v6Mask)
addr = addr.Mask(cl.ipv4Mask)
} }
return addr.String()
return addr
} }
// AddClient adds a client to our population if possible. If we can't, throws an error instead. // 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 // check exempted lists
// we don't track populations for exempted addresses or nets - this is by design // 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 return nil
} }
for _, ex := range cl.exemptedNets {
if ex.Contains(addr) {
return nil
}
}
// check population // check population
cl.maskAddr(addr) addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
addrString := addr.String()
if cl.population[addrString]+1 > cl.subnetLimit && !force { if cl.population[addrString]+1 > cl.subnetLimit && !force {
return errTooManyClients return errTooManyClients
@ -98,7 +89,7 @@ func (cl *Limiter) RemoveClient(addr net.IP) {
return return
} }
addrString := addr.String() addrString := addrToKey(addr, cl.ipv4Mask, cl.ipv6Mask)
cl.population[addrString] = cl.population[addrString] - 1 cl.population[addrString] = cl.population[addrString] - 1
// safety limiter // safety limiter
@ -121,21 +112,9 @@ func NewLimiter() *Limiter {
// ApplyConfig atomically applies a config update to a connection limit handler // ApplyConfig atomically applies a config update to a connection limit handler
func (cl *Limiter) ApplyConfig(config LimiterConfig) error { func (cl *Limiter) ApplyConfig(config LimiterConfig) error {
// assemble exempted nets // assemble exempted nets
exemptedIPs := make(map[string]bool) exemptedNets, err := utils.ParseNetList(config.Exempted)
var exemptedNets []net.IPNet if err != nil {
for _, cidr := range config.Exempted { return fmt.Errorf("Could not parse limiter exemption list: %v", err.Error())
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)
}
} }
cl.Lock() cl.Lock()
@ -151,7 +130,6 @@ func (cl *Limiter) ApplyConfig(config LimiterConfig) error {
if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 { if cl.subnetLimit == 0 && config.IPsPerSubnet != 0 {
cl.subnetLimit = config.IPsPerSubnet cl.subnetLimit = config.IPsPerSubnet
} }
cl.exemptedIPs = exemptedIPs
cl.exemptedNets = exemptedNets cl.exemptedNets = exemptedNets
return nil return nil

View File

@ -8,6 +8,8 @@ import (
"net" "net"
"sync" "sync"
"time" "time"
"github.com/oragono/oragono/irc/utils"
) )
// ThrottlerConfig controls the automated connection throttling. // ThrottlerConfig controls the automated connection throttling.
@ -82,25 +84,10 @@ type Throttler struct {
banDuration time.Duration banDuration time.Duration
banMessage string banMessage string
// exemptedIPs holds IPs that are exempt from limits
exemptedIPs map[string]bool
// exemptedNets holds networks that are exempt from limits // exemptedNets holds networks that are exempt from limits
exemptedNets []net.IPNet 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. // ResetFor removes any existing count for the given address.
func (ct *Throttler) ResetFor(addr net.IP) { func (ct *Throttler) ResetFor(addr net.IP) {
ct.Lock() ct.Lock()
@ -111,8 +98,7 @@ func (ct *Throttler) ResetFor(addr net.IP) {
} }
// remove // remove
ct.maskAddr(addr) addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
addrString := addr.String()
delete(ct.population, addrString) delete(ct.population, addrString)
} }
@ -126,18 +112,12 @@ func (ct *Throttler) AddClient(addr net.IP) error {
} }
// check exempted lists // check exempted lists
if ct.exemptedIPs[addr.String()] { if utils.IPInNets(addr, ct.exemptedNets) {
return nil return nil
} }
for _, ex := range ct.exemptedNets {
if ex.Contains(addr) {
return nil
}
}
// check throttle // check throttle
ct.maskAddr(addr) addrString := addrToKey(addr, ct.ipv4Mask, ct.ipv6Mask)
addrString := addr.String()
details := ct.population[addrString] // retrieve mutable throttle state from the map details := ct.population[addrString] // retrieve mutable throttle state from the map
// add in constant state to process the limiting operation // 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 // ApplyConfig atomically applies a config update to a throttler
func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error { func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error {
// assemble exempted nets // assemble exempted nets
exemptedIPs := make(map[string]bool) exemptedNets, err := utils.ParseNetList(config.Exempted)
var exemptedNets []net.IPNet if err != nil {
for _, cidr := range config.Exempted { return fmt.Errorf("Could not parse throttle exemption list: %v", err.Error())
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)
}
} }
ct.Lock() ct.Lock()
@ -211,7 +179,6 @@ func (ct *Throttler) ApplyConfig(config ThrottlerConfig) error {
ct.duration = config.Duration ct.duration = config.Duration
ct.banDuration = config.BanDuration ct.banDuration = config.BanDuration
ct.banMessage = config.BanMessage ct.banMessage = config.BanMessage
ct.exemptedIPs = exemptedIPs
ct.exemptedNets = exemptedNets ct.exemptedNets = exemptedNets
return nil return nil

View File

@ -62,25 +62,59 @@ func TestGenericThrottleDisabled(t *testing.T) {
} }
} }
func TestConnectionThrottle(t *testing.T) { func makeTestThrottler(v4len, v6len int) *Throttler {
minute, _ := time.ParseDuration("1m") minute, _ := time.ParseDuration("1m")
maxConnections := 3 maxConnections := 3
config := ThrottlerConfig{ config := ThrottlerConfig{
Enabled: true, Enabled: true,
CidrLenIPv4: 32, CidrLenIPv4: v4len,
CidrLenIPv6: 64, CidrLenIPv6: v6len,
ConnectionsPerCidr: maxConnections, ConnectionsPerCidr: maxConnections,
Duration: minute, Duration: minute,
} }
throttler := NewThrottler() throttler := NewThrottler()
throttler.ApplyConfig(config) throttler.ApplyConfig(config)
return throttler
}
func TestConnectionThrottle(t *testing.T) {
throttler := makeTestThrottler(32, 64)
addr := net.ParseIP("8.8.8.8") 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) err := throttler.AddClient(addr)
assertEqual(err, nil, t) assertEqual(err, nil, t)
} }
err := throttler.AddClient(addr) err := throttler.AddClient(addr)
assertEqual(err, errTooManyClients, t) 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)
}

View File

@ -25,6 +25,7 @@ type webircConfig struct {
Password []byte `yaml:"password-bytes"` Password []byte `yaml:"password-bytes"`
Fingerprint string Fingerprint string
Hosts []string Hosts []string
allowedNets []net.IPNet
} }
// Populate fills out our password or fingerprint. // Populate fills out our password or fingerprint.
@ -36,49 +37,23 @@ func (wc *webircConfig) Populate() (err error) {
if wc.PasswordString != "" { if wc.PasswordString != "" {
wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString) wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString)
} }
if err == nil {
wc.allowedNets, err = utils.ParseNetList(wc.Hosts)
}
return err 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. // 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) {
// ensure IP is sane // ensure IP is sane
parsedProxiedIP := net.ParseIP(proxiedIP) parsedProxiedIP := net.ParseIP(proxiedIP).To16()
if parsedProxiedIP == nil { if parsedProxiedIP == nil {
client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP)) client.Quit(fmt.Sprintf(client.t("Proxied IP address is not valid: [%s]"), proxiedIP))
return false 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) isBanned, banMsg := client.server.checkBans(parsedProxiedIP)
if isBanned { if isBanned {
client.Quit(banMsg) client.Quit(banMsg)
@ -117,14 +92,12 @@ func handleProxyCommand(server *Server, client *Client, line string) (err error)
return errBadProxyLine return errBadProxyLine
} }
for _, gateway := range server.ProxyAllowedFrom() { if utils.IPInNets(client.realIP, server.Config().Server.proxyAllowedFromNets) {
if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { // assume PROXY connections are always secure
// assume PROXY connections are always secure if client.ApplyProxiedIP(params[2], true) {
if client.ApplyProxiedIP(params[2], true) { return nil
return nil } else {
} else { return errBadProxyLine
return errBadProxyLine
}
} }
} }

View File

@ -32,14 +32,6 @@ func (server *Server) RecoverFromErrors() bool {
return *server.Config().Debug.RecoverFromErrors 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 { func (server *Server) DefaultChannelModes() modes.Modes {
return server.Config().Channels.defaultModes return server.Config().Channels.defaultModes
} }
@ -170,18 +162,6 @@ func (client *Client) SetAccountName(account string) (changed bool) {
return 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 { func (client *Client) HasMode(mode modes.Mode) bool {
// client.flags has its own synch // client.flags has its own synch
return client.flags.HasMode(mode) return client.flags.HasMode(mode)

View File

@ -323,10 +323,6 @@ func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage,
// let the SASL handler do its thing // let the SASL handler do its thing
exiting := handler(server, client, client.saslMechanism, data, rb) 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 // wait 'til SASL is done before emptying the sasl vars
client.saslInProgress = false client.saslInProgress = false
client.saslMechanism = "" client.saslMechanism = ""
@ -516,7 +512,7 @@ func capHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Respo
} }
case "END": case "END":
if !client.Registered() { if !client.registered {
client.capState = caps.NegotiatedState client.capState = caps.NegotiatedState
} }
@ -1577,7 +1573,7 @@ func namesHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
// NICK <nickname> // NICK <nickname>
func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { 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) performNickChange(server, client, client, msg.Params[0], rb)
} else { } else {
client.preregNick = msg.Params[0] client.preregNick = msg.Params[0]
@ -1758,7 +1754,7 @@ func partHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
// PASS <password> // PASS <password>
func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { 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")) rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister"))
return false return false
} }
@ -1766,7 +1762,6 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
// if no password exists, skip checking // if no password exists, skip checking
serverPassword := server.Password() serverPassword := server.Password()
if serverPassword == nil { if serverPassword == nil {
client.SetAuthorized(true)
return false return false
} }
@ -1778,7 +1773,7 @@ func passHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
return true return true
} }
client.SetAuthorized(true) client.sentPassCommand = true
return false return false
} }
@ -1986,7 +1981,7 @@ func resumeHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
oldnick := msg.Params[0] oldnick := msg.Params[0]
token := msg.Params[1] 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")) rb.Add(nil, server.name, ERR_CANNOT_RESUME, oldnick, client.t("Cannot resume connection, connection registration has already been completed"))
return false return false
} }
@ -2188,7 +2183,7 @@ func unKLineHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
// USER <username> * 0 <realname> // USER <username> * 0 <realname>
func userHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { 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")) rb.Add(nil, server.name, ERR_ALREADYREGISTRED, client.nick, client.t("You may not reregister"))
return false return false
} }
@ -2249,7 +2244,7 @@ func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *R
// WEBIRC <password> <gateway> <hostname> <ip> [:flag1 flag2=x flag3] // WEBIRC <password> <gateway> <hostname> <ip> [:flag1 flag2=x flag3]
func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
// only allow unregistered clients to use this command // only allow unregistered clients to use this command
if client.Registered() || client.proxiedIP != nil { if client.registered || client.proxiedIP != nil {
return false return false
} }
@ -2276,26 +2271,24 @@ func webircHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
} }
} }
for _, info := range server.WebIRCConfig() { givenPassword := []byte(msg.Params[0])
for _, gateway := range info.Hosts { for _, info := range server.Config().Server.WebIRC {
if isGatewayAllowed(client.socket.conn.RemoteAddr(), gateway) { if utils.IPInNets(client.realIP, info.allowedNets) {
// confirm password and/or fingerprint // confirm password and/or fingerprint
givenPassword := msg.Params[0] if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, []byte(givenPassword)) != nil { continue
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)
} }
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)
} }
} }

View File

@ -83,11 +83,6 @@ func (it *IdleTimer) Start() {
} }
func (it *IdleTimer) Touch() { func (it *IdleTimer) Touch() {
// ignore touches from unregistered clients
if !it.client.Registered() {
return
}
it.updateIdleDuration() it.updateIdleDuration()
it.Lock() it.Lock()

View File

@ -5,7 +5,6 @@ package irc
import ( import (
"fmt" "fmt"
"strings"
"github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircfmt"
) )
@ -123,6 +122,18 @@ SADROP forcibly de-links the given nickname from the attached user account.`,
enabled: servCmdRequiresAccreg, enabled: servCmdRequiresAccreg,
minParams: 1, minParams: 1,
}, },
"saregister": {
handler: nsSaregisterHandler,
help: `Syntax: $bSAREGISTER <username> <password>$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": { "unregister": {
handler: nsUnregisterHandler, handler: nsUnregisterHandler,
help: `Syntax: $bUNREGISTER <username> [code]$b help: `Syntax: $bUNREGISTER <username> [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) { func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
// get params // get params
username, email := params[0], params[1] account, email := params[0], params[1]
var passphrase string var passphrase string
if len(params) > 2 { if len(params) > 2 {
passphrase = 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 certfp := client.certfp
if passphrase == "" && certfp == "" { if passphrase == "" && certfp == "" {
nsNotice(rb, client.t("You need to either supply a passphrase or be connected via TLS with a client cert")) 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) err := server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, client.certfp)
if err == nil { if err == nil {
if callbackNamespace == "*" { if callbackNamespace == "*" {
@ -381,7 +379,7 @@ func nsRegisterHandler(server *Server, client *Client, command string, params []
errMsg := client.t("Could not register") errMsg := client.t("Could not register")
if err == errCertfpAlreadyExists { if err == errCertfpAlreadyExists {
errMsg = client.t("An account already exists for your certificate fingerprint") 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") errMsg = client.t("Account already exists")
} else if err == errAccountBadPassphrase { } else if err == errAccountBadPassphrase {
errMsg = client.t("Passphrase contains forbidden characters or is otherwise invalid") 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) { func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
username := params[0] username := params[0]
var verificationCode string var verificationCode string

View File

@ -381,7 +381,7 @@ func (server *Server) generateMessageID() string {
// //
func (server *Server) tryRegister(c *Client) { func (server *Server) tryRegister(c *Client) {
if c.Registered() { if c.registered {
return return
} }
@ -389,9 +389,10 @@ func (server *Server) tryRegister(c *Client) {
return 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 // before completing the other registration commands
if !c.Authorized() { config := server.Config()
if !c.isAuthorized(config) {
c.Quit(c.t("Bad password")) c.Quit(c.t("Bad password"))
c.destroy(false) c.destroy(false)
return return

View File

@ -11,43 +11,27 @@ import (
var ( var (
// subnet mask for an ipv6 /128: // 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). // AddrIsLocal returns whether the address is from a trusted local connection (loopback or unix).
func AddrIsLocal(addr net.Addr) bool { func AddrIsLocal(addr net.Addr) bool {
if tcpaddr, ok := addr.(*net.TCPAddr); ok { if tcpaddr, ok := addr.(*net.TCPAddr); ok {
return tcpaddr.IP.IsLoopback() return tcpaddr.IP.IsLoopback()
} }
_, ok := addr.(*net.UnixAddr) return AddrIsUnix(addr)
return ok
} }
// 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 { func AddrToIP(addr net.Addr) net.IP {
if tcpaddr, ok := addr.(*net.TCPAddr); ok { 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. // AddrIsUnix returns whether the address is a unix domain socket.
@ -100,6 +84,16 @@ func IsHostname(name string) bool {
return true 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 // NormalizeIPToNet represents an address (v4 or v6) as the v6 /128 CIDR
// containing only it. // containing only it.
func NormalizeIPToNet(addr net.IP) (network net.IPNet) { 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 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
}

View File

@ -67,8 +67,8 @@ server:
# if this is true, the motd is escaped using formatting codes like $c, $b, and $i # if this is true, the motd is escaped using formatting codes like $c, $b, and $i
motd-formatting: true motd-formatting: true
# addresses/hostnames the PROXY command can be used from # addresses/CIDRs the PROXY command can be used from
# this should be restricted to 127.0.0.1/8 and localhost at most # 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 # you should also add these addresses to the connection limits and throttling exemption lists
proxy-allowed-from: proxy-allowed-from:
# - localhost # - localhost
@ -85,7 +85,7 @@ server:
# password the gateway uses to connect, made with oragono genpasswd # password the gateway uses to connect, made with oragono genpasswd
password: "$2a$04$sLEFDpIOyUp55e6gTMKbOeroT6tMXTjPFvA0eGvwvImVR9pkwv7ee" 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 # you should also add these addresses to the connection limits and throttling exemption lists
hosts: hosts:
# - localhost # - localhost
@ -117,9 +117,9 @@ server:
# IPs/networks which are exempted from connection limits # IPs/networks which are exempted from connection limits
exempted: exempted:
- "127.0.0.1" - "localhost"
- "127.0.0.1/8" # - "192.168.1.1"
- "::1/128" # - "2001:0db8::/32"
# automated connection throttling # automated connection throttling
connection-throttling: connection-throttling:
@ -145,9 +145,9 @@ server:
# IPs/networks which are exempted from connection limits # IPs/networks which are exempted from connection limits
exempted: exempted:
- "127.0.0.1" - "localhost"
- "127.0.0.1/8" # - "192.168.1.1"
- "::1/128" # - "2001:0db8::/32"
# account options # account options
accounts: accounts:
@ -198,6 +198,18 @@ accounts:
# PASS as well, so it can be configured to authenticate with SASL only. # PASS as well, so it can be configured to authenticate with SASL only.
skip-server-password: false 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 controls how, and whether, nicknames are linked to accounts
nick-reservation: nick-reservation:
# is there any enforcement of reserved nicknames? # is there any enforcement of reserved nicknames?