Compare commits

...

17 commits

Author SHA1 Message Date
Gabriel Simmer 600930d466
S3 doc 2024-07-12 20:28:49 +01:00
Gabriel Simmer d21c5be4ae
Run gofmt 2024-07-12 20:28:49 +01:00
Gabriel Simmer a38931bde1
Break out file host URL to allow custom CDNs 2024-07-12 20:28:48 +01:00
Gabriel Simmer 566c122b5e
Groundwork for S3 file uploader 2024-07-12 20:28:45 +01:00
Gabriel Simmer faeb46d188
Add Containerfile 2024-07-12 20:28:32 +01:00
Simon Ser bb234c8348 conn: document conn.ReadMessage 2024-07-08 23:16:14 +02:00
Conrad Hoffmann 07502541e4 conn: fix goroutine leak
As is, soju leaks goroutines on client disconnects, because the closure
started as goroutine in newConn never finishes. It gets stuck in the
for loop annotated as "draining the outgoing channel", because the
outgoing channel is in fact never closed.

This commit fixes the issue by calling conn.Close rather than
conn.conn.Close, which closes not only the underlying net.Conn (in
c.conn), but also the channel.
2024-07-08 23:04:52 +02:00
Simon Ser 3667102e72 conn: return net.ErrClosed in conn.Close 2024-07-08 20:37:00 +02:00
Simon Ser d65c1654b8 Stop dereferencing *conn
The *conn pointer returned by newConn is dereferenced in
newDownstreamConn and connectToUpstream (a premature optimization).
As a result, the whole struct is copied and the internal newConn
goroutine works with a different chunk of memory than
downstreamConn and upstreamConn.
2024-07-08 20:35:20 +02:00
Simon Ser a3716dc2d2 downstream: reflect AWAY status in self-WHO reply without upstream 2024-07-08 08:28:50 +02:00
Simon Ser 4682bbef66 downstream: drop TODO about WHOX mask2
This is not part of the IRCv3 spec.
2024-07-08 08:24:34 +02:00
Simon Ser 9a36e6730d contrib/clients: remove note about chat.sr.ht
This document is not a good place for SourceHut-specific
recommendations.
2024-07-04 21:34:23 +02:00
Simon Ser 29bdc1aa45 Migrate to Codeberg 2024-07-04 21:28:11 +02:00
Simon Ser 0ced56a155 doc: use openssl -verify_quiet instead of discarding stderr
stderr prints useful information when openssl fails to connect to
the server. Use -verify_quiet to reduce chatter a bit and still
retain error messages.
2024-07-01 15:13:08 +02:00
Simon Ser d5dd194b01 downstream: fix WHO membership prefix order without server-specific flags
Gregory noticed that my last-minute edit was wrong [1]. Indeed,
when i == -1, that means that Flags only contains 'H'/'G'/'*' and
nothing else. We need to append the membership prefix in that case.

[1]: https://lists.sr.ht/~emersion/soju-dev/%3C20240630213249.13061-2-greg@gpanders.com%3E#%3CD2DP18U4PP40.DBYWGA8WM2KN@gpanders.com%3E

Fixes: ae203388e1 ("Fix channel membership prefixes in cached WHO replies")
Reported-by: Gregory Anders <greg@gpanders.com>
2024-07-01 00:14:06 +02:00
Gregory Anders ae203388e1 Fix channel membership prefixes in cached WHO replies
Channel membership prefixes in WHO replies (RPL_WHOREPLY and
RPL_WHOSPCRPL) were cached in the user's flags, which meant those same
prefixes were returned on future cache hits, even though the flags are
channel specific.

Strip the channel membership prefixes from the user's flags before
adding a user to the cache and add the prefixes back when reading from
the cache (using the membership info from the NAMES reply).
2024-06-30 23:50:50 +02:00
Simon Ser 965ce9cdb9 xirc: fix chunking in GenerateSASL
The SASL response needs to be encoded into base64, then split into
400 byte chunks. We were doing it in reverse order.
2024-06-30 23:42:31 +02:00
41 changed files with 293 additions and 96 deletions

View file

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

View file

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

19
Containerfile Normal file
View file

@ -0,0 +1,19 @@
FROM docker.io/golang:1.21.6-bookworm as build
ENV CGO_ENABLED=0 \
GOOS=linux \
GOFLAGS="-tags=moderncsqlite"
WORKDIR /build
COPY . /build
RUN make soju
FROM scratch
COPY --from=build /build/soju /app/soju
COPY --from=build /build/sojuctl /app/sojuctl
COPY --from=build /build/sojudb /app/sojudb
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
CMD ["/app/soju"]

