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-28 17:56:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/go-sendxmpp (Old) and /work/SRC/openSUSE:Factory/.go-sendxmpp.new.2004 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "go-sendxmpp" Tue Jul 28 17:56:59 2026 rev:30 rq:1367927 version:0.17.0 Changes: -------- --- /work/SRC/openSUSE:Factory/go-sendxmpp/go-sendxmpp.changes 2026-07-07 21:07:09.294491457 +0200 +++ /work/SRC/openSUSE:Factory/.go-sendxmpp.new.2004/go-sendxmpp.changes 2026-07-28 18:03:47.884894149 +0200 @@ -1,0 +2,19 @@ +Mon Jul 27 07:01:18 UTC 2026 - Michael Vetter <[email protected]> + +- Update to 0.17.0: + Added: + * Add --ox-transfer-private-key to transfer the encrypted private key to PEP + to transfer it to other devices (requires go-xmpp >= v0.3.7). + * Add --ox-receive-private-key to receive the encrypted private key from PEP. + * Add config option no_root_warning. + * Add config option no_legacy_pgp_warning. + * Also disable legacy PGP when running as root (Ox was already disabled). + * Disable pinning for not using PLAIN when running as root. + * Add config option ox_trust_mode with settings blind and tofu. + Changed: + * Due to new tofu trust mode for Ox, only one public key per contact is accepted for easier ID handling. + * Ox: Check that fingerprint of received key equals the advertised one. + * CVE-2026-39821: Failure to reject ASCII-only Punycode-encoded labels allows for validation bypass and privilege escalation (bsc#1266617) + Bump net to 0.57.0 + +------------------------------------------------------------------- @@ -21,2 +39,0 @@ - * 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.16.0.tar.gz New: ---- go-sendxmpp-v0.17.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ go-sendxmpp.spec ++++++ --- /var/tmp/diff_new_pack.maIKK8/_old 2026-07-28 18:03:51.313014099 +0200 +++ /var/tmp/diff_new_pack.maIKK8/_new 2026-07-28 18:03:51.313014099 +0200 @@ -17,7 +17,7 @@ Name: go-sendxmpp -Version: 0.16.0 +Version: 0.17.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.maIKK8/_old 2026-07-28 18:03:51.373016199 +0200 +++ /var/tmp/diff_new_pack.maIKK8/_new 2026-07-28 18:03:51.385016618 +0200 @@ -1,6 +1,6 @@ -mtime: 1783404265 -commit: 55e82266f2336aff9f274818472c120c2ec9d17035846b4f25f3d4445124edae +mtime: 1785135948 +commit: 2fe2da162f0cf1f4ccce5fbf6f2423185d09d733a6b5316f26b4bb82833684e0 url: https://src.opensuse.org/xmpp/go-sendxmpp -revision: 55e82266f2336aff9f274818472c120c2ec9d17035846b4f25f3d4445124edae +revision: 2fe2da162f0cf1f4ccce5fbf6f2423185d09d733a6b5316f26b4bb82833684e0 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-27 09:05:48.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ go-sendxmpp-v0.16.0.tar.gz -> go-sendxmpp-v0.17.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/.gitlab-ci.yml new/go-sendxmpp-v0.17.0/.gitlab-ci.yml --- old/go-sendxmpp-v0.16.0/.gitlab-ci.yml 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/.gitlab-ci.yml 2026-07-24 21:50:15.000000000 +0200 @@ -42,6 +42,7 @@ - codespell *.go *.md man/*.ronn - gofumpt -d . - gosec ./... + - sh -c "! grep -r println *.go" # Taken from https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20404/diffs#diff-content-8071267ea32ba69f24a8bd50bcbddf972c295ce3 # Use default .golangci.yml file from the image if one is not present in the project root. #- '[ -e .golangci.yml ] || cp /golangci/.golangci.yml .' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/CHANGELOG.md new/go-sendxmpp-v0.17.0/CHANGELOG.md --- old/go-sendxmpp-v0.16.0/CHANGELOG.md 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/CHANGELOG.md 2026-07-24 21:50:15.000000000 +0200 @@ -1,4 +1,18 @@ # Changelog +## [v0.17.0] 2026-07-24 +### Added +- Add `--ox-transfer-private-key` to transfer the encrypted private key to PEP to transfer it to other devices (requires go-xmpp >= v0.3.7). +- Add `--ox-receive-private-key` to receive the encrypted private key from PEP. +- Add config option `no_root_warning`. +- Add config option `no_legacy_pgp_warning`. +- Also disable legacy PGP when running as root (Ox was already disabled). +- Disable pinning for not using PLAIN when running as root. +- Add config option `ox_trust_mode` with settings `blind` and `tofu`. + +### Changed +- Due to new `tofu` trust mode for Ox, only one public key per contact is accepted for easier ID handling. +- Ox: Check that fingerprint of received key equals the advertised one. + ## [v0.16.0] 2026-06-20 ### Added - Add Ox support to http-upload. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/README.md new/go-sendxmpp-v0.17.0/README.md --- old/go-sendxmpp-v0.16.0/README.md 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/README.md 2026-07-24 21:50:15.000000000 +0200 @@ -77,7 +77,7 @@ the account details via command line options: ```plain -Usage: go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…] +Usage: go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [--ox-receive-private-key value] [--ox-transfer-private-key] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…] -a, --alias=value Set alias/nicknamefor chatrooms. --allow-plain Allow PLAIN authentication. --anonymous Use anonymous authentication. @@ -122,6 +122,12 @@ --ox-passphrase=value Passphrase for locking and unlocking the private OpenPGP key. + --ox-receive-private-key=value + Import your encrypted private Ox key from the server. + Requires the transfer code. + --ox-transfer-private-key + Upload your encrypted private Ox key to the server for key + transfer. -p, --password=value Password for XMPP account. --raw Send raw XML. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/const.go new/go-sendxmpp-v0.17.0/const.go --- old/go-sendxmpp-v0.16.0/const.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/const.go 2026-07-24 21:50:15.000000000 +0200 @@ -5,7 +5,7 @@ package main const ( - version = "0.16.0" + version = "0.17.0" // defaults defaultBufferSize = 100 defaultConfigColumnSep = 2 @@ -23,7 +23,7 @@ defaultTLS11 = 11 defaultTLS12 = 12 defaultTLS13 = 13 - // namespace + // namespaces nsC2SdTLS = "urn:xmpp:alt-connections:tls" nsDiscoInfo = "http://jabber.org/protocol/disco#info" nsDiscoItems = "http://jabber.org/protocol/disco#items" @@ -36,18 +36,24 @@ 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" - nsPubsubOwner = "http://jabber.org/protocol/pubsub#owner" - nsSid = "urn:xmpp:sid:0" - nsSigned = "jabber:x:signed" - nsVersion = "jabber:iq:version" - nsXMPPStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas" - // strings + // #nosec G101 -- This is a namespace and not a hardcoded secret + nsOxSecretKey = "urn:xmpp:openpgp:0:secret-key" + nsPubsub = "http://jabber.org/protocol/pubsub" + nsPubsubOwner = "http://jabber.org/protocol/pubsub#owner" + nsSid = "urn:xmpp:sid:0" + nsSigned = "jabber:x:signed" + nsVersion = "jabber:iq:version" + nsXMPPStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas" + // strings and numbers legacyPGPMsgBegin = "-----BEGIN PGP MESSAGE-----\n\n" legacyPGPMsgEnd = "\n-----END PGP MESSAGE-----" legacyPGPSigBegin = "-----BEGIN PGP SIGNATURE-----\n\n" legacyPGPSigEnd = "\n-----END PGP SIGNATURE-----" oxAltBody = "This message is encrypted (XEP-0373: OpenPGP for XMPP)." + oxSecretAlphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ" + oxSecretLength = 24 + oxSecretChunkSize = 4 + pubsubNodeConfig = "http://jabber.org/protocol/pubsub#node_config" pubsubPubOptions = "http://jabber.org/protocol/pubsub#publish-options" strChat = "chat" strEmpty = "" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/go.mod new/go-sendxmpp-v0.17.0/go.mod --- old/go-sendxmpp-v0.16.0/go.mod 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/go.mod 2026-07-24 21:50:15.000000000 +0200 @@ -4,19 +4,19 @@ require ( github.com/ProtonMail/gopenpgp/v3 v3.4.1 - github.com/beevik/etree v1.6.0 - github.com/gabriel-vasile/mimetype v1.4.13 + github.com/beevik/etree v1.7.0 + github.com/gabriel-vasile/mimetype v1.4.15 github.com/google/uuid v1.6.0 github.com/pborman/getopt/v2 v2.1.0 - github.com/xmppo/go-xmpp v0.3.6 - golang.org/x/crypto v0.53.0 - golang.org/x/net v0.56.0 + github.com/xmppo/go-xmpp v0.3.7 + golang.org/x/crypto v0.54.0 + golang.org/x/net v0.57.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.46.0 // indirect - golang.org/x/text v0.38.0 // indirect + github.com/cloudflare/circl v1.6.4 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/go.sum new/go-sendxmpp-v0.17.0/go.sum --- old/go-sendxmpp-v0.16.0/go.sum 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/go.sum 2026-07-24 21:50:15.000000000 +0200 @@ -2,14 +2,14 @@ github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/ProtonMail/gopenpgp/v3 v3.4.1 h1:K7uUhSHSJxORZ+RuHpilTT6S4MA2whCRlXNwLqd0+ys= github.com/ProtonMail/gopenpgp/v3 v3.4.1/go.mod h1:bGdV9f6edhmd581wzXsQCTKdH8bXBbyhkgDKPjwPc6U= -github.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE= -github.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= -github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= -github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/beevik/etree v1.7.0 h1:xjBk9O4p4x7D1YajePjfLzdaFC4/uYUENA7P0pv6gXA= +github.com/beevik/etree v1.7.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc= +github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U= +github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA= @@ -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.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= +github.com/xmppo/go-xmpp v0.3.7 h1:UHM/pL+MioIMBWaFYU5TNcH8RaMWJaCbI/Yg2VW2WdM= +github.com/xmppo/go-xmpp v0.3.7/go.mod h1:YD5roZgj385upOjjG4RNNQ1kdhk5JtvA944MpMAb+jo= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= 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.16.0/helpers.go new/go-sendxmpp-v0.17.0/helpers.go --- old/go-sendxmpp-v0.16.0/helpers.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/helpers.go 2026-07-24 21:50:15.000000000 +0200 @@ -298,8 +298,56 @@ return uuid.NewString()[:6] } +func getSecret(alphabet string, length int, chunkSize int) (string, error) { + if length%chunkSize != 0 { + return strEmpty, fmt.Errorf("%d is not a multiple of %d", length, chunkSize) + } + secret := make([]rune, length+length/chunkSize-1) + secretRunes := []rune(alphabet) + delimiter := []rune("-") + max := big.NewInt(int64(len(secretRunes))) + chunks := length / chunkSize + secretLength := length + chunks - 1 + count := 0 + for range chunks { + for range chunkSize { + randInt, err := rand.Int(rand.Reader, max) + if err != nil { + return strEmpty, err + } + secret[count] = secretRunes[randInt.Int64()] + count++ + } + if count >= secretLength { + break + } + secret[count] = delimiter[0] + count++ + } + return string(secret), nil +} + // Remove @ and dots func fsFriendlyJid(jid string) string { jid = strings.ReplaceAll(jid, "@", "_at_") return strings.ReplaceAll(jid, ".", "_") } + +func checkPEPsupport(jid string, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult) error { + var pep bool + slog.Info("check pep support:", "target", jid) + discoResult, err := getDiscoInfo(client, drc, jid) + if err != nil { + return fmt.Errorf("check pep support: %w", err) + } + slog.Info("ox: check disco info reply for:", "type", "pep", "category", "pubsub") + for _, identity := range discoResult.Identities { + if identity.Category == "pubsub" && identity.Type == "pep" { + pep = true + } + } + if !pep { + return fmt.Errorf("no pep support") + } + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/legacypgp.go new/go-sendxmpp-v0.17.0/legacypgp.go --- old/go-sendxmpp-v0.16.0/legacypgp.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/legacypgp.go 2026-07-24 21:50:15.000000000 +0200 @@ -120,7 +120,9 @@ return false } -func legacyPGPDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string, oxPrivKey *crypto.Key) (string, time.Time, error) { +func legacyPGPDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string, oxPrivKey *crypto.Key, + oxTrustMode string, +) (string, time.Time, error) { var legacyPGPMsg *crypto.PGPMessage var err error sender := strings.Split(m.Remote, "/")[0] @@ -143,7 +145,7 @@ return strError, time.Now(), err } slog.Info("legacy pgp: decrypting: getting senders key", "sender", sender) - senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender) + senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender, oxTrustMode) if err != nil { return strError, time.Now(), err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/main.go new/go-sendxmpp-v0.17.0/main.go --- old/go-sendxmpp-v0.16.0/main.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/main.go 2026-07-24 21:50:15.000000000 +0200 @@ -30,12 +30,15 @@ ) type configuration struct { - username string - jserver string - port string - password string - alias string - allowPLAIN bool + username string + jserver string + port string + password string + alias string + oxTrustMode string + allowPLAIN bool + noRootWarn bool + noLPGPWarn bool } func closeAndExit(client *xmpp.Client, err error) { @@ -98,12 +101,12 @@ } var ( - err error - message, user, server, password, alias string - allowPLAIN, isRoot bool - oxPrivKey *crypto.Key - recipients []recipientsType - fast xmpp.Fast + err error + message, user, server, password, alias, oxTrustMode string + allowPLAIN, pinNoPLAIN, isRoot, noRootWarn, noLPGPWarn bool + oxPrivKey *crypto.Key + recipients []recipientsType + fast xmpp.Fast // There are some errors that we ignore as we do not want to // stop the execution. Failure is used to track those to exit // with a non-success return value. @@ -150,6 +153,8 @@ flagOxImportPrivKey := getopt.StringLong("ox-import-privkey", 0, "", "Import an existing private OpenPGP key.") flagOxDeleteNodes := getopt.BoolLong("ox-delete-nodes", 0, "Delete existing OpenPGP nodes on the server.") + flagOxTransPrivKey := getopt.BoolLong("ox-transfer-private-key", 0, "Upload your encrypted private Ox key to the server for key transfer.") + flagOxRecvPrivKey := getopt.StringLong("ox-receive-private-key", 0, "", "Import your encrypted private Ox key from the server. Requires the transfer code.") flagOOBFile := getopt.StringLong("oob-file", 0, "", "URL to send a file as out of band data.") flagHeadline := getopt.BoolLong("headline", 0, "Send message as type headline.") flagSCRAMPinning := getopt.StringLong("scram-mech-pinning", 0, "", "Enforce the use of a certain SCRAM authentication mechanism.") @@ -212,37 +217,6 @@ 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. - slog.Info("checking for current OS user") - currUser, err := osUser.Current() - if err != nil { - log.Fatal("Failed to get current user: ", err) - } - slog.Info("checking if OS user is root") - slog.Info("user identification", "UID", syscall.Getuid()) - slog.Info("user identification", "GID", syscall.Getgid()) - if currUser.Username == "root" { - fmt.Println("WARNING: It seems you are running go-sendxmpp as root user.\n" + - "This is not recommended as go-sendxmpp does not require root " + - "privileges. Please consider using a less privileged user. For an " + - "example how to do this with sudo please consult the manpage chapter " + - "TIPS.") - // Logging that we're running as root - isRoot = true - *flagFastOff = true - slog.Info("disabling fast as we're running as root") - *flagOx = false - slog.Info("disabling OX as we're running as root") - } - } - switch *flagSCRAMPinning { case "", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS", "SCRAM-SHA-512", "SCRAM-SHA-512-PLUS": @@ -251,6 +225,10 @@ log.Fatal("Unknown SCRAM mechanism: ", *flagSCRAMPinning) } + if *flagOxRecvPrivKey != "" && len(*flagOxRecvPrivKey) != 29 { + log.Fatal("OX: passphrase to decrypt private key has wrong length: ", len(*flagOxRecvPrivKey), " instead of 29") + } + // Read recipients from command line, if no recipients list is specified, and quit // if none are specified. For listening or sending raw XML it's not required to // specify a recipient except when sending raw messages to MUCs (go-sendxmpp will @@ -275,7 +253,8 @@ if (len(recipientsList) == 0 && !*flagRaw && !*flagListen && !*flagOxGenPrivKeyX25519 && !*flagOxGenPrivKeyRSA && *flagOxImportPrivKey == "" && !*flagFastInvalidate) && - !*flagOxDeleteNodes || (len(recipientsList) == 0 && *flagChatroom) { + !*flagOxDeleteNodes && *flagOxRecvPrivKey == "" && !*flagOxTransPrivKey || + (len(recipientsList) == 0 && *flagChatroom) { log.Fatal("No recipient specified.") } @@ -303,9 +282,47 @@ slog.Info("setting", "server", server) } allowPLAIN = config.allowPLAIN + noRootWarn = config.noRootWarn + noLPGPWarn = config.noLPGPWarn + oxTrustMode = config.oxTrustMode slog.Info("config", "allow-plain", config.allowPLAIN) } + // Print a warning if go-sendxmpp is run by the user root on non-windows systems. + if runtime.GOOS != "windows" && !*flagNoRootWarning && !noRootWarn { + // Get the current user. + slog.Info("checking for current OS user") + currUser, err := osUser.Current() + if err != nil { + log.Fatal("Failed to get current user: ", err) + } + slog.Info("checking if OS user is root") + slog.Info("user identification", "UID", syscall.Getuid()) + slog.Info("user identification", "GID", syscall.Getgid()) + if currUser.Username == "root" { + fmt.Println("WARNING: It seems you are running go-sendxmpp as root user.\n" + + "This is not recommended as go-sendxmpp does not require root " + + "privileges. Please consider using a less privileged user. For an " + + "example how to do this with sudo please consult the manpage chapter " + + "TIPS.") + // Logging that we're running as root + isRoot = true + // Disabling FAST, legacy PGP and OX when running as root as both features + // need to read/write local date in the users context and access the network + // but we drop root privileges prior to network activity. + *flagFastOff = true + slog.Info("disabling fast as we're running as root") + *flagOx = false + *flagLegacyPGP = false + slog.Info("disabling OX as we're running as root") + } + } + + if *flagLegacyPGP && !noLPGPWarn { + slog.Warn("Legacy PGP is deprecated and will be removed in a future version, " + + "please use 'OpenPGP for XMPP' (OX) instead.") + } + // Overwrite user if specified via command line flag. if *flagUser != "" { user = *flagUser @@ -432,7 +449,9 @@ resource := "go-sendxmpp." + getShortID() // Check whether PLAIN authentication is disabled. - pinNoPLAIN, _ := parseAuthPinFile(user) + if !isRoot { + pinNoPLAIN, _ = parseAuthPinFile(user) + } noPLAIN := pinNoPLAIN || (!*flagPLAINAllow && !allowPLAIN) // Periodic server ping settings @@ -509,7 +528,8 @@ if !*flagInteractive && !*flagListen && len(*flagHTTPUpload) == 0 && !*flagOxDeleteNodes && *flagOxImportPrivKey == "" && !*flagOxGenPrivKeyX25519 && !*flagOxGenPrivKeyRSA && *flagOOBFile == "" && - !*flagFastInvalidate && message == "" { + !*flagFastInvalidate && *flagOxRecvPrivKey == "" && !*flagOxTransPrivKey && + message == "" { slog.Info("reading message from stdin") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { @@ -536,7 +556,7 @@ if message == "" && !*flagInteractive && !*flagListen && !*flagOxGenPrivKeyRSA && !*flagOxGenPrivKeyX25519 && *flagOxImportPrivKey == "" && !*flagOxDeleteNodes && len(*flagHTTPUpload) == 0 && *flagOOBFile == "" && - !*flagFastInvalidate { + !*flagFastInvalidate && !*flagOxTransPrivKey && *flagOxRecvPrivKey == "" { slog.Info("exiting due to empty message") os.Exit(0) } @@ -587,7 +607,7 @@ } // If authentication is not yet pinned to not use PLAIN and a SCRAM mechanism is // used, write the auth pin file. - if !pinNoPLAIN && (strings.HasPrefix(client.Mechanism, "SCRAM") || + if !pinNoPLAIN && !isRoot && (strings.HasPrefix(client.Mechanism, "SCRAM") || strings.HasPrefix(client.Mechanism, "HT")) { slog.Info("writing auth pin file") err = writeAuthPinFile(user) @@ -613,14 +633,14 @@ switch { case (*flagOx || *flagLegacyPGP) && !*flagChatroom: slog.Info("requesting OX key for", "JID", re.Jid) - re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, re.Jid) + re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, re.Jid, oxTrustMode) if err != nil { re.OxKeyRing = nil 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) + re.OxKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, re.Jid, oxTrustMode) if err != nil { re.OxKeyRing = nil fmt.Println("Couldn't receive keys for:", re.Jid) @@ -640,14 +660,15 @@ recipients[i].Jid = validatedJid } + slog.Info("checking validity", "JID", user) + validatedOwnJid, err := MarshalJID(user) + if err != nil { + cancel() + closeAndExit(client, err) + } + switch { case *flagOxGenPrivKeyX25519: - slog.Info("checking validity", "JID", user) - validatedOwnJid, err := MarshalJID(user) - if err != nil { - cancel() - closeAndExit(client, err) - } slog.Info("generating X25519 private OX key") err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, crypto.KeyGenerationCurve25519Legacy) @@ -660,12 +681,6 @@ } os.Exit(0) case *flagOxGenPrivKeyRSA: - slog.Info("checking validity", "JID", user) - validatedOwnJid, err := MarshalJID(user) - if err != nil { - cancel() - closeAndExit(client, err) - } slog.Info("generating RSA4096 private OX key") err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, crypto.KeyGenerationRSA4096) if err != nil { @@ -674,41 +689,44 @@ } os.Exit(0) case *flagOxImportPrivKey != "": - slog.Info("checking validity", "JID", user) - validatedOwnJid, err := MarshalJID(user) + slog.Info("importing private OX key:", "file", *flagOxImportPrivKey) + err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey, + client, iqc, oxTrustMode) if err != nil { cancel() closeAndExit(client, err) } - slog.Info("importing private OX key:", "file", *flagOxImportPrivKey) - err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey, - client, iqc) + os.Exit(0) + case *flagOxDeleteNodes: + slog.Info("delete OX pep nodes") + err = oxDeleteNodes(validatedOwnJid, client, iqc, disc) if err != nil { cancel() closeAndExit(client, err) } os.Exit(0) - case *flagOxDeleteNodes: - slog.Info("checking validity", "JID", user) - validatedOwnJid, err := MarshalJID(user) + case *flagOxTransPrivKey: + slog.Info("transferring private OX key to the server") + oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase) if err != nil { cancel() closeAndExit(client, err) } - slog.Info("delete OX pep nodes") - err = oxDeleteNodes(validatedOwnJid, client, iqc, disc) + err = oxTransPrivKey(validatedOwnJid, oxPrivKey, client, iqc, drc) if err != nil { cancel() closeAndExit(client, err) } os.Exit(0) - case *flagOx, *flagLegacyPGP: - slog.Info("checking validity", "JID", user) - validatedOwnJid, err := MarshalJID(user) + case *flagOxRecvPrivKey != "": + slog.Info("requesting private OX key from the server") + err = oxRecvPrivKey(validatedOwnJid, *flagOxRecvPrivKey, client, iqc, drc, oxTrustMode) if err != nil { cancel() closeAndExit(client, err) } + os.Exit(0) + case *flagOx, *flagLegacyPGP: slog.Info("reading private OX key") oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase) if err != nil { @@ -910,7 +928,7 @@ var t time.Time if isOx { slog.Info("message is OX encrypted") - msg, t, err = oxDecrypt(v, client, iqc, drc, user, oxPrivKey) + msg, t, err = oxDecrypt(v, client, iqc, drc, user, oxPrivKey, oxTrustMode) if err != nil { log.Println(err) continue @@ -921,7 +939,7 @@ } prefix = "[OX]" } else { - msg, t, err = legacyPGPDecrypt(v, client, iqc, user, oxPrivKey) + msg, t, err = legacyPGPDecrypt(v, client, iqc, user, oxPrivKey, oxTrustMode) if err != nil { log.Println(err) continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1 new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1 --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1 2026-07-24 21:50:15.000000000 +0200 @@ -1,10 +1,10 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GO\-SENDXMPP" "1" "June 2026" "" +.TH "GO\-SENDXMPP" "1" "July 2026" "" .SH "NAME" \fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\. .SH "SYNOPSIS" -\fBgo\-sendxmpp [\-cdilntv] [\-a value] [\-\-allow\-plain] [\-\-anonymous] [\-\-fast\-invalidate] [\-\-fast\-off] [\-f value] [\-\-headline] [\-\-help] [\-h value] [\-j value] [\-\-legacy\-pgp] [\-m value] [\-\-muc\-password value] [\-\-no\-sasl\-upgrade] [\-\-oob\-file value] [\-\-ox] [\-\-ox\-delete\-nodes] [\-\-ox\-genprivkey\-rsa] [\-\-ox\-genprivkey\-x25519] [\-\-ox\-import\-privkey value] [\-\-ox\-passphrase value] [\-p value] [\-\-raw] [\-r value] [\-\-retry\-connect value] [\-\-retry\-connect\-max value] [\-\-scram\-mech\-pinning value] [\-\-ssdp\-off] [\-s value] [\-\-suppress\-root\-warning] [\-\-timeout value] [\-\-tls\-version value] [\-u value] [\-\-version] [recipients…]\fR +\fBgo\-sendxmpp [\-cdilntv] [\-a value] [\-\-allow\-plain] [\-\-anonymous] [\-\-fast\-invalidate] [\-\-fast\-off] [\-f value] [\-\-headline] [\-\-help] [\-h value] [\-j value] [\-\-legacy\-pgp] [\-m value] [\-\-muc\-password value] [\-\-no\-sasl\-upgrade] [\-\-oob\-file value] [\-\-ox] [\-\-ox\-delete\-nodes] [\-\-ox\-genprivkey\-rsa] [\-\-ox\-genprivkey\-x25519] [\-\-ox\-import\-privkey value] [\-\-ox\-passphrase value] [\-\-ox\-receive\-private\-key value] [\-\-ox\-transfer\-private\-key] [\-p value] [\-\-raw] [\-r value] [\-\-retry\-connect value] [\-\-retry\-connect\-max value] [\-\-scram\-mech\-pinning value] [\-\-ssdp\-off] [\-s value] [\-\-suppress\-root\-warning] [\-\-timeout value] [\-\-tls\-version value] [\-u value] [\-\-version] [recipients…]\fR .SH "DESCRIPTION" A tool to send messages to an XMPP contact or MUC inspired by \fBsendxmpp\fR\. .br @@ -84,7 +84,7 @@ 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\. +Delete existing OpenPGP nodes (public key and private key backup) on the server\. .TP \fB\-\-ox\-genprivkey\-rsa\fR Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or \fB\-u\fR and \fB\-p\fR) and publish the corresponding public key\. Go\-sendxmpp will save the key in \fB$XDG_DATA_HOME/go\-sendxmpp/oxprivkeys\fR or \fB$HOME/\.local/share/go\-sendxmpp/oxprivkeys\fR\. To protect the key a passphrase might be set using \fB\-\-ox\-passphrase\fR while generating the key\. @@ -102,6 +102,12 @@ \fB\-\-ox\-passphrase\fR=[\fIvalue\fR] Passphrase for locking and unlocking the private OpenPGP key\. .TP +\fB\-\-ox\-receive\-private\-key\fR=[\fIvalue\fR] +Import your encrypted private Ox key from the server\. Requires the transfer code\. +.TP +\fB\-\-ox\-transfer\-private\-key\fR +Upload your encrypted private Ox key to the server for key transfer\. The private key will be encrypted by a random generated passphrase\. Please make sure to not lose the passphrase as it can not be recovered\. +.TP \fB\-p\fR, \fB\-\-password\fR=[\fIvalue\fR] Password for XMPP account\. .TP @@ -127,7 +133,7 @@ Set message subject\. .TP \fB\-\-suppress\-root\-warning\fR -Suppress warning when run as root\. +Suppress warning when run as root\. Note that running as root is discouraged although go\-sendxmpp tries to drop the root privileges before doing network calls\. Be aware that \fBOX\fR, \fBlegacy PGP\fR and the pinning to not use \fBPLAIN\fR authentication after a successful \fBSCRAM\fR authentication are disabled as those need file system access in the private user scope after dropping the privileges\. .TP \fB\-\-timeout=\fR[\fIvalue\fR] Connection timeout in seconds\. (Default: 10) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.html new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1.html --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.html 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1.html 2026-07-24 21:50:15.000000000 +0200 @@ -81,7 +81,7 @@ </p> <h2 id="SYNOPSIS">SYNOPSIS</h2> -<p><code>go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]</code></p> +<p><code>go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [--ox-receive-private-key value] [--ox-transfer-private-key] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]</code></p> <h2 id="DESCRIPTION">DESCRIPTION</h2> @@ -176,7 +176,7 @@ used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys.</dd> <dt><code>--ox-delete-nodes</code></dt> -<dd>Delete existing OpenPGP nodes on the server.</dd> +<dd>Delete existing OpenPGP nodes (public key and private key backup) on the server.</dd> <dt><code>--ox-genprivkey-rsa</code></dt> <dd>Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or <code>-u</code> and <code>-p</code>) and publish the corresponding public key. @@ -200,6 +200,12 @@ <code>--ox-passphrase</code>=[<var>value</var>]</dt> <dd>Passphrase for locking and unlocking the private OpenPGP key.</dd> <dt> +<code>--ox-receive-private-key</code>=[<var>value</var>]</dt> +<dd>Import your encrypted private Ox key from the server. Requires the transfer code.</dd> +<dt><code>--ox-transfer-private-key</code></dt> +<dd>Upload your encrypted private Ox key to the server for key transfer. The private key will be encrypted +by a random generated passphrase. Please make sure to not lose the passphrase as it can not be recovered.</dd> +<dt> <code>-p</code>, <code>--password</code>=[<var>value</var>]</dt> <dd>Password for XMPP account.</dd> <dt><code>--raw</code></dt> @@ -232,7 +238,11 @@ <code>-s</code>, <code>--subject</code>=[<var>value</var>]</dt> <dd>Set message subject.</dd> <dt><code>--suppress-root-warning</code></dt> -<dd>Suppress warning when run as root.</dd> +<dd>Suppress warning when run as root. +Note that running as root is discouraged although go-sendxmpp tries to drop the root privileges +before doing network calls. Be aware that <strong>OX</strong>, <strong>legacy PGP</strong> and the pinning to not use <strong>PLAIN</strong> +authentication after a successful <strong>SCRAM</strong> authentication are disabled as those need file system access +in the private user scope after dropping the privileges.</dd> <dt> <code>--timeout=</code>[<var>value</var>]</dt> <dd>Connection timeout in seconds. (Default: 10)</dd> @@ -323,11 +333,11 @@ <h2 id="SEE-ALSO">SEE ALSO</h2> -<p><span class="man-ref">go-sendxmpp<span class="s">(5)</span></span>, <span class="man-ref">xmppc<span class="s">(1)</span></span>, <span class="man-ref">sendxmpp<span class="s">(1)</span></span></p> +<p><a class="man-ref" href="go-sendxmpp.5.html">go-sendxmpp<span class="s">(5)</span></a>, <span class="man-ref">xmppc<span class="s">(1)</span></span>, <span class="man-ref">sendxmpp<span class="s">(1)</span></span></p> <ol class='man-decor man-foot man foot'> <li class='tl'></li> - <li class='tc'>June 2026</li> + <li class='tc'>July 2026</li> <li class='tr'>go-sendxmpp(1)</li> </ol> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.ronn new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1.ronn --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.1.ronn 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.1.ronn 2026-07-24 21:50:15.000000000 +0200 @@ -3,7 +3,7 @@ ## SYNOPSIS -`go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]` +`go-sendxmpp [-cdilntv] [-a value] [--allow-plain] [--anonymous] [--fast-invalidate] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [--legacy-pgp] [-m value] [--muc-password value] [--no-sasl-upgrade] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [--ox-receive-private-key value] [--ox-transfer-private-key] [-p value] [--raw] [-r value] [--retry-connect value] [--retry-connect-max value] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--suppress-root-warning] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]` ## DESCRIPTION @@ -102,7 +102,7 @@ due to changed keys. * `--ox-delete-nodes`: -Delete existing OpenPGP nodes on the server. +Delete existing OpenPGP nodes (public key and private key backup) on the server. * `--ox-genprivkey-rsa`: Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or `-u` and `-p`) @@ -128,6 +128,13 @@ * `--ox-passphrase`=[<value>]: Passphrase for locking and unlocking the private OpenPGP key. +* `--ox-receive-private-key`=[<value>]: +Import your encrypted private Ox key from the server. Requires the transfer code. + +* `--ox-transfer-private-key`: +Upload your encrypted private Ox key to the server for key transfer. The private key will be encrypted +by a random generated passphrase. Please make sure to not lose the passphrase as it can not be recovered. + * `-p`, `--password`=[<value>]: Password for XMPP account. @@ -165,6 +172,10 @@ * `--suppress-root-warning`: Suppress warning when run as root. +Note that running as root is discouraged although go-sendxmpp tries to drop the root privileges +before doing network calls. Be aware that **OX**, **legacy PGP** and the pinning to not use **PLAIN** +authentication after a successful **SCRAM** authentication are disabled as those need file system access +in the private user scope after dropping the privileges. * `--timeout=`[<value>]: Connection timeout in seconds. (Default: 10) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5 new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5 --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5 2026-07-24 21:50:15.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" "5" "May 2026" "" +.TH "GO\-SENDXMPP" "5" "July 2026" "" .SH "NAME" \fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\. .SH "LOCATION" @@ -19,6 +19,12 @@ alias: [\fIyour_alias\fR] .br allow_plain: [\fIbool\fR] +.br +no_root_warning: [\fIbool\fR] +.br +no_legacy_pgp_warning: [\fIbool\fR] +.br +ox_trust_mode: [blind|tofu] .SH "REQUIRED SETTINGS" If all necessary settings are supplied as command line arguments no config file is needed at all\. Setting \fBjserver\fR and \fBport\fR might not be necessary depending on the used server\. .br @@ -47,6 +53,21 @@ Boolean to allow PLAIN authentication\. Note that this setting has no effect if there was a successful connection using a SCRAM authentication mechanism before\. In that case PLAIN is not allowed to prevent downgrades by a man\-in\-the\-middle attack\. .br Allowed values: \fBtrue\fR/\fBfalse\fR (Default: false) +.TP +\fBno_root_warning\fR +Boolean to disable the warning when running as root\. Note that running as root is discouraged although go\-sendxmpp tries to drop the root privileges before doing network calls\. Be aware that \fBOX\fR, \fBlegacy PGP\fR and the pinning to not use \fBPLAIN\fR authentication after a successful \fBSCRAM\fR authentication are disabled as those need file system access in the private user scope after dropping the privileges\. +.br +Allowed values: \fBtrue\fR/\fBfalse\fR (Default: false) +.TP +\fBno_legacy_pgp_warning\fR +Boolean to disable the warning when using \fBlegacy PGP\fR\. Please note, that *legacy PGP** is deprecated and will be removed in an upcoming update\. It is highly recommended to use OX instead\. +.br +Allowed values: \fBtrue\fR/\fBfalse\fR (Default: false) +.TP +\fBox_trust_mode\fR +Accepts the strings \fBtofu\fR and \fBblind\fR\. Tofu means trust on first usage and accepts any OX key if none is stored in the cache yet\. If the key ID changes go\-sendxmpp will refuse to encrypt\. Blind means go\-sendxmpp will import and trust any new key that is advertised by the recipient\. +.br +Allowed values: \fBtofu\fR/\fBblind\fR (Default: blind) .SH "AUTHOR" Written by Martin Dosch\. .SH "REPORTING BUGS" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.html new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5.html --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.html 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5.html 2026-07-24 21:50:15.000000000 +0200 @@ -90,7 +90,10 @@ password: [<var>your_jabber_password</var>] <br> eval_password: [<var>command_to_unlock_your_password</var>] <br> alias: [<var>your_alias</var>] <br> -allow_plain: [<var>bool</var>]</p> +allow_plain: [<var>bool</var>] <br> +no_root_warning: [<var>bool</var>] <br> +no_legacy_pgp_warning: [<var>bool</var>] <br> +ox_trust_mode: [blind|tofu]</p> <h2 id="REQUIRED-SETTINGS">REQUIRED SETTINGS</h2> @@ -123,6 +126,22 @@ connection using a SCRAM authentication mechanism before. In that case PLAIN is not allowed to prevent downgrades by a man-in-the-middle attack. <br> Allowed values: <strong>true</strong>/<strong>false</strong> (Default: false)</dd> +<dt><code>no_root_warning</code></dt> +<dd>Boolean to disable the warning when running as root. Note that running as root is discouraged although +go-sendxmpp tries to drop the root privileges before doing network calls. Be aware that <strong>OX</strong>, +<strong>legacy PGP</strong> and the pinning to not use <strong>PLAIN</strong> authentication after a successful <strong>SCRAM</strong> +authentication are disabled as those need file system access in the private user scope after dropping +the privileges. <br> +Allowed values: <strong>true</strong>/<strong>false</strong> (Default: false)</dd> +<dt><code>no_legacy_pgp_warning</code></dt> +<dd>Boolean to disable the warning when using <strong>legacy PGP</strong>. Please note, that *legacy PGP** is deprecated +and will be removed in an upcoming update. It is highly recommended to use OX instead. <br> +Allowed values: <strong>true</strong>/<strong>false</strong> (Default: false)</dd> +<dt><code>ox_trust_mode</code></dt> +<dd>Accepts the strings <strong>tofu</strong> and <strong>blind</strong>. Tofu means trust on first usage and accepts any OX key if none +is stored in the cache yet. If the key ID changes go-sendxmpp will refuse to encrypt. Blind means +go-sendxmpp will import and trust any new key that is advertised by the recipient. <br> +Allowed values: <strong>tofu</strong>/<strong>blind</strong> (Default: blind)</dd> </dl> <h2 id="AUTHOR">AUTHOR</h2> @@ -144,7 +163,7 @@ <ol class='man-decor man-foot man foot'> <li class='tl'></li> - <li class='tc'>May 2026</li> + <li class='tc'>July 2026</li> <li class='tr'>go-sendxmpp(5)</li> </ol> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.ronn new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5.ronn --- old/go-sendxmpp-v0.16.0/man/go-sendxmpp.5.ronn 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/man/go-sendxmpp.5.ronn 2026-07-24 21:50:15.000000000 +0200 @@ -15,7 +15,10 @@ password: [<your_jabber_password>] eval_password: [<command_to_unlock_your_password>] alias: [<your_alias>] -allow_plain: [<bool>] +allow_plain: [<bool>] +no_root_warning: [<bool>] +no_legacy_pgp_warning: [<bool>] +ox_trust_mode: [blind|tofu] ## REQUIRED SETTINGS @@ -54,6 +57,25 @@ downgrades by a man-in-the-middle attack. Allowed values: **true**/**false** (Default: false) +* `no_root_warning`: +Boolean to disable the warning when running as root. Note that running as root is discouraged although +go-sendxmpp tries to drop the root privileges before doing network calls. Be aware that **OX**, +**legacy PGP** and the pinning to not use **PLAIN** authentication after a successful **SCRAM** +authentication are disabled as those need file system access in the private user scope after dropping +the privileges. +Allowed values: **true**/**false** (Default: false) + +* `no_legacy_pgp_warning`: +Boolean to disable the warning when using **legacy PGP**. Please note, that *legacy PGP** is deprecated +and will be removed in an upcoming update. It is highly recommended to use OX instead. +Allowed values: **true**/**false** (Default: false) + +* `ox_trust_mode`: +Accepts the strings **tofu** and **blind**. Tofu means trust on first usage and accepts any OX key if none +is stored in the cache yet. If the key ID changes go-sendxmpp will refuse to encrypt. Blind means +go-sendxmpp will import and trust any new key that is advertised by the recipient. +Allowed values: **tofu**/**blind** (Default: blind) + ## AUTHOR Written by Martin Dosch. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/ox.go new/go-sendxmpp-v0.17.0/ox.go --- old/go-sendxmpp-v0.16.0/ox.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/ox.go 2026-07-24 21:50:15.000000000 +0200 @@ -3,6 +3,8 @@ // license that can be found in the LICENSE file. // TODO: Add more logging +// TODO: Deduplicate code + package main import ( @@ -50,7 +52,7 @@ } func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult, - user string, oxPrivKey *crypto.Key, + user string, oxPrivKey *crypto.Key, oxTrustMode string, ) (string, time.Time, error) { var cryptMsgByte []byte var senderKeyRing *crypto.KeyRing @@ -72,12 +74,12 @@ } slog.Info("ox: decrypting: getting senders key", "sender", sender) if m.Type == "groupchat" { - senderKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, sender) + senderKeyRing, err = oxGetPublicKeyRingChatroom(client, iqc, drc, sender, oxTrustMode) if err != nil { return strError, time.Now(), err } } else { - senderKeyRing, err = oxGetPublicKeyRing(client, iqc, sender) + senderKeyRing, err = oxGetPublicKeyRing(client, iqc, sender, oxTrustMode) if err != nil { return strError, time.Now(), err } @@ -166,7 +168,9 @@ return false } -func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iqc chan xmpp.IQ) error { +func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iqc chan xmpp.IQ, + oxTrustMode string, +) error { xmppURI := "xmpp:" + jid slog.Info("ox: importing private key:", "xmpp-uri", xmppURI) slog.Info("ox: importing private key:", "location", privKeyLocation) @@ -235,7 +239,7 @@ 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) + pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid, oxTrustMode) if err == nil { slog.Info("ox: importing private key: checking if public key ring contains", "fingerprint", fingerprint) pubKeys := pubKeyRing.GetKeys() @@ -420,13 +424,13 @@ return dataFile, nil } -func oxGetPubKeyLoc(fingerprint string) (string, error) { +func oxGetPubKeyLoc(jid string) (string, error) { dataDir, err := getDataPath("oxpubkeys/", true) slog.Info("ox: getting public key", "location", dataDir) if err != nil { return strError, fmt.Errorf("ox: get pubkey location: %w", err) } - dataFile := dataDir + fingerprint + dataFile := dataDir + fsFriendlyJid(jid) slog.Info("ox: getting public key location", "file", dataFile) return dataFile, nil } @@ -593,34 +597,37 @@ return nil, fmt.Errorf("ox: no item element in reply to public key request") } slog.Info("ox: receiving public keys: received public keys") - oxPublicKeyXMLPubkeys := oxPublicKeyXMLItem.SelectElements("pubkey") - for _, r := range oxPublicKeyXMLPubkeys { - slog.Info("ox: receiving public keys: decoding public", "key", r) - data := r.SelectElement("data") - if data == nil { - continue - } - decodedPubKey, err := base64.StdEncoding.DecodeString(data.Text()) - if err != nil { - return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err) - } - key, err := crypto.NewKey(decodedPubKey) - if err != nil { - return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err) - } - slog.Info("ox: receiving public keys: checking for expiry:", "fingerprint", fingerprint) - if key.IsExpired(time.Now().Unix()) { - return nil, fmt.Errorf("ox: key is expired: %s", fingerprint) - } - err = keyring.AddKey(key) - if err != nil { - return nil, fmt.Errorf("ox: receive public keys: failed adding public key to keyring: %w", err) - } + oxPublicKeyXMLPubkey := oxPublicKeyXMLItem.SelectElements("pubkey") + slog.Info("ox: receiving public keys: decoding public", "key", oxPublicKeyXMLPubkey[0]) + data := oxPublicKeyXMLPubkey[0].SelectElement("data") + if data == nil { + return nil, fmt.Errorf("ox: receive public keys: no data element") + } + decodedPubKey, err := base64.StdEncoding.DecodeString(data.Text()) + if err != nil { + return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err) + } + key, err := crypto.NewKey(decodedPubKey) + if err != nil { + return nil, fmt.Errorf("ox: receive public keys: failed to decode public key: %w", err) + } + if strings.ToUpper(key.GetFingerprint()) != fingerprint { + return nil, fmt.Errorf("ox: receive public keys: received key has fingerprint %s but %s was advertised", + strings.ToUpper(key.GetFingerprint()), fingerprint) + } + slog.Info("ox: receiving public keys: checking for expiry:", "fingerprint", fingerprint) + if key.IsExpired(time.Now().Unix()) { + return nil, fmt.Errorf("ox: key is expired: %s", fingerprint) + } + err = keyring.AddKey(key) + if err != nil { + return nil, fmt.Errorf("ox: receive public keys: failed adding public key to keyring: %w", err) } return keyring, nil } -func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string) (*crypto.KeyRing, error) { +func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, oxTrustMode string, +) (*crypto.KeyRing, error) { publicKeyRing, err := crypto.NewKeyRing(nil) if err != nil { return nil, fmt.Errorf("ox: get public keyring: failed to create a new keyring: %w", err) @@ -696,7 +703,7 @@ return nil, fmt.Errorf("ox: server didn't provide public key fingerprints for %s", recipient) } - pubKeyRingLocation, err := oxGetPubKeyLoc(pubKeyRingID) + pubKeyRingLocation, err := oxGetPubKeyLoc(recipient) if err != nil { return nil, fmt.Errorf("ox: get public keyring: failed to get public key ring location: %w", err) } @@ -710,31 +717,37 @@ if err != nil { return nil, fmt.Errorf("ox: get public keyring: failed to parse time for saved key: %w", err) } - if !savedKeysDate.Before(newestKey) { - pubKeys := pubKeyReadXML.SelectElements("pubkey") - if pubKeys == nil { - return nil, fmt.Errorf("ox: couldn't read public keys from cache") - } - for _, r := range pubKeys { - keyByte, err := base64.StdEncoding.DecodeString(r.Text()) - if err != nil { - return nil, fmt.Errorf("ox: get public keyring: failed to decode saved key: %w", err) - } - key, err := crypto.NewKey(keyByte) - if err != nil { - return nil, fmt.Errorf("ox: get public keyring: failed to parse saved key: %w", err) - } - if !key.IsExpired(time.Now().Unix()) { - err = publicKeyRing.AddKey(key) - if err != nil { - return nil, fmt.Errorf("ox: get public keyring: failed to add key to public keyring: %w", err) - } - } + pubKey := pubKeyReadXML.SelectElements("pubkey") + if pubKey == nil { + return nil, fmt.Errorf("ox: couldn't read public key from cache") + } + keyByte, err := base64.StdEncoding.DecodeString(pubKey[0].Text()) + if err != nil { + return nil, fmt.Errorf("ox: get public keyring: failed to decode saved key: %w", err) + } + key, err := crypto.NewKey(keyByte) + if err != nil { + return nil, fmt.Errorf("ox: get public keyring: failed to parse saved key: %w", err) + } + if !key.IsExpired(time.Now().Unix()) { + err = publicKeyRing.AddKey(key) + if err != nil { + return nil, fmt.Errorf("ox: get public keyring: failed to add key to public keyring: %w", err) } + } + storedKeyFingerprint := strings.ToUpper(key.GetFingerprint()) + if oxTrustMode != "blind" && storedKeyFingerprint != pubKeyRingID { + return nil, fmt.Errorf("ox: get public keyring: failed to use stored key %s for %s as server advertises %s but trust mode is set to \"%s\"", + storedKeyFingerprint, recipient, pubKeyRingID, oxTrustMode) + } + if !savedKeysDate.Before(newestKey) { if publicKeyRing.CanEncrypt(time.Now().Unix()) { return publicKeyRing, nil } } + } else if oxTrustMode != "blind" { + return nil, fmt.Errorf("ox: get public keyring: failed to parse date of store key for %s but trust mode is set to \"%s\"", + recipient, oxTrustMode) } } pubKeyRing, err := oxRecvPublicKeys(client, iqc, recipient, pubKeyRingID) @@ -834,7 +847,7 @@ } func oxGetPublicKeyRingChatroom(client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult, - chatroom string, + chatroom string, oxTrustMode string, ) (*crypto.KeyRing, error) { keyRing, err := crypto.NewKeyRing(nil) if err != nil { @@ -886,7 +899,7 @@ if jid == nil { continue } - memberKeyRing, err := oxGetPublicKeyRing(client, iqc, jid.Value) + memberKeyRing, err := oxGetPublicKeyRing(client, iqc, jid.Value, oxTrustMode) if err != nil { continue } @@ -903,3 +916,229 @@ } return keyRing, nil } + +func oxCheckPrivKeyNode(jid string, client *xmpp.Client, iqc chan xmpp.IQ) (xmpp.IQ, error) { + slog.Info("ox: parsing private key request to IQ") + recvPrivKeyRequest := etree.NewDocument() + recvPrivKeyRequest.WriteSettings.AttrSingleQuote = true + rpkrPubsub := recvPrivKeyRequest.CreateElement("pubsub") + rpkrPubsub.CreateAttr("xmlns", nsPubsub) + rpkrItems := rpkrPubsub.CreateElement("items") + rpkrItems.CreateAttr("node", nsOxSecretKey) + rpkrItems.CreateAttr("max_items", "1") + rpkr, err := recvPrivKeyRequest.WriteToString() + if err != nil { + return xmpp.IQ{}, fmt.Errorf("ox: check private key node: failed to create private key request: %w", err) + } + slog.Info("ox: sending IQ to request private key", "node", nsOxSecretKey) + oxPrivKeyReply, err := sendIQ(client, iqc, jid, "get", rpkr) + if err != nil { + return xmpp.IQ{}, fmt.Errorf("ox: check private key node: failed parsing iq reply to private key request: %w", err) + } + if oxPrivKeyReply.Type != strResult { + slog.Info("ox: received iq reply", "error", oxPrivKeyReply.Error.Condition) + if oxPrivKeyReply.Error.Condition == "item-not-found" { + slog.Info("ox: creating pep node") + createPrivKeyNodeRequest := etree.NewDocument() + createPrivKeyNodeRequest.WriteSettings.AttrSingleQuote = true + cpnrPubsub := createPrivKeyNodeRequest.CreateElement("pubsub") + cpnrPubsub.CreateAttr("xmlns", nsPubsub) + cpnrCreate := cpnrPubsub.CreateElement("create") + cpnrCreate.CreateAttr("node", nsOxSecretKey) + cpnrConfigure := cpnrPubsub.CreateElement("configure") + cpnrX := cpnrConfigure.CreateElement("x") + cpnrX.CreateAttr("xmlns", nsJabberData) + cpnrX.CreateAttr("type", "submit") + cpnrField1 := cpnrX.CreateElement("field") + cpnrField1.CreateAttr("var", "FORM_TYPE") + cpnrField1.CreateAttr("type", "hidden") + cpnrField1Val := cpnrField1.CreateElement("value") + cpnrField1Val.CreateText(pubsubNodeConfig) + cpnrField2 := cpnrX.CreateElement("field") + cpnrField2.CreateAttr("var", "pubsub#access_model") + cpnrField2Val := cpnrField2.CreateElement("value") + cpnrField2Val.CreateText("whitelist") + cpnrField3 := cpnrX.CreateElement("field") + cpnrField3.CreateAttr("var", "pubsub#send_last_published_item") + cpnrField3Val := cpnrField3.CreateElement("value") + cpnrField3Val.CreateText("on_sub") + cpnr, err := createPrivKeyNodeRequest.WriteToString() + if err != nil { + return xmpp.IQ{}, fmt.Errorf("ox: failed to create private key node: %w", err) + } + slog.Info("ox: sending IQ to create private key node", "node", nsOxSecretKey) + oxCreateNodeReply, err := sendIQ(client, iqc, jid, "set", cpnr) + if err != nil { + return xmpp.IQ{}, fmt.Errorf("ox: create private key node: failed parsing iq reply to private key node creation request: %w", err) + } + if oxCreateNodeReply.Type != strResult { + return xmpp.IQ{}, fmt.Errorf("ox: failed to create private key node: %s", oxCreateNodeReply.Error) + } + + } + } + return oxPrivKeyReply, nil +} + +func oxRecvPrivKey(jid string, secret string, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult, + oxTrustMode string, +) error { + err := checkPEPsupport(jid, client, iqc, drc) + if err != nil { + return fmt.Errorf("ox: receive private key: %w", err) + } + oxPrivKeyReply, err := oxCheckPrivKeyNode(jid, client, iqc) + if err != nil { + return fmt.Errorf("ox: receive private key: %w", err) + } + slog.Info("ox: receiving private key: received result") + slog.Info("ox: receiving private key: receiving own private key from pubsub") + oxPrivKeyXML := etree.NewDocument() + err = oxPrivKeyXML.ReadFromBytes(oxPrivKeyReply.Query) + if err != nil { + return fmt.Errorf("ox: receive private key: failed parsing iq reply to private key request: %w", err) + } + oxPrivKeyXMLPubsub := oxPrivKeyXML.SelectElement("pubsub") + if oxPrivKeyXMLPubsub == nil { + return fmt.Errorf("ox: receive private key: no pubsub element in reply to private key request") + } + oxPrivKeyXMLItems := oxPrivKeyXMLPubsub.SelectElement("items") + if oxPrivKeyXMLItems == nil { + return fmt.Errorf("ox: receive private key: no items element in reply to private key request") + } + oxPrivKeyXMLItemsNode := oxPrivKeyXMLItems.SelectAttr("node") + if oxPrivKeyXMLItemsNode.Value != nsOxSecretKey { + return fmt.Errorf("ox: receive private key: wrong node: %s instead of %s", oxPrivKeyXMLItemsNode.Value, nsOxSecretKey) + } + oxPrivKeyXMLItem := oxPrivKeyXMLItems.SelectElement("item") + if oxPrivKeyXMLItem == nil { + return fmt.Errorf("ox: receive private key: no item element in reply to private key request") + } + oxPrivKeyXMLSecretKey := oxPrivKeyXMLItem.SelectElement("secretkey") + if oxPrivKeyXMLSecretKey == nil { + return fmt.Errorf("ox: receive private key: no secretkey element in reply to private key request") + } + if oxPrivKeyXMLSecretKey.NamespaceURI() != nsOx { + return fmt.Errorf("ox: receive private key: wrong namespace: %s instead of %s", oxPrivKeyXMLSecretKey.NamespaceURI(), nsOx) + } + decodedPrivKey, err := base64.StdEncoding.DecodeString(oxPrivKeyXMLSecretKey.Text()) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to decode private key: %w", err) + } + pgpDecrypt, err := crypto.PGP(). + Decryption(). + Password([]byte(secret)). + New() + if err != nil { + return fmt.Errorf("ox: receive private key: failed to decode private key: %w", err) + } + oxPrivKey, err := pgpDecrypt.Decrypt(decodedPrivKey, crypto.Bytes) + if err != nil { + return fmt.Errorf("ox receive private key: failed to decrypt private key: %w", err) + } + location, err := oxGetPrivKeyLoc(jid) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to determine private key location: %w", err) + } + slog.Info("ox: receive private key: getting private key location", "jid", jid, "location", location) + key, err := crypto.NewKey(oxPrivKey.Bytes()) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to read private key: %w", err) + } + keySerialized, err := key.Serialize() + if err != nil { + return fmt.Errorf("ox: receive private key: failed to serialize private key: %w", err) + } + err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to store private key: %w", err) + } + slog.Info("ox: receive private key: requesting public key ring") + fingerprint := key.GetFingerprint() + pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid, oxTrustMode) + if err == nil { + slog.Info("ox: receive private key: checking if public key ring contains", "fingerprint", fingerprint) + pubKeys := pubKeyRing.GetKeys() + for _, r := range pubKeys { + fp := strings.ToUpper(r.GetFingerprint()) + slog.Info("ox: receive private key: checking", "fingerprint", fp) + if fp == fingerprint { + slog.Info("ox: receive private key: checking if public key ring contains fingerprint: success") + return nil + } + } + } + pk, err := key.GetPublicKey() + if err != nil { + return fmt.Errorf("ox: receive private key: failed to derive public key: %w", err) + } + pubKey, err := crypto.NewKey(pk) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to serialize public key: %w", err) + } + slog.Info("ox: import private key: publishing public key") + err = oxPublishPubKey(jid, client, iqc, pubKey) + if err != nil { + return fmt.Errorf("ox: receive private key: failed to publish public key: %w", err) + } + return nil +} + +func oxTransPrivKey(jid string, oxPrivKey *crypto.Key, client *xmpp.Client, iqc chan xmpp.IQ, drc chan xmpp.DiscoResult) error { + err := checkPEPsupport(jid, client, iqc, drc) + if err != nil { + return fmt.Errorf("ox: transfer private key: %w", err) + } + slog.Info("ox: transferring private key: generating secret") + secret, err := getSecret(oxSecretAlphabet, oxSecretLength, oxSecretChunkSize) + if err != nil { + return fmt.Errorf("ox: transfer private key: failed to get secret: %w", err) + } + slog.Info("ox: transferring private key: creating encryption interface") + pgpEncrypt, err := crypto.PGP(). + Encryption(). + Password([]byte(secret)). + New() + if err != nil { + return fmt.Errorf("ox: transfer private key: failed to create pgp encryption interface: %w", err) + } + slog.Info("ox: transferring private key: serializing private key") + oxPrivKeySerialized, err := oxPrivKey.Serialize() + if err != nil { + return fmt.Errorf("ox: transfer private key: failed to serialize private key: %w", err) + } + slog.Info("ox: transferring private key: encrypting private key") + pgpMessage, err := pgpEncrypt.Encrypt(oxPrivKeySerialized) + if err != nil { + return fmt.Errorf("ox: transfer private key: failed to create pgp message: %w", err) + } + _, err = oxCheckPrivKeyNode(jid, client, iqc) + if err != nil { + return fmt.Errorf("ox: transfer private key: %w", err) + } + slog.Info("ox: publishing private key") + publishPrivKeyRequest := etree.NewDocument() + publishPrivKeyRequest.WriteSettings.AttrSingleQuote = true + ppkrPubsub := publishPrivKeyRequest.CreateElement("pubsub") + ppkrPubsub.CreateAttr("xmlns", nsPubsub) + ppkrPublish := ppkrPubsub.CreateElement("publish") + ppkrPublish.CreateAttr("node", nsOxSecretKey) + ppkrItem := ppkrPublish.CreateElement("item") + ppkrSecretKey := ppkrItem.CreateElement("secretkey") + ppkrSecretKey.CreateAttr("xmlns", nsOx) + ppkrSecretKey.CreateText(base64.StdEncoding.EncodeToString(pgpMessage.Bytes())) + ppkr, err := publishPrivKeyRequest.WriteToString() + if err != nil { + return fmt.Errorf("ox: failed to create private key publish request: %w", err) + } + slog.Info("ox: sending IQ to publish private key") + oxPublishPrivateKeyReply, err := sendIQ(client, iqc, jid, "set", ppkr) + if err != nil { + return fmt.Errorf("ox: publish private key: failed parsing iq reply to publish private key request: %w", err) + } + if oxPublishPrivateKeyReply.Type != strResult { + return fmt.Errorf("ox: publish private key: %s", oxPublishPrivateKeyReply.Error.Condition) + } + fmt.Printf("OX: Secret key published: Encrypted with the secret phrase: %s\n", secret) + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/parseconfig.go new/go-sendxmpp-v0.17.0/parseconfig.go --- old/go-sendxmpp-v0.16.0/parseconfig.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/parseconfig.go 2026-07-24 21:50:15.000000000 +0200 @@ -52,6 +52,8 @@ // Opens the config file and returns the specified values // for username, server and port. func parseConfig(configPath string) (output configuration, err error) { + // Set default OX trust mode to "blind" + output.oxTrustMode = "blind" // Use $XDG_CONFIG_HOME/.config/go-sendxmpp/config, // $XDG_CONFIG_HOME/.config/go-sendxmpp/sendxmpprc or // ~/.sendxmpprc if no config path is specified. @@ -95,7 +97,7 @@ column := strings.SplitN(scanner.Text(), " ", defaultConfigColumnSep) if len(column) > 1 { - switch column[0] { + switch strings.ToLower(column[0]) { case "username:": output.username = column[1] case "jserver:": @@ -131,6 +133,23 @@ } else { output.allowPLAIN = false } + case "no_root_warning:": + if strings.ToLower(column[1]) == "true" { + output.noRootWarn = true + } else { + output.noRootWarn = false + } + case "no_legacy_pgp_warning:": + if strings.ToLower(column[1]) == "true" { + output.noLPGPWarn = true + } else { + output.noLPGPWarn = false + } + case "ox_trust_mode:": + switch strings.ToLower(column[1]) { + case "tofu", "blind": + output.oxTrustMode = strings.ToLower(column[1]) + } default: // Try to parse legacy sendxmpp config files. if len(column) >= defaultConfigColumnSep && strings.Contains(column[0], "@") { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/go-sendxmpp-v0.16.0/stanzahandling.go new/go-sendxmpp-v0.17.0/stanzahandling.go --- old/go-sendxmpp-v0.16.0/stanzahandling.go 2026-06-20 09:54:34.000000000 +0200 +++ new/go-sendxmpp-v0.17.0/stanzahandling.go 2026-07-24 21:50:15.000000000 +0200 @@ -171,8 +171,8 @@ Text string `xml:"text"` } var me messageError - var received interface{} - r := make(chan interface{}, defaultBufferSize) + var received any + r := make(chan any, defaultBufferSize) e := make(chan error, defaultBufferSize) go func() { for { ++++++ vendor.tar.zst ++++++ ++++ 3196 lines of diff (skipped)
