3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-10 22:19:31 +01:00

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) {
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
}

View File

@ -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

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:
client.idletimer.Touch()
if client.registered {
client.idletimer.Touch()
}
if !cmd.leaveClientIdle {
client.Active()

View File

@ -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)

View File

@ -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

View File

@ -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

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")
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)
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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 <nickname>
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 <password>
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 <username> * 0 <realname>
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 <password> <gateway> <hostname> <ip> [: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)
}
}

View File

@ -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()

View File

@ -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 <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": {
handler: nsUnregisterHandler,
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) {
// 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

View File

@ -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

View File

@ -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
}

View File

@ -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?