View file

@ -11,8 +11,8 @@ RUNDIR ?= /run
sharedstatedir := /var/lib
config_path := $(SYSCONFDIR)/soju/config
admin_socket_path := $(RUNDIR)/soju/admin
goldflags := -X 'git.sr.ht/~emersion/soju/config.DefaultPath=$(config_path)' \
-X 'git.sr.ht/~emersion/soju/config.DefaultUnixAdminPath=$(admin_socket_path)'
goldflags := -X 'codeberg.org/emersion/soju/config.DefaultPath=$(config_path)' \
-X 'codeberg.org/emersion/soju/config.DefaultUnixAdminPath=$(admin_socket_path)'
goflags := $(GOFLAGS) -ldflags="$(goldflags)"
commands := soju sojuctl sojudb
man_pages := doc/soju.1 doc/sojuctl.1
@ -38,4 +38,9 @@ install:
cp -f $(man_pages) $(DESTDIR)$(PREFIX)/$(MANDIR)/man1
[ -f $(DESTDIR)$(config_path) ] || cp -f config.in $(DESTDIR)$(config_path)
container:
buildah manifest create soju-multiarch
buildah bud --tag soju:latest --manifest soju-multiarch --arch amd64 .
buildah bud --tag soju:latest --manifest soju-multiarch --arch arm64 .
.PHONY: soju sojudb sojuctl clean install

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -22,12 +22,12 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"git.sr.ht/~emersion/soju"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/config"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/fileupload"
"git.sr.ht/~emersion/soju/identd"
"codeberg.org/emersion/soju"
"codeberg.org/emersion/soju/auth"
"codeberg.org/emersion/soju/config"
"codeberg.org/emersion/soju/database"
"codeberg.org/emersion/soju/fileupload"
"codeberg.org/emersion/soju/identd"
)
// TCP keep-alive interval for downstream TCP connections
@ -113,6 +113,7 @@ func loadConfig() (*config.Server, *soju.Config, error) {
MOTD: motd,
Auth: auth,
FileUploader: fileUploader,
FileCdn: raw.FileCdn,
}
return raw, cfg, nil
}
@ -151,7 +152,6 @@ func main() {
srv := soju.NewServer(db)
srv.SetConfig(serverCfg)
srv.Logger = soju.NewLogger(log.Writer(), debug)
fileUploadHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cfg := srv.Config()
h := fileupload.Handler{
@ -159,6 +159,7 @@ func main() {
DB: db,
Auth: cfg.Auth,
HTTPOrigins: cfg.HTTPOrigins,
Cdn: cfg.FileCdn,
}
h.ServeHTTP(w, r)
})

View file

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

View file

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

View file

