Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package go-sendxmpp for openSUSE:Factory checked in at 2026-07-07 21:04:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/go-sendxmpp (Old) and /work/SRC/openSUSE:Factory/.go-sendxmpp.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "go-sendxmpp" Tue Jul 7 21:04:17 2026 rev:29 rq:1364165 version:0.16.0 Changes: -------- --- /work/SRC/openSUSE:Factory/go-sendxmpp/go-sendxmpp.changes 2026-05-13 17:24:07.227193388 +0200 +++ /work/SRC/openSUSE:Factory/.go-sendxmpp.new.1982/go-sendxmpp.changes 2026-07-07 21:07:09.294491457 +0200 @@ -1,0 +2,23 @@ +Tue Jun 23 05:22:03 UTC 2026 - Michael Vetter <[email protected]> + +- Update to 0.16.0: + Added: + * Add Ox support to http-upload. + * Add Ox support for private group chats. + * Show error cause if joining MUCs failedi (requires go-xmpp >= v0.3.5). + Changed: + * Fix --ox-delete-nodes. + * Fix receiving of 1-1 messages while joined in a MUC. + * Use go-sendxmpp + a random ID as fallback MUC alias. + * Strip leading "xmpp:" from recipients. + * Strip trailing "?join" from MUC JIDs. + * Add context for timeouts in stanza handling. + * Check ID for disco items reply (requires go-xmpp >= v0.3.6). + * Reduce channel buffer size to 1 where only one item will be returned. + * Deprecate legacy PGP. + * CVE-2026-1229: The CombinedMult function produces an incorrect value (bsc#1265538) + Bump circl to 1.6.3 + * CVE-2026-39821: Failure to reject ASCII-only Punycode-encoded labels allows for validation bypass and privilege escalation (bsc#1266617) + Bump net to 0.56.0 + +------------------------------------------------------------------- Old: ---- go-sendxmpp-v0.15.8.tar.gz New: ---- go-sendxmpp-v0.16.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ go-sendxmpp.spec ++++++ --- /var/tmp/diff_new_pack.fpBMHE/_old 2026-07-07 21:07:10.626537830 +0200 +++ /var/tmp/diff_new_pack.fpBMHE/_new 2026-07-07 21:07:10.626537830 +0200 @@ -17,7 +17,7 @@ Name: go-sendxmpp -Version: 0.15.8 +Version: 0.16.0 Release: 0 Summary: A little tool to send messages to an XMPP contact or MUC License: BSD-2-Clause ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.fpBMHE/_old 2026-07-07 21:07:10.738541730 +0200 +++ /var/tmp/diff_new_pack.fpBMHE/_new 2026-07-07 21:07:10.754542287 +0200 @@ -1,6 +1,6 @@ -mtime: 1778651892 -commit: 15e740b0889cf05bf7051482dafc10882cf35eff3337bc71f45a16de950180b2 +mtime: 1783404265 +commit: 55e82266f2336aff9f274818472c120c2ec9d17035846b4f25f3d4445124edae url: https://src.opensuse.org/xmpp/go-sendxmpp -revision: 15e740b0889cf05bf7051482dafc10882cf35eff3337bc71f45a16de950180b2 +revision: 55e82266f2336aff9f274818472c120c2ec9d17035846b4f25f3d4445124edae projectscmsync: https://src.opensuse.org/xmpp/_ObsPrj.git ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-07-07 08:04:25.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ go-sendxmpp-v0.15.8.tar.gz -> go-sendxmpp-v0.16.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/CHANGELOG.md new/go-sendxmpp-v0.16.0/CHANGELOG.md --- old/go-sendxmpp-v0.15.8/CHANGELOG.md 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/CHANGELOG.md 2026-06-20 09:54:34.000000000 +0200 @@ -1,4 +1,21 @@ # Changelog +## [v0.16.0] 2026-06-20 +### Added +- Add Ox support to http-upload. +- Add Ox support for private group chats. +- Show error cause if joining MUCs failedi (requires go-xmpp >= v0.3.5). + +### Changed +- Fix `--ox-delete-nodes`. +- Fix receiving of 1-1 messages while joined in a MUC. +- Use go-sendxmpp + a random ID as fallback MUC alias. +- Strip leading "xmpp:" from recipients. +- Strip trailing "?join" from MUC JIDs. +- Add context for timeouts in stanza handling. +- Check ID for disco items reply (requires go-xmpp >= v0.3.6). +- Reduce channel buffer size to 1 where only one item will be returned. +- Deprecate legacy PGP. + ## [v0.15.8] 2026-05-12 ### Changed - Fix windows build (windows doesn't support syscalls Setgid and Setuid). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/README.md new/go-sendxmpp-v0.16.0/README.md --- old/go-sendxmpp-v0.15.8/README.md 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/README.md 2026-06-20 09:54:34.000000000 +0200 @@ -96,7 +96,7 @@ -j, --jserver=value XMPP server address. --legacy-pgp Use "Legacy PGP" encryption using the Ox key infrastructure - (experimental and not encouraged). + (deprecated and not encouraged). -l, --listen Listen for messages and print them to stdout. -m, --message=value Set file including the message. @@ -152,6 +152,8 @@ --version Show version information. ``` +For further information check the manpages [go-sendxmpp(1)](https://manpages.debian.org/unstable/go-sendxmpp/go-sendxmpp.1.en.html) and [go-sendxmpp(5)](https://manpages.debian.org/unstable/go-sendxmpp/go-sendxmpp.5.en.html). + ### examples Send a message to two recipients using a configuration file. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/const.go new/go-sendxmpp-v0.16.0/const.go --- old/go-sendxmpp-v0.15.8/const.go 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/const.go 2026-06-20 09:54:34.000000000 +0200 @@ -5,7 +5,7 @@ package main const ( - version = "0.15.8" + version = "0.16.0" // defaults defaultBufferSize = 100 defaultConfigColumnSep = 2 @@ -33,6 +33,7 @@ nsJabberClient = "jabber:client" nsJabberData = "jabber:x:data" nsJabberEncrypted = "jabber:x:encrypted" + nsMUCAdmin = "http://jabber.org/protocol/muc#admin" nsOx = "urn:xmpp:openpgp:0" nsOxPubKeys = "urn:xmpp:openpgp:0:public-keys" nsPubsub = "http://jabber.org/protocol/pubsub" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/go.mod new/go-sendxmpp-v0.16.0/go.mod --- old/go-sendxmpp-v0.15.8/go.mod 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/go.mod 2026-06-20 09:54:34.000000000 +0200 @@ -8,15 +8,15 @@ github.com/gabriel-vasile/mimetype v1.4.13 github.com/google/uuid v1.6.0 github.com/pborman/getopt/v2 v2.1.0 - github.com/xmppo/go-xmpp v0.3.4 - golang.org/x/crypto v0.51.0 - golang.org/x/net v0.54.0 + github.com/xmppo/go-xmpp v0.3.6 + golang.org/x/crypto v0.53.0 + golang.org/x/net v0.56.0 salsa.debian.org/mdosch/xmppsrv v0.3.3 ) require ( github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/cloudflare/circl v1.6.3 // indirect - golang.org/x/sys v0.44.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.0 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/go.sum new/go-sendxmpp-v0.16.0/go.sum --- old/go-sendxmpp-v0.15.8/go.sum 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/go.sum 2026-06-20 09:54:34.000000000 +0200 @@ -18,16 +18,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/xmppo/go-xmpp v0.3.4 h1:w7EBGsEzz2TBGJ2FQcOEn0CDzkuJ9CpUBIA+oJB5EuE= -github.com/xmppo/go-xmpp v0.3.4/go.mod h1:03Za3Szic72CqdDIdlZtnay9R7t2MWJsj6ZpCvxlGDs= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +github.com/xmppo/go-xmpp v0.3.6 h1:CbTrXAotlt7UsjGqpqfNIXfmoSziP73eSFqhH4IS8ng= +github.com/xmppo/go-xmpp v0.3.6/go.mod h1:YD5roZgj385upOjjG4RNNQ1kdhk5JtvA944MpMAb+jo= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= salsa.debian.org/mdosch/xmppsrv v0.3.3 h1:F8FGyw1Q1LkAs/UbIXd6Obd33q2CKWrIxxrzvuLSVuM= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/httpupload.go new/go-sendxmpp-v0.16.0/httpupload.go --- old/go-sendxmpp-v0.15.8/httpupload.go 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/httpupload.go 2026-06-20 09:54:34.000000000 +0200 @@ -6,6 +6,9 @@ import ( "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" "encoding/xml" "fmt" "log/slog" @@ -25,12 +28,14 @@ ) func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, disc chan xmpp.DiscoItems, drc chan xmpp.DiscoResult, slc chan xmpp.Slot, - jserver string, filePaths []string, timeout time.Duration, oxPrivKey *crypto.Key, keyRing *crypto.KeyRing, + jserver string, filePaths []string, timeout time.Duration, oxPrivKey *crypto.Key, keyRing *crypto.KeyRing, oxEnc bool, ) (urls []string, err error) { var ( uploadComponent string maxFileSize int64 req *http.Request + aesKey []byte + nonce []byte ) buffer := new(bytes.Buffer) @@ -116,6 +121,42 @@ slog.Info("http-upload:", "mime type", mimeType) var mimeTypeEscaped bytes.Buffer xml.Escape(&mimeTypeEscaped, []byte(mimeType)) + if oxEnc { + buf, err := readFile(filePath) + if err != nil { + err2 := file.Close() + if err2 != nil { + slog.Warn("closing file failed", "error", err2) + } + return nil, fmt.Errorf("http-upload: %w", err) + } + + aesKey = make([]byte, 32) + _, err = rand.Read(aesKey) + if err != nil { + return nil, fmt.Errorf("http-upload: ox: generating aes key: %w", err) + } + block, err := aes.NewCipher(aesKey) + if err != nil { + return nil, fmt.Errorf("http-upload: ox: generating block cipher: %w", err) + } + nonce = make([]byte, 12) + _, err = rand.Read(nonce) + if err != nil { + return nil, fmt.Errorf("http-upload: ox: generating nonce: %w", err) + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("http-upload: ox: %w", err) + } + encBytes := aesgcm.Seal(nil, nonce, buf.Bytes(), nil) + buffer = bytes.NewBuffer(encBytes) + fileSize = int64(buffer.Len()) + err = file.Close() + if err != nil { + slog.Warn("closing file failed", "error", err) + } + } if oxPrivKey != nil { buf, err := readFile(filePath) if err != nil { @@ -215,7 +256,7 @@ httpTransport.Proxy = http.ProxyURL(proxyURL) } httpClient := &http.Client{Transport: httpTransport} - if oxPrivKey != nil { + if oxEnc || oxPrivKey != nil { req, err = http.NewRequest(http.MethodPut, uploadSlot.Put.Url, buffer) } else { req, err = http.NewRequest(http.MethodPut, uploadSlot.Put.Url, file) @@ -276,11 +317,14 @@ if err != nil { fmt.Println("http-upload: error while closing http request body:", err) } - urls = append(urls, uploadSlot.Get.Url) - err = file.Close() - if err != nil { - slog.Warn("closing file failed", "error", err) + var url string + if oxEnc { + url = strings.TrimPrefix(uploadSlot.Get.Url, "https://") + url = fmt.Sprintf("aesgcm://%s#%x%x", url, nonce, aesKey) + } else { + url = uploadSlot.Get.Url } + urls = append(urls, url) } return urls, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/main.go new/go-sendxmpp-v0.16.0/main.go --- old/go-sendxmpp-v0.15.8/main.go 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/main.go 2026-06-20 09:54:34.000000000 +0200 @@ -164,7 +164,7 @@ flagRecipientsFile := getopt.StringLong("recipients", 'r', "", "Read recipients from file.") flagRetryConnect := getopt.IntLong("retry-connect", 0, 0, "Time to retry to connect after failed connection in seconds. (0 = disabled)") flagRetryConnectMax := getopt.IntLong("retry-connect-max", 0, 0, "Number of maximum retries to perform for '--retry-connect'. (0 = unlimited)") - flagLegacyPGP := getopt.BoolLong("legacy-pgp", 0, "Use \"Legacy PGP\" encryption using the Ox key infrastructure (experimental and not encouraged).") + flagLegacyPGP := getopt.BoolLong("legacy-pgp", 0, "Use \"Legacy PGP\" encryption using the Ox key infrastructure (deprecated and not encouraged).") // Parse command line flags. getopt.Parse() @@ -186,10 +186,8 @@ os.Exit(0) // Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like // groupchat, http-upload or listening. - case *flagOx && len(*flagHTTPUpload) != 0: - log.Fatal("No Ox support for http-upload available.") - case (*flagOx || *flagLegacyPGP) && *flagChatroom: - log.Fatal("No Ox or legacy PGP support for chat rooms available.") + case *flagLegacyPGP && *flagChatroom: + log.Fatal("No legacy PGP support for chat rooms available.") case len(*flagHTTPUpload) != 0 && *flagInteractive: log.Fatal("Interactive mode and http upload can't" + " be used at the same time.") @@ -214,6 +212,11 @@ slog.SetLogLoggerLevel(slog.LevelDebug) } + if *flagLegacyPGP { + slog.Warn("Legacy PGP is deprecated and will be removed in a future version, " + + "please use 'OpenPGP for XMPP' (OX) instead.") + } + // Print a warning if go-sendxmpp is run by the user root on non-windows systems. if runtime.GOOS != "windows" && !*flagNoRootWarning { // Get the current user. @@ -343,7 +346,7 @@ // Use "go-sendxmpp" if no nick is specified via config or command line flag. case *flagChatroom && alias == "" && *flagAlias == "": slog.Info("no alias defined, using go-sendxmpp") - alias = "go-sendxmpp" + alias = "go-sendxmpp." + getShortID() // Overwrite configured alias if a nick is specified via command line flag. case *flagAlias != "": alias = *flagAlias @@ -603,15 +606,25 @@ go rcvStanzas(client, ctx, iqc, msgc, prsc, disc, drc, slc) for _, r := range recipientsList { var re recipientsType - re.Jid = r - if *flagOx || *flagLegacyPGP { + re.Jid = strings.TrimPrefix(strings.ToLower(r), "xmpp:") + if *flagChatroom { + re.Jid = strings.TrimSuffix(re.Jid, "?join") + } + switch { + case (*flagOx || *flagLegacyPGP) && !*flagChatroom: slog.Info("requesting OX key for", "JID", re.Jid) - re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, r) + re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, re.Jid) if err != nil { re.OxKeyRing = nil - fmt.Printf("ox: error fetching key for %s: %v\n", r, err) + fmt.Printf("ox: error fetching key for %s: %v\n", re.Jid, err) failure = err } + case *flagOx && *flagChatroom: + re.OxKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, re.Jid) + if err != nil { + re.OxKeyRing = nil + fmt.Println("Couldn't receive keys for:", re.Jid) + } } recipients = append(recipients, re) } @@ -683,7 +696,7 @@ closeAndExit(client, err) } slog.Info("delete OX pep nodes") - err = oxDeleteNodes(validatedOwnJid, client, iqc) + err = oxDeleteNodes(validatedOwnJid, client, iqc, disc) if err != nil { cancel() closeAndExit(client, err) @@ -705,10 +718,10 @@ } var uploadMessages []string - if len(*flagHTTPUpload) != 0 && !*flagLegacyPGP { + if len(*flagHTTPUpload) != 0 && !*flagLegacyPGP && !*flagOx { slog.Info("http-upload:", "file", *flagHTTPUpload) uploadMessages, err = httpUpload(client, iqc, disc, drc, slc, - jserver, *flagHTTPUpload, timeout, nil, nil) + jserver, *flagHTTPUpload, timeout, nil, nil, false) if err != nil { cancel() closeAndExit(client, err) @@ -760,8 +773,8 @@ select { case presence = <-pc: if presence.Type == "error" { - fmt.Printf("Could not join MUC %s\n", recipient.Jid) - failure = fmt.Errorf("failed to join one or more MUCs") + fmt.Printf("Could not join MUC %s: %s\n", recipient.Jid, presence.Error) + failure = fmt.Errorf("failed to join one or more MUCs: last error: %s: %s", recipient.Jid, presence.Error) } case <-time.After(60 * time.Second): fmt.Printf("Could not join muc: didn't receive a presence from %s\n", recipient.Jid) @@ -832,7 +845,7 @@ case *flagOx: slog.Info("encrypting message with OX for", "JID", recipient.Jid) cryptMessage, err = oxEncrypt(client, oxPrivKey, - recipient.Jid, *recipient.OxKeyRing, message, *flagSubject) + recipient.Jid, *recipient.OxKeyRing, message, *flagSubject, *flagChatroom) if err != nil { fmt.Printf("ox: couldn't encrypt to %s: %v\n", recipient.Jid, err) @@ -897,7 +910,7 @@ var t time.Time if isOx { slog.Info("message is OX encrypted") - msg, t, err = oxDecrypt(v, client, iqc, user, oxPrivKey) + msg, t, err = oxDecrypt(v, client, iqc, drc, user, oxPrivKey) if err != nil { log.Println(err) continue @@ -928,8 +941,11 @@ default: bareFrom = strings.Split(v.Remote, "/")[0] } - // Print any messages if no recipients are specified - if len(recipients) == 0 { + // Print any messages if no recipients are specified and if we are not + // joined to a MUC. MUC messages are only received when joining them, + // therefore a recipient (MUC) is specified even if we did not intend + // to block receiving of other messages. + if len(recipients) == 0 || *flagChatroom { fmt.Println(t.In(tz).Format(time.RFC3339), prefix, bareFrom+":", msg) } else { @@ -981,7 +997,7 @@ break } switch { - case len(*flagHTTPUpload) != 0 && !*flagLegacyPGP: + case len(*flagHTTPUpload) != 0 && !*flagLegacyPGP && !*flagOx: for _, message = range uploadMessages { slog.Info("sending http-upload message:", "file", message, "JID", recipient.Jid) _, err = client.SendOOB(xmpp.Chat{ @@ -993,9 +1009,36 @@ recipient.Jid) } } + case len(*flagHTTPUpload) != 0 && *flagOx: + uploadMessages, err = httpUpload(client, iqc, disc, drc, slc, + jserver, *flagHTTPUpload, timeout, nil, nil, true) + if err != nil { + cancel() + closeAndExit(client, err) + } + if recipient.OxKeyRing == nil { + continue + } + for _, message = range uploadMessages { + slog.Info("sending ox encrypted http-upload message:", "file", message, "JID", recipient.Jid) + cryptMessage, err := oxEncrypt(client, oxPrivKey, + recipient.Jid, *recipient.OxKeyRing, message, *flagSubject, *flagChatroom) + if err != nil { + fmt.Printf("ox: couldn't encrypt to %s: %v\n", + recipient.Jid, err) + failure = err + continue + } + _, err = client.SendOrg(cryptMessage) + if err != nil { + cancel() + closeAndExit(client, err) + } + } + case len(*flagHTTPUpload) != 0 && *flagLegacyPGP: uploadMessages, err = httpUpload(client, iqc, disc, drc, slc, - jserver, *flagHTTPUpload, timeout, oxPrivKey, recipient.OxKeyRing) + jserver, *flagHTTPUpload, timeout, oxPrivKey, recipient.OxKeyRing, false) if err != nil { cancel() closeAndExit(client, err) @@ -1033,7 +1076,7 @@ case *flagOx: slog.Info("encrypting message with OX for", "JID", recipient.Jid) cryptMessage, err = oxEncrypt(client, oxPrivKey, - recipient.Jid, *recipient.OxKeyRing, message, *flagSubject) + recipient.Jid, *recipient.OxKeyRing, message, *flagSubject, *flagChatroom) if err != nil { fmt.Printf("ox: couldn't encrypt to %s: %v\n", recipient.Jid, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1 new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1 --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1 2026-06-20 09:54:34.000000000 +0200 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GO\-SENDXMPP" "1" "May 2026" "" +.TH "GO\-SENDXMPP" "1" "June 2026" "" .SH "NAME" \fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\. .SH "SYNOPSIS" @@ -51,7 +51,7 @@ XMPP server address\. .TP \fB\-\-legacy\-pgp\fR -"Legacy PGP" encryption using the Ox key infrastructure\. This means the recipient must have an Ox key published and use the same key for legacy PGP\. The use of legacy PGP is actually discouraged and only provided as a workaround for conversations users\. If the existing key has no ID in the form of \fBxmpp:user@example\.org\fR, it can be added by using the tool \fIsq\fR: +"Legacy PGP" encryption using the Ox key infrastructure\. This means the recipient must have an Ox key published and use the same key for legacy PGP\. This encryption method is deprecated and will be removed in a future version, please consider switching to \fIOpenPGP for XMPP\fR (OX)\. The use of legacy PGP is actually discouraged and only provided as a workaround for conversations users\. If the existing key has no ID in the form of \fBxmpp:user@example\.org\fR, it can be added by using the tool \fIsq\fR: .IP "" 4 .nf $ sq key userid add \-u xmpp:user@example\.org \-Bo newkey\.asc oldkey\.asc` @@ -61,7 +61,7 @@ This key (\fBnewkey\.asc\fR in the \fIsq\fR example) can than be imported by using \fB\-\-ox\-import\-privkey\fR\. Please note that this will store the secret key at \fB~/\.local/share/go\-sendxmpp/user_at_example_org/oxprivkey\fR\. If you don't want to use that account with go\-sendxmpp and only imported the key to upload the public key to pubsub you might consider deleting the file\. .TP \fB\-l\fR, \fB\-\-listen\fR -Listen for messages and print them to stdout\. If JIDs are specified only messages from those contacts are shown\. If no JIDs are specified all received messages will be shown\. +Listen for messages and print them to stdout\. If JIDs are specified only messages from those contacts are shown\. If no JIDs are specified or a chat room is joined all received messages will be shown\. .TP \fB\-m\fR, \fB\-\-message\fR=[\fIvalue\fR] Set file including the message\. @@ -81,7 +81,7 @@ \fB\-\-ox\fR Use "OpenPGP for XMPP" encryption (experimental)\. .br -Ox in go\-sendxmpp only supports sending encrypted 1\-1 messages\. Sending to groupchats and sending encrypted files is not supported\. There is no check whether the recipients key is trusted as there is no local keyring used\. Go\-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired\. As a user facing client a notification would be shown that a new key is available and ask the user whether to use the new key or stick to the old one\. As go\-sendxmpp is usually used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys\. +There is no check whether the recipients key is trusted as there is no local keyring used\. Go\-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired\. As a user facing client a notification would be shown that a new key is available and ask the user whether to use the new key or stick to the old one\. As go\-sendxmpp is usually used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys\. .TP \fB\-\-ox\-delete\-nodes\fR Delete existing OpenPGP nodes on the server\. @@ -165,7 +165,7 @@ .fi .IP "" 0 .P -If go\-sendxmpp is run as root it will drop the root privileges before connecting to the server\. This means \fBFAST\fR and \fBOX\fR will be disabled as go\-sendxmpp will not have the necessary rights to store the necessary data (e\.g\. FAST tokes or OX key material) after dropping the privileges\. This is just to avoid network connections as root and doesn't mean that running as root as a supported use case, in fact it is highly recommended to never run go\-sendxmpp as root\. +If go\-sendxmpp is run as root it will drop the root privileges before connecting to the server\. This means \fBFAST\fR and \fBOX\fR will be disabled as go\-sendxmpp will not have the necessary rights to store the necessary data (e\.g\. FAST tokens or OX key material) after dropping the privileges\. This is just to avoid network connections as root and doesn't mean that running as root as a supported use case, in fact it is highly recommended to never run go\-sendxmpp as root\. .SH "SHELL COMPLETIONS" .SS "ZSH" There are no shell completions yet (contributions welcome) but for zsh it is possible to automatically create completions from \fB\-\-help\fR which might work good enough\. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1.html new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.html --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1.html 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.html 2026-06-20 09:54:34.000000000 +0200 @@ -134,6 +134,8 @@ <dd>XMPP server address.</dd> <dt><code>--legacy-pgp</code></dt> <dd>"Legacy PGP" encryption using the Ox key infrastructure. This means the recipient must have an Ox key published and use the same key for legacy PGP. +This encryption method is deprecated and will be removed in a future version, please consider switching to +<em>OpenPGP for XMPP</em> (OX). The use of legacy PGP is actually discouraged and only provided as a workaround for conversations users. If the existing key has no ID in the form of <code>xmpp:[email protected]</code>, it can be added by using the tool <em>sq</em>: @@ -149,7 +151,7 @@ <code>-l</code>, <code>--listen</code> </dt> <dd>Listen for messages and print them to stdout. If JIDs are specified only messages from those contacts -are shown. If no JIDs are specified all received messages will be shown.</dd> +are shown. If no JIDs are specified or a chat room is joined all received messages will be shown.</dd> <dt> <code>-m</code>, <code>--message</code>=[<var>value</var>]</dt> <dd>Set file including the message.</dd> @@ -167,8 +169,6 @@ <dd>URL to send a file as out of band data.</dd> <dt><code>--ox</code></dt> <dd>Use "OpenPGP for XMPP" encryption (experimental). <br> -Ox in go-sendxmpp only supports sending encrypted 1-1 messages. Sending to groupchats and -sending encrypted files is not supported. There is no check whether the recipients key is trusted as there is no local keyring used. Go-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired. As a user facing client a notification would be shown that a new key is available and @@ -280,7 +280,7 @@ <p>If go-sendxmpp is run as root it will drop the root privileges before connecting to the server. This means <strong>FAST</strong> and <strong>OX</strong> will be disabled as go-sendxmpp will not have the necessary rights to store -the necessary data (e.g. FAST tokes or OX key material) after dropping the privileges. +the necessary data (e.g. FAST tokens or OX key material) after dropping the privileges. This is just to avoid network connections as root and doesn't mean that running as root as a supported use case, in fact it is highly recommended to never run go-sendxmpp as root.</p> @@ -327,7 +327,7 @@ <ol class='man-decor man-foot man foot'> <li class='tl'></li> - <li class='tc'>May 2026</li> + <li class='tc'>June 2026</li> <li class='tr'>go-sendxmpp(1)</li> </ol> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1.ronn new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.ronn --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.1.ronn 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.ronn 2026-06-20 09:54:34.000000000 +0200 @@ -59,6 +59,8 @@ * `--legacy-pgp`: "Legacy PGP" encryption using the Ox key infrastructure. This means the recipient must have an Ox key published and use the same key for legacy PGP. +This encryption method is deprecated and will be removed in a future version, please consider switching to +*OpenPGP for XMPP* (OX). The use of legacy PGP is actually discouraged and only provided as a workaround for conversations users. If the existing key has no ID in the form of `xmpp:[email protected]`, it can be added by using the tool *sq*: @@ -73,7 +75,7 @@ * `-l`, `--listen`: Listen for messages and print them to stdout. If JIDs are specified only messages from those contacts -are shown. If no JIDs are specified all received messages will be shown. +are shown. If no JIDs are specified or a chat room is joined all received messages will be shown. * `-m`, `--message`=[<value>]: Set file including the message. @@ -92,8 +94,6 @@ * `--ox`: Use "OpenPGP for XMPP" encryption (experimental). -Ox in go-sendxmpp only supports sending encrypted 1-1 messages. Sending to groupchats and -sending encrypted files is not supported. There is no check whether the recipients key is trusted as there is no local keyring used. Go-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired. As a user facing client a notification would be shown that a new key is available and @@ -212,7 +212,7 @@ If go-sendxmpp is run as root it will drop the root privileges before connecting to the server. This means **FAST** and **OX** will be disabled as go-sendxmpp will not have the necessary rights to store -the necessary data (e.g. FAST tokes or OX key material) after dropping the privileges. +the necessary data (e.g. FAST tokens or OX key material) after dropping the privileges. This is just to avoid network connections as root and doesn't mean that running as root as a supported use case, in fact it is highly recommended to never run go-sendxmpp as root. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5 new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5 --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5 2026-06-20 09:54:34.000000000 +0200 @@ -35,7 +35,7 @@ The client\-to\-server port of the XMPP server, e\.g\. \fB5222\fR\. Should not be necessary if the server is set up properly\. .TP \fBpassword\fR -Your accounts password, e\.g\. \fBhunter2\fR (please, do not use \fBhunter2\fR)\. Use \fB"\fR if your password contains spaces, e\.g\. \fB"hunter 2"\fR (please, also do nut use \fBhunter 2\fR)\. +Your accounts password, e\.g\. \fBhunter2\fR (please, do not use \fBhunter2\fR)\. Use \fB"\fR if your password contains spaces, e\.g\. \fB"hunter 2"\fR (please, also do not use \fBhunter 2\fR)\. .TP \fBeval_password\fR A command to retrieve the password from a password manager, e\.g\. \fBpass xmpp/user@example\.com\fR\. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5.html new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.html --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5.html 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.html 2026-06-20 09:54:34.000000000 +0200 @@ -113,7 +113,7 @@ Should not be necessary if the server is set up properly.</dd> <dt><code>password</code></dt> <dd>Your accounts password, e.g. <strong>hunter2</strong> (please, do not use <strong>hunter2</strong>). -Use <code>"</code> if your password contains spaces, e.g. <strong>"hunter 2"</strong> (please, also do nut use <strong>hunter 2</strong>).</dd> +Use <code>"</code> if your password contains spaces, e.g. <strong>"hunter 2"</strong> (please, also do not use <strong>hunter 2</strong>).</dd> <dt><code>eval_password</code></dt> <dd>A command to retrieve the password from a password manager, e.g. <strong>pass xmpp/[email protected]</strong>.</dd> <dt><code>alias</code></dt> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5.ronn new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.ronn --- old/go-sendxmpp-v0.15.8/man/go-sendxmpp.5.ronn 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.ronn 2026-06-20 09:54:34.000000000 +0200 @@ -40,7 +40,7 @@ * `password`: Your accounts password, e.g. **hunter2** (please, do not use **hunter2**). -Use `"` if your password contains spaces, e.g. **"hunter 2"** (please, also do nut use **hunter 2**). +Use `"` if your password contains spaces, e.g. **"hunter 2"** (please, also do not use **hunter 2**). * `eval_password`: A command to retrieve the password from a password manager, e.g. **pass xmpp/[email protected]**. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/ox.go new/go-sendxmpp-v0.16.0/ox.go --- old/go-sendxmpp-v0.15.8/ox.go 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/ox.go 2026-06-20 09:54:34.000000000 +0200 @@ -9,7 +9,6 @@ "encoding/base64" "fmt" "io" - "log" "log/slog" "os" "runtime" @@ -21,40 +20,14 @@ "github.com/xmppo/go-xmpp" // BSD-3-Clause ) -func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error { - nodeListRequest := etree.NewDocument() - nodeListRequest.WriteSettings.AttrSingleQuote = true - query := nodeListRequest.CreateElement("query") - query.CreateAttr("xmlns", nsDiscoItems) - nlr, err := nodeListRequest.WriteToString() +func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ, disc chan xmpp.DiscoItems) error { + discoItems, err := getDiscoItems(client, disc, jid) if err != nil { return fmt.Errorf("ox: delete nodes: failed to create node list request %w", err) } - slog.Info("ox: sending IQ to list pep nodes") - iqReply, err := sendIQ(client, iqc, jid, "get", nlr) - if err != nil { - return fmt.Errorf("ox: delete nodes: failure with node list request iq: %w", err) - } slog.Info("ox: parsing reply to IQ") - nodeListReply := etree.NewDocument() - err = nodeListReply.ReadFromBytes(iqReply.Query) - if err != nil { - return fmt.Errorf("ox: delete nodes: failed to read node list reply iq: %w", err) - } - query = nodeListReply.SelectElement("query") - if query == nil { - return fmt.Errorf("ox: error parsing iq reply") - } - items := query.SelectElements("item") - if items == nil { - return fmt.Errorf("ox: error parsing iq reply") - } - for _, item := range items { - node := item.SelectAttr("node") - if node == nil { - continue - } - if !strings.Contains(node.Value, nsOx) { + for _, item := range discoItems.Items { + if !strings.Contains(item.Node, nsOx) { continue } deleteNodeRequest := etree.NewDocument() @@ -62,12 +35,12 @@ pubsub := deleteNodeRequest.CreateElement("pubsub") pubsub.CreateAttr("xmlns", nsPubsubOwner) del := pubsub.CreateElement("delete") - del.CreateAttr("node", node.Value) + del.CreateAttr("node", item.Node) dnr, err := deleteNodeRequest.WriteToString() if err != nil { continue } - slog.Info("ox: sending IQ to list pep nodes", "node", node.Value) + slog.Info("ox: sending IQ to list pep nodes", "node", item.Node) _, err = sendIQ(client, iqc, jid, "set", dnr) if err != nil { continue @@ -76,8 +49,11 @@ return nil } -func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string, oxPrivKey *crypto.Key) (string, time.Time, error) { +func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult, + user string, oxPrivKey *crypto.Key, +) (string, time.Time, error) { var cryptMsgByte []byte + var senderKeyRing *crypto.KeyRing var err error sender := strings.Split(m.Remote, "/")[0] for _, r := range m.OtherElem { @@ -95,9 +71,16 @@ return strError, time.Now(), err } slog.Info("ox: decrypting: getting senders key", "sender", sender) - senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender) - if err != nil { - return strError, time.Now(), err + if m.Type == "groupchat" { + senderKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, sender) + if err != nil { + return strError, time.Now(), err + } + } else { + senderKeyRing, err = oxGetPublicKeyRing(client, iqc, sender) + if err != nil { + return strError, time.Now(), err + } } slog.Info("ox: decrypting message") pgpDecrypt, err := crypto.PGP().Decryption(). @@ -137,7 +120,7 @@ jidAttrib := strings.Split(jid.Value, "/")[0] slog.Info("ox: decrypting: comparing jid attribute with local user", "jid-attribute", jidAttrib, "user", user) - if jidAttrib != user { + if jidAttrib != user && m.Type != "groupchat" { return strError, time.Now(), fmt.Errorf("ox: encrypted for wrong user") } slog.Info("ox: decrypting: checking time element") @@ -249,7 +232,7 @@ err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { - log.Fatal(err) + return fmt.Errorf("ox: import privkey: failed to store private key: %w", err) } slog.Info("ox: importing private key: requesting public key ring") pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid) @@ -451,12 +434,12 @@ func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) { dataFile, err := oxGetPrivKeyLoc(jid) if err != nil { - log.Fatal(err) + return nil, fmt.Errorf("ox: get privkey: failed to get private key location: %w", err) } slog.Info("ox: getting private key:", "file", dataFile) keyBuffer, err := readFile(dataFile) if err != nil { - log.Fatal(err) + return nil, fmt.Errorf("ox: get privkey: failed to read private key: %w", err) } keyString := keyBuffer.String() slog.Info("ox: getting private key: base64 decoding key") @@ -473,7 +456,7 @@ slog.Info("ox: getting private key: unlocking key", "passphrase", "REDACTED") key, err = key.Unlock([]byte(passphrase)) if err != nil { - log.Fatal("ox: couldn't unlock private key") + return nil, fmt.Errorf("ox: failed to unlock private key: %w", err) } } isLocked, err := key.IsLocked() @@ -481,7 +464,7 @@ return nil, fmt.Errorf("ox: get privkey: failed to check whether private key is locked: %w", err) } if isLocked { - log.Fatal("ox: private key is locked") + return nil, fmt.Errorf("ox: get privkey: failed to unlock private key: %w", err) } slog.Info("ox: getting private checking if key is expired") if key.IsExpired(time.Now().Unix()) { @@ -549,7 +532,7 @@ err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { - log.Fatal(err) + return fmt.Errorf("ox: generate privkey: failed store private key: %w", err) } slog.Info("ox: generating privkey: deriving public key") decodedPubKey, err := key.GetPublicKey() @@ -652,11 +635,11 @@ oxPubKeyListReqPsItems.CreateAttr("max_items", "1") opkl, err := oxPubKeyListReq.WriteToString() if err != nil { - log.Fatal(err) + return nil, fmt.Errorf("ox: %w", err) } oxPublicKeyList, err := sendIQ(client, iqc, recipient, "get", opkl) if err != nil { - log.Fatal(err) + return nil, fmt.Errorf("ox: %w", err) } if oxPublicKeyList.Type != strResult { return nil, fmt.Errorf("ox: error while requesting public openpgp keys for %s", @@ -776,12 +759,12 @@ return pubKeyRing, nil } -func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing crypto.KeyRing, message string, subject string) (string, error) { +func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing crypto.KeyRing, message string, subject string, groupchat bool) (string, error) { if message == "" { return "", nil } ownJid := strings.Split(client.JID(), "/")[0] - if recipient != ownJid { + if recipient != ownJid && !groupchat { opk, err := oxPrivKey.GetPublicKey() if err == nil { ownKey, _ := crypto.NewKey(opk) @@ -826,6 +809,9 @@ omMessage := om.CreateElement("message") omMessage.CreateAttr("to", recipient) omMessage.CreateAttr("id", id) + if groupchat { + omMessage.CreateAttr("type", "groupchat") + } omMessageStore := omMessage.CreateElement("store") omMessageStore.CreateAttr("xmlns", nsHints) omMessageEme := omMessage.CreateElement("encryption") @@ -846,3 +832,74 @@ return oms, nil } + +func oxGetPublicKeyRingChatroom(client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult, + chatroom string, +) (*crypto.KeyRing, error) { + keyRing, err := crypto.NewKeyRing(nil) + if err != nil { + return nil, err + } + discoResult, err := getDiscoInfo(client, drc, chatroom) + if err != nil { + return nil, fmt.Errorf("ox: %w", err) + } + nonAnon := false + for _, feature := range discoResult.Features { + if feature == "muc_nonanonymous" { + nonAnon = true + } + } + if !nonAnon { + return nil, fmt.Errorf("ox: muc is not nonanonymous") + } + affiliations := [3]string{"admin", "owner", "member"} + for _, affiliation := range affiliations { + memberlistRequest := etree.NewDocument() + query := memberlistRequest.CreateElement("query") + query.CreateAttr("xmlns", nsMUCAdmin) + item := query.CreateElement("item") + item.CreateAttr("affiliation", affiliation) + mlr, err := memberlistRequest.WriteToString() + if err != nil { + return nil, err + } + iqReply, err := sendIQ(client, iqc, chatroom, "get", mlr) + if err != nil { + return nil, err + } + memberlist := etree.NewDocument() + err = memberlist.ReadFromBytes(iqReply.Query) + if err != nil { + return nil, err + } + query = memberlist.SelectElement("query") + if query == nil { + return nil, fmt.Errorf("ox: error during disco info query") + } + items := query.SelectElements("item") + if items == nil { + continue + } + for _, item := range items { + jid := item.SelectAttr("jid") + if jid == nil { + continue + } + memberKeyRing, err := oxGetPublicKeyRing(client, iqc, jid.Value) + if err != nil { + continue + } + for _, key := range memberKeyRing.GetKeys() { + err = keyRing.AddKey(key) + if err != nil { + continue + } + } + } + } + if keyRing == nil { + return nil, fmt.Errorf("ox: didn't receive public keys for muc members") + } + return keyRing, nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.15.8/stanzahandling.go new/go-sendxmpp-v0.16.0/stanzahandling.go --- old/go-sendxmpp-v0.15.8/stanzahandling.go 2026-05-12 19:59:13.000000000 +0200 +++ new/go-sendxmpp-v0.16.0/stanzahandling.go 2026-06-20 09:54:34.000000000 +0200 @@ -21,8 +21,10 @@ func getDiscoInfo(client *xmpp.Client, drc chan xmpp.DiscoResult, target string) (dr xmpp.DiscoResult, err error) { id := getID() - c := make(chan xmpp.DiscoResult, defaultBufferSize) - go getDr(c, drc) + c := make(chan xmpp.DiscoResult, 1) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + go getDr(ctx, c, drc) slog.Info("stanzahandling: requesting disco info") _, err = client.RawInformation(client.JID(), target, id, "get", fmt.Sprintf("<query xmlns='%s'/>", nsDiscoInfo)) @@ -32,25 +34,31 @@ select { case dr = <-c: slog.Info("stanzahandling: requesting disco info: received reply") - case <-time.After(60 * time.Second): + case <-ctx.Done(): slog.Info("stanzahandling: requesting disco info: timeout") return dr, fmt.Errorf("get disco info: %s didn't reply to disco info request", target) } return dr, err } -func getDr(c chan xmpp.DiscoResult, drc chan xmpp.DiscoResult) { +func getDr(ctx context.Context, c chan xmpp.DiscoResult, drc chan xmpp.DiscoResult) { for { - dr := <-drc - c <- dr - return + select { + case <-ctx.Done(): + return + case dr := <-drc: + c <- dr + return + } } } func getSlot(client *xmpp.Client, slc chan xmpp.Slot, target, request string) (sl xmpp.Slot, err error) { id := getID() - c := make(chan xmpp.Slot, defaultBufferSize) - go getSl(c, slc) + c := make(chan xmpp.Slot, 1) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + go getSl(ctx, c, slc) slog.Info("stanzahandling: requesting upload slot") _, err = client.RawInformation(client.JID(), target, id, "get", request) if err != nil { @@ -59,25 +67,31 @@ select { case sl = <-c: slog.Info("stanzahandling: requesting upload slot: received reply") - case <-time.After(60 * time.Second): + case <-ctx.Done(): slog.Info("stanzahandling: requesting upload slot: timeout") return sl, fmt.Errorf("get upload slot: %s didn't reply to upload slot request", target) } return sl, err } -func getSl(c chan xmpp.Slot, slc chan xmpp.Slot) { +func getSl(ctx context.Context, c chan xmpp.Slot, slc chan xmpp.Slot) { for { - sl := <-slc - c <- sl - return + select { + case sl := <-slc: + c <- sl + return + case <-ctx.Done(): + return + } } } func getDiscoItems(client *xmpp.Client, disc chan xmpp.DiscoItems, target string) (dis xmpp.DiscoItems, err error) { id := getID() - c := make(chan xmpp.DiscoItems, defaultBufferSize) - go getDis(target, c, disc) + c := make(chan xmpp.DiscoItems, 1) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + go getDis(ctx, target, id, c, disc) slog.Info("stanzahandling: requesting disco items") _, err = client.RawInformation(client.JID(), target, id, "get", fmt.Sprintf("<query xmlns='%s'/>", nsDiscoItems)) @@ -87,19 +101,22 @@ select { case dis = <-c: slog.Info("stanzahandling: requesting disco items: received reply") - case <-time.After(60 * time.Second): + case <-ctx.Done(): slog.Info("stanzahandling: requesting disco items: timeout") return dis, fmt.Errorf("get disco items: %s didn't reply to disco items request", target) } return dis, err } -func getDis(jid string, c chan xmpp.DiscoItems, disc chan xmpp.DiscoItems) { +func getDis(ctx context.Context, jid string, id string, c chan xmpp.DiscoItems, disc chan xmpp.DiscoItems) { for { - dis := <-disc - if dis.Jid == jid { - c <- dis - return + select { + case dis := <-disc: + if dis.Jid == jid && id == dis.ID { + c <- dis + return + } + case <-ctx.Done(): } } } @@ -107,8 +124,10 @@ func sendIQ(client *xmpp.Client, iqc chan xmpp.IQ, target string, iQtype string, content string) (xmpp.IQ, error) { var iq xmpp.IQ id := getID() - c := make(chan xmpp.IQ, defaultBufferSize) - go getIQ(id, c, iqc) + c := make(chan xmpp.IQ, 1) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + go getIQ(ctx, id, c, iqc) _, err := client.RawInformation(client.JID(), target, id, iQtype, content) if err != nil { @@ -116,17 +135,21 @@ } select { case iq = <-c: - case <-time.After(60 * time.Second): + case <-ctx.Done(): return iq, fmt.Errorf("send iq: server didn't reply to IQ: %s", content) } return iq, nil } -func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) { +func getIQ(ctx context.Context, id string, c chan xmpp.IQ, iqc chan xmpp.IQ) { for { - iq := <-iqc - if iq.ID == id { - c <- iq + select { + case iq := <-iqc: + if iq.ID == id { + c <- iq + return + } + case <-ctx.Done(): return } } ++++++ vendor.tar.zst ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/xmppo/go-xmpp/const.go new/vendor/github.com/xmppo/go-xmpp/const.go --- old/vendor/github.com/xmppo/go-xmpp/const.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/github.com/xmppo/go-xmpp/const.go 2026-06-20 09:54:34.000000000 +0200 @@ -2,7 +2,7 @@ const ( // Version is current go-xmpp software version. - Version = "0.3.4" + Version = "0.3.6" // HT_SHA_256_ENDP used in XEP-0484: Fast Authentication Streamlining Tokens, https://xmpp.org/extensions/xep-0484.html HT_SHA_256_ENDP = "HT-SHA-256-ENDP" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/xmppo/go-xmpp/xmpp.go new/vendor/github.com/xmppo/go-xmpp/xmpp.go --- old/vendor/github.com/xmppo/go-xmpp/xmpp.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/github.com/xmppo/go-xmpp/xmpp.go 2026-06-20 09:54:34.000000000 +0200 @@ -932,7 +932,7 @@ } switch v := val.(type) { case *streamFeatures: - // Update the max. stanza size limit if provided again as some servers might apply greater limit after auth. + // Update the max. stanza size and idle seconds limit if provided again as some servers might apply greater limit after auth. if v.Limits.MaxBytes != "" { lim, err := strconv.Atoi(v.Limits.MaxBytes) // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. @@ -940,6 +940,13 @@ c.LimitMaxBytes = lim } } + if v.Limits.IdleSeconds != "" { + lim, err := strconv.Atoi(v.Limits.IdleSeconds) + // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. + if err == nil { + c.LimitIdleSeconds = lim + } + } } if o.Session { // if server support session, open it @@ -1227,7 +1234,7 @@ } switch v := val.(type) { case *streamFeatures: - // Update the max. stanza size limit if provided again as some servers might apply greater limit after auth. + // Update the max. stanza size and idle limits if provided again as some servers might apply greater limit after auth. if v.Limits.MaxBytes != "" { lim, err := strconv.Atoi(v.Limits.MaxBytes) // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. @@ -1235,6 +1242,13 @@ c.LimitMaxBytes = lim } } + if v.Limits.IdleSeconds != "" { + lim, err := strconv.Atoi(v.Limits.IdleSeconds) + // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. + if err == nil { + c.LimitIdleSeconds = lim + } + } } case *saslSuccess: if strings.HasPrefix(mechanism, "SCRAM-SHA") { @@ -1281,7 +1295,7 @@ if f, err = c.startStream(o, domain); err != nil { return err } - // Update the max. stanza size limit if provided again as some servers might apply greater limit after auth. + // Update the max. stanza size and idle seconds limits if provided again as some servers might apply greater limit after auth. if f.Limits.MaxBytes != "" { lim, err := strconv.Atoi(f.Limits.MaxBytes) // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. @@ -1289,6 +1303,13 @@ c.LimitMaxBytes = lim } } + if f.Limits.IdleSeconds != "" { + lim, err := strconv.Atoi(f.Limits.IdleSeconds) + // Only overwrite if it can be parsed successfully, otherwise keep the previous limit. + if err == nil { + c.LimitIdleSeconds = lim + } + } } // Make the servers time limit after which it might consider the stream idle available. if f.Limits.IdleSeconds != "" { @@ -1525,6 +1546,7 @@ Affiliation string Role string JID string + Error string } type IQ struct { @@ -1615,6 +1637,7 @@ v.X.Item.Affiliation, v.X.Item.Role, v.X.Item.Jid, + v.Error.Any.Local, }, nil case *clientIQ: switch { @@ -1630,7 +1653,7 @@ var osName string if c.Options.ReportSoftwareOS { - osName = (strings.SplitN(runtime.GOOS, "/", 2))[0] + osName = strings.SplitN(runtime.GOOS, "/", 2)[0] } id, err := c.IqVersionResponse( @@ -1710,6 +1733,7 @@ } return DiscoItems{ + ID: v.ID, Jid: v.From, Items: clientDiscoItemsToReturn(itemsQuery.Items), }, nil @@ -2289,7 +2313,11 @@ Show string `xml:"show"` // away, chat, dnd, xa Status string `xml:"status"` // sb []clientText Priority string `xml:"priority,attr"` - Error *clientError + Error struct { + By string `xml:"by,attr"` + Type string `xml:"type,attr"` + Any xml.Name `xml:",any"` + } `xml:"error"` } type clientIQ struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/xmppo/go-xmpp/xmpp_disco.go new/vendor/github.com/xmppo/go-xmpp/xmpp_disco.go --- old/vendor/github.com/xmppo/go-xmpp/xmpp_disco.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/github.com/xmppo/go-xmpp/xmpp_disco.go 2026-06-20 09:54:34.000000000 +0200 @@ -68,6 +68,7 @@ } type DiscoItems struct { + ID string Jid string Items []DiscoItem } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/crypto/blake2b/go125.go new/vendor/golang.org/x/crypto/blake2b/go125.go --- old/vendor/golang.org/x/crypto/blake2b/go125.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/golang.org/x/crypto/blake2b/go125.go 1970-01-01 01:00:00.000000000 +0100 @@ -1,11 +0,0 @@ -// 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.25 - -package blake2b - -import "hash" - -var _ hash.XOF = (*xof)(nil) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/cpu/cpu.go new/vendor/golang.org/x/sys/cpu/cpu.go --- old/vendor/golang.org/x/sys/cpu/cpu.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/golang.org/x/sys/cpu/cpu.go 2026-06-20 09:54:34.000000000 +0200 @@ -152,13 +152,17 @@ // The booleans in Loong64 contain the correspondingly named cpu feature bit. // The struct is padded to avoid false sharing. var Loong64 struct { - _ CacheLinePad - HasLSX bool // support 128-bit vector extension - HasLASX bool // support 256-bit vector extension - HasCRC32 bool // support CRC instruction - HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction - HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} instruction - _ CacheLinePad + _ CacheLinePad + HasLSX bool // support 128-bit vector extension + HasLASX bool // support 256-bit vector extension + HasCRC32 bool // support CRC instruction + HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} + HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction + HasLLACQ_SCREL bool // support LLACQ.{W/D}, SCREL.{W/D} instruction + HasSCQ bool // support SC.Q instruction + HasDBAR_HINTS bool // supports finer-grained DBAR hints + + _ CacheLinePad } // MIPS64X contains the supported CPU features of the current mips64/mips64le @@ -232,6 +236,7 @@ HasZba bool // Address generation instructions extension HasZbb bool // Basic bit-manipulation extension HasZbs bool // Single-bit instructions extension + HasZbc bool // Carryless multiplication extension HasZvbb bool // Vector Basic Bit-manipulation HasZvbc bool // Vector Carryless Multiplication HasZvkb bool // Vector Cryptography Bit-manipulation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go new/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go --- old/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go 2026-06-20 09:54:34.000000000 +0200 @@ -58,6 +58,7 @@ riscv_HWPROBE_EXT_ZBA = 0x8 riscv_HWPROBE_EXT_ZBB = 0x10 riscv_HWPROBE_EXT_ZBS = 0x20 + riscv_HWPROBE_EXT_ZBC = 0x80 riscv_HWPROBE_EXT_ZVBB = 0x20000 riscv_HWPROBE_EXT_ZVBC = 0x40000 riscv_HWPROBE_EXT_ZVKB = 0x80000 @@ -108,6 +109,7 @@ RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA) RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB) RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS) + RISCV64.HasZbc = isSet(v, riscv_HWPROBE_EXT_ZBC) RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB) RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC) RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/cpu/cpu_loong64.go new/vendor/golang.org/x/sys/cpu/cpu_loong64.go --- old/vendor/golang.org/x/sys/cpu/cpu_loong64.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/golang.org/x/sys/cpu/cpu_loong64.go 2026-06-20 09:54:34.000000000 +0200 @@ -15,8 +15,13 @@ cpucfg1_CRC32 = 1 << 25 // CPUCFG2 bits - cpucfg2_LAM_BH = 1 << 27 - cpucfg2_LAMCAS = 1 << 28 + cpucfg2_LAM_BH = 1 << 27 + cpucfg2_LAMCAS = 1 << 28 + cpucfg2_LLACQ_SCREL = 1 << 29 + cpucfg2_SCQ = 1 << 30 + + // CPUCFG3 bits + cpucfg3_DBAR_HINTS = 1 << 17 ) func initOptions() { @@ -26,6 +31,9 @@ {Name: "crc32", Feature: &Loong64.HasCRC32}, {Name: "lam_bh", Feature: &Loong64.HasLAM_BH}, {Name: "lamcas", Feature: &Loong64.HasLAMCAS}, + {Name: "llacq_screl", Feature: &Loong64.HasLLACQ_SCREL}, + {Name: "scq", Feature: &Loong64.HasSCQ}, + {Name: "dbar_hints", Feature: &Loong64.HasDBAR_HINTS}, } // The CPUCFG data on Loong64 only reflects the hardware capabilities, @@ -37,10 +45,14 @@ // through CPUCFG cfg1 := get_cpucfg(1) cfg2 := get_cpucfg(2) + cfg3 := get_cpucfg(3) Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32) Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS) Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH) + Loong64.HasLLACQ_SCREL = cfgIsSet(cfg2, cpucfg2_LLACQ_SCREL) + Loong64.HasSCQ = cfgIsSet(cfg2, cpucfg2_SCQ) + Loong64.HasDBAR_HINTS = cfgIsSet(cfg3, cpucfg3_DBAR_HINTS) } func get_cpucfg(reg uint32) uint32 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/cpu/cpu_riscv64.go new/vendor/golang.org/x/sys/cpu/cpu_riscv64.go --- old/vendor/golang.org/x/sys/cpu/cpu_riscv64.go 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/golang.org/x/sys/cpu/cpu_riscv64.go 2026-06-20 09:54:34.000000000 +0200 @@ -16,6 +16,7 @@ {Name: "zba", Feature: &RISCV64.HasZba}, {Name: "zbb", Feature: &RISCV64.HasZbb}, {Name: "zbs", Feature: &RISCV64.HasZbs}, + {Name: "zbc", Feature: &RISCV64.HasZbc}, // RISC-V Cryptography Extensions {Name: "zvbb", Feature: &RISCV64.HasZvbb}, {Name: "zvbc", Feature: &RISCV64.HasZvbc}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt --- old/vendor/modules.txt 2026-05-12 19:59:13.000000000 +0200 +++ new/vendor/modules.txt 2026-06-20 09:54:34.000000000 +0200 @@ -63,10 +63,10 @@ # github.com/pborman/getopt/v2 v2.1.0 ## explicit; go 1.13 github.com/pborman/getopt/v2 -# github.com/xmppo/go-xmpp v0.3.4 +# github.com/xmppo/go-xmpp v0.3.6 ## explicit; go 1.25.0 github.com/xmppo/go-xmpp -# golang.org/x/crypto v0.51.0 +# golang.org/x/crypto v0.53.0 ## explicit; go 1.25.0 golang.org/x/crypto/argon2 golang.org/x/crypto/blake2b @@ -77,15 +77,15 @@ golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt golang.org/x/crypto/sha3 -# golang.org/x/net v0.54.0 +# golang.org/x/net v0.56.0 ## explicit; go 1.25.0 golang.org/x/net/idna golang.org/x/net/internal/socks golang.org/x/net/proxy -# golang.org/x/sys v0.44.0 +# golang.org/x/sys v0.46.0 ## explicit; go 1.25.0 golang.org/x/sys/cpu -# golang.org/x/text v0.37.0 +# golang.org/x/text v0.38.0 ## explicit; go 1.25.0 golang.org/x/text/secure/bidirule golang.org/x/text/transform
