1
0
Fork 0
forked from External/ergo

Merge pull request 'sync' (#1) from External/ergo:master into master

Reviewed-on: #1
This commit is contained in:
Failure 2024-11-18 11:42:02 -08:00
commit d26235e9a9
20 changed files with 110 additions and 41 deletions

View file

@ -19,7 +19,7 @@ jobs:
- name: "setup go" - name: "setup go"
uses: "actions/setup-go@v3" uses: "actions/setup-go@v3"
with: with:
go-version: "1.22" go-version: "1.23"
- name: "install python3-pytest" - name: "install python3-pytest"
run: "sudo apt install -y python3-pytest" run: "sudo apt install -y python3-pytest"
- name: "make install" - name: "make install"

View file

@ -1,5 +1,5 @@
## build ergo binary ## build ergo binary
FROM docker.io/golang:1.22-alpine AS build-env FROM docker.io/golang:1.23-alpine AS build-env
RUN apk upgrade -U --force-refresh --no-cache RUN apk upgrade -U --force-refresh --no-cache
RUN apk add --no-cache --purge --clean-protected -l -u make git RUN apk add --no-cache --purge --clean-protected -l -u make git

View file

@ -820,7 +820,7 @@ lock-file: "ircd.lock"
# datastore configuration # datastore configuration
datastore: datastore:
# path to the datastore # path to the database file (used to store account and channel registrations):
path: ircd.db path: ircd.db
# if the database schema requires an upgrade, `autoupgrade` will attempt to # if the database schema requires an upgrade, `autoupgrade` will attempt to

View file

@ -219,6 +219,12 @@ CAPDEFS = [
url="https://github.com/ircv3/ircv3-specifications/pull/527", url="https://github.com/ircv3/ircv3-specifications/pull/527",
standard="proposed IRCv3", standard="proposed IRCv3",
), ),
CapDef(
identifier="ExtendedISupport",
name="draft/extended-isupport",
url="https://github.com/ircv3/ircv3-specifications/pull/543",
standard="proposed IRCv3",
),
] ]
def validate_defs(): def validate_defs():

4
go.mod
View file