@ -83,6 +83,7 @@ type Server struct {
MsgStore MsgStore
Auth Auth
FileUpload *FileUpload
FileCdn string
HTTPOrigins []string
HTTPIngress string
@ -130,6 +131,7 @@ func Load(filename string) (*Server, error) {
Log []string `scfg:"log"`
Auth []string `scfg:"auth"`
FileUpload []string `scfg:"file-upload"`
FileCdn string `scfg:"file-cdn"`
HTTPOrigin []string `scfg:"http-origin"`
HTTPIngress string `scfg:"http-ingress"`
AcceptProxyIP []string `scfg:"accept-proxy-ip"`
@ -214,11 +216,17 @@ func Load(filename string) (*Server, error) {
if source == "" {
return nil, fmt.Errorf("directive file-upload: driver %q requires a source", driver)
}
case "s3":
if source == "" {
return nil, fmt.Errorf("directive file-upload: driver %q requires a source", driver)
}
default:
return nil, fmt.Errorf("directive file-upload: unknown driver %q", driver)
}
srv.FileUpload = &FileUpload{driver, source}
}
srv.FileCdn = raw.FileCdn
for _, origin := range raw.HTTPOrigin {
if _, err := path.Match(origin, origin); err != nil {
return nil, fmt.Errorf("directive http-origin: %v", err)

View file

@ -3,7 +3,6 @@ package soju
import (
"context"
"errors"
"fmt"
"io"
"net"
"strings"
@ -158,7 +157,7 @@ func newConn(srv *Server, ic ircConn, options *connOptions) *conn {
break
}
}
if err := c.conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
if err := c.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
c.logger.Printf("failed to close connection: %v", err)
} else {
c.logger.Debugf("connection closed")
@ -185,7 +184,7 @@ func (c *conn) Close() error {
defer c.lock.Unlock()
if c.closed {
return fmt.Errorf("connection already closed")
return net.ErrClosed
}
err := c.conn.Close()
@ -195,6 +194,10 @@ func (c *conn) Close() error {
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) {
msg, err := c.conn.ReadMessage()
if errors.Is(err, net.ErrClosed) {

View file

@ -70,12 +70,6 @@ outgoing messages:
/set irc_reconnect_rejoin 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]
To connect irssi to a network, for example Libera Chat:
@ -120,6 +114,5 @@ See `/help cap` for more information.
[read_marker.py]: https://weechat.org/scripts/source/read_marker.py.html/
[Hexchat]: https://hexchat.github.io/
[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/
[irssi]: https://irssi.org/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -166,6 +166,10 @@ The following directives are supported:
Supported drivers:
- _fs_ stores uploaded files on disk. _source_ is required.
- _s3_ stores uploaded files in an s3-compatible api. _source_ is required and should be <s3 endpoint>/<bucket name>.
*file-cdn* <url>
Set the base url/cdn for file uploads, useful if using s3.
*http-origin* <patterns...>
List of allowed HTTP origins for WebSocket listeners. The parameters are
@ -296,7 +300,7 @@ character.
of an IRC server:
```
openssl s_client -connect irc.example.org:6697 </dev/null 2>/dev/null | openssl x509 -fingerprint -sha512 -noout -in /dev/stdin
openssl s_client -connect irc.example.org:6697 -verify_quiet </dev/null | openssl x509 -fingerprint -sha512 -noout -in /dev/stdin
```
*-nick* <nickname>
@ -556,7 +560,7 @@ character.
Maintained by Simon Ser <contact@emersion.fr>, who is assisted by other
open-source contributors. For more information about soju development, see
<https://sr.ht/~emersion/soju>.
<https://soju.im>.
# 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
open-source contributors. For more information about soju development, see
<https://sr.ht/~emersion/soju>.
<https://soju.im>.
# SEE ALSO

View file

@ -17,10 +17,10 @@ import (
"github.com/emersion/go-sasl"
"gopkg.in/irc.v4"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/msgstore"
"git.sr.ht/~emersion/soju/xirc"
"codeberg.org/emersion/soju/auth"
"codeberg.org/emersion/soju/database"
"codeberg.org/emersion/soju/msgstore"
"codeberg.org/emersion/soju/xirc"
)
type ircError struct {
@ -328,7 +328,7 @@ func serverSASLMechanisms(srv *Server) []string {
}
type downstreamConn struct {
conn
*conn
id uint64
@ -363,7 +363,7 @@ func newDownstreamConn(srv *Server, ic ircConn, id uint64) *downstreamConn {
options := connOptions{Logger: logger}
cm := xirc.CaseMappingASCII
dc := &downstreamConn{
conn: *newConn(srv, ic, &options),
conn: newConn(srv, ic, &options),
id: id,
nick: "*",
nickCM: "*",
@ -2142,7 +2142,6 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
// Clients will use the first mask to match RPL_ENDOFWHO
endOfWhoToken := msg.Params[0]
// TODO: add support for WHOX mask2
mask := msg.Params[0]
var options string
if len(msg.Params) > 1 {
@ -2154,8 +2153,10 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
// TODO: support mixed bouncer/upstream WHO queries
maskCM := dc.casemap(mask)
if dc.network == nil && maskCM == dc.nickCM {
// TODO: support AWAY (H/G) in self WHO reply
flags := "H"
if dc.away != nil {
flags = "G"
}
if dc.user.Admin {
flags += "*"
}
@ -2230,6 +2231,23 @@ func (dc *downstreamConn) handleMessageRegistered(ctx context.Context, msg *irc.
}
if uc.isChannel(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))
}

View file

@ -12,8 +12,8 @@ import (
"strings"
"time"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/database"
"codeberg.org/emersion/soju/auth"
"codeberg.org/emersion/soju/database"
)
const maxSize = 50 * 1024 * 1024 // 50 MiB
@ -58,13 +58,15 @@ var primaryExts = map[string]string{
type Uploader interface {
load(filename string) (basename string, modTime time.Time, content io.ReadSeekCloser, err error)
store(r io.Reader, username, mimeType, basename string) (outFilename string, err error)
store(r io.Reader, username, mimeType, basename string, contentLength int64) (outFilename string, err error)
}
func New(driver, source string) (Uploader, error) {
switch driver {
case "fs":
return &fs{source}, nil
case "s3":
return &s3{source}, nil
default:
return nil, fmt.Errorf("unknown file upload driver %q", driver)
}
@ -75,6 +77,7 @@ type Handler struct {
Auth auth.Authenticator
DB database.Database
HTTPOrigins []string
Cdn string
}
func (h *Handler) checkOrigin(reqOrigin string) bool {
@ -151,6 +154,12 @@ func (h *Handler) fetch(resp http.ResponseWriter, req *http.Request) {
return
}
if h.Cdn != "" {
resp.Header().Set("Location", h.Cdn+"/"+filename)
resp.WriteHeader(http.StatusCreated)
return
}
basename, modTime, content, err := h.Uploader.load(filename)
if err != nil {
http.Error(resp, "failed to open file", http.StatusNotFound)
@ -276,13 +285,18 @@ func (h *Handler) store(resp http.ResponseWriter, req *http.Request) {
}
r := &limitedReader{r: req.Body, n: maxSize}
outFilename, err := h.Uploader.store(r, username, mimeType, basename)
outFilename, err := h.Uploader.store(r, username, mimeType, basename, req.ContentLength)
if err != nil {
fmt.Println(err)
http.Error(resp, "failed to write file", http.StatusInternalServerError)
return
}
if h.Cdn != "" {
resp.Header().Set("Location", h.Cdn+"/"+outFilename)
} else {
resp.Header().Set("Location", "/uploads/"+outFilename)
}
resp.WriteHeader(http.StatusCreated)
}

View file

@ -40,7 +40,7 @@ func (fs *fs) load(filename string) (basename string, modTime time.Time, content
return basename, fi.ModTime(), f, nil
}
func (fs *fs) store(r io.Reader, username, mimeType, origBasename string) (outFilename string, err error) {
func (fs *fs) store(r io.Reader, username, mimeType, origBasename string, contentLength int64) (outFilename string, err error) {
origBasename = filepath.Base(origBasename)
var suffix string

55
fileupload/s3.go Normal file
View file

@ -0,0 +1,55 @@
package fileupload
import (
"context"
"fmt"
"io"
"net/url"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/google/uuid"
)
type s3 struct {
endpoint string
}
// load implements Uploader.
func (s *s3) load(filename string) (basename string, modTime time.Time, content io.ReadSeekCloser, err error) {
panic("unimplemented")
}
// store implements Uploader.
func (s *s3) store(r io.Reader, username string, mimeType string, basename string, contentLength int64) (outFilename string, err error) {
split := strings.Split(s.endpoint, "/")
bucket := split[len(split)-1]
filename := url.PathEscape(username) + "/" + uuid.NewString()
endpointResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: s.endpoint,
}, nil
})
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithEndpointResolverWithOptions(endpointResolver),
config.WithRegion("auto"),
)
s3client := awss3.NewFromConfig(cfg)
_, err = s3client.PutObject(context.TODO(), &awss3.PutObjectInput{
Bucket: &bucket,
Key: &filename,
ContentType: &mimeType,
ContentLength: &contentLength,
Body: r,
})
if err != nil {
return "", fmt.Errorf("failed to upload to s3 api: %v", err)
}
return filename, nil
}

25
go.mod
View file

@ -1,4 +1,4 @@
module git.sr.ht/~emersion/soju
module codeberg.org/emersion/soju
go 1.19
@ -21,6 +21,29 @@ require (
)
require (
github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.23 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
)
require (
github.com/aws/aws-sdk-go v1.54.11
github.com/aws/aws-sdk-go-v2/config v1.27.23
github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect

43
go.sum
View file

@ -7,6 +7,44 @@ git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9 h1:Ahny8Ud1LjVMMA
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA=
github.com/SherClockHolmes/webpush-go v1.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKlZhp5QpYnu6k=
github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw=
github.com/aws/aws-sdk-go v1.54.11 h1:Zxuv/R+IVS0B66yz4uezhxH9FN9/G2nbxejYqAMFjxk=
github.com/aws/aws-sdk-go v1.54.11/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.27.23 h1:Cr/gJEa9NAS7CDAjbnB7tHYb3aLZI2gVggfmSAasDac=
github.com/aws/aws-sdk-go-v2/config v1.27.23/go.mod h1:WMMYHqLCFu5LH05mFOF5tsq1PGEMfKbu083VKqLCd0o=
github.com/aws/aws-sdk-go-v2/credentials v1.17.23 h1:G1CfmLVoO2TdQ8z9dW+JBc/r8+MqyPQhXCafNZcXVZo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.23/go.mod h1:V/DvSURn6kKgcuKEk4qwSwb/fZ2d++FFARtWSbXnLqY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13 h1:THZJJ6TU/FOiM7DZFnisYV9d49oxXWUzsVIMTuf3VNU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.13/go.mod h1:VISUTg6n+uBaYIWPBaIG0jk7mbBxm7DUqBtU2cUDDWI=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15 h1:2jyRZ9rVIMisyQRnhSS/SqlckveoxXneIumECVFP91Y=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.15/go.mod h1:bDRG3m382v1KJBk1cKz7wIajg87/61EiiymEyfLvAe0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13 h1:Eq2THzHt6P41mpjS2sUzz/3dJYFRqdWZ+vQaEMm98EM=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.13/go.mod h1:FgwTca6puegxgCInYwGjmd4tB9195Dd6LCuA+8MjpWw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1 h1:aHPtNY87GZ214N4rShgIo+5JQz7ICrJ50i17JbueUTw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.57.1/go.mod h1:hdV0NTYd0RwV4FvNKhKUNbPLZoq9CTr/lke+3I7aCAI=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1 h1:lCEv9f8f+zJ8kcFeAjRZsekLd/x5SAm96Cva+VbUdo8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.1/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@ -26,6 +64,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -109,6 +151,7 @@ google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/irc.v4 v4.0.0 h1:5jsLkU2Tg+R2nGNqmkGCrciasyi4kNkDXhyZD+C31yY=
gopkg.in/irc.v4 v4.0.0/go.mod h1:BfjDz9MmuWW6OZY7iq4naOhudO8+QQCdO4Ko18jcsRE=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

15
irc.go
View file

@ -9,7 +9,7 @@ import (
"gopkg.in/irc.v4"
"git.sr.ht/~emersion/soju/xirc"
"codeberg.org/emersion/soju/xirc"
)
// TODO: generalize and move helpers to the xirc package
@ -194,6 +194,19 @@ func formatMemberPrefix(ms xirc.MembershipSet, dc *downstreamConn) string {
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 {
if len(msg.Params) < len(out) {
return newNeedMoreParamsError(msg.Command)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,11 +20,11 @@ import (
"gopkg.in/irc.v4"
"nhooyr.io/websocket"
"git.sr.ht/~emersion/soju/auth"
"git.sr.ht/~emersion/soju/config"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/fileupload"
"git.sr.ht/~emersion/soju/identd"
"codeberg.org/emersion/soju/auth"
"codeberg.org/emersion/soju/config"
"codeberg.org/emersion/soju/database"
"codeberg.org/emersion/soju/fileupload"
"codeberg.org/emersion/soju/identd"
)
var (
@ -152,6 +152,7 @@ type Config struct {
EnableUsersOnAuth bool
Auth auth.Authenticator
FileUploader fileupload.Uploader
FileCdn string
}
type Server struct {

View file

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

View file

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

View file

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

View file

@ -14,13 +14,13 @@ import (
"sync/atomic"
"time"
"git.sr.ht/~emersion/soju/xirc"
"codeberg.org/emersion/soju/xirc"
"github.com/SherClockHolmes/webpush-go"
"gopkg.in/irc.v4"
"git.sr.ht/~emersion/soju/database"
"git.sr.ht/~emersion/soju/msgstore"
"codeberg.org/emersion/soju/database"
"codeberg.org/emersion/soju/msgstore"
)
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 {
encoded := base64.StdEncoding.EncodeToString(resp)
// <= instead of < because we need to send a final empty response if
// the last chunk is exactly 400 bytes long
var msgs []*irc.Message
for i := 0; i <= len(resp); i += MaxSASLLength {
for i := 0; i <= len(encoded); i += MaxSASLLength {
j := i + MaxSASLLength
if j > len(resp) {
j = len(resp)
if j > len(encoded) {
j = len(encoded)
}
chunk := resp[i:j]
var respStr = "+"
if len(chunk) != 0 {
respStr = base64.StdEncoding.EncodeToString(chunk)
chunk := encoded[i:j]
if chunk == "" {
chunk = "+"
}
msgs = append(msgs, &irc.Message{
Command: "AUTHENTICATE",
Params: []string{respStr},
Params: []string{chunk},
})
}
return msgs