Compare commits

..

1 commit

Author SHA1 Message Date
Gabriel Simmer 9bd3bebd19
Add Containerfile 2024-06-30 13:04:59 +01:00
36 changed files with 91 additions and 115 deletions

2
.b4-config Normal file
View file

@ -0,0 +1,2 @@
[b4]
send-series-to = ~emersion/soju-dev@lists.sr.ht

View file

@ -4,7 +4,7 @@ packages:
- scdoc - scdoc
- postgresql - postgresql
sources: sources:
- https://codeberg.org/emersion/soju.git - https://git.sr.ht/~emersion/soju
tasks: tasks:
- build: | - build: |
cd soju cd soju

View file

@ -11,8 +11,8 @@ RUNDIR ?= /run
sharedstatedir := /var/lib sharedstatedir := /var/lib
config_path := $(SYSCONFDIR)/soju/config config_path := $(SYSCONFDIR)/soju/config
admin_socket_path := $(RUNDIR)/soju/admin admin_socket_path := $(RUNDIR)/soju/admin
goldflags := -X 'codeberg.org/emersion/soju/config.DefaultPath=$(config_path)' \ goldflags := -X 'git.sr.ht/~emersion/soju/config.DefaultPath=$(config_path)' \
-X 'codeberg.org/emersion/soju/config.DefaultUnixAdminPath=$(admin_socket_path)' -X 'git.sr.ht/~emersion/soju/config.DefaultUnixAdminPath=$(admin_socket_path)'
goflags := $(GOFLAGS) -ldflags="$(goldflags)" goflags := $(GOFLAGS) -ldflags="$(goldflags)"
commands := soju sojuctl sojudb commands := soju sojuctl sojudb
man_pages := doc/soju.1 doc/sojuctl.1 man_pages := doc/soju.1 doc/sojuctl.1

View file

