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:
commit
928caba922
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
30
oragono.yaml
30
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?
|
||||
|
Loading…
Reference in New Issue
Block a user