diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 923fd90f..d8772310 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: - name: "setup go" uses: "actions/setup-go@v3" with: - go-version: "1.24" + go-version: "1.25" - name: "install python3-pytest" run: "sudo apt install -y python3-pytest" - name: "make install" diff --git a/CHANGELOG.md b/CHANGELOG.md index 81da32a6..b24ce6b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,37 @@ # Changelog All notable changes to Ergo will be documented in this file. +## [2.17.0-rc1] - 2025-12-14 + +We're pleased to be publishing the release candidate for v2.17.0 (the official release should follow within a week or so). This release adds support for the [IRCv3 metadata specification](https://ircv3.net/specs/extensions/metadata), thanks to [@thatcher-gaming](https://github.com/thatcher-gaming), as well as bug fixes and minor updates. + +This release includes changes to the config file format, all of which are fully backwards-compatible and do not require updating the file before upgrading. It includes no changes to the database file format. + +Many thanks to [@branchgrove](https://github.com/branchgrove), [@Brutus5000](https://github.com/Brutus5000), [@progval](https://github.com/progval), [@SarahRoseLives](https://github.com/SarahRoseLives), [@thatcher-gaming](https://github.com/thatcher-gaming), [@ValwareIRC](https://github.com/ValwareIRC), and Xogium for contributing patches, reporting issues, and helping test. + +### Config changes +* Added `accounts.metadata` block to configure the new metadata feature. If this block is absent, metadata is disabled. See `default.yaml` for an example. (#2273) +* Added `server.idle-timeouts` for configurable idle timeouts; when unset, the previous hardcoded defaults are used (#2292, thanks [@Brutus5000](https://github.com/Brutus5000)!) +* Added `server.oper-throttle` to configure throttling for failed `OPER` attempts; when unset, this defaults to 1 attempt every 10 seconds (#2296) + +### Added +* Implemented support for the [draft/metadata-2](https://ircv3.net/specs/extensions/metadata) specification, allowing clients to set and retrieve metadata on accounts and channels (#2273, #2277, #2281, #2282, #2301, thanks [@thatcher-gaming](https://github.com/thatcher-gaming)!) +* Added `/v1/status` and `/v1/account_list` HTTP API endpoints (#2261, thanks [@SarahRoseLives](https://github.com/SarahRoseLives)!) +* Enhanced `/v1/account_details` API response with additional fields (#2261, thanks [@SarahRoseLives](https://github.com/SarahRoseLives)!) + +### Fixed +* Fixed `REGISTER` command to strip guest format when applicable, matching `NS REGISTER` behavior (#2270, #2271, thanks [@ValwareIRC](https://github.com/ValwareIRC) and [@thatcher-gaming](https://github.com/thatcher-gaming)!) +* Fixed invalid `FAIL` codes in `REGISTER` command (#2269, thanks [@ValwareIRC](https://github.com/ValwareIRC)!) +* Fixed validation of web push URLs to reject non-HTTPS URLs (#2295) +* Fixed inconsistent behavior when `history.enabled` is set but `history.chathistory-maxmessages` is not (#2303, #2304, thanks [@branchgrove](https://github.com/branchgrove)!) + +### Changed +* The `OPER` command now imposes a throttle on all attempts, never disconnects the client on failure, and logs non-sensitive information about failed attempts (#2296, #2298, thanks Xogium!) + +### Internal +* Official release builds use Go 1.25 (#2290) +* Upgraded the Docker base image from Alpine 3.19 to 3.22 (#2306) + ## [2.16.0] - 2025-05-18 We're pleased to be publishing v2.16.0, a new stable release. This release contains bug fixes and some minor updates. @@ -9,6 +40,7 @@ This release includes changes to the config file format, all of which are fully Many thanks to [@csmith](https://github.com/csmith), [@delthas](https://github.com/delthas), donio, [@emersion](https://github.com/emersion), [@KlaasT](https://github.com/KlaasT), [@knolley](https://github.com/knolley), [@Mailaender](https://github.com/Mailaender), and [@prdes](https://github.com/prdes) for reporting issues and helping test. ### Config changes +* Added `api` block for configuring the new HTTP API. If this block is absent, the API is disabled (#2231) * Added `server.additional-isupport` for publishing arbitrary ISUPPORT tokens (#2220, #2240) * Added `server.command-aliases` to configure aliases for server commands (#2229, #2236) * Added options to `roleplay` to customize the NUH's sent for `NPC` and `SCENE`. Roleplay remains deprecated and disabled by default. (#2237) diff --git a/Dockerfile b/Dockerfile index ffffc7a6..1d5bb800 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ## build ergo binary -FROM docker.io/golang:1.24-alpine AS build-env +FROM docker.io/golang:1.25-alpine3.22 AS build-env RUN apk upgrade -U --force-refresh --no-cache && apk add --no-cache --purge --clean-protected -l -u make git @@ -16,7 +16,7 @@ RUN sed -i 's/^\(\s*\)\"127.0.0.1:6667\":.*$/\1":6667":/' /go/src/github.com/erg RUN make install ## build ergo container -FROM docker.io/alpine:3.19 +FROM docker.io/alpine:3.22 # metadata LABEL maintainer="Daniel Oaks ,Daniel Thamdrup " \ diff --git a/Makefile b/Makefile index 804efeae..af4240d1 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,3 @@ -.PHONY: all install build release capdefs test smoke gofmt irctest - GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null) GIT_TAG := $(shell git tag --points-at HEAD 2> /dev/null | head -n 1) @@ -9,33 +7,42 @@ export CGO_ENABLED ?= 0 capdef_file = ./irc/caps/defs.go +.PHONY: all all: build +.PHONY: install install: go install -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)" +.PHONY: build build: go build -v -ldflags "-X main.commit=$(GIT_COMMIT) -X main.version=$(GIT_TAG)" +.PHONY: release release: goreleaser --skip=publish --clean +.PHONY: capdefs capdefs: python3 ./gencapdefs.py > ${capdef_file} +.PHONY: test test: python3 ./gencapdefs.py | diff - ${capdef_file} go test ./... go vet ./... ./.check-gofmt.sh +.PHONY: smoke smoke: install ergo mkcerts --conf ./default.yaml || true ergo run --conf ./default.yaml --smoke +.PHONY: gofmt gofmt: ./.check-gofmt.sh --fix +.PHONY: irctest irctest: install git submodule update --init cd irctest && make ergo diff --git a/default.yaml b/default.yaml index 000cbd89..007d8e18 100644 --- a/default.yaml +++ b/default.yaml @@ -180,6 +180,17 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # idle timeouts for inactive clients + idle-timeouts: + # give the client this long to complete connection registration (i.e. the initial + # IRC handshake, including capability negotiation and SASL) + registration: 60s + # if the client hasn't sent anything for this long, send them a PING + ping: 1m30s + # if the client hasn't sent anything for this long (including the PONG to the + # above PING), disconnect them + disconnect: 2m30s + # relaying using the RELAYMSG command relaymsg: # is relaymsg enabled at all? @@ -358,6 +369,10 @@ server: secure-nets: # - "10.0.0.0/8" + # allow attempts to OPER with a password at most this often. defaults to + # 10 seconds when unset. + oper-throttle: 10s + # Ergo will write files to disk under certain circumstances, e.g., # CPU profiling or data export. by default, these files will be written # to the working directory. set this to customize: @@ -522,7 +537,7 @@ accounts: # 1. these nicknames cannot be registered or reserved # 2. if a client is automatically renamed by the server, # this is the template that will be used (e.g., Guest-nccj6rgmt97cg) - # 3. if enforce-guest-format (see below) is enabled, clients without + # 3. if force-guest-format (see below) is enabled, clients without # a registered account will have this template applied to their # nicknames (e.g., 'katie' will become 'Guest-katie') guest-nickname-format: "Guest-*" @@ -724,6 +739,7 @@ oper-classes: - "history" # modify or delete history messages - "defcon" # use the DEFCON command (restrict server capabilities) - "massmessage" # message all users on the server + - "metadata" # modify arbitrary metadata on channels and users # ircd operators opers: @@ -1087,6 +1103,20 @@ history: # e.g., ERGO__SERVER__MAX_SENDQ=128k. see the manual for more details. allow-environment-overrides: true +# metadata support for setting key/value data on channels and nicknames. +metadata: + # can clients store metadata? + enabled: true + # how many keys can a client subscribe to? + max-subs: 100 + # how many keys can be stored per entity? + max-keys: 100 + # rate limiting for client metadata updates, which are expensive to process + client-throttle: + enabled: true + duration: 2m + max-attempts: 10 + # experimental support for mobile push notifications # see the manual for potential security, privacy, and performance implications. # DO NOT enable if you are running a Tor or I2P hidden service (i.e. one diff --git a/docs/API.md b/docs/API.md index 056f88c8..524e8077 100644 --- a/docs/API.md +++ b/docs/API.md @@ -51,6 +51,8 @@ The response is a JSON object with fields: * `success`: whether the account exists or not * `accountName`: canonical, case-unfolded version of the account name * `email`: email address of the account provided +* `registeredAt`: string, registration date/time of the account (in ISO8601 format) +* `channels`: array of strings, list of channels the account is registered on or associated with `/v1/check_auth` ---------------- @@ -86,3 +88,37 @@ The response is a JSON object with fields: * `success`: whether the account creation succeeded * `errorCode`: string, optional, machine-readable description of the error. Possible values include: `ACCOUNT_EXISTS`, `INVALID_PASSPHRASE`, `UNKNOWN_ERROR`. * `error`: string, optional, human-readable description of the failure. + +`/v1/account_list` +------------------- + +This endpoint fetches a list of all accounts. The request body is ignored and can be empty. + +The response is a JSON object with fields: + +* `success`: whether the request succeeded +* `accounts`: array of objects, each with fields: + * `success`: boolean, whether this individual account query succeeded + * `accountName`: string, canonical, case-unfolded version of the account name +* `totalCount`: integer, total number of accounts returned + + +`/v1/status` +------------- + +This endpoint returns status information about the running Ergo server. The request body is ignored and can be empty. + +The response is a JSON object with fields: + +* `success`: whether the request succeeded +* `version`: string, Ergo server version string +* `go_version`: string, version of Go runtime used +* `start_time`: string, server start time in ISO8601 format +* `users`: object with fields: + * `total`: total number of users connected + * `invisible`: number of invisible users + * `operators`: number of operators connected + * `unknown`: number of users with unknown status + * `max`: maximum number of users seen connected at once +* `channels`: integer, number of channels currently active +* `servers`: integer, number of servers connected in the network diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 17cfcc94..cfa3ddc9 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -171,6 +171,7 @@ Rehashing also reloads TLS certificates and the MOTD. Some configuration setting Ergo can also be configured using environment variables, using the following technique: +1. Ensure that `allow-environment-variables` is set to `true` in the YAML config file itself (see `default.yaml` for an example) 1. Find the "path" of the config variable you want to override in the YAML file, e.g., `server.websockets.allowed-origins` 1. Convert each path component from "kebab case" to "screaming snake case", e.g., `SERVER`, `WEBSOCKETS`, and `ALLOWED_ORIGINS`. 1. Prepend `ERGO` to the components, then join them all together using `__` as the separator, e.g., `ERGO__SERVER__WEBSOCKETS__ALLOWED_ORIGINS`. @@ -526,7 +527,7 @@ If your client or bot is failing to connect to Ergo, here are some things to che ## Why can't I oper? -If you try to oper unsuccessfully, Ergo will disconnect you from the network. If you're unable to oper, here are some things to double-check: +If your `OPER` command fails, check your server logs for more information. Here are some general issues to double-check: 1. Did you correctly generate the hashed password with `ergo genpasswd`? 1. Did you add the password hash to the correct config file, then save the file? diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index 427fd01e..65fe88d4 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -86,7 +86,7 @@ Once you've registered your nickname, you can use it to register channels. By de /msg ChanServ register #myChannel ``` -You must already be an operator (have the `+o` channel mode --- your client may display this as an `@` next to your nickname). If you're not a channel operator in the channel you want to register, ask your server administrator for help. +The channel must exist (if it doesn't, you can create it with `/join #myChannel`) and you must already be an operator (have the `+o` channel mode --- your client may display this as an `@` next to your nickname). If you're not a channel operator in the channel you want to register, ask your server administrator for help. # Always-on diff --git a/gencapdefs.py b/gencapdefs.py index 859aaa3d..45453c2b 100644 --- a/gencapdefs.py +++ b/gencapdefs.py @@ -237,6 +237,13 @@ CAPDEFS = [ url="https://github.com/ircv3/ircv3-specifications/pull/471", standard="Soju/Goguma vendor", ), + CapDef( + identifier="Metadata", + name="draft/metadata-2", + url="https://ircv3.net/specs/extensions/metadata", + standard="draft IRCv3", + ), + ] def validate_defs(): diff --git a/go.mod b/go.mod index 6b3daa4a..a577c776 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ergochat/ergo -go 1.24 +go 1.25 require ( code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 @@ -18,14 +18,14 @@ require ( github.com/stretchr/testify v1.4.0 // indirect github.com/tidwall/buntdb v1.3.2 github.com/xdg-go/scram v1.0.2 - golang.org/x/crypto v0.32.0 - golang.org/x/term v0.28.0 - golang.org/x/text v0.21.0 + golang.org/x/crypto v0.38.0 + golang.org/x/term v0.32.0 + golang.org/x/text v0.25.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/emersion/go-msgauth v0.6.8 + github.com/emersion/go-msgauth v0.7.0 github.com/ergochat/webpush-go/v2 v2.0.0 github.com/golang-jwt/jwt/v5 v5.2.2 ) @@ -39,7 +39,7 @@ require ( github.com/tidwall/rtred v0.1.2 // indirect github.com/tidwall/tinyqueue v0.1.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.33.0 // indirect ) replace github.com/gorilla/websocket => github.com/ergochat/websocket v1.4.2-oragono1 diff --git a/go.sum b/go.sum index 9cc360fc..24ef25c3 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emersion/go-msgauth v0.6.8 h1:kW/0E9E8Zx5CdKsERC/WnAvnXvX7q9wTHia1OA4944A= github.com/emersion/go-msgauth v0.6.8/go.mod h1:YDwuyTCUHu9xxmAeVj0eW4INnwB6NNZoPdLerpSxRrc= +github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g1INc= +github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck= github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 h1:WLHTOodthVyv5NvYLIvWl112kSFv5IInKKrRN2qpons= github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1/go.mod h1:mov+uh1DPWsltdQnOdzn08UO9GsJ3MEvhtu0Ci37fdk= github.com/ergochat/go-ident v0.0.0-20230911071154-8c30606d6881 h1:+J5m88nvybxB5AnBVGzTXM/yHVytt48rXBGcJGzSbms= @@ -70,6 +72,8 @@ github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyh github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= @@ -78,12 +82,18 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/irc/accounts.go b/irc/accounts.go index c85a22c1..fd55aeff 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -52,6 +52,7 @@ const ( // (not to be confused with their amodes, which a non-always-on client can have): keyAccountChannelToModes = "account.channeltomodes %s" keyAccountPushSubscriptions = "account.pushsubscriptions %s" + keyAccountMetadata = "account.metadata %s" maxCertfpsPerAccount = 5 ) @@ -137,6 +138,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) { am.loadModes(accountName), am.loadRealname(accountName), am.loadPushSubscriptions(accountName), + am.loadMetadata(accountName), ) } } @@ -751,6 +753,40 @@ func (am *AccountManager) loadPushSubscriptions(account string) (result []stored } } +func (am *AccountManager) saveMetadata(account string, metadata map[string]string) { + j, err := json.Marshal(metadata) + if err != nil { + am.server.logger.Error("internal", "error storing metadata", err.Error()) + return + } + val := string(j) + key := fmt.Sprintf(keyAccountMetadata, account) + am.server.store.Update(func(tx *buntdb.Tx) error { + tx.Set(key, val, nil) + return nil + }) + return +} + +func (am *AccountManager) loadMetadata(account string) (result map[string]string) { + key := fmt.Sprintf(keyAccountMetadata, account) + var val string + am.server.store.View(func(tx *buntdb.Tx) error { + val, _ = tx.Get(key) + return nil + }) + + if val == "" { + return nil + } + if err := json.Unmarshal([]byte(val), &result); err == nil { + return result + } else { + am.server.logger.Error("internal", "error loading metadata", err.Error()) + return nil + } +} + func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasPrivs bool) (err error) { certfp, err = utils.NormalizeCertfp(certfp) if err != nil { @@ -1880,6 +1916,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error { pwResetKey := fmt.Sprintf(keyAccountPwReset, casefoldedAccount) emailChangeKey := fmt.Sprintf(keyAccountEmailChange, casefoldedAccount) pushSubscriptionsKey := fmt.Sprintf(keyAccountPushSubscriptions, casefoldedAccount) + metadataKey := fmt.Sprintf(keyAccountMetadata, casefoldedAccount) var clients []*Client defer func() { @@ -1939,6 +1976,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error { tx.Delete(pwResetKey) tx.Delete(emailChangeKey) tx.Delete(pushSubscriptionsKey) + tx.Delete(metadataKey) return nil }) diff --git a/irc/api.go b/irc/api.go index a25b6537..cb313cbc 100644 --- a/irc/api.go +++ b/irc/api.go @@ -5,7 +5,10 @@ import ( "encoding/json" "fmt" "net/http" + "runtime" "strings" + + "github.com/ergochat/ergo/irc/utils" ) func newAPIHandler(server *Server) http.Handler { @@ -18,6 +21,8 @@ func newAPIHandler(server *Server) http.Handler { api.mux.HandleFunc("POST /v1/check_auth", api.handleCheckAuth) api.mux.HandleFunc("POST /v1/saregister", api.handleSaregister) api.mux.HandleFunc("POST /v1/account_details", api.handleAccountDetails) + api.mux.HandleFunc("POST /v1/account_list", api.handleAccountList) + api.mux.HandleFunc("POST /v1/status", api.handleStatus) return api } @@ -29,7 +34,6 @@ type ergoAPI struct { func (a *ergoAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer a.server.HandlePanic(nil) - defer a.server.logger.Debug("api", r.URL.Path) if a.checkBearerAuth(r.Header.Get("Authorization")) { @@ -117,8 +121,6 @@ func (a *ergoAPI) handleCheckAuth(w http.ResponseWriter, r *http.Request) { // try passphrase if present if request.AccountName != "" && request.Passphrase != "" { - // TODO this only checks the internal database, not auth-script; - // it's a little weird to use both auth-script and the API but we should probably handle it account, err := a.server.accounts.checkPassphrase(request.AccountName, request.Passphrase) switch err { case nil: @@ -133,7 +135,6 @@ func (a *ergoAPI) handleCheckAuth(w http.ResponseWriter, r *http.Request) { response.Error = err.Error() } } - // try certfp if present if !response.Success && request.Certfp != "" { // TODO support cerftp @@ -175,8 +176,10 @@ func (a *ergoAPI) handleSaregister(w http.ResponseWriter, r *http.Request) { type apiAccountDetailsResponse struct { apiGenericResponse - AccountName string `json:"accountName,omitempty"` - Email string `json:"email,omitempty"` + AccountName string `json:"accountName,omitempty"` + Email string `json:"email,omitempty"` + RegisteredAt string `json:"registeredAt,omitempty"` + Channels []string `json:"channels,omitempty"` } type apiAccountDetailsRequest struct { @@ -191,8 +194,6 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) { var response apiAccountDetailsResponse - // TODO could probably use better error handling and more details - if request.AccountName != "" { accountData, err := a.server.accounts.LoadAccount(request.AccountName) if err == nil { @@ -207,6 +208,12 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) { case nil: response.AccountName = accountData.Name response.Email = accountData.Settings.Email + if !accountData.RegisteredAt.IsZero() { + response.RegisteredAt = accountData.RegisteredAt.Format(utils.IRCv3TimestampFormat) + } + + // Get channels the account is in + response.Channels = a.server.channels.ChannelsForAccount(accountData.NameCasefolded) response.Success = true case errAccountDoesNotExist, errAccountUnverified, errAccountSuspended: response.Success = false @@ -222,3 +229,83 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) { a.writeJSONResponse(response, w, r) } + +type apiAccountListResponse struct { + apiGenericResponse + Accounts []apiAccountDetailsResponse `json:"accounts"` + TotalCount int `json:"totalCount"` +} + +func (a *ergoAPI) handleAccountList(w http.ResponseWriter, r *http.Request) { + var response apiAccountListResponse + + // Get all account names + accounts := a.server.accounts.AllNicks() + response.TotalCount = len(accounts) + + // Load account details + response.Accounts = make([]apiAccountDetailsResponse, len(accounts)) + for i, account := range accounts { + accountData, err := a.server.accounts.LoadAccount(account) + if err != nil { + response.Accounts[i] = apiAccountDetailsResponse{ + apiGenericResponse: apiGenericResponse{ + Success: false, + Error: err.Error(), + }, + } + continue + } + + response.Accounts[i] = apiAccountDetailsResponse{ + apiGenericResponse: apiGenericResponse{ + Success: true, + }, + AccountName: accountData.Name, + Email: accountData.Settings.Email, + } + } + + response.Success = true + a.writeJSONResponse(response, w, r) +} + +type apiStatusResponse struct { + apiGenericResponse + Version string `json:"version"` + GoVersion string `json:"go_version"` + Commit string `json:"commit,omitempty"` + StartTime string `json:"start_time"` + Users struct { + Total int `json:"total"` + Invisible int `json:"invisible"` + Operators int `json:"operators"` + Unknown int `json:"unknown"` + Max int `json:"max"` + } `json:"users"` + Channels int `json:"channels"` + Servers int `json:"servers"` +} + +func (a *ergoAPI) handleStatus(w http.ResponseWriter, r *http.Request) { + server := a.server + stats := server.stats.GetValues() + + response := apiStatusResponse{ + apiGenericResponse: apiGenericResponse{Success: true}, + Version: SemVer, + GoVersion: runtime.Version(), + Commit: Commit, + StartTime: server.ctime.Format(utils.IRCv3TimestampFormat), + } + + response.Users.Total = stats.Total + response.Users.Invisible = stats.Invisible + response.Users.Operators = stats.Operators + response.Users.Unknown = stats.Unknown + response.Users.Max = stats.Max + response.Channels = server.channels.Len() + response.Servers = 1 + + a.writeJSONResponse(response, w, r) +} diff --git a/irc/bunt/bunt_datastore.go b/irc/bunt/bunt_datastore.go index 76c831e3..869c6461 100644 --- a/irc/bunt/bunt_datastore.go +++ b/irc/bunt/bunt_datastore.go @@ -44,10 +44,11 @@ func (b *buntdbDatastore) GetAll(table datastore.Table) (result []datastore.KV, tablePrefix := fmt.Sprintf("%x ", table) err = b.db.View(func(tx *buntdb.Tx) error { err := tx.AscendGreaterOrEqual("", tablePrefix, func(key, value string) bool { - if !strings.HasPrefix(key, tablePrefix) { + encUUID, ok := strings.CutPrefix(key, tablePrefix) + if !ok { return false } - uuid, err := utils.DecodeUUID(strings.TrimPrefix(key, tablePrefix)) + uuid, err := utils.DecodeUUID(encUUID) if err == nil { result = append(result, datastore.KV{UUID: uuid, Value: []byte(value)}) } else { diff --git a/irc/caps/defs.go b/irc/caps/defs.go index 5f747d49..a6377884 100644 --- a/irc/caps/defs.go +++ b/irc/caps/defs.go @@ -7,7 +7,7 @@ package caps const ( // number of recognized capabilities: - numCapabs = 37 + numCapabs = 38 // length of the uint32 array that represents the bitset: bitsetLen = 2 ) @@ -65,6 +65,10 @@ const ( // https://github.com/progval/ircv3-specifications/blob/redaction/extensions/message-redaction.md MessageRedaction Capability = iota + // Metadata is the draft IRCv3 capability named "draft/metadata-2": + // https://ircv3.net/specs/extensions/metadata + Metadata Capability = iota + // Multiline is the proposed IRCv3 capability named "draft/multiline": // https://github.com/ircv3/ircv3-specifications/pull/398 Multiline Capability = iota @@ -178,6 +182,7 @@ var ( "draft/extended-isupport", "draft/languages", "draft/message-redaction", + "draft/metadata-2", "draft/multiline", "draft/no-implicit-names", "draft/persistence", diff --git a/irc/channel.go b/irc/channel.go index 1dc55f43..ade4b5b4 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -7,6 +7,7 @@ package irc import ( "fmt" + "iter" "maps" "strconv" "strings" @@ -55,6 +56,7 @@ type Channel struct { dirtyBits uint settings ChannelSettings uuid utils.UUID + metadata map[string]string // these caches are paired to allow iteration over channel members without holding the lock membersCache []*Client memberDataCache []*memberData @@ -126,6 +128,7 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) { channel.userLimit = chanReg.UserLimit channel.settings = chanReg.Settings channel.forward = chanReg.Forward + channel.metadata = chanReg.Metadata for _, mode := range chanReg.Modes { channel.flags.SetMode(mode, true) @@ -163,6 +166,7 @@ func (channel *Channel) ExportRegistration() (info RegisteredChannel) { info.AccountToUMode = maps.Clone(channel.accountToUMode) info.Settings = channel.settings + info.Metadata = channel.metadata return } @@ -892,6 +896,10 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp rb.Add(nil, client.server.name, "MARKREAD", chname, client.GetReadMarker(chcfname)) } + if rb.session.capabilities.Has(caps.Metadata) { + syncChannelMetadata(client.server, rb, channel) + } + if rb.session.client == client { // don't send topic and names for a SAJOIN of a different client channel.SendTopic(client, rb, false) @@ -1669,6 +1677,20 @@ func (channel *Channel) auditoriumFriends(client *Client) (friends []*Client) { return } +func (channel *Channel) sessionsWithCaps(capabs ...caps.Capability) iter.Seq[*Session] { + return func(yield func(*Session) bool) { + for _, member := range channel.Members() { + for _, sess := range member.Sessions() { + if sess.capabilities.HasAll(capabs...) { + if !yield(sess) { + return + } + } + } + } + } +} + // returns whether the client is visible to unprivileged users in the channel // (i.e., respecting auditorium mode). note that this assumes that the client // is a member; if the client is not, it may return true anyway diff --git a/irc/channelmanager.go b/irc/channelmanager.go index 5934ab43..f5bbed39 100644 --- a/irc/channelmanager.go +++ b/irc/channelmanager.go @@ -206,6 +206,10 @@ func (cm *ChannelManager) Cleanup(channel *Channel) { } func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) { + if account == "" { + return errAuthRequired // this is already enforced by ChanServ, but do a final check + } + if cm.server.Defcon() <= 4 { return errFeatureDisabled } diff --git a/irc/channelreg.go b/irc/channelreg.go index 1978b4ef..3e61e460 100644 --- a/irc/channelreg.go +++ b/irc/channelreg.go @@ -63,6 +63,8 @@ type RegisteredChannel struct { Invites map[string]MaskInfo // Settings are the chanserv-modifiable settings Settings ChannelSettings + // Metadata set using the METADATA command + Metadata map[string]string } func (r *RegisteredChannel) Serialize() ([]byte, error) { diff --git a/irc/client.go b/irc/client.go index 14efecd8..42a6dc95 100644 --- a/irc/client.go +++ b/irc/client.go @@ -41,8 +41,7 @@ const ( DefaultMaxLineLen = 512 // IdentTimeout is how long before our ident (username) check times out. - IdentTimeout = time.Second + 500*time.Millisecond - IRCv3TimestampFormat = utils.IRCv3TimestampFormat + IdentTimeout = time.Second + 500*time.Millisecond // limit the number of device IDs a client can use, as a DoS mitigation maxDeviceIDsPerClient = 64 // maximum total read markers that can be stored @@ -54,18 +53,16 @@ const ( pushQueueLengthPerClient = 16 ) +var ( + // idle timeouts for client connections, set from the config + RegisterTimeout, PingTimeout, DisconnectTimeout time.Duration +) + const ( - // RegisterTimeout is how long clients have to register before we disconnect them - RegisterTimeout = time.Minute - // DefaultIdleTimeout is how long without traffic before we send the client a PING - DefaultIdleTimeout = time.Minute + 30*time.Second // For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug // (single-onion circuits will close unless the client sends data once every 60 seconds): // https://bugs.torproject.org/29665 - TorIdleTimeout = time.Second * 30 - // This is how long a client gets without sending any message, including the PONG to our - // PING, before we disconnect them: - DefaultTotalTimeout = 2*time.Minute + 30*time.Second + TorPingTimeout = time.Second * 30 // round off the ping interval by this much, see below: PingCoalesceThreshold = time.Second @@ -132,6 +129,8 @@ type Client struct { clearablePushMessages map[string]time.Time pushSubscriptionsExist atomic.Uint32 // this is a cache on len(pushSubscriptions) != 0 pushQueue pushQueue + metadata map[string]string + metadataThrottle connection_limits.ThrottleDetails } type saslStatus struct { @@ -189,6 +188,8 @@ type Session struct { fakelag Fakelag deferredFakelagCount int + lastOperAttempt time.Time + certfp string peerCerts []*x509.Certificate sasl saslStatus @@ -215,6 +216,9 @@ type Session struct { batch MultilineBatch webPushEndpoint string // goroutine-local: web push endpoint registered by the current session + + metadataSubscriptions utils.HashSet[string] + metadataPreregVals map[string]string } // MultilineBatch tracks the state of a client-to-server multiline batch. @@ -424,7 +428,7 @@ func (server *Server) RunClient(conn IRCConn) { client.run(session) } -func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus map[string]alwaysOnChannelStatus, lastSeen, readMarkers map[string]time.Time, uModes modes.Modes, realname string, pushSubscriptions []storedPushSubscription) { +func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus map[string]alwaysOnChannelStatus, lastSeen, readMarkers map[string]time.Time, uModes modes.Modes, realname string, pushSubscriptions []storedPushSubscription, metadata map[string]string) { now := time.Now().UTC() config := server.Config() if lastSeen == nil && account.Settings.AutoreplayMissed { @@ -509,6 +513,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus m } } client.rebuildPushSubscriptionCache() + + if len(metadata) != 0 { + client.metadata = metadata + } } func (client *Client) resizeHistory(config *Config) { @@ -675,7 +683,7 @@ func (client *Client) run(session *Session) { isReattach := client.Registered() if isReattach { client.Touch(session) - client.playReattachMessages(session) + client.performReattach(session) } firstLine := !isReattach @@ -775,7 +783,9 @@ func (client *Client) run(session *Session) { } } -func (client *Client) playReattachMessages(session *Session) { +func (client *Client) performReattach(session *Session) { + client.applyPreregMetadata(session) + client.server.playRegistrationBurst(session) hasHistoryCaps := session.HasHistoryCaps() for _, channel := range session.client.Channels() { @@ -799,6 +809,34 @@ func (client *Client) playReattachMessages(session *Session) { session.autoreplayMissedSince = time.Time{} } +func (client *Client) applyPreregMetadata(session *Session) { + if session.metadataPreregVals == nil { + return + } + + defer func() { + session.metadataPreregVals = nil + }() + + updates := client.UpdateMetadataFromPrereg(session.metadataPreregVals, client.server.Config().Metadata.MaxKeys) + if len(updates) == 0 { + return + } + + // TODO this is expensive + friends := client.FriendsMonitors(caps.Metadata) + for _, s := range client.Sessions() { + if s != session { + friends.Add(s) + } + } + + target := client.Nick() + for k, v := range updates { + broadcastMetadataUpdate(client.server, maps.Keys(friends), session, target, k, v, true) + } +} + // // idle, quit, timers and timeouts // @@ -830,19 +868,19 @@ func (client *Client) updateIdleTimer(session *Session, now time.Time) { session.pingSent = false if session.idleTimer == nil { - pingTimeout := DefaultIdleTimeout - if session.isTor { - pingTimeout = TorIdleTimeout + pingTimeout := PingTimeout + if session.isTor && TorPingTimeout < pingTimeout { + pingTimeout = TorPingTimeout } session.idleTimer = time.AfterFunc(pingTimeout, session.handleIdleTimeout) } } func (session *Session) handleIdleTimeout() { - totalTimeout := DefaultTotalTimeout - pingTimeout := DefaultIdleTimeout - if session.isTor { - pingTimeout = TorIdleTimeout + totalTimeout := DisconnectTimeout + pingTimeout := PingTimeout + if session.isTor && TorPingTimeout < pingTimeout { + pingTimeout = TorPingTimeout } session.client.stateMutex.Lock() @@ -1130,6 +1168,7 @@ func (client *Client) SetNick(nick, nickCasefolded, skeleton string) (success bo client.nickCasefolded = nickCasefolded client.skeleton = skeleton client.updateNickMaskNoMutex() + return true } @@ -1363,7 +1402,7 @@ func (client *Client) destroy(session *Session) { // alert monitors if registered { - client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false) + client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false, nil) } // clean up channels @@ -1463,7 +1502,7 @@ func (session *Session) sendFromClientInternal(blocking bool, serverTime time.Ti func composeMultilineBatch(batchID, fromNickMask, fromAccount string, isBot bool, tags map[string]string, command, target string, message utils.SplitMessage) (result []ircmsg.Message) { batchStart := ircmsg.MakeMessage(tags, fromNickMask, "BATCH", "+"+batchID, caps.MultilineBatchType, target) - batchStart.SetTag("time", message.Time.Format(IRCv3TimestampFormat)) + batchStart.SetTag("time", message.Time.Format(utils.IRCv3TimestampFormat)) batchStart.SetTag("msgid", message.Msgid) if fromAccount != "*" { batchStart.SetTag("account", fromAccount) @@ -1571,7 +1610,7 @@ func (session *Session) setTimeTag(msg *ircmsg.Message, serverTime time.Time) { if serverTime.IsZero() { serverTime = time.Now() } - msg.SetTag("time", serverTime.UTC().Format(IRCv3TimestampFormat)) + msg.SetTag("time", serverTime.UTC().Format(utils.IRCv3TimestampFormat)) } } @@ -1815,6 +1854,7 @@ const ( IncludeUserModes IncludeRealname IncludePushSubscriptions + IncludeMetadata ) func (client *Client) markDirty(dirtyBits uint) { @@ -1896,6 +1936,9 @@ func (client *Client) performWrite(additionalDirtyBits uint) { if (dirtyBits & IncludePushSubscriptions) != 0 { client.server.accounts.savePushSubscriptions(account, client.getPushSubscriptions(true)) } + if (dirtyBits & IncludeMetadata) != 0 { + client.server.accounts.saveMetadata(account, client.ListMetadata()) + } } // Blocking store; see Channel.Store and Socket.BlockingWrite diff --git a/irc/commands.go b/irc/commands.go index 54016b96..b8be684d 100644 --- a/irc/commands.go +++ b/irc/commands.go @@ -209,6 +209,11 @@ func init() { handler: markReadHandler, minParams: 0, // send FAIL instead of ERR_NEEDMOREPARAMS }, + "METADATA": { + handler: metadataHandler, + minParams: 2, + usablePreReg: true, + }, "MODE": { handler: modeHandler, minParams: 1, diff --git a/irc/config.go b/irc/config.go index b10e08e6..fd997512 100644 --- a/irc/config.go +++ b/irc/config.go @@ -45,6 +45,10 @@ import ( "github.com/ergochat/ergo/irc/webpush" ) +const ( + defaultProxyDeadline = time.Minute +) + // here's how this works: exported (capitalized) members of the config structs // are defined in the YAML file and deserialized directly from there. They may // be postprocessed and overwritten by LoadConfig. Unexported (lowercase) members @@ -577,7 +581,12 @@ type Config struct { MOTD string motdLines []string MOTDFormatting bool `yaml:"motd-formatting"` - Relaymsg struct { + IdleTimeouts struct { + Registration time.Duration + Ping time.Duration + Disconnect time.Duration + } `yaml:"idle-timeouts"` + Relaymsg struct { Enabled bool Separators string AvailableToChanops bool `yaml:"available-to-chanops"` @@ -599,6 +608,7 @@ type Config struct { Cloaks cloaks.CloakConfig `yaml:"ip-cloaking"` SecureNetDefs []string `yaml:"secure-nets"` secureNets []net.IPNet + OperThrottle time.Duration `yaml:"oper-throttle"` supportedCaps *caps.Set supportedCapsWithoutSTS *caps.Set capValues caps.Values @@ -723,6 +733,14 @@ type Config struct { } `yaml:"tagmsg-storage"` } + Metadata struct { + Enabled bool + MaxSubs int `yaml:"max-subs"` + MaxKeys int `yaml:"max-keys"` + MaxValueBytes int `yaml:"max-value-length"` + ClientThrottle ThrottleConfig `yaml:"client-throttle"` + } + WebPush struct { Enabled bool Timeout time.Duration @@ -979,7 +997,7 @@ func (conf *Config) prepareListeners() (err error) { conf.Server.trueListeners = make(map[string]utils.ListenerConfig) for addr, block := range conf.Server.Listeners { var lconf utils.ListenerConfig - lconf.ProxyDeadline = RegisterTimeout + lconf.ProxyDeadline = defaultProxyDeadline lconf.Tor = block.Tor lconf.STSOnly = block.STSOnly if lconf.STSOnly && !conf.Server.STS.Enabled { @@ -1229,6 +1247,23 @@ func LoadConfig(filename string) (config *Config, err error) { } } + if config.Server.IdleTimeouts.Registration <= 0 { + config.Server.IdleTimeouts.Registration = time.Minute + } + if config.Server.IdleTimeouts.Ping <= 0 { + config.Server.IdleTimeouts.Ping = time.Minute + 30*time.Second + } + if config.Server.IdleTimeouts.Disconnect <= 0 { + config.Server.IdleTimeouts.Disconnect = 2*time.Minute + 30*time.Second + } + + if !(config.Server.IdleTimeouts.Ping < config.Server.IdleTimeouts.Disconnect) { + return nil, fmt.Errorf( + "ping timeout %v must be strictly less than disconnect timeout %v, to give the client time to respond", + config.Server.IdleTimeouts.Ping, config.Server.IdleTimeouts.Disconnect, + ) + } + if config.Server.CoerceIdent != "" { if config.Server.CheckIdent { return nil, errors.New("Can't configure both check-ident and coerce-ident") @@ -1473,6 +1508,10 @@ func LoadConfig(filename string) (config *Config, err error) { config.Server.supportedCaps.Disable(caps.SASL) } + if config.Server.OperThrottle <= 0 { + config.Server.OperThrottle = 10 * time.Second + } + if err := config.Accounts.OAuth2.Postprocess(); err != nil { return nil, err } @@ -1572,7 +1611,7 @@ func LoadConfig(filename string) (config *Config, err error) { // in the current implementation, we disable history by creating a history buffer // with zero capacity. but the `enabled` config option MUST be respected regardless // of this detail - if !config.History.Enabled { + if !config.History.Enabled || config.History.ChathistoryMax == 0 { config.History.ChannelLength = 0 config.History.ClientLength = 0 config.Server.supportedCaps.Disable(caps.Chathistory) @@ -1637,6 +1676,27 @@ func LoadConfig(filename string) (config *Config, err error) { } } + if !config.Metadata.Enabled { + config.Server.supportedCaps.Disable(caps.Metadata) + } else { + metadataValues := make([]string, 0, 4) + metadataValues = append(metadataValues, "before-connect") + // these are required for normal operation, so set sane defaults: + if config.Metadata.MaxSubs == 0 { + config.Metadata.MaxSubs = 10 + } + metadataValues = append(metadataValues, fmt.Sprintf("max-subs=%d", config.Metadata.MaxSubs)) + if config.Metadata.MaxKeys == 0 { + config.Metadata.MaxKeys = 10 + } + metadataValues = append(metadataValues, fmt.Sprintf("max-keys=%d", config.Metadata.MaxKeys)) + // this is not required since we enforce a hardcoded upper bound on key+value + if config.Metadata.MaxValueBytes > 0 { + metadataValues = append(metadataValues, fmt.Sprintf("max-value-bytes=%d", config.Metadata.MaxValueBytes)) + } + config.Server.capValues[caps.Metadata] = strings.Join(metadataValues, ",") + } + err = config.processExtjwt() if err != nil { return nil, err diff --git a/irc/errors.go b/irc/errors.go index aed797eb..6c3a790a 100644 --- a/irc/errors.go +++ b/irc/errors.go @@ -33,6 +33,7 @@ var ( errAccountVerificationInvalidCode = errors.New("Invalid account verification code") errAccountUpdateFailed = errors.New(`Error while updating your account information`) errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`) + errAuthRequired = errors.New("You must be logged into an account to do this") errAuthzidAuthcidMismatch = errors.New(`authcid and authzid must be the same`) errCertfpAlreadyExists = errors.New(`An account already exists for your certificate fingerprint`) errChannelNotOwnedByAccount = errors.New("Channel not owned by the specified account") diff --git a/irc/getters.go b/irc/getters.go index 902614f7..fc85bce3 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -7,9 +7,11 @@ import ( "fmt" "maps" "net" + "slices" "time" "github.com/ergochat/ergo/irc/caps" + "github.com/ergochat/ergo/irc/connection_limits" "github.com/ergochat/ergo/irc/languages" "github.com/ergochat/ergo/irc/modes" "github.com/ergochat/ergo/irc/utils" @@ -517,7 +519,7 @@ func (client *Client) GetReadMarker(cfname string) (result string) { t, ok := client.readMarkers[cfname] client.stateMutex.RUnlock() if ok { - return fmt.Sprintf("timestamp=%s", t.Format(IRCv3TimestampFormat)) + return fmt.Sprintf("timestamp=%s", t.Format(utils.IRCv3TimestampFormat)) } return "*" } @@ -797,10 +799,12 @@ func (channel *Channel) Settings() (result ChannelSettings) { } func (channel *Channel) SetSettings(settings ChannelSettings) { + defer channel.MarkDirty(IncludeSettings) + channel.stateMutex.Lock() + defer channel.stateMutex.Unlock() + channel.settings = settings - channel.stateMutex.Unlock() - channel.MarkDirty(IncludeSettings) } func (channel *Channel) setForward(forward string) { @@ -827,3 +831,262 @@ func (channel *Channel) UUID() utils.UUID { defer channel.stateMutex.RUnlock() return channel.uuid } + +func (session *Session) isSubscribedTo(key string) bool { + session.client.stateMutex.RLock() + defer session.client.stateMutex.RUnlock() + + return session.metadataSubscriptions.Has(key) +} + +func (session *Session) SubscribeTo(keys ...string) ([]string, error) { + maxSubs := session.client.server.Config().Metadata.MaxSubs + + session.client.stateMutex.Lock() + defer session.client.stateMutex.Unlock() + + if session.metadataSubscriptions == nil { + session.metadataSubscriptions = make(utils.HashSet[string]) + } + + var added []string + + for _, k := range keys { + if !session.metadataSubscriptions.Has(k) { + if len(session.metadataSubscriptions) > maxSubs { + return added, errMetadataTooManySubs + } + added = append(added, k) + session.metadataSubscriptions.Add(k) + } + } + + return added, nil +} + +func (session *Session) UnsubscribeFrom(keys ...string) []string { + session.client.stateMutex.Lock() + defer session.client.stateMutex.Unlock() + + var removed []string + + for k := range session.metadataSubscriptions { + if slices.Contains(keys, k) { + removed = append(removed, k) + session.metadataSubscriptions.Remove(k) + } + } + + return removed +} + +func (session *Session) MetadataSubscriptions() utils.HashSet[string] { + session.client.stateMutex.Lock() + defer session.client.stateMutex.Unlock() + + return maps.Clone(session.metadataSubscriptions) +} + +func (channel *Channel) GetMetadata(key string) (string, bool) { + channel.stateMutex.RLock() + defer channel.stateMutex.RUnlock() + + val, ok := channel.metadata[key] + return val, ok +} + +func (channel *Channel) SetMetadata(key string, value string, limit int) (updated bool, err error) { + defer channel.MarkDirty(IncludeAllAttrs) + + channel.stateMutex.Lock() + defer channel.stateMutex.Unlock() + + if channel.metadata == nil { + channel.metadata = make(map[string]string) + } + + existing, ok := channel.metadata[key] + if !ok && len(channel.metadata) >= limit { + return false, errLimitExceeded + } + updated = !ok || value != existing + if updated { + channel.metadata[key] = value + } + return updated, nil +} + +func (channel *Channel) ListMetadata() map[string]string { + channel.stateMutex.RLock() + defer channel.stateMutex.RUnlock() + + return maps.Clone(channel.metadata) +} + +func (channel *Channel) DeleteMetadata(key string) (updated bool) { + defer channel.MarkDirty(IncludeAllAttrs) + + channel.stateMutex.Lock() + defer channel.stateMutex.Unlock() + + _, updated = channel.metadata[key] + if updated { + delete(channel.metadata, key) + } + return updated +} + +func (channel *Channel) ClearMetadata() map[string]string { + defer channel.MarkDirty(IncludeAllAttrs) + channel.stateMutex.Lock() + defer channel.stateMutex.Unlock() + + oldMap := channel.metadata + channel.metadata = nil + + return oldMap +} + +func (channel *Channel) CountMetadata() int { + channel.stateMutex.RLock() + defer channel.stateMutex.RUnlock() + + return len(channel.metadata) +} + +func (client *Client) GetMetadata(key string) (string, bool) { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + + val, ok := client.metadata[key] + return val, ok +} + +func (client *Client) SetMetadata(key string, value string, limit int) (updated bool, err error) { + var alwaysOn bool + defer func() { + if alwaysOn && updated { + client.markDirty(IncludeMetadata) + } + }() + + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + + alwaysOn = client.registered && client.alwaysOn + + if client.metadata == nil { + client.metadata = make(map[string]string) + } + + existing, ok := client.metadata[key] + if !ok && len(client.metadata) >= limit { + return false, errLimitExceeded + } + updated = !ok || value != existing + if updated { + client.metadata[key] = value + } + return updated, nil +} + +func (client *Client) UpdateMetadataFromPrereg(preregData map[string]string, limit int) (updates map[string]string) { + var alwaysOn bool + defer func() { + if alwaysOn && len(updates) > 0 { + client.markDirty(IncludeMetadata) + } + }() + + updates = make(map[string]string, len(preregData)) + + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + + alwaysOn = client.registered && client.alwaysOn + + if client.metadata == nil { + client.metadata = make(map[string]string) + } + + for k, v := range preregData { + // do not overwrite any existing keys + _, ok := client.metadata[k] + if ok { + continue + } + if len(client.metadata) >= limit { + return // we know this is a new key + } + client.metadata[k] = v + updates[k] = v + } + + return +} + +func (client *Client) ListMetadata() map[string]string { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + + return maps.Clone(client.metadata) +} + +func (client *Client) DeleteMetadata(key string) (updated bool) { + defer func() { + if updated { + client.markDirty(IncludeMetadata) + } + }() + + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + + _, updated = client.metadata[key] + if updated { + delete(client.metadata, key) + } + return updated +} + +func (client *Client) ClearMetadata() (oldMap map[string]string) { + defer func() { + if len(oldMap) > 0 { + client.markDirty(IncludeMetadata) + } + }() + + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + + oldMap = client.metadata + client.metadata = nil + + return oldMap +} + +func (client *Client) CountMetadata() int { + client.stateMutex.RLock() + defer client.stateMutex.RUnlock() + + return len(client.metadata) +} + +func (client *Client) checkMetadataThrottle() (throttled bool, remainingTime time.Duration) { + config := client.server.Config() + if !config.Metadata.ClientThrottle.Enabled { + return false, 0 + } + + client.stateMutex.Lock() + defer client.stateMutex.Unlock() + + // copy client.metadataThrottle locally and then back for processing + var throttle connection_limits.GenericThrottle + throttle.ThrottleDetails = client.metadataThrottle + throttle.Duration = config.Metadata.ClientThrottle.Duration + throttle.Limit = config.Metadata.ClientThrottle.MaxAttempts + throttled, remainingTime = throttle.Touch() + client.metadataThrottle = throttle.ThrottleDetails + return +} diff --git a/irc/handlers.go b/irc/handlers.go index d0d619c0..b74800f0 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -9,11 +9,13 @@ package irc import ( "bytes" "fmt" + "maps" "net" "os" "runtime" "runtime/debug" "runtime/pprof" + "slices" "sort" "strconv" "strings" @@ -149,11 +151,8 @@ func (server *Server) sendLoginSnomask(nickMask, accountName string) { // to indicate that it should be removed from the list func acceptHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { for _, tNick := range strings.Split(msg.Params[0], ",") { - add := true - if strings.HasPrefix(tNick, "-") { - add = false - tNick = strings.TrimPrefix(tNick, "-") - } + tNick, negPrefix := strings.CutPrefix(tNick, "-") + add := !negPrefix target := server.clients.Get(tNick) if target == nil { @@ -701,11 +700,13 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * var channel *Channel var sequence history.Sequence var err error - var listTargets bool + var disabled, listTargets bool var targets []history.TargetListing defer func() { // errors are sent either without a batch, or in a draft/labeled-response batch as usual - if err == utils.ErrInvalidParams { + if disabled { + rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "MESSAGE_ERROR", msg.Params[0], client.t("That feature is disabled")) + } else if err == utils.ErrInvalidParams { rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "INVALID_PARAMS", msg.Params[0], client.t("Invalid parameters")) } else if !listTargets && sequence == nil { rb.Add(nil, server.name, "FAIL", "CHATHISTORY", "INVALID_TARGET", msg.Params[0], utils.SafeErrorParam(target), client.t("Messages could not be retrieved")) @@ -719,7 +720,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * for _, target := range targets { name := server.UnfoldName(target.CfName) rb.Add(nil, server.name, "CHATHISTORY", "TARGETS", name, - target.Time.Format(IRCv3TimestampFormat)) + target.Time.Format(utils.IRCv3TimestampFormat)) } } else if channel != nil { channel.replayHistoryItems(rb, items, true) @@ -731,7 +732,8 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * config := server.Config() maxChathistoryLimit := config.History.ChathistoryMax - if maxChathistoryLimit == 0 { + if !config.History.Enabled || maxChathistoryLimit == 0 { + disabled = true return } preposition := strings.ToLower(msg.Params[0]) @@ -754,7 +756,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb * msgid, err = history.NormalizeMsgid(value), nil return } else if identifier == "timestamp" { - timestamp, err = time.Parse(IRCv3TimestampFormat, value) + timestamp, err = time.Parse(utils.IRCv3TimestampFormat, value) if err == nil { timestamp = timestamp.UTC() if timestamp.Before(unixEpoch) { @@ -2546,8 +2548,19 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons return false } + config := server.Config() + now := time.Now() + nextAllowableAttempt := rb.session.lastOperAttempt.Add(config.Server.OperThrottle) + if now.Before(nextAllowableAttempt) { + timeLeft := nextAllowableAttempt.Sub(now).Round(time.Millisecond) + rb.Add(nil, server.name, ERR_NOOPERHOST, client.Nick(), fmt.Sprintf(client.t("You must wait %v before issuing OPER again"), timeLeft)) + return false + } + + rb.session.lastOperAttempt = now + // must pass at least one check, and all enabled checks - var checkPassed, checkFailed, passwordFailed bool + var checkPassed, checkFailed, certFailed, passwordFailed bool oper := server.GetOperator(msg.Params[0]) if oper != nil { if oper.Certfp != "" { @@ -2555,11 +2568,13 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons checkPassed = true } else { checkFailed = true + certFailed = true } } if !checkFailed && oper.Pass != nil { if len(msg.Params) == 1 { checkFailed = true + passwordFailed = true } else if bcrypt.CompareHashAndPassword(oper.Pass, []byte(msg.Params[1])) != nil { checkFailed = true passwordFailed = true @@ -2570,14 +2585,21 @@ func operHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons } if !checkPassed || checkFailed { - rb.Add(nil, server.name, ERR_PASSWDMISMATCH, client.Nick(), client.t("Password incorrect")) - // #951: only disconnect them if we actually tried to check a password for them - if passwordFailed { - client.Quit(client.t("Password incorrect"), rb.session) - return true + rb.Add(nil, server.name, ERR_NOOPERHOST, client.Nick(), client.t("OPER failed; check the server logs for details.")) + + // hopefully not too spammy given the throttling: + if oper == nil { + server.logger.Info("opers", "OPER failed with invalid oper name", msg.Params[0]) + } else if certFailed { + server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid certfp") + } else if passwordFailed { + server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid password") } else { - return false + // should not be possible given config validation + server.logger.Info("opers", "OPER attempt for", msg.Params[0], "failed with invalid config") } + + return false } if oper != nil { @@ -2910,11 +2932,23 @@ func quitHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons // REGISTER < account | * > < email | * > func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (exiting bool) { - accountName := client.Nick() - if accountName == "*" { + var accountName string + if client.registered { + accountName = client.Nick() + } else { accountName = client.preregNick } + config := server.Config() + if client.registered && config.Accounts.NickReservation.ForceGuestFormat { + matches := config.Accounts.NickReservation.guestRegexp.FindStringSubmatch(accountName) + if matches == nil || len(matches) < 2 { + rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_USERNAME", utils.SafeErrorParam(accountName), client.t("Username invalid or not given")) + return + } + accountName = matches[1] + } + switch msg.Params[0] { case "*", accountName: // ok @@ -2931,7 +2965,6 @@ func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res return } - config := server.Config() if !config.Accounts.Registration.Enabled { rb.Add(nil, server.name, "FAIL", "REGISTER", "DISALLOWED", accountName, client.t("Account registration is disabled")) return @@ -2977,7 +3010,7 @@ func registerHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res announcePendingReg(client, rb, accountName) } case errAccountAlreadyRegistered, errAccountAlreadyUnregistered, errAccountMustHoldNick: - rb.Add(nil, server.name, "FAIL", "REGISTER", "USERNAME_EXISTS", accountName, client.t("Username is already registered or otherwise unavailable")) + rb.Add(nil, server.name, "FAIL", "REGISTER", "ACCOUNT_EXISTS", accountName, client.t("Username is already registered or otherwise unavailable")) case errAccountBadPassphrase: rb.Add(nil, server.name, "FAIL", "REGISTER", "INVALID_PASSWORD", accountName, client.t("Password was invalid")) default: @@ -3055,14 +3088,14 @@ func markReadHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res // "MARKREAD client set command": MARKREAD readTimestamp := strings.TrimPrefix(msg.Params[1], "timestamp=") - readTime, err := time.Parse(IRCv3TimestampFormat, readTimestamp) + readTime, err := time.Parse(utils.IRCv3TimestampFormat, readTimestamp) if err != nil { rb.Add(nil, server.name, "FAIL", "MARKREAD", "INVALID_PARAMS", utils.SafeErrorParam(readTimestamp), client.t("Invalid timestamp")) return } readTime = readTime.UTC() result := client.SetReadMarker(cftarget, readTime) - readTimestamp = fmt.Sprintf("timestamp=%s", result.Format(IRCv3TimestampFormat)) + readTimestamp = fmt.Sprintf("timestamp=%s", result.Format(utils.IRCv3TimestampFormat)) // inform the originating session whether it was a success or a no-op: rb.Add(nil, server.name, "MARKREAD", unfoldedTarget, readTimestamp) if result.Equal(readTime) { @@ -3089,6 +3122,287 @@ func markReadHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res return } +// METADATA [...] +func metadataHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) (exiting bool) { + config := server.Config() + if !config.Metadata.Enabled { + rb.Add(nil, server.name, "FAIL", "METADATA", "FORBIDDEN", utils.SafeErrorParam(msg.Params[0]), "Metadata is disabled on this server") + return + } + + subcommand := strings.ToLower(msg.Params[1]) + needsKey := subcommand == "set" || subcommand == "get" || subcommand == "sub" || subcommand == "unsub" + if needsKey && len(msg.Params) < 3 { + rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters")) + return + } + + switch subcommand { + case "sub", "unsub", "subs": + // these are session-local and function the same whether or not the client is registered + return metadataSubsHandler(client, subcommand, msg.Params, rb) + case "get", "set", "list", "clear", "sync": + if client.registered { + return metadataRegisteredHandler(client, config, subcommand, msg.Params, rb) + } else { + return metadataUnregisteredHandler(client, config, subcommand, msg.Params, rb) + } + default: + rb.Add(nil, server.name, "FAIL", "METADATA", "SUBCOMMAND_INVALID", utils.SafeErrorParam(msg.Params[1]), client.t("Invalid subcommand")) + return + } +} + +// metadataRegisteredHandler handles metadata-modifying commands from registered clients +func metadataRegisteredHandler(client *Client, config *Config, subcommand string, params []string, rb *ResponseBuffer) (exiting bool) { + server := client.server + target := params[0] + + noKeyPerms := func(key string) { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_NO_PERMISSION", target, key, client.t("You do not have permission to perform this action")) + } + + if target == "*" { + target = client.Nick() + } + + var targetObj MetadataHaver + var targetClient *Client + var targetChannel *Channel + if strings.HasPrefix(target, "#") { + targetChannel = server.channels.Get(target) + if targetChannel != nil { + targetObj = targetChannel + target = targetChannel.Name() // canonicalize case + } + } else { + targetClient = server.clients.Get(target) + if targetClient != nil { + targetObj = targetClient + target = targetClient.Nick() // canonicalize case + } + } + if targetObj == nil { + rb.Add(nil, server.name, "FAIL", "METADATA", "INVALID_TARGET", target, client.t("Invalid metadata target")) + return + } + + switch subcommand { + case "set": + key := params[2] + if metadataKeyIsEvil(key) { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_INVALID", utils.SafeErrorParam(key), client.t("Invalid key name")) + return + } + + if !metadataCanIEditThisKey(client, targetObj, key) { + noKeyPerms(key) + return + } + + // only rate limit clients changing their own metadata: + // channel metadata updates are not any more costly than a PRIVMSG + if client == targetClient { + if throttled, remainingTime := client.checkMetadataThrottle(); throttled { + retryAfter := strconv.Itoa(int(remainingTime.Seconds()) + 1) + rb.Add(nil, server.name, "FAIL", "METADATA", "RATE_LIMITED", + target, utils.SafeErrorParam(key), retryAfter, + fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime.Round(time.Millisecond))) + return + } + } + + if len(params) > 3 { + value := params[3] + + config := client.server.Config() + if failMsg := metadataValueIsEvil(config, key, value); failMsg != "" { + rb.Add(nil, server.name, "FAIL", "METADATA", "VALUE_INVALID", client.t(failMsg)) + return + } + + updated, err := targetObj.SetMetadata(key, value, config.Metadata.MaxKeys) + if err != nil { + // errLimitExceeded is the only possible error + rb.Add(nil, server.name, "FAIL", "METADATA", "LIMIT_REACHED", client.t("Too many metadata keys")) + return + } + // echo the value to the client whether or not there was a real update + rb.Add(nil, server.name, RPL_KEYVALUE, client.Nick(), target, key, "*", value) + if updated { + notifySubscribers(server, rb.session, targetObj, target, key, value, true) + } + } else { + if updated := targetObj.DeleteMetadata(key); updated { + notifySubscribers(server, rb.session, targetObj, target, key, "", false) + rb.Add(nil, server.name, RPL_KEYNOTSET, client.Nick(), target, key, client.t("Key deleted")) + } else { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_NOT_SET", utils.SafeErrorParam(key), client.t("Metadata key not set")) + } + } + + case "get": + if !metadataCanISeeThisTarget(client, targetObj) { + noKeyPerms("*") + return + } + + batchId := rb.StartNestedBatch("metadata", target) + defer rb.EndNestedBatch(batchId) + + for _, key := range params[2:] { + if metadataKeyIsEvil(key) { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_INVALID", utils.SafeErrorParam(key), client.t("Invalid key name")) + continue + } + + val, ok := targetObj.GetMetadata(key) + if !ok { + rb.Add(nil, server.name, RPL_KEYNOTSET, client.Nick(), target, key, client.t("Key is not set")) + continue + } + + visibility := "*" + rb.Add(nil, server.name, RPL_KEYVALUE, client.Nick(), target, key, visibility, val) + } + + case "list": + playMetadataList(rb, client.Nick(), target, targetObj.ListMetadata()) + + case "clear": + if !metadataCanIEditThisTarget(client, targetObj) { + noKeyPerms("*") + return + } + + values := targetObj.ClearMetadata() + + playMetadataList(rb, client.Nick(), target, values) + + case "sync": + if targetChannel != nil { + syncChannelMetadata(server, rb, targetChannel) + } + if targetClient != nil { + syncClientMetadata(server, rb, targetClient) + } + } + + return +} + +// metadataUnregisteredHandler handles metadata-modifying commands for pre-connection-registration +// clients. these operations act on a session-local buffer; if/when the client completes registration, +// they are applied to the final Client object (possibly a different client if there was a reattach) +// on a best-effort basis. +func metadataUnregisteredHandler(client *Client, config *Config, subcommand string, params []string, rb *ResponseBuffer) (exiting bool) { + server := client.server + if params[0] != "*" { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_NO_PERMISSION", utils.SafeErrorParam(params[0]), "*", client.t("You can only modify your own metadata before completing connection registration")) + return + } + + switch subcommand { + case "set": + if rb.session.metadataPreregVals == nil { + rb.session.metadataPreregVals = make(map[string]string) + } + key := params[2] + if metadataKeyIsEvil(key) { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_INVALID", utils.SafeErrorParam(key), client.t("Invalid key name")) + return + } + if len(params) >= 4 { + value := params[3] + // enforce a sane limit on prereg keys. we don't need to enforce the exact limit, + // that will be done when applying the buffer after registration + if len(rb.session.metadataPreregVals) > config.Metadata.MaxKeys { + rb.Add(nil, server.name, "FAIL", "METADATA", "LIMIT_REACHED", client.t("Too many metadata keys")) + return + } + if failMsg := metadataValueIsEvil(config, key, value); failMsg != "" { + rb.Add(nil, server.name, "FAIL", "METADATA", "VALUE_INVALID", client.t(failMsg)) + return + } + rb.session.metadataPreregVals[key] = value + rb.Add(nil, server.name, RPL_KEYVALUE, "*", "*", key, "*", value) + } else { + // unset + _, present := rb.session.metadataPreregVals[key] + if present { + delete(rb.session.metadataPreregVals, key) + rb.Add(nil, server.name, RPL_KEYNOTSET, "*", "*", key, client.t("Key deleted")) + } else { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_NOT_SET", utils.SafeErrorParam(key), client.t("Metadata key not set")) + } + } + case "list": + playMetadataList(rb, "*", "*", rb.session.metadataPreregVals) + case "clear": + oldMetadata := rb.session.metadataPreregVals + rb.session.metadataPreregVals = nil + playMetadataList(rb, "*", "*", oldMetadata) + case "sync": + rb.Add(nil, server.name, RPL_METADATASYNCLATER, "*", utils.SafeErrorParam(params[1]), "60") // lol + } + return false +} + +// metadataSubsHandler handles subscription-related commands; +// these are handled the same whether the client is registered or not +func metadataSubsHandler(client *Client, subcommand string, params []string, rb *ResponseBuffer) (exiting bool) { + server := client.server + switch subcommand { + case "sub": + keys := params[2:] + for _, key := range keys { + if metadataKeyIsEvil(key) { + rb.Add(nil, server.name, "FAIL", "METADATA", "KEY_INVALID", utils.SafeErrorParam(key), client.t("Invalid key name")) + return + } + } + added, err := rb.session.SubscribeTo(keys...) + if err == errMetadataTooManySubs { + bad := keys[len(added)] // get the key that broke the camel's back + rb.Add(nil, server.name, "FAIL", "METADATA", "TOO_MANY_SUBS", utils.SafeErrorParam(bad), client.t("Too many subscriptions")) + } + + lineLength := MaxLineLen - len(server.name) - len(RPL_METADATASUBOK) - len(client.Nick()) - 10 + + chunked := utils.ChunkifyParams(slices.Values(added), lineLength) + for _, line := range chunked { + params := append([]string{client.Nick()}, line...) + rb.Add(nil, server.name, RPL_METADATASUBOK, params...) + } + + case "unsub": + keys := params[2:] + removed := rb.session.UnsubscribeFrom(keys...) + + lineLength := MaxLineLen - len(server.name) - len(RPL_METADATAUNSUBOK) - len(client.Nick()) - 10 + chunked := utils.ChunkifyParams(slices.Values(removed), lineLength) + for _, line := range chunked { + params := append([]string{client.Nick()}, line...) + rb.Add(nil, server.name, RPL_METADATAUNSUBOK, params...) + } + + case "subs": + lineLength := MaxLineLen - len(server.name) - len(RPL_METADATASUBS) - len(client.Nick()) - 10 + + subs := rb.session.MetadataSubscriptions() + + batchID := rb.StartNestedBatch("metadata-subs") + defer rb.EndNestedBatch(batchID) + + chunked := utils.ChunkifyParams(maps.Keys(subs), lineLength) + for _, line := range chunked { + params := append([]string{client.Nick()}, line...) + rb.Add(nil, server.name, RPL_METADATASUBS, params...) + } + } + return false +} + // REHASH func rehashHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { nick := client.Nick() @@ -3655,6 +3969,7 @@ func webpushHandler(server *Server, client *Client, msg ircmsg.Message, rb *Resp if err := webpush.SanityCheckWebPushEndpoint(endpoint); err != nil { rb.Add(nil, server.name, "FAIL", "WEBPUSH", "INVALID_PARAMS", subcommand, client.t("Invalid web push URL")) + return false } switch subcommand { @@ -4118,9 +4433,9 @@ func zncHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response // fake handler for unknown commands func unknownCommandHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { var message string - if strings.HasPrefix(msg.Command, "/") { + if trimmedCmd, initialSlash := strings.CutPrefix(msg.Command, "/"); initialSlash { message = fmt.Sprintf(client.t("Unknown command; if you are using /QUOTE, the correct syntax is /QUOTE %[1]s, not /QUOTE %[2]s"), - strings.TrimPrefix(msg.Command, "/"), msg.Command) + trimmedCmd, msg.Command) } else { message = client.t("Unknown command") } diff --git a/irc/help.go b/irc/help.go index 6a6824a0..ee1fb762 100644 --- a/irc/help.go +++ b/irc/help.go @@ -238,11 +238,10 @@ Get an explanation of , or "index" for a list of help topics.`, "history": { text: `HISTORY [limit] -Replay message history. can be a channel name, "me" to replay direct -message history, or a nickname to replay another client's direct message -history (they must be logged into the same account as you). [limit] can be -either an integer (the maximum number of messages to replay), or a time -duration like 10m or 1h (the time window within which to replay messages).`, +Replay message history. can be a channel name or a nickname you have +direct message history with. [limit] can be either an integer (the maximum +number of messages to replay), or a time duration like 10m or 1h (the time +window within which to replay messages).`, }, "info": { text: `INFO @@ -339,6 +338,12 @@ command is processed by that server.`, MARKREAD updates an IRCv3 read message marker. It is not intended for use by end users. For more details, see the latest draft of the read-marker specification.`, + }, + "metadata": { + text: `METADATA [...] + +Retrieve and meddle with metadata for the given target. +Have a look at https://ircv3.net/specs/extensions/metadata for interesting technical information.`, }, "mode": { text: `MODE [ [...]] diff --git a/irc/histserv.go b/irc/histserv.go index 8e28dafc..35b2e9b4 100644 --- a/irc/histserv.go +++ b/irc/histserv.go @@ -164,7 +164,7 @@ func histservExportHandler(service *ircService, server *Server, client *Client, config := server.Config() // don't include the account name in the filename because of escaping concerns - filename := fmt.Sprintf("%s-%s.json", utils.GenerateSecretToken(), time.Now().UTC().Format(IRCv3TimestampFormat)) + filename := fmt.Sprintf("%s-%s.json", utils.GenerateSecretToken(), time.Now().UTC().Format(utils.IRCv3TimestampFormat)) pathname := config.getOutputPath(filename) outfile, err := os.Create(pathname) if err != nil { diff --git a/irc/message_cache.go b/irc/message_cache.go index 2d731823..f56568b3 100644 --- a/irc/message_cache.go +++ b/irc/message_cache.go @@ -45,7 +45,7 @@ type MessageCache struct { func addAllTags(msg *ircmsg.Message, tags map[string]string, serverTime time.Time, msgid, accountName string, isBot bool) { msg.UpdateTags(tags) - msg.SetTag("time", serverTime.Format(IRCv3TimestampFormat)) + msg.SetTag("time", serverTime.Format(utils.IRCv3TimestampFormat)) if accountName != "*" { msg.SetTag("account", accountName) } diff --git a/irc/metadata.go b/irc/metadata.go new file mode 100644 index 00000000..80b0b063 --- /dev/null +++ b/irc/metadata.go @@ -0,0 +1,174 @@ +package irc + +import ( + "errors" + "iter" + "maps" + "regexp" + "unicode/utf8" + + "github.com/ergochat/ergo/irc/caps" + "github.com/ergochat/ergo/irc/modes" +) + +const ( + // metadata key + value need to be relayable on a single IRC RPL_KEYVALUE line + maxCombinedMetadataLenBytes = 350 +) + +var ( + errMetadataTooManySubs = errors.New("too many subscriptions") + errMetadataNotFound = errors.New("key not found") +) + +type MetadataHaver interface { + SetMetadata(key string, value string, limit int) (updated bool, err error) + GetMetadata(key string) (string, bool) + DeleteMetadata(key string) (updated bool) + ListMetadata() map[string]string + ClearMetadata() map[string]string + CountMetadata() int +} + +func notifySubscribers(server *Server, session *Session, targetObj MetadataHaver, targetName, key, value string, set bool) { + var recipientSessions iter.Seq[*Session] + + switch target := targetObj.(type) { + case *Client: + // TODO this case is expensive and might warrant rate-limiting + friends := target.FriendsMonitors(caps.Metadata) + // broadcast metadata update to other connected sessions + for _, s := range target.Sessions() { + friends.Add(s) + } + recipientSessions = maps.Keys(friends) + case *Channel: + recipientSessions = target.sessionsWithCaps(caps.Metadata) + default: + return // impossible + } + + broadcastMetadataUpdate(server, recipientSessions, session, targetName, key, value, set) +} + +func broadcastMetadataUpdate(server *Server, sessions iter.Seq[*Session], originator *Session, target, key, value string, set bool) { + for s := range sessions { + // don't notify the session that made the change + if s == originator || !s.isSubscribedTo(key) { + continue + } + + if set { + s.Send(nil, server.name, "METADATA", target, key, "*", value) + } else { + s.Send(nil, server.name, "METADATA", target, key, "*") + } + } +} + +func syncClientMetadata(server *Server, rb *ResponseBuffer, target *Client) { + batchId := rb.StartNestedBatch("metadata", target.Nick()) + defer rb.EndNestedBatch(batchId) + + subs := rb.session.MetadataSubscriptions() + values := target.ListMetadata() + for k, v := range values { + if subs.Has(k) { + visibility := "*" + rb.Add(nil, server.name, "METADATA", target.Nick(), k, visibility, v) + } + } +} + +func syncChannelMetadata(server *Server, rb *ResponseBuffer, channel *Channel) { + batchId := rb.StartNestedBatch("metadata", channel.Name()) + defer rb.EndNestedBatch(batchId) + + subs := rb.session.MetadataSubscriptions() + chname := channel.Name() + + values := channel.ListMetadata() + for k, v := range values { + if subs.Has(k) { + visibility := "*" + rb.Add(nil, server.name, "METADATA", chname, k, visibility, v) + } + } + + for _, client := range channel.Members() { + values := client.ListMetadata() + for k, v := range values { + if subs.Has(k) { + visibility := "*" + rb.Add(nil, server.name, "METADATA", client.Nick(), k, visibility, v) + } + } + } +} + +func playMetadataList(rb *ResponseBuffer, nick, target string, values map[string]string) { + batchId := rb.StartNestedBatch("metadata", target) + defer rb.EndNestedBatch(batchId) + + for key, val := range values { + visibility := "*" + rb.Add(nil, rb.session.client.server.name, RPL_KEYVALUE, nick, target, key, visibility, val) + } +} + +func playMetadataVerbBatch(rb *ResponseBuffer, target string, values map[string]string) { + batchId := rb.StartNestedBatch("metadata", target) + defer rb.EndNestedBatch(batchId) + + for key, val := range values { + visibility := "*" + rb.Add(nil, rb.session.client.server.name, "METADATA", target, key, visibility, val) + } +} + +var validMetadataKeyRegexp = regexp.MustCompile("^[a-z0-9_./-]+$") + +func metadataKeyIsEvil(key string) bool { + return !validMetadataKeyRegexp.MatchString(key) +} + +func metadataValueIsEvil(config *Config, key, value string) (failMsg string) { + if !globalUtf8EnforcementSetting && !utf8.ValidString(value) { + return `METADATA values must be UTF-8` + } + + if len(key)+len(value) > maxCombinedMetadataLenBytes || + (config.Metadata.MaxValueBytes > 0 && len(value) > config.Metadata.MaxValueBytes) { + + return `Value is too long` + } + + return "" // success +} + +func metadataCanIEditThisKey(client *Client, targetObj MetadataHaver, key string) bool { + // no key-specific logic as yet + return metadataCanIEditThisTarget(client, targetObj) +} + +func metadataCanIEditThisTarget(client *Client, targetObj MetadataHaver) bool { + switch target := targetObj.(type) { + case *Client: + return client == target || client.HasRoleCapabs("metadata") + case *Channel: + return target.ClientIsAtLeast(client, modes.Operator) || client.HasRoleCapabs("metadata") + default: + return false // impossible + } +} + +func metadataCanISeeThisTarget(client *Client, targetObj MetadataHaver) bool { + switch target := targetObj.(type) { + case *Client: + return true + case *Channel: + return target.hasClient(client) || client.HasRoleCapabs("metadata") + default: + return false // impossible + } +} diff --git a/irc/metadata_test.go b/irc/metadata_test.go new file mode 100644 index 00000000..c90dbcec --- /dev/null +++ b/irc/metadata_test.go @@ -0,0 +1,25 @@ +package irc + +import "testing" + +func TestKeyCheck(t *testing.T) { + cases := []struct { + input string + isEvil bool + }{ + {"ImNormalButIHaveCaps", true}, + {"imnormalandidonthavecaps", false}, + {"ergo.chat/vendor-extension", false}, + {"", true}, + {":imevil", true}, + {"im:evil", true}, + {"key£with$not%allowed^chars", true}, + {"key.thats_completely/normal-and.fine", false}, + } + + for _, c := range cases { + if metadataKeyIsEvil(c.input) != c.isEvil { + t.Errorf("%s should have returned %v. but it didn't. so that's not great", c.input, c.isEvil) + } + } +} diff --git a/irc/monitor.go b/irc/monitor.go index 9131e81b..46ab46e6 100644 --- a/irc/monitor.go +++ b/irc/monitor.go @@ -38,7 +38,7 @@ func (manager *MonitorManager) AddMonitors(users utils.HashSet[*Session], cfnick } // AlertAbout alerts everyone monitoring `client`'s nick that `client` is now {on,off}line. -func (manager *MonitorManager) AlertAbout(nick, cfnick string, online bool) { +func (manager *MonitorManager) AlertAbout(nick, cfnick string, online bool, client *Client) { var watchers []*Session // safely copy the list of clients watching our nick manager.RLock() @@ -52,8 +52,21 @@ func (manager *MonitorManager) AlertAbout(nick, cfnick string, online bool) { command = RPL_MONONLINE } + var metadata map[string]string + if online && client != nil { + metadata = client.ListMetadata() + } + for _, session := range watchers { session.Send(nil, session.client.server.name, command, session.client.Nick(), nick) + + if metadata != nil && session.capabilities.Has(caps.Metadata) { + for key := range session.MetadataSubscriptions() { + if val, ok := metadata[key]; ok { + session.Send(nil, client.server.name, "METADATA", nick, key, "*", val) + } + } + } } } diff --git a/irc/nickname.go b/irc/nickname.go index 0f2f8232..28781fff 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -128,9 +128,11 @@ func performNickChange(server *Server, client *Client, target *Client, session * } newCfnick := target.NickCasefolded() - if newCfnick != details.nickCasefolded { - client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false) - client.server.monitorManager.AlertAbout(assignedNickname, newCfnick, true) + // send MONITOR updates only for nick changes, not for new connection registration; + // defer MONITOR for new connection registration until pre-registration metadata is applied + if hadNick && newCfnick != details.nickCasefolded { + client.server.monitorManager.AlertAbout(details.nick, details.nickCasefolded, false, nil) + client.server.monitorManager.AlertAbout(assignedNickname, newCfnick, true, target) } return nil } diff --git a/irc/nickserv.go b/irc/nickserv.go index 51735be1..c973dc89 100644 --- a/irc/nickserv.go +++ b/irc/nickserv.go @@ -1055,10 +1055,10 @@ func nsSaregisterHandler(service *ircService, server *Server, client *Client, co var failCode string if err == errAccountAlreadyRegistered || err == errAccountAlreadyVerified { errMsg = client.t("Account already exists") - failCode = "USERNAME_EXISTS" + failCode = "ACCOUNT_EXISTS" } else if err == errNameReserved { errMsg = client.t(err.Error()) - failCode = "USERNAME_EXISTS" + failCode = "ACCOUNT_EXISTS" } else if err == errAccountBadPassphrase { errMsg = client.t("Passphrase contains forbidden characters or is otherwise invalid") failCode = "INVALID_PASSWORD" diff --git a/irc/numerics.go b/irc/numerics.go index 97d4604d..fb2c1f07 100644 --- a/irc/numerics.go +++ b/irc/numerics.go @@ -183,6 +183,13 @@ const ( RPL_MONLIST = "732" RPL_ENDOFMONLIST = "733" ERR_MONLISTFULL = "734" + RPL_WHOISKEYVALUE = "760" // metadata numerics + RPL_KEYVALUE = "761" + RPL_KEYNOTSET = "766" + RPL_METADATASUBOK = "770" + RPL_METADATAUNSUBOK = "771" + RPL_METADATASUBS = "772" + RPL_METADATASYNCLATER = "774" // end metadata numerics RPL_LOGGEDIN = "900" RPL_LOGGEDOUT = "901" ERR_NICKLOCKED = "902" diff --git a/irc/server.go b/irc/server.go index 5974f633..641d63f2 100644 --- a/irc/server.go +++ b/irc/server.go @@ -428,6 +428,10 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { c.SetMode(defaultMode, true) } + c.applyPreregMetadata(session) + + c.server.monitorManager.AlertAbout(c.Nick(), c.NickCasefolded(), true, c) + // this is not a reattach, so if the client is always-on, this is the first time // the Client object was created during the current server uptime. mark dirty in // order to persist the realname and the user modes: @@ -496,6 +500,9 @@ func (server *Server) playRegistrationBurst(session *Session) { if !(rb.session.capabilities.Has(caps.ExtendedISupport) && rb.session.isupportSentPrereg) { server.RplISupport(c, rb) } + if session.capabilities.Has(caps.Metadata) { + playMetadataVerbBatch(rb, d.nick, c.ListMetadata()) + } if d.account != "" && session.capabilities.Has(caps.Persistence) { reportPersistenceStatus(c, rb, false) } @@ -630,7 +637,6 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff if target.HasMode(modes.Bot) { rb.Add(nil, client.server.name, RPL_WHOISBOT, cnick, tnick, fmt.Sprintf(ircfmt.Unescape(client.t("is a $bBot$b on %s")), client.server.Config().Network.Name)) } - if client == target || oper.HasRoleCapab("ban") { for _, session := range target.Sessions() { if session.certfp != "" { @@ -642,6 +648,11 @@ func (client *Client) getWhoisOf(target *Client, hasPrivs bool, rb *ResponseBuff if away, awayMessage := target.Away(); away { rb.Add(nil, client.server.name, RPL_AWAY, cnick, tnick, awayMessage) } + if rb.session.capabilities.Has(caps.Metadata) { + for key, value := range target.ListMetadata() { + rb.Add(nil, client.server.name, RPL_WHOISKEYVALUE, cnick, tnick, key, "*", value) + } + } } // rehash reloads the config and applies the changes from the config file. @@ -685,6 +696,9 @@ func (server *Server) applyConfig(config *Config) (err error) { globalCasemappingSetting = config.Server.Casemapping globalUtf8EnforcementSetting = config.Server.EnforceUtf8 MaxLineLen = config.Server.MaxLineLen + RegisterTimeout = config.Server.IdleTimeouts.Registration + PingTimeout = config.Server.IdleTimeouts.Ping + DisconnectTimeout = config.Server.IdleTimeouts.Disconnect } else { // enforce configs that can't be changed after launch: if server.name != config.Server.Name { @@ -710,6 +724,8 @@ func (server *Server) applyConfig(config *Config) (err error) { return fmt.Errorf("Cannot enable MySQL after launching the server, rehash aborted") } else if oldConfig.Server.MaxLineLen != config.Server.MaxLineLen { return fmt.Errorf("Cannot change max-line-len after launching the server, rehash aborted") + } else if oldConfig.Server.IdleTimeouts != config.Server.IdleTimeouts { + return fmt.Errorf("Cannot change idle-timeouts after launching the server, rehash aborted") } } @@ -896,6 +912,9 @@ func (server *Server) applyConfig(config *Config) (err error) { if config.Accounts.RequireSasl.Enabled && config.Accounts.Registration.Enabled { server.logger.Warning("server", "Warning: although require-sasl is enabled, users can still register accounts. If your server is not intended to be public, you must set accounts.registration.enabled to false.") } + if config.History.Enabled && config.History.ChathistoryMax == 0 { + server.logger.Warning("server", "Warning: for history to work correctly, you must set history.chathistory-maxmessages (see default.yaml for a recommendation).") + } return err } diff --git a/irc/strings.go b/irc/strings.go index fb5c3da0..03e13c41 100644 --- a/irc/strings.go +++ b/irc/strings.go @@ -73,7 +73,7 @@ var globalCasemappingSetting Casemapping = CasemappingPRECIS // XXX analogous unsynchronized global variable controlling utf8 validation // if this is off, you get the traditional IRC behavior (relaying any valid RFC1459 -// octets) and invalid utf8 messages are silently dropped for websocket clients only. +// octets), and websocket listeners are disabled. // if this is on, invalid utf8 inputs get a FAIL reply. var globalUtf8EnforcementSetting bool diff --git a/irc/utils/chunks.go b/irc/utils/chunks.go new file mode 100644 index 00000000..30376626 --- /dev/null +++ b/irc/utils/chunks.go @@ -0,0 +1,28 @@ +package utils + +import "iter" + +func ChunkifyParams(params iter.Seq[string], maxChars int) [][]string { + var chunked [][]string + + var acc []string + var length = 0 + + for p := range params { + length = length + len(p) + 1 // (accounting for the space) + + if length > maxChars { + chunked = append(chunked, acc) + acc = []string{} + length = 0 + } + + acc = append(acc, p) + } + + if len(acc) != 0 { + chunked = append(chunked, acc) + } + + return chunked +} diff --git a/irc/version.go b/irc/version.go index 929a0960..f7a311a6 100644 --- a/irc/version.go +++ b/irc/version.go @@ -7,7 +7,7 @@ import "fmt" const ( // SemVer is the semantic version of Ergo. - SemVer = "2.16.0" + SemVer = "2.17.0-rc1" ) var ( diff --git a/irctest b/irctest index e9e37f54..13a76e45 160000 --- a/irctest +++ b/irctest @@ -1 +1 @@ -Subproject commit e9e37f5438bd5f02656b89dab0cd40ef113edac6 +Subproject commit 13a76e4501749dbc1a604e16978e128ff40edace diff --git a/traditional.yaml b/traditional.yaml index c4c16e0e..0e36ec8c 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -154,6 +154,17 @@ server: # if this is true, the motd is escaped using formatting codes like $c, $b, and $i motd-formatting: true + # idle timeouts for inactive clients + idle-timeouts: + # give the client this long to complete connection registration (i.e. the initial + # IRC handshake, including capability negotiation and SASL) + registration: 60s + # if the client hasn't sent anything for this long, send them a PING + ping: 1m30s + # if the client hasn't sent anything for this long (including the PONG to the + # above PING), disconnect them + disconnect: 2m30s + # relaying using the RELAYMSG command relaymsg: # is relaymsg enabled at all? @@ -330,6 +341,10 @@ server: secure-nets: # - "10.0.0.0/8" + # allow attempts to OPER with a password at most this often. defaults to + # 10 seconds when unset. + oper-throttle: 10s + # Ergo will write files to disk under certain circumstances, e.g., # CPU profiling or data export. by default, these files will be written # to the working directory. set this to customize: @@ -494,7 +509,7 @@ accounts: # 1. these nicknames cannot be registered or reserved # 2. if a client is automatically renamed by the server, # this is the template that will be used (e.g., Guest-nccj6rgmt97cg) - # 3. if enforce-guest-format (see below) is enabled, clients without + # 3. if force-guest-format (see below) is enabled, clients without # a registered account will have this template applied to their # nicknames (e.g., 'katie' will become 'Guest-katie') guest-nickname-format: "Guest-*" @@ -695,6 +710,7 @@ oper-classes: - "history" # modify or delete history messages - "defcon" # use the DEFCON command (restrict server capabilities) - "massmessage" # message all users on the server + - "metadata" # modify arbitrary metadata on channels and users # ircd operators opers: @@ -1058,6 +1074,20 @@ history: # e.g., ERGO__SERVER__MAX_SENDQ=128k. see the manual for more details. allow-environment-overrides: true +# metadata support for setting key/value data on channels and nicknames. +metadata: + # can clients store metadata? + enabled: true + # how many keys can a client subscribe to? + max-subs: 100 + # how many keys can be stored per entity? + max-keys: 100 + # rate limiting for client metadata updates, which are expensive to process + client-throttle: + enabled: true + duration: 2m + max-attempts: 10 + # experimental support for mobile push notifications # see the manual for potential security, privacy, and performance implications. # DO NOT enable if you are running a Tor or I2P hidden service (i.e. one diff --git a/vendor/github.com/emersion/go-msgauth/dkim/header.go b/vendor/github.com/emersion/go-msgauth/dkim/header.go index dddd81f7..09f90212 100644 --- a/vendor/github.com/emersion/go-msgauth/dkim/header.go +++ b/vendor/github.com/emersion/go-msgauth/dkim/header.go @@ -83,7 +83,12 @@ func parseHeaderParams(s string) (map[string]string, error) { return params, errors.New("dkim: malformed header params") } - params[strings.TrimSpace(key)] = strings.TrimSpace(value) + trimmedKey := strings.TrimSpace(key) + _, present := params[trimmedKey] + if present { + return params, errors.New("dkim: duplicate tag name") + } + params[trimmedKey] = strings.TrimSpace(value) } return params, nil } diff --git a/vendor/github.com/emersion/go-msgauth/dkim/query.go b/vendor/github.com/emersion/go-msgauth/dkim/query.go index 33f8556e..45134cdd 100644 --- a/vendor/github.com/emersion/go-msgauth/dkim/query.go +++ b/vendor/github.com/emersion/go-msgauth/dkim/query.go @@ -100,7 +100,7 @@ func queryDNSTXT(domain, selector string, txtLookup txtLookupFunc) (*queryResult func parsePublicKey(s string) (*queryResult, error) { params, err := parseHeaderParams(s) if err != nil { - return nil, permFailError("key syntax error: " + err.Error()) + return nil, permFailError("key record error: " + err.Error()) } res := new(queryResult) diff --git a/vendor/golang.org/x/sys/unix/auxv.go b/vendor/golang.org/x/sys/unix/auxv.go new file mode 100644 index 00000000..37a82528 --- /dev/null +++ b/vendor/golang.org/x/sys/unix/auxv.go @@ -0,0 +1,36 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) + +package unix + +import ( + "syscall" + "unsafe" +) + +//go:linkname runtime_getAuxv runtime.getAuxv +func runtime_getAuxv() []uintptr + +// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs. +// The returned slice is always a fresh copy, owned by the caller. +// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed, +// which happens in some locked-down environments and build modes. +func Auxv() ([][2]uintptr, error) { + vec := runtime_getAuxv() + vecLen := len(vec) + + if vecLen == 0 { + return nil, syscall.ENOENT + } + + if vecLen%2 != 0 { + return nil, syscall.EINVAL + } + + result := make([]uintptr, vecLen) + copy(result, vec) + return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil +} diff --git a/vendor/golang.org/x/sys/unix/auxv_unsupported.go b/vendor/golang.org/x/sys/unix/auxv_unsupported.go new file mode 100644 index 00000000..1200487f --- /dev/null +++ b/vendor/golang.org/x/sys/unix/auxv_unsupported.go @@ -0,0 +1,13 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) + +package unix + +import "syscall" + +func Auxv() ([][2]uintptr, error) { + return nil, syscall.ENOTSUP +} diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 099867de..798f61ad 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -602,7 +602,150 @@ func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocI return } -//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) +// sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) +const minIovec = 8 + +func Readv(fd int, iovs [][]byte) (n int, err error) { + if !darwinKernelVersionMin(11, 0, 0) { + return 0, ENOSYS + } + + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) + n, err = readv(fd, iovecs) + readvRacedetect(iovecs, n, err) + return n, err +} + +func Preadv(fd int, iovs [][]byte, offset int64) (n int, err error) { + if !darwinKernelVersionMin(11, 0, 0) { + return 0, ENOSYS + } + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) + n, err = preadv(fd, iovecs, offset) + readvRacedetect(iovecs, n, err) + return n, err +} + +func Writev(fd int, iovs [][]byte) (n int, err error) { + if !darwinKernelVersionMin(11, 0, 0) { + return 0, ENOSYS + } + + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) + if raceenabled { + raceReleaseMerge(unsafe.Pointer(&ioSync)) + } + n, err = writev(fd, iovecs) + writevRacedetect(iovecs, n) + return n, err +} + +func Pwritev(fd int, iovs [][]byte, offset int64) (n int, err error) { + if !darwinKernelVersionMin(11, 0, 0) { + return 0, ENOSYS + } + + iovecs := make([]Iovec, 0, minIovec) + iovecs = appendBytes(iovecs, iovs) + if raceenabled { + raceReleaseMerge(unsafe.Pointer(&ioSync)) + } + n, err = pwritev(fd, iovecs, offset) + writevRacedetect(iovecs, n) + return n, err +} + +func appendBytes(vecs []Iovec, bs [][]byte) []Iovec { + for _, b := range bs { + var v Iovec + v.SetLen(len(b)) + if len(b) > 0 { + v.Base = &b[0] + } else { + v.Base = (*byte)(unsafe.Pointer(&_zero)) + } + vecs = append(vecs, v) + } + return vecs +} + +func writevRacedetect(iovecs []Iovec, n int) { + if !raceenabled { + return + } + for i := 0; n > 0 && i < len(iovecs); i++ { + m := int(iovecs[i].Len) + if m > n { + m = n + } + n -= m + if m > 0 { + raceReadRange(unsafe.Pointer(iovecs[i].Base), m) + } + } +} + +func readvRacedetect(iovecs []Iovec, n int, err error) { + if !raceenabled { + return + } + for i := 0; n > 0 && i < len(iovecs); i++ { + m := int(iovecs[i].Len) + if m > n { + m = n + } + n -= m + if m > 0 { + raceWriteRange(unsafe.Pointer(iovecs[i].Base), m) + } + } + if err == nil { + raceAcquire(unsafe.Pointer(&ioSync)) + } +} + +func darwinMajorMinPatch() (maj, min, patch int, err error) { + var un Utsname + err = Uname(&un) + if err != nil { + return + } + + var mmp [3]int + c := 0 +Loop: + for _, b := range un.Release[:] { + switch { + case b >= '0' && b <= '9': + mmp[c] = 10*mmp[c] + int(b-'0') + case b == '.': + c++ + if c > 2 { + return 0, 0, 0, ENOTSUP + } + case b == 0: + break Loop + default: + return 0, 0, 0, ENOTSUP + } + } + if c != 2 { + return 0, 0, 0, ENOTSUP + } + return mmp[0], mmp[1], mmp[2], nil +} + +func darwinKernelVersionMin(maj, min, patch int) bool { + actualMaj, actualMin, actualPatch, err := darwinMajorMinPatch() + if err != nil { + return false + } + return actualMaj > maj || actualMaj == maj && (actualMin > min || actualMin == min && actualPatch >= patch) +} + //sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) //sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error) @@ -705,3 +848,7 @@ func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocI //sys write(fd int, p []byte) (n int, err error) //sys mmap(addr uintptr, length uintptr, prot int, flag int, fd int, pos int64) (ret uintptr, err error) //sys munmap(addr uintptr, length uintptr) (err error) +//sys readv(fd int, iovecs []Iovec) (n int, err error) +//sys preadv(fd int, iovecs []Iovec, offset int64) (n int, err error) +//sys writev(fd int, iovecs []Iovec) (n int, err error) +//sys pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 230a9454..4958a657 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -13,6 +13,7 @@ package unix import ( "encoding/binary" + "slices" "strconv" "syscall" "time" @@ -417,7 +418,7 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, _Socklen, error) { return nil, 0, EINVAL } sa.raw.Family = AF_UNIX - for i := 0; i < n; i++ { + for i := range n { sa.raw.Path[i] = int8(name[i]) } // length is family (uint16), name, NUL. @@ -507,7 +508,7 @@ func (sa *SockaddrL2) sockaddr() (unsafe.Pointer, _Socklen, error) { psm := (*[2]byte)(unsafe.Pointer(&sa.raw.Psm)) psm[0] = byte(sa.PSM) psm[1] = byte(sa.PSM >> 8) - for i := 0; i < len(sa.Addr); i++ { + for i := range len(sa.Addr) { sa.raw.Bdaddr[i] = sa.Addr[len(sa.Addr)-1-i] } cid := (*[2]byte)(unsafe.Pointer(&sa.raw.Cid)) @@ -589,11 +590,11 @@ func (sa *SockaddrCAN) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Family = AF_CAN sa.raw.Ifindex = int32(sa.Ifindex) rx := (*[4]byte)(unsafe.Pointer(&sa.RxID)) - for i := 0; i < 4; i++ { + for i := range 4 { sa.raw.Addr[i] = rx[i] } tx := (*[4]byte)(unsafe.Pointer(&sa.TxID)) - for i := 0; i < 4; i++ { + for i := range 4 { sa.raw.Addr[i+4] = tx[i] } return unsafe.Pointer(&sa.raw), SizeofSockaddrCAN, nil @@ -618,11 +619,11 @@ func (sa *SockaddrCANJ1939) sockaddr() (unsafe.Pointer, _Socklen, error) { sa.raw.Family = AF_CAN sa.raw.Ifindex = int32(sa.Ifindex) n := (*[8]byte)(unsafe.Pointer(&sa.Name)) - for i := 0; i < 8; i++ { + for i := range 8 { sa.raw.Addr[i] = n[i] } p := (*[4]byte)(unsafe.Pointer(&sa.PGN)) - for i := 0; i < 4; i++ { + for i := range 4 { sa.raw.Addr[i+8] = p[i] } sa.raw.Addr[12] = sa.Addr @@ -911,7 +912,7 @@ func (sa *SockaddrIUCV) sockaddr() (unsafe.Pointer, _Socklen, error) { // These are EBCDIC encoded by the kernel, but we still need to pad them // with blanks. Initializing with blanks allows the caller to feed in either // a padded or an unpadded string. - for i := 0; i < 8; i++ { + for i := range 8 { sa.raw.Nodeid[i] = ' ' sa.raw.User_id[i] = ' ' sa.raw.Name[i] = ' ' @@ -1148,7 +1149,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { var user [8]byte var name [8]byte - for i := 0; i < 8; i++ { + for i := range 8 { user[i] = byte(pp.User_id[i]) name[i] = byte(pp.Name[i]) } @@ -1173,11 +1174,11 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { Ifindex: int(pp.Ifindex), } name := (*[8]byte)(unsafe.Pointer(&sa.Name)) - for i := 0; i < 8; i++ { + for i := range 8 { name[i] = pp.Addr[i] } pgn := (*[4]byte)(unsafe.Pointer(&sa.PGN)) - for i := 0; i < 4; i++ { + for i := range 4 { pgn[i] = pp.Addr[i+8] } addr := (*[1]byte)(unsafe.Pointer(&sa.Addr)) @@ -1188,11 +1189,11 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { Ifindex: int(pp.Ifindex), } rx := (*[4]byte)(unsafe.Pointer(&sa.RxID)) - for i := 0; i < 4; i++ { + for i := range 4 { rx[i] = pp.Addr[i] } tx := (*[4]byte)(unsafe.Pointer(&sa.TxID)) - for i := 0; i < 4; i++ { + for i := range 4 { tx[i] = pp.Addr[i+4] } return sa, nil @@ -2216,10 +2217,7 @@ func readvRacedetect(iovecs []Iovec, n int, err error) { return } for i := 0; n > 0 && i < len(iovecs); i++ { - m := int(iovecs[i].Len) - if m > n { - m = n - } + m := min(int(iovecs[i].Len), n) n -= m if m > 0 { raceWriteRange(unsafe.Pointer(iovecs[i].Base), m) @@ -2270,10 +2268,7 @@ func writevRacedetect(iovecs []Iovec, n int) { return } for i := 0; n > 0 && i < len(iovecs); i++ { - m := int(iovecs[i].Len) - if m > n { - m = n - } + m := min(int(iovecs[i].Len), n) n -= m if m > 0 { raceReadRange(unsafe.Pointer(iovecs[i].Base), m) @@ -2320,12 +2315,7 @@ func isGroupMember(gid int) bool { return false } - for _, g := range groups { - if g == gid { - return true - } - } - return false + return slices.Contains(groups, gid) } func isCapDacOverrideSet() bool { diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index 21974af0..abc39554 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -1102,3 +1102,90 @@ func (s *Strioctl) SetInt(i int) { func IoctlSetStrioctlRetInt(fd int, req int, s *Strioctl) (int, error) { return ioctlPtrRet(fd, req, unsafe.Pointer(s)) } + +// Ucred Helpers +// See ucred(3c) and getpeerucred(3c) + +//sys getpeerucred(fd uintptr, ucred *uintptr) (err error) +//sys ucredFree(ucred uintptr) = ucred_free +//sys ucredGet(pid int) (ucred uintptr, err error) = ucred_get +//sys ucredGeteuid(ucred uintptr) (uid int) = ucred_geteuid +//sys ucredGetegid(ucred uintptr) (gid int) = ucred_getegid +//sys ucredGetruid(ucred uintptr) (uid int) = ucred_getruid +//sys ucredGetrgid(ucred uintptr) (gid int) = ucred_getrgid +//sys ucredGetsuid(ucred uintptr) (uid int) = ucred_getsuid +//sys ucredGetsgid(ucred uintptr) (gid int) = ucred_getsgid +//sys ucredGetpid(ucred uintptr) (pid int) = ucred_getpid + +// Ucred is an opaque struct that holds user credentials. +type Ucred struct { + ucred uintptr +} + +// We need to ensure that ucredFree is called on the underlying ucred +// when the Ucred is garbage collected. +func ucredFinalizer(u *Ucred) { + ucredFree(u.ucred) +} + +func GetPeerUcred(fd uintptr) (*Ucred, error) { + var ucred uintptr + err := getpeerucred(fd, &ucred) + if err != nil { + return nil, err + } + result := &Ucred{ + ucred: ucred, + } + // set the finalizer on the result so that the ucred will be freed + runtime.SetFinalizer(result, ucredFinalizer) + return result, nil +} + +func UcredGet(pid int) (*Ucred, error) { + ucred, err := ucredGet(pid) + if err != nil { + return nil, err + } + result := &Ucred{ + ucred: ucred, + } + // set the finalizer on the result so that the ucred will be freed + runtime.SetFinalizer(result, ucredFinalizer) + return result, nil +} + +func (u *Ucred) Geteuid() int { + defer runtime.KeepAlive(u) + return ucredGeteuid(u.ucred) +} + +func (u *Ucred) Getruid() int { + defer runtime.KeepAlive(u) + return ucredGetruid(u.ucred) +} + +func (u *Ucred) Getsuid() int { + defer runtime.KeepAlive(u) + return ucredGetsuid(u.ucred) +} + +func (u *Ucred) Getegid() int { + defer runtime.KeepAlive(u) + return ucredGetegid(u.ucred) +} + +func (u *Ucred) Getrgid() int { + defer runtime.KeepAlive(u) + return ucredGetrgid(u.ucred) +} + +func (u *Ucred) Getsgid() int { + defer runtime.KeepAlive(u) + return ucredGetsgid(u.ucred) +} + +func (u *Ucred) Getpid() int { + defer runtime.KeepAlive(u) + return ucredGetpid(u.ucred) +} diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 6ebc48b3..4f432bfe 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1245,6 +1245,7 @@ const ( FAN_REPORT_DFID_NAME = 0xc00 FAN_REPORT_DFID_NAME_TARGET = 0x1e00 FAN_REPORT_DIR_FID = 0x400 + FAN_REPORT_FD_ERROR = 0x2000 FAN_REPORT_FID = 0x200 FAN_REPORT_NAME = 0x800 FAN_REPORT_PIDFD = 0x80 @@ -1330,8 +1331,10 @@ const ( FUSE_SUPER_MAGIC = 0x65735546 FUTEXFS_SUPER_MAGIC = 0xbad1dea F_ADD_SEALS = 0x409 + F_CREATED_QUERY = 0x404 F_DUPFD = 0x0 F_DUPFD_CLOEXEC = 0x406 + F_DUPFD_QUERY = 0x403 F_EXLCK = 0x4 F_GETFD = 0x1 F_GETFL = 0x3 @@ -1551,6 +1554,7 @@ const ( IPPROTO_ROUTING = 0x2b IPPROTO_RSVP = 0x2e IPPROTO_SCTP = 0x84 + IPPROTO_SMC = 0x100 IPPROTO_TCP = 0x6 IPPROTO_TP = 0x1d IPPROTO_UDP = 0x11 @@ -1623,6 +1627,8 @@ const ( IPV6_UNICAST_IF = 0x4c IPV6_USER_FLOW = 0xe IPV6_V6ONLY = 0x1a + IPV6_VERSION = 0x60 + IPV6_VERSION_MASK = 0xf0 IPV6_XFRM_POLICY = 0x23 IP_ADD_MEMBERSHIP = 0x23 IP_ADD_SOURCE_MEMBERSHIP = 0x27 @@ -1867,6 +1873,7 @@ const ( MADV_UNMERGEABLE = 0xd MADV_WILLNEED = 0x3 MADV_WIPEONFORK = 0x12 + MAP_DROPPABLE = 0x8 MAP_FILE = 0x0 MAP_FIXED = 0x10 MAP_FIXED_NOREPLACE = 0x100000 @@ -1967,6 +1974,7 @@ const ( MSG_PEEK = 0x2 MSG_PROXY = 0x10 MSG_RST = 0x1000 + MSG_SOCK_DEVMEM = 0x2000000 MSG_SYN = 0x400 MSG_TRUNC = 0x20 MSG_TRYHARD = 0x4 @@ -2083,6 +2091,7 @@ const ( NFC_ATR_REQ_MAXSIZE = 0x40 NFC_ATR_RES_GB_MAXSIZE = 0x2f NFC_ATR_RES_MAXSIZE = 0x40 + NFC_ATS_MAXSIZE = 0x14 NFC_COMM_ACTIVE = 0x0 NFC_COMM_PASSIVE = 0x1 NFC_DEVICE_NAME_MAXSIZE = 0x8 @@ -2163,6 +2172,7 @@ const ( NFNL_SUBSYS_QUEUE = 0x3 NFNL_SUBSYS_ULOG = 0x4 NFS_SUPER_MAGIC = 0x6969 + NFT_BITWISE_BOOL = 0x0 NFT_CHAIN_FLAGS = 0x7 NFT_CHAIN_MAXNAMELEN = 0x100 NFT_CT_MAX = 0x17 @@ -2491,6 +2501,7 @@ const ( PR_GET_PDEATHSIG = 0x2 PR_GET_SECCOMP = 0x15 PR_GET_SECUREBITS = 0x1b + PR_GET_SHADOW_STACK_STATUS = 0x4a PR_GET_SPECULATION_CTRL = 0x34 PR_GET_TAGGED_ADDR_CTRL = 0x38 PR_GET_THP_DISABLE = 0x2a @@ -2499,6 +2510,7 @@ const ( PR_GET_TIMING = 0xd PR_GET_TSC = 0x19 PR_GET_UNALIGN = 0x5 + PR_LOCK_SHADOW_STACK_STATUS = 0x4c PR_MCE_KILL = 0x21 PR_MCE_KILL_CLEAR = 0x0 PR_MCE_KILL_DEFAULT = 0x2 @@ -2525,6 +2537,8 @@ const ( PR_PAC_GET_ENABLED_KEYS = 0x3d PR_PAC_RESET_KEYS = 0x36 PR_PAC_SET_ENABLED_KEYS = 0x3c + PR_PMLEN_MASK = 0x7f000000 + PR_PMLEN_SHIFT = 0x18 PR_PPC_DEXCR_CTRL_CLEAR = 0x4 PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC = 0x10 PR_PPC_DEXCR_CTRL_EDITABLE = 0x1 @@ -2592,6 +2606,7 @@ const ( PR_SET_PTRACER = 0x59616d61 PR_SET_SECCOMP = 0x16 PR_SET_SECUREBITS = 0x1c + PR_SET_SHADOW_STACK_STATUS = 0x4b PR_SET_SPECULATION_CTRL = 0x35 PR_SET_SYSCALL_USER_DISPATCH = 0x3b PR_SET_TAGGED_ADDR_CTRL = 0x37 @@ -2602,6 +2617,9 @@ const ( PR_SET_UNALIGN = 0x6 PR_SET_VMA = 0x53564d41 PR_SET_VMA_ANON_NAME = 0x0 + PR_SHADOW_STACK_ENABLE = 0x1 + PR_SHADOW_STACK_PUSH = 0x4 + PR_SHADOW_STACK_WRITE = 0x2 PR_SME_GET_VL = 0x40 PR_SME_SET_VL = 0x3f PR_SME_SET_VL_ONEXEC = 0x40000 @@ -2911,7 +2929,6 @@ const ( RTM_NEWNEXTHOP = 0x68 RTM_NEWNEXTHOPBUCKET = 0x74 RTM_NEWNSID = 0x58 - RTM_NEWNVLAN = 0x70 RTM_NEWPREFIX = 0x34 RTM_NEWQDISC = 0x24 RTM_NEWROUTE = 0x18 @@ -2920,6 +2937,7 @@ const ( RTM_NEWTCLASS = 0x28 RTM_NEWTFILTER = 0x2c RTM_NEWTUNNEL = 0x78 + RTM_NEWVLAN = 0x70 RTM_NR_FAMILIES = 0x1b RTM_NR_MSGTYPES = 0x6c RTM_SETDCB = 0x4f diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index c0d45e32..75207613 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -116,6 +116,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -304,6 +306,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index c731d24f..c68acda5 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -116,6 +116,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -305,6 +307,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index 680018a4..a8c607ab 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -310,6 +312,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index a63909f3..18563dd8 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -109,6 +109,7 @@ const ( F_SETOWN = 0x8 F_UNLCK = 0x2 F_WRLCK = 0x1 + GCS_MAGIC = 0x47435300 HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESC = 0x90044802 HIDIOCGRDESCSIZE = 0x80044801 @@ -119,6 +120,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -302,6 +305,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 9b0a2573..22912cda 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -116,6 +116,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -297,6 +299,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 958e6e06..29344eb3 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -303,6 +305,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 50c7f25b..20d51fb9 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -303,6 +305,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index ced21d66..321b6090 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -303,6 +305,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 226c0441..9bacdf1e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x80 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -303,6 +305,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 3122737c..c2242726 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -358,6 +360,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index eb5d3467..6270c8ee 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -362,6 +364,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index e921ebc6..9966c194 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x80 IUCLC = 0x1000 IXOFF = 0x400 @@ -362,6 +364,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 38ba81c5..848e5fcc 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xffffff0f + IPV6_FLOWLABEL_MASK = 0xffff0f00 ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -294,6 +296,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 71f04009..669b2adb 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -115,6 +115,8 @@ const ( IN_CLOEXEC = 0x80000 IN_NONBLOCK = 0x800 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -366,6 +368,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x36 SCM_TIMESTAMPING_PKTINFO = 0x3a SCM_TIMESTAMPNS = 0x23 + SCM_TS_OPT_ID = 0x51 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index c44a3133..4834e575 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -119,6 +119,8 @@ const ( IN_CLOEXEC = 0x400000 IN_NONBLOCK = 0x4000 IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9 + IPV6_FLOWINFO_MASK = 0xfffffff + IPV6_FLOWLABEL_MASK = 0xfffff ISIG = 0x1 IUCLC = 0x200 IXOFF = 0x1000 @@ -357,6 +359,7 @@ const ( SCM_TIMESTAMPING_OPT_STATS = 0x38 SCM_TIMESTAMPING_PKTINFO = 0x3c SCM_TIMESTAMPNS = 0x21 + SCM_TS_OPT_ID = 0x5a SCM_TXTIME = 0x3f SCM_WIFI_STATUS = 0x25 SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index 24b346e1..813c05b6 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -2512,6 +2512,90 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func readv(fd int, iovecs []Iovec) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall(libc_readv_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs))) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_readv_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_readv readv "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func preadv(fd int, iovecs []Iovec, offset int64) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall6(libc_preadv_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs)), uintptr(offset), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_preadv_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_preadv preadv "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func writev(fd int, iovecs []Iovec) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall(libc_writev_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs))) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_writev_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_writev writev "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall6(libc_pwritev_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs)), uintptr(offset), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pwritev_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pwritev pwritev "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Fstat(fd int, stat *Stat_t) (err error) { _, _, e1 := syscall_syscall(libc_fstat64_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(stat)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index ebd21310..fda32858 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -738,6 +738,26 @@ TEXT libc_munmap_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_munmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_munmap_trampoline_addr(SB)/8, $libc_munmap_trampoline<>(SB) +TEXT libc_readv_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_readv(SB) +GLOBL ·libc_readv_trampoline_addr(SB), RODATA, $8 +DATA ·libc_readv_trampoline_addr(SB)/8, $libc_readv_trampoline<>(SB) + +TEXT libc_preadv_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_preadv(SB) +GLOBL ·libc_preadv_trampoline_addr(SB), RODATA, $8 +DATA ·libc_preadv_trampoline_addr(SB)/8, $libc_preadv_trampoline<>(SB) + +TEXT libc_writev_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_writev(SB) +GLOBL ·libc_writev_trampoline_addr(SB), RODATA, $8 +DATA ·libc_writev_trampoline_addr(SB)/8, $libc_writev_trampoline<>(SB) + +TEXT libc_pwritev_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pwritev(SB) +GLOBL ·libc_pwritev_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pwritev_trampoline_addr(SB)/8, $libc_pwritev_trampoline<>(SB) + TEXT libc_fstat64_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstat64(SB) GLOBL ·libc_fstat64_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 824b9c2d..e6f58f3c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -2512,6 +2512,90 @@ var libc_munmap_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func readv(fd int, iovecs []Iovec) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall(libc_readv_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs))) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_readv_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_readv readv "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func preadv(fd int, iovecs []Iovec, offset int64) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall6(libc_preadv_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs)), uintptr(offset), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_preadv_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_preadv preadv "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func writev(fd int, iovecs []Iovec) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall(libc_writev_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs))) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_writev_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_writev writev "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func pwritev(fd int, iovecs []Iovec, offset int64) (n int, err error) { + var _p0 unsafe.Pointer + if len(iovecs) > 0 { + _p0 = unsafe.Pointer(&iovecs[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + r0, _, e1 := syscall_syscall6(libc_pwritev_trampoline_addr, uintptr(fd), uintptr(_p0), uintptr(len(iovecs)), uintptr(offset), 0, 0) + n = int(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_pwritev_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_pwritev pwritev "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Fstat(fd int, stat *Stat_t) (err error) { _, _, e1 := syscall_syscall(libc_fstat_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(stat)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index 4f178a22..7f8998b9 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -738,6 +738,26 @@ TEXT libc_munmap_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_munmap_trampoline_addr(SB), RODATA, $8 DATA ·libc_munmap_trampoline_addr(SB)/8, $libc_munmap_trampoline<>(SB) +TEXT libc_readv_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_readv(SB) +GLOBL ·libc_readv_trampoline_addr(SB), RODATA, $8 +DATA ·libc_readv_trampoline_addr(SB)/8, $libc_readv_trampoline<>(SB) + +TEXT libc_preadv_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_preadv(SB) +GLOBL ·libc_preadv_trampoline_addr(SB), RODATA, $8 +DATA ·libc_preadv_trampoline_addr(SB)/8, $libc_preadv_trampoline<>(SB) + +TEXT libc_writev_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_writev(SB) +GLOBL ·libc_writev_trampoline_addr(SB), RODATA, $8 +DATA ·libc_writev_trampoline_addr(SB)/8, $libc_writev_trampoline<>(SB) + +TEXT libc_pwritev_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_pwritev(SB) +GLOBL ·libc_pwritev_trampoline_addr(SB), RODATA, $8 +DATA ·libc_pwritev_trampoline_addr(SB)/8, $libc_pwritev_trampoline<>(SB) + TEXT libc_fstat_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_fstat(SB) GLOBL ·libc_fstat_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go index 829b87fe..c6545413 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go @@ -141,6 +141,16 @@ import ( //go:cgo_import_dynamic libc_getpeername getpeername "libsocket.so" //go:cgo_import_dynamic libc_setsockopt setsockopt "libsocket.so" //go:cgo_import_dynamic libc_recvfrom recvfrom "libsocket.so" +//go:cgo_import_dynamic libc_getpeerucred getpeerucred "libc.so" +//go:cgo_import_dynamic libc_ucred_get ucred_get "libc.so" +//go:cgo_import_dynamic libc_ucred_geteuid ucred_geteuid "libc.so" +//go:cgo_import_dynamic libc_ucred_getegid ucred_getegid "libc.so" +//go:cgo_import_dynamic libc_ucred_getruid ucred_getruid "libc.so" +//go:cgo_import_dynamic libc_ucred_getrgid ucred_getrgid "libc.so" +//go:cgo_import_dynamic libc_ucred_getsuid ucred_getsuid "libc.so" +//go:cgo_import_dynamic libc_ucred_getsgid ucred_getsgid "libc.so" +//go:cgo_import_dynamic libc_ucred_getpid ucred_getpid "libc.so" +//go:cgo_import_dynamic libc_ucred_free ucred_free "libc.so" //go:cgo_import_dynamic libc_port_create port_create "libc.so" //go:cgo_import_dynamic libc_port_associate port_associate "libc.so" //go:cgo_import_dynamic libc_port_dissociate port_dissociate "libc.so" @@ -280,6 +290,16 @@ import ( //go:linkname procgetpeername libc_getpeername //go:linkname procsetsockopt libc_setsockopt //go:linkname procrecvfrom libc_recvfrom +//go:linkname procgetpeerucred libc_getpeerucred +//go:linkname procucred_get libc_ucred_get +//go:linkname procucred_geteuid libc_ucred_geteuid +//go:linkname procucred_getegid libc_ucred_getegid +//go:linkname procucred_getruid libc_ucred_getruid +//go:linkname procucred_getrgid libc_ucred_getrgid +//go:linkname procucred_getsuid libc_ucred_getsuid +//go:linkname procucred_getsgid libc_ucred_getsgid +//go:linkname procucred_getpid libc_ucred_getpid +//go:linkname procucred_free libc_ucred_free //go:linkname procport_create libc_port_create //go:linkname procport_associate libc_port_associate //go:linkname procport_dissociate libc_port_dissociate @@ -420,6 +440,16 @@ var ( procgetpeername, procsetsockopt, procrecvfrom, + procgetpeerucred, + procucred_get, + procucred_geteuid, + procucred_getegid, + procucred_getruid, + procucred_getrgid, + procucred_getsuid, + procucred_getsgid, + procucred_getpid, + procucred_free, procport_create, procport_associate, procport_dissociate, @@ -2029,6 +2059,90 @@ func recvfrom(fd int, p []byte, flags int, from *RawSockaddrAny, fromlen *_Sockl // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func getpeerucred(fd uintptr, ucred *uintptr) (err error) { + _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgetpeerucred)), 2, uintptr(fd), uintptr(unsafe.Pointer(ucred)), 0, 0, 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGet(pid int) (ucred uintptr, err error) { + r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procucred_get)), 1, uintptr(pid), 0, 0, 0, 0, 0) + ucred = uintptr(r0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGeteuid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_geteuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetegid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getegid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetruid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getruid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetrgid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getrgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetsuid(ucred uintptr) (uid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + uid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetsgid(ucred uintptr) (gid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + gid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredGetpid(ucred uintptr) (pid int) { + r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getpid)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + pid = int(r0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func ucredFree(ucred uintptr) { + sysvicall6(uintptr(unsafe.Pointer(&procucred_free)), 1, uintptr(ucred), 0, 0, 0, 0, 0) + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func port_create() (n int, err error) { r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_create)), 0, 0, 0, 0, 0, 0, 0) n = int(r0) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index 524b0820..c79aaff3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -458,4 +458,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index f485dbf4..5eb45069 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -381,4 +381,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index 70b35bf3..05e50297 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -422,4 +422,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 1893e2fe..38c53ec5 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -325,4 +325,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 16a4017d..31d2e71a 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -321,4 +321,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index 7e567f1e..f4184a33 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -442,4 +442,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 SYS_MSEAL = 4462 + SYS_SETXATTRAT = 4463 + SYS_GETXATTRAT = 4464 + SYS_LISTXATTRAT = 4465 + SYS_REMOVEXATTRAT = 4466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index 38ae55e5..05b99622 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -372,4 +372,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 SYS_MSEAL = 5462 + SYS_SETXATTRAT = 5463 + SYS_GETXATTRAT = 5464 + SYS_LISTXATTRAT = 5465 + SYS_REMOVEXATTRAT = 5466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index 55e92e60..43a256e9 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -372,4 +372,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 SYS_MSEAL = 5462 + SYS_SETXATTRAT = 5463 + SYS_GETXATTRAT = 5464 + SYS_LISTXATTRAT = 5465 + SYS_REMOVEXATTRAT = 5466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index 60658d6a..eea5ddfc 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -442,4 +442,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 SYS_MSEAL = 4462 + SYS_SETXATTRAT = 4463 + SYS_GETXATTRAT = 4464 + SYS_LISTXATTRAT = 4465 + SYS_REMOVEXATTRAT = 4466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index e203e8a7..0d777bfb 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -449,4 +449,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index 5944b97d..b4463650 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -421,4 +421,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index c66d416d..0c7d21c1 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -421,4 +421,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index a5459e76..84053916 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -326,4 +326,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index 01d86825..fcf1b790 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -387,4 +387,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index 7b703e77..52d15b5f 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -400,4 +400,8 @@ const ( SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 SYS_MSEAL = 462 + SYS_SETXATTRAT = 463 + SYS_GETXATTRAT = 464 + SYS_LISTXATTRAT = 465 + SYS_REMOVEXATTRAT = 466 ) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 5537148d..a46abe64 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -4747,7 +4747,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14c + NL80211_ATTR_MAX = 0x14d NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -5519,7 +5519,7 @@ const ( NL80211_MNTR_FLAG_CONTROL = 0x3 NL80211_MNTR_FLAG_COOK_FRAMES = 0x5 NL80211_MNTR_FLAG_FCSFAIL = 0x1 - NL80211_MNTR_FLAG_MAX = 0x6 + NL80211_MNTR_FLAG_MAX = 0x7 NL80211_MNTR_FLAG_OTHER_BSS = 0x4 NL80211_MNTR_FLAG_PLCPFAIL = 0x2 NL80211_MPATH_FLAG_ACTIVE = 0x1 @@ -6174,3 +6174,5 @@ type SockDiagReq struct { Family uint8 Protocol uint8 } + +const RTM_NEWNVLAN = 0x70 diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index b6e1ab76..a8b0364c 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -1303,7 +1303,10 @@ func (selfRelativeSD *SECURITY_DESCRIPTOR) ToAbsolute() (absoluteSD *SECURITY_DE return nil, err } if absoluteSDSize > 0 { - absoluteSD = (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&make([]byte, absoluteSDSize)[0])) + absoluteSD = new(SECURITY_DESCRIPTOR) + if unsafe.Sizeof(*absoluteSD) < uintptr(absoluteSDSize) { + panic("sizeof(SECURITY_DESCRIPTOR) too small") + } } var ( dacl *ACL @@ -1312,19 +1315,55 @@ func (selfRelativeSD *SECURITY_DESCRIPTOR) ToAbsolute() (absoluteSD *SECURITY_DE group *SID ) if daclSize > 0 { - dacl = (*ACL)(unsafe.Pointer(&make([]byte, daclSize)[0])) + dacl = (*ACL)(unsafe.Pointer(unsafe.SliceData(make([]byte, daclSize)))) } if saclSize > 0 { - sacl = (*ACL)(unsafe.Pointer(&make([]byte, saclSize)[0])) + sacl = (*ACL)(unsafe.Pointer(unsafe.SliceData(make([]byte, saclSize)))) } if ownerSize > 0 { - owner = (*SID)(unsafe.Pointer(&make([]byte, ownerSize)[0])) + owner = (*SID)(unsafe.Pointer(unsafe.SliceData(make([]byte, ownerSize)))) } if groupSize > 0 { - group = (*SID)(unsafe.Pointer(&make([]byte, groupSize)[0])) + group = (*SID)(unsafe.Pointer(unsafe.SliceData(make([]byte, groupSize)))) } + // We call into Windows via makeAbsoluteSD, which sets up + // pointers within absoluteSD that point to other chunks of memory + // we pass into makeAbsoluteSD, and that happens outside the view of the GC. + // We therefore take some care here to then verify the pointers are as we expect + // and set them explicitly in view of the GC. See https://go.dev/issue/73199. + // TODO: consider weak pointers once Go 1.24 is appropriate. See suggestion in https://go.dev/cl/663575. err = makeAbsoluteSD(selfRelativeSD, absoluteSD, &absoluteSDSize, dacl, &daclSize, sacl, &saclSize, owner, &ownerSize, group, &groupSize) + if err != nil { + // Don't return absoluteSD, which might be partially initialized. + return nil, err + } + // Before using any fields, verify absoluteSD is in the format we expect according to Windows. + // See https://learn.microsoft.com/en-us/windows/win32/secauthz/absolute-and-self-relative-security-descriptors + absControl, _, err := absoluteSD.Control() + if err != nil { + panic("absoluteSD: " + err.Error()) + } + if absControl&SE_SELF_RELATIVE != 0 { + panic("absoluteSD not in absolute format") + } + if absoluteSD.dacl != dacl { + panic("dacl pointer mismatch") + } + if absoluteSD.sacl != sacl { + panic("sacl pointer mismatch") + } + if absoluteSD.owner != owner { + panic("owner pointer mismatch") + } + if absoluteSD.group != group { + panic("group pointer mismatch") + } + absoluteSD.dacl = dacl + absoluteSD.sacl = sacl + absoluteSD.owner = owner + absoluteSD.group = group + return } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 4a325438..640f6b15 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -870,6 +870,7 @@ const socket_error = uintptr(^uint32(0)) //sys WSARecvFrom(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, from *RawSockaddrAny, fromlen *int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecvFrom //sys WSASendTo(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *RawSockaddrAny, tolen int32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSASendTo //sys WSASocket(af int32, typ int32, protocol int32, protoInfo *WSAProtocolInfo, group uint32, flags uint32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.WSASocketW +//sys WSADuplicateSocket(s Handle, processID uint32, info *WSAProtocolInfo) (err error) [failretval!=0] = ws2_32.WSADuplicateSocketW //sys GetHostByName(name string) (h *Hostent, err error) [failretval==nil] = ws2_32.gethostbyname //sys GetServByName(name string, proto string) (s *Servent, err error) [failretval==nil] = ws2_32.getservbyname //sys Ntohs(netshort uint16) (u uint16) = ws2_32.ntohs @@ -1698,8 +1699,9 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) { // Slice returns a uint16 slice that aliases the data in the NTUnicodeString. func (s *NTUnicodeString) Slice() []uint16 { - slice := unsafe.Slice(s.Buffer, s.MaximumLength) - return slice[:s.Length] + // Note: this rounds the length down, if it happens + // to (incorrectly) be odd. Probably safer than rounding up. + return unsafe.Slice(s.Buffer, s.MaximumLength/2)[:s.Length/2] } func (s *NTUnicodeString) String() string { diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 9d138de5..958bcf47 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -1074,6 +1074,7 @@ const ( IP_ADD_MEMBERSHIP = 0xc IP_DROP_MEMBERSHIP = 0xd IP_PKTINFO = 0x13 + IP_MTU_DISCOVER = 0x47 IPV6_V6ONLY = 0x1b IPV6_UNICAST_HOPS = 0x4 @@ -1083,6 +1084,7 @@ const ( IPV6_JOIN_GROUP = 0xc IPV6_LEAVE_GROUP = 0xd IPV6_PKTINFO = 0x13 + IPV6_MTU_DISCOVER = 0x47 MSG_OOB = 0x1 MSG_PEEK = 0x2 @@ -1132,6 +1134,15 @@ const ( WSASYS_STATUS_LEN = 128 ) +// enum PMTUD_STATE from ws2ipdef.h +const ( + IP_PMTUDISC_NOT_SET = 0 + IP_PMTUDISC_DO = 1 + IP_PMTUDISC_DONT = 2 + IP_PMTUDISC_PROBE = 3 + IP_PMTUDISC_MAX = 4 +) + type WSABuf struct { Len uint32 Buf *byte @@ -1146,6 +1157,22 @@ type WSAMsg struct { Flags uint32 } +type WSACMSGHDR struct { + Len uintptr + Level int32 + Type int32 +} + +type IN_PKTINFO struct { + Addr [4]byte + Ifindex uint32 +} + +type IN6_PKTINFO struct { + Addr [16]byte + Ifindex uint32 +} + // Flags for WSASocket const ( WSA_FLAG_OVERLAPPED = 0x01 @@ -2673,6 +2700,8 @@ type CommTimeouts struct { // NTUnicodeString is a UTF-16 string for NT native APIs, corresponding to UNICODE_STRING. type NTUnicodeString struct { + // Note: Length and MaximumLength are in *bytes*, not uint16s. + // They should always be even. Length uint16 MaximumLength uint16 Buffer *uint16 @@ -3601,3 +3630,213 @@ const ( KLF_NOTELLSHELL = 0x00000080 KLF_SETFORPROCESS = 0x00000100 ) + +// Virtual Key codes +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +const ( + VK_LBUTTON = 0x01 + VK_RBUTTON = 0x02 + VK_CANCEL = 0x03 + VK_MBUTTON = 0x04 + VK_XBUTTON1 = 0x05 + VK_XBUTTON2 = 0x06 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_CLEAR = 0x0C + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_PAUSE = 0x13 + VK_CAPITAL = 0x14 + VK_KANA = 0x15 + VK_HANGEUL = 0x15 + VK_HANGUL = 0x15 + VK_IME_ON = 0x16 + VK_JUNJA = 0x17 + VK_FINAL = 0x18 + VK_HANJA = 0x19 + VK_KANJI = 0x19 + VK_IME_OFF = 0x1A + VK_ESCAPE = 0x1B + VK_CONVERT = 0x1C + VK_NONCONVERT = 0x1D + VK_ACCEPT = 0x1E + VK_MODECHANGE = 0x1F + VK_SPACE = 0x20 + VK_PRIOR = 0x21 + VK_NEXT = 0x22 + VK_END = 0x23 + VK_HOME = 0x24 + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_SELECT = 0x29 + VK_PRINT = 0x2A + VK_EXECUTE = 0x2B + VK_SNAPSHOT = 0x2C + VK_INSERT = 0x2D + VK_DELETE = 0x2E + VK_HELP = 0x2F + VK_LWIN = 0x5B + VK_RWIN = 0x5C + VK_APPS = 0x5D + VK_SLEEP = 0x5F + VK_NUMPAD0 = 0x60 + VK_NUMPAD1 = 0x61 + VK_NUMPAD2 = 0x62 + VK_NUMPAD3 = 0x63 + VK_NUMPAD4 = 0x64 + VK_NUMPAD5 = 0x65 + VK_NUMPAD6 = 0x66 + VK_NUMPAD7 = 0x67 + VK_NUMPAD8 = 0x68 + VK_NUMPAD9 = 0x69 + VK_MULTIPLY = 0x6A + VK_ADD = 0x6B + VK_SEPARATOR = 0x6C + VK_SUBTRACT = 0x6D + VK_DECIMAL = 0x6E + VK_DIVIDE = 0x6F + VK_F1 = 0x70 + VK_F2 = 0x71 + VK_F3 = 0x72 + VK_F4 = 0x73 + VK_F5 = 0x74 + VK_F6 = 0x75 + VK_F7 = 0x76 + VK_F8 = 0x77 + VK_F9 = 0x78 + VK_F10 = 0x79 + VK_F11 = 0x7A + VK_F12 = 0x7B + VK_F13 = 0x7C + VK_F14 = 0x7D + VK_F15 = 0x7E + VK_F16 = 0x7F + VK_F17 = 0x80 + VK_F18 = 0x81 + VK_F19 = 0x82 + VK_F20 = 0x83 + VK_F21 = 0x84 + VK_F22 = 0x85 + VK_F23 = 0x86 + VK_F24 = 0x87 + VK_NUMLOCK = 0x90 + VK_SCROLL = 0x91 + VK_OEM_NEC_EQUAL = 0x92 + VK_OEM_FJ_JISHO = 0x92 + VK_OEM_FJ_MASSHOU = 0x93 + VK_OEM_FJ_TOUROKU = 0x94 + VK_OEM_FJ_LOYA = 0x95 + VK_OEM_FJ_ROYA = 0x96 + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 + VK_LMENU = 0xA4 + VK_RMENU = 0xA5 + VK_BROWSER_BACK = 0xA6 + VK_BROWSER_FORWARD = 0xA7 + VK_BROWSER_REFRESH = 0xA8 + VK_BROWSER_STOP = 0xA9 + VK_BROWSER_SEARCH = 0xAA + VK_BROWSER_FAVORITES = 0xAB + VK_BROWSER_HOME = 0xAC + VK_VOLUME_MUTE = 0xAD + VK_VOLUME_DOWN = 0xAE + VK_VOLUME_UP = 0xAF + VK_MEDIA_NEXT_TRACK = 0xB0 + VK_MEDIA_PREV_TRACK = 0xB1 + VK_MEDIA_STOP = 0xB2 + VK_MEDIA_PLAY_PAUSE = 0xB3 + VK_LAUNCH_MAIL = 0xB4 + VK_LAUNCH_MEDIA_SELECT = 0xB5 + VK_LAUNCH_APP1 = 0xB6 + VK_LAUNCH_APP2 = 0xB7 + VK_OEM_1 = 0xBA + VK_OEM_PLUS = 0xBB + VK_OEM_COMMA = 0xBC + VK_OEM_MINUS = 0xBD + VK_OEM_PERIOD = 0xBE + VK_OEM_2 = 0xBF + VK_OEM_3 = 0xC0 + VK_OEM_4 = 0xDB + VK_OEM_5 = 0xDC + VK_OEM_6 = 0xDD + VK_OEM_7 = 0xDE + VK_OEM_8 = 0xDF + VK_OEM_AX = 0xE1 + VK_OEM_102 = 0xE2 + VK_ICO_HELP = 0xE3 + VK_ICO_00 = 0xE4 + VK_PROCESSKEY = 0xE5 + VK_ICO_CLEAR = 0xE6 + VK_OEM_RESET = 0xE9 + VK_OEM_JUMP = 0xEA + VK_OEM_PA1 = 0xEB + VK_OEM_PA2 = 0xEC + VK_OEM_PA3 = 0xED + VK_OEM_WSCTRL = 0xEE + VK_OEM_CUSEL = 0xEF + VK_OEM_ATTN = 0xF0 + VK_OEM_FINISH = 0xF1 + VK_OEM_COPY = 0xF2 + VK_OEM_AUTO = 0xF3 + VK_OEM_ENLW = 0xF4 + VK_OEM_BACKTAB = 0xF5 + VK_ATTN = 0xF6 + VK_CRSEL = 0xF7 + VK_EXSEL = 0xF8 + VK_EREOF = 0xF9 + VK_PLAY = 0xFA + VK_ZOOM = 0xFB + VK_NONAME = 0xFC + VK_PA1 = 0xFD + VK_OEM_CLEAR = 0xFE +) + +// Mouse button constants. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001 + RIGHTMOST_BUTTON_PRESSED = 0x0002 + FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004 + FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008 + FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010 +) + +// Control key state constaints. +// https://docs.microsoft.com/en-us/windows/console/key-event-record-str +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + CAPSLOCK_ON = 0x0080 + ENHANCED_KEY = 0x0100 + LEFT_ALT_PRESSED = 0x0002 + LEFT_CTRL_PRESSED = 0x0008 + NUMLOCK_ON = 0x0020 + RIGHT_ALT_PRESSED = 0x0001 + RIGHT_CTRL_PRESSED = 0x0004 + SCROLLLOCK_ON = 0x0040 + SHIFT_PRESSED = 0x0010 +) + +// Mouse event record event flags. +// https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str +const ( + MOUSE_MOVED = 0x0001 + DOUBLE_CLICK = 0x0002 + MOUSE_WHEELED = 0x0004 + MOUSE_HWHEELED = 0x0008 +) + +// Input Record Event Types +// https://learn.microsoft.com/en-us/windows/console/input-record-str +const ( + FOCUS_EVENT = 0x0010 + KEY_EVENT = 0x0001 + MENU_EVENT = 0x0008 + MOUSE_EVENT = 0x0002 + WINDOW_BUFFER_SIZE_EVENT = 0x0004 +) diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 01c0716c..a58bc48b 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -511,6 +511,7 @@ var ( procFreeAddrInfoW = modws2_32.NewProc("FreeAddrInfoW") procGetAddrInfoW = modws2_32.NewProc("GetAddrInfoW") procWSACleanup = modws2_32.NewProc("WSACleanup") + procWSADuplicateSocketW = modws2_32.NewProc("WSADuplicateSocketW") procWSAEnumProtocolsW = modws2_32.NewProc("WSAEnumProtocolsW") procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") procWSAIoctl = modws2_32.NewProc("WSAIoctl") @@ -4391,6 +4392,14 @@ func WSACleanup() (err error) { return } +func WSADuplicateSocket(s Handle, processID uint32, info *WSAProtocolInfo) (err error) { + r1, _, e1 := syscall.Syscall(procWSADuplicateSocketW.Addr(), 3, uintptr(s), uintptr(processID), uintptr(unsafe.Pointer(info))) + if r1 != 0 { + err = errnoErr(e1) + } + return +} + func WSAEnumProtocols(protocols *int32, protocolBuffer *WSAProtocolInfo, bufferLength *uint32) (n int32, err error) { r0, _, e1 := syscall.Syscall(procWSAEnumProtocolsW.Addr(), 3, uintptr(unsafe.Pointer(protocols)), uintptr(unsafe.Pointer(protocolBuffer)), uintptr(unsafe.Pointer(bufferLength))) n = int32(r0) diff --git a/vendor/golang.org/x/term/terminal.go b/vendor/golang.org/x/term/terminal.go index f636667f..13e9a64a 100644 --- a/vendor/golang.org/x/term/terminal.go +++ b/vendor/golang.org/x/term/terminal.go @@ -6,6 +6,7 @@ package term import ( "bytes" + "fmt" "io" "runtime" "strconv" @@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{ Reset: []byte{keyEscape, '[', '0', 'm'}, } +// A History provides a (possibly bounded) queue of input lines read by [Terminal.ReadLine]. +type History interface { + // Add will be called by [Terminal.ReadLine] to add + // a new, most recent entry to the history. + // It is allowed to drop any entry, including + // the entry being added (e.g., if it's deemed an invalid entry), + // the least-recent entry (e.g., to keep the history bounded), + // or any other entry. + Add(entry string) + + // Len returns the number of entries in the history. + Len() int + + // At returns an entry from the history. + // Index 0 is the most-recently added entry and + // index Len()-1 is the least-recently added entry. + // If index is < 0 or >= Len(), it panics. + At(idx int) string +} + // Terminal contains the state for running a VT100 terminal that is capable of // reading lines of input. type Terminal struct { @@ -44,6 +65,8 @@ type Terminal struct { // bytes, as an index into |line|). If it returns ok=false, the key // press is processed normally. Otherwise it returns a replacement line // and the new cursor position. + // + // This will be disabled during ReadPassword. AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) // Escape contains a pointer to the escape codes for this terminal. @@ -84,9 +107,14 @@ type Terminal struct { remainder []byte inBuf [256]byte - // history contains previously entered commands so that they can be - // accessed with the up and down keys. - history stRingBuffer + // History records and retrieves lines of input read by [ReadLine] which + // a user can retrieve and navigate using the up and down arrow keys. + // + // It is not safe to call ReadLine concurrently with any methods on History. + // + // [NewTerminal] sets this to a default implementation that records the + // last 100 lines of input. + History History // historyIndex stores the currently accessed history entry, where zero // means the immediately previous entry. historyIndex int @@ -109,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { termHeight: 24, echo: true, historyIndex: -1, + History: &stRingBuffer{}, } } @@ -448,6 +477,23 @@ func visualLength(runes []rune) int { return length } +// histroryAt unlocks the terminal and relocks it while calling History.At. +func (t *Terminal) historyAt(idx int) (string, bool) { + t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer. + defer t.lock.Lock() // panic in At (or Len) protection. + if idx < 0 || idx >= t.History.Len() { + return "", false + } + return t.History.At(idx), true +} + +// historyAdd unlocks the terminal and relocks it while calling History.Add. +func (t *Terminal) historyAdd(entry string) { + t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer. + defer t.lock.Lock() // panic in Add protection. + t.History.Add(entry) +} + // handleKey processes the given key and, optionally, returns a line of text // that the user has entered. func (t *Terminal) handleKey(key rune) (line string, ok bool) { @@ -495,7 +541,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) { t.pos = len(t.line) t.moveCursorToPos(t.pos) case keyUp: - entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) + entry, ok := t.historyAt(t.historyIndex + 1) if !ok { return "", false } @@ -514,7 +560,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) { t.setLine(runes, len(runes)) t.historyIndex-- default: - entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) + entry, ok := t.historyAt(t.historyIndex - 1) if ok { t.historyIndex-- runes := []rune(entry) @@ -692,6 +738,8 @@ func (t *Terminal) Write(buf []byte) (n int, err error) { // ReadPassword temporarily changes the prompt and reads a password, without // echo, from the terminal. +// +// The AutoCompleteCallback is disabled during this call. func (t *Terminal) ReadPassword(prompt string) (line string, err error) { t.lock.Lock() defer t.lock.Unlock() @@ -699,6 +747,11 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) { oldPrompt := t.prompt t.prompt = []rune(prompt) t.echo = false + oldAutoCompleteCallback := t.AutoCompleteCallback + t.AutoCompleteCallback = nil + defer func() { + t.AutoCompleteCallback = oldAutoCompleteCallback + }() line, err = t.readLine() @@ -772,7 +825,7 @@ func (t *Terminal) readLine() (line string, err error) { if lineOk { if t.echo { t.historyIndex = -1 - t.history.Add(line) + t.historyAdd(line) } if lineIsPasted { err = ErrPasteIndicator @@ -929,19 +982,23 @@ func (s *stRingBuffer) Add(a string) { } } -// NthPreviousEntry returns the value passed to the nth previous call to Add. +func (s *stRingBuffer) Len() int { + return s.size +} + +// At returns the value passed to the nth previous call to Add. // If n is zero then the immediately prior value is returned, if one, then the // next most recent, and so on. If such an element doesn't exist then ok is // false. -func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { +func (s *stRingBuffer) At(n int) string { if n < 0 || n >= s.size { - return "", false + panic(fmt.Sprintf("term: history index [%d] out of range [0,%d)", n, s.size)) } index := s.head - n if index < 0 { index += s.max } - return s.entries[index], true + return s.entries[index] } // readPasswordLine reads from reader until it finds \n or io.EOF. diff --git a/vendor/golang.org/x/text/language/parse.go b/vendor/golang.org/x/text/language/parse.go index 4d57222e..053336e2 100644 --- a/vendor/golang.org/x/text/language/parse.go +++ b/vendor/golang.org/x/text/language/parse.go @@ -59,7 +59,7 @@ func (c CanonType) Parse(s string) (t Tag, err error) { if changed { tt.RemakeString() } - return makeTag(tt), err + return makeTag(tt), nil } // Compose creates a Tag from individual parts, which may be of type Tag, Base, diff --git a/vendor/modules.txt b/vendor/modules.txt index 291ca839..1a218695 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/GehirnInc/crypt/md5_crypt # github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 ## explicit github.com/docopt/docopt-go -# github.com/emersion/go-msgauth v0.6.8 +# github.com/emersion/go-msgauth v0.7.0 ## explicit; go 1.18 github.com/emersion/go-msgauth/dkim # github.com/ergochat/confusables v0.0.0-20201108231250-4ab98ab61fb1 @@ -31,8 +31,6 @@ github.com/ergochat/webpush-go/v2 # github.com/go-sql-driver/mysql v1.7.0 ## explicit; go 1.13 github.com/go-sql-driver/mysql -# github.com/go-test/deep v1.0.6 -## explicit; go 1.13 # github.com/gofrs/flock v0.8.1 ## explicit github.com/gofrs/flock @@ -76,31 +74,29 @@ github.com/tidwall/rtred/base # github.com/tidwall/tinyqueue v0.1.1 ## explicit; go 1.15 github.com/tidwall/tinyqueue -# github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 -## explicit # github.com/xdg-go/pbkdf2 v1.0.0 ## explicit; go 1.9 github.com/xdg-go/pbkdf2 # github.com/xdg-go/scram v1.0.2 => github.com/ergochat/scram v1.0.2-ergo1 ## explicit; go 1.11 github.com/xdg-go/scram -# golang.org/x/crypto v0.32.0 -## explicit; go 1.20 +# golang.org/x/crypto v0.38.0 +## explicit; go 1.23.0 golang.org/x/crypto/bcrypt golang.org/x/crypto/blowfish golang.org/x/crypto/ed25519 golang.org/x/crypto/hkdf golang.org/x/crypto/pbkdf2 -# golang.org/x/sys v0.29.0 -## explicit; go 1.18 +# golang.org/x/sys v0.33.0 +## explicit; go 1.23.0 golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.28.0 -## explicit; go 1.18 +# golang.org/x/term v0.32.0 +## explicit; go 1.23.0 golang.org/x/term -# golang.org/x/text v0.21.0 -## explicit; go 1.18 +# golang.org/x/text v0.25.0 +## explicit; go 1.23.0 golang.org/x/text/cases golang.org/x/text/internal golang.org/x/text/internal/language