@ -1,5 +1,7 @@
# [soju] # [soju]
[![builds.sr.ht status](https://builds.sr.ht/~emersion/soju/commits/master.svg)](https://builds.sr.ht/~emersion/soju/commits/master?)
soju is a user-friendly IRC bouncer. soju connects to upstream IRC servers on soju is a user-friendly IRC bouncer. soju connects to upstream IRC servers on
behalf of the user to provide extra functionality. soju supports many features behalf of the user to provide extra functionality. soju supports many features
such as multiple users, numerous [IRCv3] extensions, chat history playback and such as multiple users, numerous [IRCv3] extensions, chat history playback and
@ -33,8 +35,8 @@ build with PAM authentication support, set `GOFLAGS="-tags=pam"`.
## Contributing ## Contributing
Send patches on [Codeberg] or on [GitHub], report bugs on the [issue tracker]. Send patches on the [mailing list] or on [GitHub], report bugs on the
Discuss in [#soju on Libera Chat][IRC channel]. [issue tracker]. Discuss in [#soju on Libera Chat][IRC channel].
## License ## License
@ -45,7 +47,7 @@ Copyright (C) 2020 The soju Contributors
[soju]: https://soju.im [soju]: https://soju.im
[Getting started]: doc/getting-started.md [Getting started]: doc/getting-started.md
[Man page]: https://soju.im/doc/soju.1.html [Man page]: https://soju.im/doc/soju.1.html
[Codeberg]: https://codeberg.org/emersion/soju [mailing list]: https://lists.sr.ht/~emersion/soju-dev
[GitHub]: https://github.com/emersion/soju [GitHub]: https://github.com/emersion/soju
[issue tracker]: https://todo.sr.ht/~emersion/soju [issue tracker]: https://todo.sr.ht/~emersion/soju
[IRC channel]: ircs://irc.libera.chat/#soju [IRC channel]: ircs://irc.libera.chat/#soju

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
type Authenticator interface{} type Authenticator interface{}

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
type internal struct{} type internal struct{}

View file

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
type oauth2 struct { type oauth2 struct {

View file

@ -8,7 +8,7 @@ import (
"github.com/msteinert/pam/v2" "github.com/msteinert/pam/v2"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
type pamAuth struct{} type pamAuth struct{}

View file

@ -22,12 +22,12 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"codeberg.org/emersion/soju" "git.sr.ht/~emersion/soju"
"codeberg.org/emersion/soju/auth" "git.sr.ht/~emersion/soju/auth"
"codeberg.org/emersion/soju/config" "git.sr.ht/~emersion/soju/config"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/fileupload" "git.sr.ht/~emersion/soju/fileupload"
"codeberg.org/emersion/soju/identd" "git.sr.ht/~emersion/soju/identd"
) )
// TCP keep-alive interval for downstream TCP connections // TCP keep-alive interval for downstream TCP connections

View file

@ -11,7 +11,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"codeberg.org/emersion/soju/config" "git.sr.ht/~emersion/soju/config"
) )
const usage = `usage: sojuctl [-config path] <command> const usage = `usage: sojuctl [-config path] <command>

View file

@ -11,8 +11,8 @@ import (
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"codeberg.org/emersion/soju/config" "git.sr.ht/~emersion/soju/config"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const usage = `usage: sojudb [-config path] <action> [options...] const usage = `usage: sojudb [-config path] <action> [options...]

View file

@ -3,6 +3,7 @@ package soju
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"strings" "strings"
@ -157,7 +158,7 @@ func newConn(srv *Server, ic ircConn, options *connOptions) *conn {
break break
} }
} }
if err := c.Close(); err != nil && !errors.Is(err, net.ErrClosed) { if err := c.conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
c.logger.Printf("failed to close connection: %v", err) c.logger.Printf("failed to close connection: %v", err)
} else { } else {
c.logger.Debugf("connection closed") c.logger.Debugf("connection closed")
@ -184,7 +185,7 @@ func (c *conn) Close() error {
defer c.lock.Unlock() defer c.lock.Unlock()
if c.closed { if c.closed {
return net.ErrClosed return fmt.Errorf("connection already closed")
} }
err := c.conn.Close() err := c.conn.Close()
@ -194,10 +195,6 @@ func (c *conn) Close() error {
return err return err
} }
// Read reads an incoming message. It must be called from a single goroutine
// at a time.
//
// io.EOF is returned when there are no more messages to read.
func (c *conn) ReadMessage() (*irc.Message, error) { func (c *conn) ReadMessage() (*irc.Message, error) {
msg, err := c.conn.ReadMessage() msg, err := c.conn.ReadMessage()
if errors.Is(err, net.ErrClosed) { if errors.Is(err, net.ErrClosed) {

View file

@ -70,6 +70,12 @@ outgoing messages:
/set irc_reconnect_rejoin off /set irc_reconnect_rejoin off
/set net_throttle off /set net_throttle off
Older Hexchat versions (without the [hexchat password length fix]) do not
support long passwords, which include personal access tokens from sourcehut with
limited scope. To work around this issue for sourcehut, [generate a sourcehut
personal access token] without limiting the grant (by not selecting any
permissions).
# [irssi] # [irssi]
To connect irssi to a network, for example Libera Chat: To connect irssi to a network, for example Libera Chat:
@ -114,5 +120,6 @@ See `/help cap` for more information.
[read_marker.py]: https://weechat.org/scripts/source/read_marker.py.html/ [read_marker.py]: https://weechat.org/scripts/source/read_marker.py.html/
[Hexchat]: https://hexchat.github.io/ [Hexchat]: https://hexchat.github.io/
[hexchat password length fix]: https://github.com/hexchat/hexchat/commit/778047bc65e529804c3342ee0f3a8d5d7550fde5 [hexchat password length fix]: https://github.com/hexchat/hexchat/commit/778047bc65e529804c3342ee0f3a8d5d7550fde5
[generate a sourcehut personal access token]: https://meta.sr.ht/oauth2/personal-token
[Emacs]: https://www.gnu.org/software/emacs/ [Emacs]: https://www.gnu.org/software/emacs/
[irssi]: https://irssi.org/ [irssi]: https://irssi.org/

View file

@ -7,7 +7,7 @@ import (
"log" "log"
"strings" "strings"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const usage = `usage: migrate-db <source database> <destination database> const usage = `usage: migrate-db <source database> <destination database>

View file

@ -14,9 +14,9 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/msgstore" "git.sr.ht/~emersion/soju/msgstore"
"codeberg.org/emersion/soju/msgstore/znclog" "git.sr.ht/~emersion/soju/msgstore/znclog"
) )
const usage = `usage: migrate-logs <source logs> <destination database> const usage = `usage: migrate-logs <source logs> <destination database>

View file

@ -12,8 +12,8 @@ import (
"strings" "strings"
"unicode" "unicode"
"codeberg.org/emersion/soju/config" "git.sr.ht/~emersion/soju/config"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const usage = `usage: znc-import [options...] <znc config path> const usage = `usage: znc-import [options...] <znc config path>

View file

@ -10,7 +10,7 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
_ "github.com/lib/pq" _ "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
promcollectors "github.com/prometheus/client_golang/prometheus/collectors" promcollectors "github.com/prometheus/client_golang/prometheus/collectors"

View file

@ -13,7 +13,7 @@ import (
"time" "time"
"unicode" "unicode"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
promcollectors "github.com/prometheus/client_golang/prometheus/collectors" promcollectors "github.com/prometheus/client_golang/prometheus/collectors"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"

View file

@ -296,7 +296,7 @@ character.
of an IRC server: of an IRC server:
``` ```
openssl s_client -connect irc.example.org:6697 -verify_quiet </dev/null | openssl x509 -fingerprint -sha512 -noout -in /dev/stdin openssl s_client -connect irc.example.org:6697 </dev/null 2>/dev/null | openssl x509 -fingerprint -sha512 -noout -in /dev/stdin
``` ```
*-nick* <nickname> *-nick* <nickname>
@ -556,7 +556,7 @@ character.
Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other
open-source contributors. For more information about soju development, see open-source contributors. For more information about soju development, see
<https://soju.im>. <https://sr.ht/~emersion/soju>.
# SEE ALSO # SEE ALSO

View file

@ -29,7 +29,7 @@ file. sojuctl needs to be run with write permissions on the soju admin socket.
Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other
open-source contributors. For more information about soju development, see open-source contributors. For more information about soju development, see
<https://soju.im>. <https://sr.ht/~emersion/soju>.
# SEE ALSO # SEE ALSO

View file

@ -17,10 +17,10 @@ import (
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/auth" "git.sr.ht/~emersion/soju/auth"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/msgstore" "git.sr.ht/~emersion/soju/msgstore"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
type ircError struct { type ircError struct {
@ -328,7 +328,7 @@ func serverSASLMechanisms(srv *Server) []string {
} }
type downstreamConn struct { type downstreamConn struct {
*conn conn
id uint64 id uint64
@ -363,7 +363,7 @@ func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
options := connOptions{Logger: logger} options := connOptions{Logger: logger}
cm := xirc.CaseMappingASCII cm := xirc.CaseMappingASCII
dc := &downstreamConn{ dc := &downstreamConn{
conn: newConn(srv, ic, &options), conn: *newConn(srv, ic, &options),
id: id, id: id,
nick: "*", nick: "*",
nickCM: "*", nickCM: "*",
@ -2142,6 +2142,7 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
// Clients will use the first mask to match RPL_ENDOFWHO // Clients will use the first mask to match RPL_ENDOFWHO
endOfWhoToken := msg.Params[0] endOfWhoToken := msg.Params[0]
// TODO: add support for WHOX mask2
mask := msg.Params[0] mask := msg.Params[0]
var options string var options string
if len(msg.Params) > 1 { if len(msg.Params) > 1 {
@ -2153,10 +2154,8 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
// TODO: support mixed bouncer/upstream WHO queries // TODO: support mixed bouncer/upstream WHO queries
maskCM := dc.casemap(mask) maskCM := dc.casemap(mask)
if dc.network == nil && maskCM == dc.nickCM { if dc.network == nil && maskCM == dc.nickCM {
// TODO: support AWAY (H/G) in self WHO reply
flags := "H" flags := "H"
if dc.away != nil {
flags = "G"
}
if dc.user.Admin { if dc.user.Admin {
flags += "*" flags += "*"
} }
@ -2231,23 +2230,6 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
} }
if uc.isChannel(mask) { if uc.isChannel(mask) {
info.Channel = mask info.Channel = mask
// Set channel membership prefixes from cached NAMES reply
ch := uc.channels.Get(info.Channel)
memberships := ch.Members.Get(info.Nickname)
prefixes := formatMemberPrefix(*memberships, dc)
// Channel membership prefixes are listed after away status ('G'/'H')
// and optional server operator indicator ('*')
i := strings.IndexFunc(info.Flags, func(f rune) bool {
return f != 'G' && f != 'H' && f != '*'
})
if i == -1 {
info.Flags += prefixes
} else {
info.Flags = info.Flags[:i] + prefixes + info.Flags[i:]
}
} }
dc.SendMessage(ctx, xirc.GenerateWHOXReply(fields, &info)) dc.SendMessage(ctx, xirc.GenerateWHOXReply(fields, &info))
} }

View file

@ -12,8 +12,8 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/emersion/soju/auth" "git.sr.ht/~emersion/soju/auth"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const maxSize = 50 * 1024 * 1024 // 50 MiB const maxSize = 50 * 1024 * 1024 // 50 MiB

2
go.mod
View file

@ -1,4 +1,4 @@
module codeberg.org/emersion/soju module git.sr.ht/~emersion/soju
go 1.19 go 1.19

15
irc.go
View file

@ -9,7 +9,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
// TODO: generalize and move helpers to the xirc package // TODO: generalize and move helpers to the xirc package
@ -194,19 +194,6 @@ func formatMemberPrefix(ms xirc.MembershipSet, dc *downstreamConn) string {
return string(prefixes) return string(prefixes)
} }
// Remove channel membership prefixes from flags
func stripMemberPrefixes(flags string, uc *upstreamConn) string {
return strings.Map(func(r rune) rune {
for _, v := range uc.availableMemberships {
if byte(r) == v.Prefix {
return -1
}
}
return r
}, flags)
}
func parseMessageParams(msg *irc.Message, out ...*string) error { func parseMessageParams(msg *irc.Message, out ...*string) error {
if len(msg.Params) < len(out) { if len(msg.Params) < len(out) {
return newNeedMoreParamsError(msg.Command) return newNeedMoreParamsError(msg.Command)

View file

@ -4,7 +4,7 @@ import (
"context" "context"
"time" "time"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"git.sr.ht/~sircmpwn/go-bare" "git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
) )

View file

@ -14,9 +14,9 @@ import (
"git.sr.ht/~sircmpwn/go-bare" "git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/msgstore/znclog" "git.sr.ht/~emersion/soju/msgstore/znclog"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
const ( const (

View file

@ -8,7 +8,7 @@ import (
"git.sr.ht/~sircmpwn/go-bare" "git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const messageRingBufferCap = 4096 const messageRingBufferCap = 4096

View file

@ -10,7 +10,7 @@ import (
"git.sr.ht/~sircmpwn/go-bare" "git.sr.ht/~sircmpwn/go-bare"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
type LoadMessageOptions struct { type LoadMessageOptions struct {

View file

@ -7,8 +7,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
var timestampPrefixLen = len("[01:02:03] ") var timestampPrefixLen = len("[01:02:03] ")

View file

@ -7,7 +7,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
func MarshalLine(msg *irc.Message, t time.Time) string { func MarshalLine(msg *irc.Message, t time.Time) string {

View file

@ -20,11 +20,11 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"nhooyr.io/websocket" "nhooyr.io/websocket"
"codeberg.org/emersion/soju/auth" "git.sr.ht/~emersion/soju/auth"
"codeberg.org/emersion/soju/config" "git.sr.ht/~emersion/soju/config"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/fileupload" "git.sr.ht/~emersion/soju/fileupload"
"codeberg.org/emersion/soju/identd" "git.sr.ht/~emersion/soju/identd"
) )
var ( var (

View file

@ -10,8 +10,8 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
var testServerPrefix = &irc.Prefix{Name: "soju-test-server"} var testServerPrefix = &irc.Prefix{Name: "soju-test-server"}

View file

@ -17,7 +17,7 @@ import (
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
) )
const serviceNick = "BouncerServ" const serviceNick = "BouncerServ"

View file

@ -21,8 +21,8 @@ import (
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
) )
// permanentUpstreamCaps is the static list of upstream capabilities always // permanentUpstreamCaps is the static list of upstream capabilities always
@ -191,7 +191,7 @@ type pendingUpstreamCommand struct {
} }
type upstreamConn struct { type upstreamConn struct {
*conn conn
network *network network *network
user *user user *user
@ -353,7 +353,7 @@ func connectToUpstream(ctx context.Context, network *network) (*upstreamConn, er
cm := stdCaseMapping cm := stdCaseMapping
uc := &upstreamConn{ uc := &upstreamConn{
conn: newConn(network.user.srv, newNetIRCConn(netConn), &options), conn: *newConn(network.user.srv, newNetIRCConn(netConn), &options),
network: network, network: network,
user: network.user, user: network.user,
channels: xirc.NewCaseMappingMap[*upstreamChannel](cm), channels: xirc.NewCaseMappingMap[*upstreamChannel](cm),
@ -1541,7 +1541,7 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
Hostname: host, Hostname: host,
Server: server, Server: server,
Nickname: nick, Nickname: nick,
Flags: stripMemberPrefixes(flags, uc), Flags: flags,
Realname: realname, Realname: realname,
}) })
} }
@ -1564,14 +1564,13 @@ func (uc *upstreamConn) handleMessage(ctx context.Context, msg *irc.Message) err
if err != nil { if err != nil {
return err return err
} }
if uc.shouldCacheUserInfo(info.Nickname) { if uc.shouldCacheUserInfo(info.Nickname) {
uc.cacheUserInfo(info.Nickname, &upstreamUser{ uc.cacheUserInfo(info.Nickname, &upstreamUser{
Nickname: info.Nickname, Nickname: info.Nickname,
Username: info.Username, Username: info.Username,
Hostname: info.Hostname, Hostname: info.Hostname,
Server: info.Server, Server: info.Server,
Flags: stripMemberPrefixes(info.Flags, uc), Flags: info.Flags,
Account: info.Account, Account: info.Account,
Realname: info.Realname, Realname: info.Realname,
}) })

View file

@ -14,13 +14,13 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"codeberg.org/emersion/soju/xirc" "git.sr.ht/~emersion/soju/xirc"
"github.com/SherClockHolmes/webpush-go" "github.com/SherClockHolmes/webpush-go"
"gopkg.in/irc.v4" "gopkg.in/irc.v4"
"codeberg.org/emersion/soju/database" "git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/msgstore" "git.sr.ht/~emersion/soju/msgstore"
) )
type UserUpdateFunc func(record *database.User) error type UserUpdateFunc func(record *database.User) error

View file

@ -206,25 +206,25 @@ func GenerateNamesReply(channel string, status ChannelStatus, members []string)
} }
func GenerateSASL(resp []byte) []*irc.Message { func GenerateSASL(resp []byte) []*irc.Message {
encoded := base64.StdEncoding.EncodeToString(resp)
// <= instead of < because we need to send a final empty response if // <= instead of < because we need to send a final empty response if
// the last chunk is exactly 400 bytes long // the last chunk is exactly 400 bytes long
var msgs []*irc.Message var msgs []*irc.Message
for i := 0; i <= len(encoded); i += MaxSASLLength { for i := 0; i <= len(resp); i += MaxSASLLength {
j := i + MaxSASLLength j := i + MaxSASLLength
if j > len(encoded) { if j > len(resp) {
j = len(encoded) j = len(resp)
} }
chunk := encoded[i:j] chunk := resp[i:j]
if chunk == "" {
chunk = "+" var respStr = "+"
if len(chunk) != 0 {
respStr = base64.StdEncoding.EncodeToString(chunk)
} }
msgs = append(msgs, &irc.Message{ msgs = append(msgs, &irc.Message{
Command: "AUTHENTICATE", Command: "AUTHENTICATE",
Params: []string{chunk}, Params: []string{respStr},
}) })
} }
return msgs return msgs