@ -1,6 +1,6 @@
module github.com/ergochat/ergo module github.com/ergochat/ergo
go 1.22 go 1.23
require ( require (
code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48
@ -8,7 +8,7 @@ require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1
github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
github.com/ergochat/irc-go v0.5.0-rc1 github.com/ergochat/irc-go v0.5.0-rc2
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.0
github.com/go-test/deep v1.0.6 // indirect github.com/go-test/deep v1.0.6 // indirect
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1

2
go.sum
View file

@ -22,6 +22,8 @@ github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 h1:+J5m88nvybxB5
github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM= github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881/go.mod h1:ASYJtQujNitna6cVHsNQTGrfWvMPJ5Sa2lZlmsH65uM=
github.com/ergochat/irc-go v0.5.0-rc1 h1:kFoIHExoNFQ2CV+iShAVna/H4xrXQB4t4jK5Sep2j9k= github.com/ergochat/irc-go v0.5.0-rc1 h1:kFoIHExoNFQ2CV+iShAVna/H4xrXQB4t4jK5Sep2j9k=
github.com/ergochat/irc-go v0.5.0-rc1/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0= github.com/ergochat/irc-go v0.5.0-rc1/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/ergochat/irc-go v0.5.0-rc2 h1:VuSQJF5K4hWvYSzGa4b8vgL6kzw8HF6LSOejE+RWpAo=
github.com/ergochat/irc-go v0.5.0-rc2/go.mod h1:2vi7KNpIPWnReB5hmLpl92eMywQvuIeIIGdt/FQCph0=
github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g= github.com/ergochat/scram v1.0.2-ergo1 h1:2bYXiRFQH636pT0msOG39fmEYl4Eq+OuutcyDsCix/g=
github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/ergochat/scram v1.0.2-ergo1/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM= github.com/ergochat/websocket v1.4.2-oragono1 h1:plMUunFBM6UoSCIYCKKclTdy/TkkHfUslhOfJQzfueM=

View file

@ -64,10 +64,11 @@ const (
BotTagName = "bot" BotTagName = "bot"
// https://ircv3.net/specs/extensions/chathistory // https://ircv3.net/specs/extensions/chathistory
ChathistoryTargetsBatchType = "draft/chathistory-targets" ChathistoryTargetsBatchType = "draft/chathistory-targets"
ExtendedISupportBatchType = "draft/extended-isupport"
) )
func init() { func init() {
nameToCapability = make(map[string]Capability) nameToCapability = make(map[string]Capability, numCapabs)
for capab, name := range capabilityNames { for capab, name := range capabilityNames {
nameToCapability[name] = Capability(capab) nameToCapability[name] = Capability(capab)
} }

View file

@ -53,6 +53,10 @@ const (
// https://github.com/ircv3/ircv3-specifications/pull/362 // https://github.com/ircv3/ircv3-specifications/pull/362
EventPlayback Capability = iota EventPlayback Capability = iota
// ExtendedISupport is the proposed IRCv3 capability named "draft/extended-isupport":
// https://github.com/ircv3/ircv3-specifications/pull/543
ExtendedISupport Capability = iota
// Languages is the proposed IRCv3 capability named "draft/languages": // Languages is the proposed IRCv3 capability named "draft/languages":
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6 // https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
Languages Capability = iota Languages Capability = iota
@ -165,6 +169,7 @@ var (
"draft/channel-rename", "draft/channel-rename",
"draft/chathistory", "draft/chathistory",
"draft/event-playback", "draft/event-playback",
"draft/extended-isupport",
"draft/languages", "draft/languages",
"draft/message-redaction", "draft/message-redaction",
"draft/multiline", "draft/multiline",

View file

@ -179,6 +179,8 @@ type Session struct {
batchCounter atomic.Uint32 batchCounter atomic.Uint32
isupportSentPrereg bool
quitMessage string quitMessage string
awayMessage string awayMessage string

View file

@ -152,6 +152,10 @@ func init() {
handler: isonHandler, handler: isonHandler,
minParams: 1, minParams: 1,
}, },
"ISUPPORT": {
handler: isupportHandler,
usablePreReg: true,
},
"JOIN": { "JOIN": {
handler: joinHandler, handler: joinHandler,
minParams: 1, minParams: 1,

View file

@ -1664,6 +1664,7 @@ func (config *Config) generateISupport() (err error) {
isupport.Add("RPCHAN", "E") isupport.Add("RPCHAN", "E")
isupport.Add("RPUSER", "E") isupport.Add("RPUSER", "E")
} }
isupport.Add("SAFELIST", "")
isupport.Add("STATUSMSG", "~&@%+") isupport.Add("STATUSMSG", "~&@%+")
isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries)) isupport.Add("TARGMAX", fmt.Sprintf("NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:%s,TAGMSG:%s,NOTICE:%s,MONITOR:%d", maxTargetsString, maxTargetsString, maxTargetsString, config.Limits.MonitorEntries))
isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen)) isupport.Add("TOPICLEN", strconv.Itoa(config.Limits.TopicLen))

View file

@ -1,4 +1,4 @@
//go:build !plan9 //go:build !(plan9 || solaris)
package flock package flock

View file

@ -1,4 +1,4 @@
//go:build plan9 //go:build plan9 || solaris
package flock package flock

View file

@ -873,7 +873,6 @@ func debugHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respon
switch param { switch param {
case "GCSTATS": case "GCSTATS":
stats := debug.GCStats{ stats := debug.GCStats{
Pause: make([]time.Duration, 10),
PauseQuantiles: make([]time.Duration, 5), PauseQuantiles: make([]time.Duration, 5),
} }
debug.ReadGCStats(&stats) debug.ReadGCStats(&stats)
@ -1350,6 +1349,15 @@ func isonHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons
return false return false
} }
// ISUPPORT
func isupportHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
server.RplISupport(client, rb)
if !client.registered {
rb.session.isupportSentPrereg = true
}
return false
}
// JOIN <channel>{,<channel>} [<key>{,<key>}] // JOIN <channel>{,<channel>} [<key>{,<key>}]
func joinHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { func joinHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
// #1417: allow `JOIN 0` with a confirmation code // #1417: allow `JOIN 0` with a confirmation code

View file

@ -259,6 +259,11 @@ appropriate channel privs.`,
text: `ISON <nickname>{ <nickname>} text: `ISON <nickname>{ <nickname>}
Returns whether the given nicks exist on the network.`, Returns whether the given nicks exist on the network.`,
},
"isupport": {
text: `ISUPPORT
Returns RPL_ISUPPORT lines describing the server's capabilities.`,
}, },
"join": { "join": {
text: `JOIN <channel>{,<channel>} [<key>{,<key>}] text: `JOIN <channel>{,<channel>} [<key>{,<key>}]

View file

@ -446,7 +446,9 @@ func (server *Server) playRegistrationBurst(session *Session) {
session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, rplMyInfo1, rplMyInfo2, rplMyInfo3) session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, rplMyInfo1, rplMyInfo2, rplMyInfo3)
rb := NewResponseBuffer(session) rb := NewResponseBuffer(session)
if !(rb.session.capabilities.Has(caps.ExtendedISupport) && rb.session.isupportSentPrereg) {
server.RplISupport(c, rb) server.RplISupport(c, rb)
}
if d.account != "" && session.capabilities.Has(caps.Persistence) { if d.account != "" && session.capabilities.Has(caps.Persistence) {
reportPersistenceStatus(c, rb, false) reportPersistenceStatus(c, rb, false)
} }
@ -468,10 +470,17 @@ func (server *Server) playRegistrationBurst(session *Session) {
// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses. // RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses.
func (server *Server) RplISupport(client *Client, rb *ResponseBuffer) { func (server *Server) RplISupport(client *Client, rb *ResponseBuffer) {
server.sendRplISupportLines(client, rb, server.Config().Server.isupport.CachedReply)
}
func (server *Server) sendRplISupportLines(client *Client, rb *ResponseBuffer, lines [][]string) {
if rb.session.capabilities.Has(caps.ExtendedISupport) {
batchID := rb.StartNestedBatch(caps.ExtendedISupportBatchType)
defer rb.EndNestedBatch(batchID)
}
translatedISupport := client.t("are supported by this server") translatedISupport := client.t("are supported by this server")
nick := client.Nick() nick := client.Nick()
config := server.Config() for _, cachedTokenLine := range lines {
for _, cachedTokenLine := range config.Server.isupport.CachedReply {
length := len(cachedTokenLine) + 2 length := len(cachedTokenLine) + 2
tokenline := make([]string, length) tokenline := make([]string, length)
tokenline[0] = nick tokenline[0] = nick
@ -806,13 +815,19 @@ func (server *Server) applyConfig(config *Config) (err error) {
} }
if !initial { if !initial {
// push new info to all of our clients // send 005 updates (somewhat rare)
if len(newISupportReplies) != 0 {
for _, sClient := range server.clients.AllClients() { for _, sClient := range server.clients.AllClients() {
for _, tokenline := range newISupportReplies { for _, session := range sClient.Sessions() {
sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...) rb := NewResponseBuffer(session)
server.sendRplISupportLines(sClient, rb, newISupportReplies)
rb.Send(false)
}
}
} }
if sendRawOutputNotice { if sendRawOutputNotice {
for _, sClient := range server.clients.AllClients() {
sClient.Notice(sClient.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect.")) sClient.Notice(sClient.t("This server is in debug mode and is logging all user I/O. If you do not wish for everything you send to be readable by the server owner(s), please disconnect."))
} }
} }

View file

@ -791,7 +791,7 @@ lock-file: "ircd.lock"
# datastore configuration # datastore configuration
datastore: datastore:
# path to the datastore # path to the database file (used to store account and channel registrations):
path: ircd.db path: ircd.db
# if the database schema requires an upgrade, `autoupgrade` will attempt to # if the database schema requires an upgrade, `autoupgrade` will attempt to

View file

@ -196,6 +196,15 @@ func trimInitialSpaces(str string) string {
return str[i:] return str[i:]
} }
func isASCII(str string) bool {
for i := 0; i < len(str); i++ {
if str[i] > 127 {
return false
}
}
return true
}
func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg Message, err error) { func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg Message, err error) {
// remove either \n or \r\n from the end of the line: // remove either \n or \r\n from the end of the line:
line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\n")
@ -265,11 +274,16 @@ func parseLine(line string, maxTagDataLength int, truncateLen int) (ircmsg Messa
commandEnd = len(line) commandEnd = len(line)
paramStart = len(line) paramStart = len(line)
} }
// normalize command to uppercase: baseCommand := line[:commandEnd]
ircmsg.Command = strings.ToUpper(line[:commandEnd]) if len(baseCommand) == 0 {
if len(ircmsg.Command) == 0 {
return ircmsg, ErrorLineIsEmpty return ircmsg, ErrorLineIsEmpty
} }
// technically this must be either letters or a 3-digit numeric:
if !isASCII(baseCommand) {
return ircmsg, ErrorLineContainsBadChar
}
// normalize command to uppercase:
ircmsg.Command = strings.ToUpper(baseCommand)
line = line[paramStart:] line = line[paramStart:]
for { for {

View file

@ -3,7 +3,6 @@ package ircutils
import ( import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"strings"
) )
var ( var (
@ -25,6 +24,7 @@ func EncodeSASLResponse(raw []byte) (result []string) {
} }
response := base64.StdEncoding.EncodeToString(raw) response := base64.StdEncoding.EncodeToString(raw)
result = make([]string, 0, (len(response)/400)+1)
lastLen := 0 lastLen := 0
for len(response) > 0 { for len(response) > 0 {
// TODO once we require go 1.21, this can be: lastLen = min(len(response), 400) // TODO once we require go 1.21, this can be: lastLen = min(len(response), 400)
@ -48,11 +48,11 @@ func EncodeSASLResponse(raw []byte) (result []string) {
// Do not copy a SASLBuffer after first use. // Do not copy a SASLBuffer after first use.
type SASLBuffer struct { type SASLBuffer struct {
maxLength int maxLength int
buffer strings.Builder buf []byte
} }
// NewSASLBuffer returns a new SASLBuffer. maxLength is the maximum amount of // NewSASLBuffer returns a new SASLBuffer. maxLength is the maximum amount of
// base64'ed data to buffer (0 for no limit). // data to buffer (0 for no limit).
func NewSASLBuffer(maxLength int) *SASLBuffer { func NewSASLBuffer(maxLength int) *SASLBuffer {
result := new(SASLBuffer) result := new(SASLBuffer)
result.Initialize(maxLength) result.Initialize(maxLength)
@ -69,37 +69,43 @@ func (b *SASLBuffer) Initialize(maxLength int) {
// response along with any decoding or protocol errors detected. // response along with any decoding or protocol errors detected.
func (b *SASLBuffer) Add(value string) (done bool, output []byte, err error) { func (b *SASLBuffer) Add(value string) (done bool, output []byte, err error) {
if value == "+" { if value == "+" {
output, err = b.getAndReset() // total size is a multiple of 400 (possibly 0)
return true, output, err output = b.buf
b.Clear()
return true, output, nil
} }
if len(value) > 400 { if len(value) > 400 {
b.buffer.Reset() b.Clear()
return true, nil, ErrSASLTooLong return true, nil, ErrSASLTooLong
} }
if b.maxLength != 0 && (b.buffer.Len()+len(value)) > b.maxLength { curLen := len(b.buf)
b.buffer.Reset() chunkDecodedLen := base64.StdEncoding.DecodedLen(len(value))
if b.maxLength != 0 && (curLen+chunkDecodedLen) > b.maxLength {
b.Clear()
return true, nil, ErrSASLLimitExceeded return true, nil, ErrSASLLimitExceeded
} }
b.buffer.WriteString(value) // "append-make pattern" as in the bytes.Buffer implementation:
b.buf = append(b.buf, make([]byte, chunkDecodedLen)...)
n, err := base64.StdEncoding.Decode(b.buf[curLen:], []byte(value))
b.buf = b.buf[0 : curLen+n]
if err != nil {
b.Clear()
return true, nil, err
}
if len(value) < 400 { if len(value) < 400 {
output, err = b.getAndReset() output = b.buf
return true, output, err b.Clear()
return true, output, nil
} else { } else {
// 400 bytes, wait for continuation line or +
return false, nil, nil return false, nil, nil
} }
} }
// Clear resets the buffer state. // Clear resets the buffer state.
func (b *SASLBuffer) Clear() { func (b *SASLBuffer) Clear() {
b.buffer.Reset() // we can't reuse this buffer in general since we may have returned it
} b.buf = nil
func (b *SASLBuffer) getAndReset() (output []byte, err error) {
output, err = base64.StdEncoding.DecodeString(b.buffer.String())
b.buffer.Reset()
return
} }

2
vendor/modules.txt vendored
View file

@ -25,7 +25,7 @@ github.com/ergochat/confusables
# github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 # github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881
## explicit; go 1.18 ## explicit; go 1.18
github.com/ergochat/go-ident github.com/ergochat/go-ident
# github.com/ergochat/irc-go v0.5.0-rc1 # github.com/ergochat/irc-go v0.5.0-rc2
## explicit; go 1.15 ## explicit; go 1.15
github.com/ergochat/irc-go/ircfmt github.com/ergochat/irc-go/ircfmt
github.com/ergochat/irc-go/ircmsg github.com/ergochat/irc-go/ircmsg