Compare commits

..

No commits in common. 'master' and 'v0.8.2' have entirely different histories.

@ -38,31 +38,28 @@ format:
#- '[ -e .golangci.yml ] || cp /golangci/.golangci.yml .'
#- golangci-lint run
#allow_failure: true
artifacts:
expire_in: 1 year
compile:
stage: build
only:
- tags
before_script:
- echo "deb https://deb.debian.org/debian/ bookworm-backports main" >> /etc/apt/sources.list
- apt-get -qq update && apt-get -qq install -y upx-ucl
script:
- echo "${CI_JOB_ID}" > CI_JOB_ID.txt
- env GOOS=linux GOARCH=amd64 go build -buildmode=pie -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/linux-amd64/go-sendxmpp
- upx $CI_PROJECT_DIR/linux-amd64/go-sendxmpp || true
- env GOOS=linux GOARCH=arm64 go build -buildmode=pie -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/linux-arm64/go-sendxmpp
- upx $CI_PROJECT_DIR/linux-arm64/go-sendxmpp || true
- env GOOS=linux GOARCH=386 go build -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/linux-386/go-sendxmpp
- env GOOS=linux GOARCH=arm go build -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/linux-arm/go-sendxmpp
- env GOOS=windows GOARCH=386 go build -buildmode=pie -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/win386/go-sendxmpp.exe
- env GOOS=windows GOARCH=amd64 go build -buildmode=pie -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/win64/go-sendxmpp.exe
- upx $CI_PROJECT_DIR/win64/go-sendxmpp.exe || true
artifacts:
paths:
- linux-amd64/go-sendxmpp
- linux-arm64/go-sendxmpp
- linux-386/go-sendxmpp
- linux-arm/go-sendxmpp
- win386/go-sendxmpp.exe
- win64/go-sendxmpp.exe
- CI_JOB_ID.txt
expire_in: 2 years
release:
stage: release
@ -74,6 +71,7 @@ release:
release-cli create --name "Release $CI_COMMIT_TAG" --tag-name $CI_COMMIT_TAG --description="`head -n $(expr "$(grep -nm2 "^## " CHANGELOG.md|awk '(NR>1) {print $1}'|cut -f1 -d:) - 2"|bc) CHANGELOG.md`" \
--assets-link "{\"name\":\"Linux amd64\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/linux-amd64/go-sendxmpp\"}" \
--assets-link "{\"name\":\"Linux arm64\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/linux-arm64/go-sendxmpp\"}" \
--assets-link "{\"name\":\"Linux 386\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/linux-386/go-sendxmpp\"}" \
--assets-link "{\"name\":\"Linux arm\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/linux-arm/go-sendxmpp\"}" \
--assets-link "{\"name\":\"Windows 386\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/win386/go-sendxmpp.exe\"}" \
--assets-link "{\"name\":\"Windows amd64\",\"url\":\"https://salsa.debian.org/mdosch/go-sendxmpp/-/jobs/`cat CI_JOB_ID.txt`/artifacts/file/win64/go-sendxmpp.exe\"}"
artifacts:
expire_in: 2 years

@ -1,54 +1,5 @@
# Changelog
## UNRELEASED
### Changed
- Fix Ox encryption in interactive mode (do not add the same recipient key to the keyring over and over again).
## [v0.11.0] 2024-05-29
### Changed
- Move private Ox key into JID folder in ~/.local/share/go-sendxmpp.
- Use `fmt.Errorf()` instead of `errors.New()` to create new error messages.
### Added
- Add new parameter `--subject`.
- Added flag `--fast-off` to disable XEP-0484: Fast Authentication Streamlining Tokens (requires go-xmpp >= 0.2.1).
## [v0.10.0] 2024-04-13
### Changed
- Fixed a race condition in receiving stanzas (requires go-xmpp >= v0.1.5).
### Added
- Add support for SASL2 and BIND2 (via go-xmpp >= v0.2.0).
- Add support for FAST authentication (via go-xmpp >= v0.2.0).
- Add a warning when run by the user *root*.
## [v0.9.0] 2024-03-28
### Changed
- Properly close stream if `Ctrl+C` is pressed in interactive mode.
- Properly close stream if `Ctrl+C` is pressed in listening mode.
- Print OS, architecture and go version for flag `--version`.
- Improve closing of connection (via go-xmpp v0.1.4).
- Don't send stanzas that exceed the size limit provided by XEP-0478 (requires go-xmpp >= v0.1.4).
- Fixed hanging forever in stream close if the server doesn't reply with a closing stream element (via go-xmpp >= v0.1.4).
### Added
- New command line flag `ssdp-off` to disable XEP-0474: SASL SCRAM Downgrade Protection (requires go-xmpp >= v0.1.4).
## [v0.8.4] 2024-03-09
### Changed
- Properly handle lost connection.
- Better compatibility with perl sendxmpp config files.
- Improve file name for private Ox keys.
- Improve fallback behavior if no SRV records are provided.
- Remove 100ms sleep before closing the connection. This should be no more needed since go-xmpp commit 9684a8ff690f0d75e284f8845696c5057926d276.
- Return an error if there is no answer to an IQ within 60s.
- Check for errors after sending the auth message during SCRAM authentication (via go-xmpp v0.1.2).
## [v0.8.3] 2024-02-17
### Changed
- Use a human readable file name for private Ox keys.
- Fix specifying a message via command line flag `-m`.
## [v0.8.2] 2024-01-19
### Changed
- Fix an issue in look up of SRV records (via xmppsrv v0.2.6). Thx mtp.

@ -75,11 +75,10 @@ If no configuration file is present or if the values should be overridden it is
the account details via command line options:
```plain
Usage: go-sendxmpp [-cdilnt] [-a value] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]
Usage: go-sendxmpp [-cdilnt] [-a value] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value] [--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value] [--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…]
-a, --alias=value Set alias/nicknamefor chatrooms.
-c, --chatroom Send message to a chatroom.
-d, --debug Show debugging info.
--fast-off Disable XEP-0484: Fast Authentication Streamlining Tokens.
-f, --file=value Set configuration file. (Default:
~/.config/go-sendxmpp/sendxmpprc)
--headline Send message as type headline.
@ -117,9 +116,6 @@ Usage: go-sendxmpp [-cdilnt] [-a value] [--fast-off] [-f value] [--headline] [--
--raw Send raw XML.
--scram-mech-pinning=value
Enforce the use of a certain SCRAM authentication mechanism.
--ssdp-off Disable XEP-0474: SASL SCRAM Downgrade Protection.
-s, --subject=value
Set message subject.
--timeout=value
Connection timeout in seconds. [10]
-t, --tls Use direct TLS.
@ -142,7 +138,7 @@ cat message.txt | ./go-sendxmpp -f ./sendxmpp recipient1@example.com recipient2@
Send a message to two recipients directly defining account credentials.
```bash
cat message.txt | ./go-sendxmpp -u bob@example.com -p swordfish recipient1@example.com recipient2@example.com
cat message.txt | ./go-sendxmpp -u bob@example.com -j example.com -p swordfish recipient1@example.com recipient2@example.com
```
Send a message to two groupchats (`-c`) using a configuration file.
@ -156,11 +152,6 @@ Send file changes to two groupchats (`-c`) using a configuration file.
```bash
tail -f example.log | ./go-sendxmpp -cif ./sendxmpp chat1@conference.example.com chat2@conference.example.com
```
Send a notification if a long running process finishes.
```bash
waitpid $(pidof -s rsync) && echo "Rsync finished successfully."|go-sendxmpp recipient@example.com || echo "Rsync failed."|go-sendxmpp recipient@example.com
```
### shell completion

@ -16,9 +16,9 @@ import (
func connect(options xmpp.Options, directTLS bool) (*xmpp.Client, error) {
proxy := os.Getenv("HTTP_PROXY")
server := options.User[strings.Index(options.User, "@")+1:]
// Look up SRV records if server is not specified manually.
if options.Host == "" {
server := options.User[strings.Index(options.User, "@")+1:]
// Don't do SRV look ups if proxy is set.
if proxy == "" {
// Look up xmpp-client SRV records.
@ -43,22 +43,16 @@ func connect(options xmpp.Options, directTLS bool) (*xmpp.Client, error) {
}
}
}
}
_, port, _ := net.SplitHostPort(options.Host)
if port == "" {
if options.Host == "" {
options.Host = server
}
// Try port 5223 if directTLS is set and no port is provided.
// Try port 5223 if directTLS is set and no xmpp-client SRV records are provided.
if directTLS {
options.NoTLS = false
options.StartTLS = false
options.Host = net.JoinHostPort(options.Host, "5223")
options.Host = net.JoinHostPort(server, "5223")
} else {
// Try port 5222 if no port is provided and directTLS is not set.
// Try port 5222 if no xmpp-client SRV records are provided and directTLS is not set.
options.NoTLS = true
options.StartTLS = true
options.Host = net.JoinHostPort(options.Host, "5222")
options.Host = net.JoinHostPort(server, "5222")
}
}
// Connect to server

@ -5,7 +5,7 @@
package main
const (
version = "0.11.1-dev"
version = "0.8.2"
// defaults
defaultBufferSize = 100
defaultConfigRowSep = 2
@ -17,6 +17,7 @@ const (
defaultRpadMultiple = 100
defaultRSABits = 4096
defaultShortIDBytes = 4
defaultSleepTime = 100
defaultTimeout = 10
defaultTLSMinVersion = 12
defaultTLS10 = 10
@ -41,7 +42,6 @@ const (
oxAltBody = "This message is encrypted (XEP-0373: OpenPGP for XMPP)."
pubsubPubOptions = "http://jabber.org/protocol/pubsub#publish-options"
strChat = "chat"
strEmpty = ""
strError = "error"
strGroupchat = "groupchat"
strHeadline = "headline"

@ -3,22 +3,21 @@ module salsa.debian.org/mdosch/go-sendxmpp
go 1.21.5
require (
github.com/ProtonMail/gopenpgp/v2 v2.8.0-alpha.1
github.com/beevik/etree v1.4.0
github.com/gabriel-vasile/mimetype v1.4.4
github.com/google/uuid v1.6.0
github.com/ProtonMail/gopenpgp/v2 v2.7.4
github.com/beevik/etree v1.3.0
github.com/gabriel-vasile/mimetype v1.4.3
github.com/pborman/getopt/v2 v2.1.0
github.com/xmppo/go-xmpp v0.2.2-0.20240621203446-ca6fa9ced5fa
golang.org/x/crypto v0.24.0
github.com/xmppo/go-xmpp v0.0.2-0.20240118190409-62928b348398
salsa.debian.org/mdosch/xmppsrv v0.2.6
)
require (
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

@ -1,44 +1,49 @@
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton h1:HKz85FwoXx86kVtTvFke7rgHvq/HoloSUvW5semjFWs=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.8.0-alpha.1 h1:FzlISNTMQiDhsg7q92Cd3Bkvu+7i/iEoAthswamhYrQ=
github.com/ProtonMail/gopenpgp/v2 v2.8.0-alpha.1/go.mod h1:TvBY11CZdahgzQ4iSUMv80emDXYwKlTl3FB1R4EuKnU=
github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs=
github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/ProtonMail/gopenpgp/v2 v2.7.4 h1:Vz/8+HViFFnf2A6XX8JOvZMrA6F5puwNvvF21O1mRlo=
github.com/ProtonMail/gopenpgp/v2 v2.7.4/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=
github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
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/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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA=
github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xmppo/go-xmpp v0.2.1 h1:8Bw6W6RNGTq6ajgMiKxn0iJKYt6Atef5Y0A5AkGWn9Q=
github.com/xmppo/go-xmpp v0.2.1/go.mod h1:H46WSy/5uHW1SWsyJYI6fRZqFrK326qWmFRpVJ2Wwhs=
github.com/xmppo/go-xmpp v0.2.2-0.20240621203446-ca6fa9ced5fa h1:Mq5ks6uzJXT1lLJY8+7A6IcYaFQ8svTkD6eOUt27cWw=
github.com/xmppo/go-xmpp v0.2.2-0.20240621203446-ca6fa9ced5fa/go.mod h1:M7bq312fy4uiXD+3DghCC99YlINWhMmzTqokfR4/QJQ=
github.com/xmppo/go-xmpp v0.0.2-0.20240118190409-62928b348398 h1:uhOv5PCU63UzAHPc6gHHkA/sHKg1WuAulUybPOJmR1g=
github.com/xmppo/go-xmpp v0.0.2-0.20240118190409-62928b348398/go.mod h1:goIl07FSeu+OGrFpTC8fMUTG4Dc7B6LnOZ+qxe0WVi8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -47,24 +52,31 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
salsa.debian.org/mdosch/xmppsrv v0.2.6 h1:+S7ZxRP7BQwQHGJvYhr408UD0XGAd7+RZaVA/xN4EIc=

@ -6,22 +6,14 @@ package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/gob"
"fmt"
"log"
"math/big"
"net/url"
"os"
"regexp"
"runtime"
"strings"
"github.com/google/uuid" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
"golang.org/x/crypto/scrypt" // BSD-3-Clause
)
func validUTF8(s string) string {
@ -36,10 +28,7 @@ func validUTF8(s string) string {
func validURI(s string) (*url.URL, error) {
// Check if URI is valid
uri, err := url.ParseRequestURI(s)
if err != nil {
return uri, fmt.Errorf("validURI: %w", err)
}
return uri, nil
return uri, fmt.Errorf("validURI: %w", err)
}
func readFile(path string) (*bytes.Buffer, error) {
@ -47,179 +36,16 @@ func readFile(path string) (*bytes.Buffer, error) {
if err != nil {
return nil, fmt.Errorf("readFile: %w", err)
}
defer file.Close()
buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(file)
if err != nil {
return nil, fmt.Errorf("readFile: %w", err)
}
return buffer, nil
}
func getFastData(jid string, password string) (xmpp.Fast, error) {
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
var fast xmpp.Fast
fastPath, err := getDataPath(folder)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache folder: %w", err)
}
fastFileLoc := fastPath + "fast.bin"
buf, err := readFile(fastFileLoc)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
decBuf := bytes.NewBuffer(buf.Bytes())
decoder := gob.NewDecoder(decBuf)
err = decoder.Decode(&fast)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
salt := make([]byte, 32)
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to create aes key: %w", err)
}
c, err := aes.NewCipher([]byte(key))
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
nonceSize := gcm.NonceSize()
cryptBuf := []byte(fast.Token)
nonce, cryptBuf := cryptBuf[:nonceSize], cryptBuf[nonceSize:]
tokenBuf, err := gcm.Open(nil, []byte(nonce), cryptBuf, nil)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
fast.Token = string(tokenBuf)
return fast, nil
}
func writeFastData(jid string, password string, fast xmpp.Fast) error {
var encBuf bytes.Buffer
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
fastPath, err := getDataPath(folder)
if err != nil {
return fmt.Errorf("writeFastData: failed to write fast cache file: %w", err)
}
fastFileLoc := fastPath + "fast.bin"
salt := make([]byte, 32)
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
c, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
nonce := make([]byte, gcm.NonceSize())
_, err = rand.Read(nonce)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
buf := gcm.Seal(nonce, nonce, []byte(fast.Token), nil)
fast.Token = string(buf)
encode := gob.NewEncoder(&encBuf)
err = encode.Encode(fast)
if err != nil {
return fmt.Errorf("writeFastData: failed to create fast token file: %w", err)
}
file, err := os.Create(fastFileLoc)
err = file.Close()
if err != nil {
return fmt.Errorf("writeFastData: failed to create fast token file: %w", err)
fmt.Println("error while closing file:", err)
}
defer file.Close()
if runtime.GOOS != "windows" {
_ = file.Chmod(os.FileMode(defaultFileRights))
} else {
_ = file.Chmod(os.FileMode(defaultFileRightsWin))
}
_, err = file.Write(encBuf.Bytes())
if err != nil {
return fmt.Errorf("writeFastData: failed to write fast token file: %w", err)
}
return nil
}
func getClientID(jid string) (string, error) {
var clientID string
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
clientIDLoc, err := getClientIDLoc(folder)
if err != nil {
return strError, err
}
buf, err := readFile(clientIDLoc)
if err != nil {
clientID = uuid.NewString()
file, err := os.Create(clientIDLoc)
if err != nil {
return strEmpty, fmt.Errorf("getClientID: failed to create clientid file: %w", err)
}
defer file.Close()
if runtime.GOOS != "windows" {
_ = file.Chmod(os.FileMode(defaultFileRights))
} else {
_ = file.Chmod(os.FileMode(defaultFileRightsWin))
}
_, err = file.Write([]byte(clientID))
if err != nil {
return strEmpty, fmt.Errorf("getClientID: failed to write client id file: %w", err)
}
} else {
clientID = buf.String()
}
return clientID, nil
}
func getDataPath(folder string) (string, error) {
var err error
var homeDir, dataDir string
switch {
case os.Getenv("$XDG_DATA_HOME") != "":
dataDir = os.Getenv("$XDG_DATA_HOME")
case os.Getenv("$XDG_HOME") != "":
homeDir = os.Getenv("$XDG_HOME")
dataDir = homeDir + "/.local/share"
case os.Getenv("$HOME") != "":
homeDir = os.Getenv("$HOME")
dataDir = homeDir + "/.local/share"
default:
homeDir, err = os.UserHomeDir()
if err != nil {
return strError, fmt.Errorf("getDataPath: failed to determine user dir: %w", err)
}
if homeDir == "" {
return strError, fmt.Errorf("getDataPath: received empty string for home directory")
}
dataDir = homeDir + "/.local/share"
}
if folder != "" && !strings.HasSuffix(folder, "/") {
folder = fmt.Sprintf("%s/", folder)
}
dataDir = fmt.Sprintf("%s/go-sendxmpp/%s", dataDir, folder)
if _, err = os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, defaultDirRights)
if err != nil {
return strError, fmt.Errorf("getDataPath: could not create folder: %w", err)
}
}
return dataDir, nil
}
func getClientIDLoc(folder string) (string, error) {
dataDir, err := getDataPath(folder)
if err != nil {
return strError, fmt.Errorf("getClientIDLoc: %w", err)
}
dataFile := dataDir + "clientid"
return dataFile, nil
return buffer, nil
}
func getRpad(messageLength int) string {
@ -238,9 +64,19 @@ func getRpad(messageLength int) string {
}
func getID() string {
return uuid.NewString()
id := make([]byte, defaultIDBytes)
_, err := rand.Read(id)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x-%x-%x", id[0:4], id[4:8], id[8:])
}
func getShortID() string {
return uuid.NewString()[:6]
id := make([]byte, defaultShortIDBytes)
_, err := rand.Read(id)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x", id[0:4])
}

@ -7,6 +7,7 @@ package main
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"net/http"
"net/url"
@ -67,7 +68,7 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
}
iqDiscoItemsXMLQuery = iqDiscoItemsXML.SelectElement("query")
if iqDiscoItemsXMLQuery == nil {
return "", fmt.Errorf("http-upload: no query element in disco items reply")
return "", errors.New("http-upload: no query element in disco items reply")
}
iqDiscoItemsXMLItems := iqDiscoItemsXMLQuery.SelectElements("item")
@ -117,7 +118,7 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
}
}
if uploadComponent == "" {
return "", fmt.Errorf("http-upload: no http upload component found.")
return "", errors.New("http-upload: no http upload component found.")
}
iqDiscoInfoXMLX := iqDiscoInfoXMLQuery.SelectElements("x")
for _, r := range iqDiscoInfoXMLX {
@ -142,7 +143,7 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
if prevFieldVal.Text() == nsHTTPUpload {
maxFileSize, err = strconv.ParseInt(curFieldVal.Text(), 10, 64)
if err != nil {
return "", fmt.Errorf("http-upload: error while checking server maximum http upload file size.")
return "", errors.New("http-upload: error while checking server maximum http upload file size.")
}
}
}
@ -153,8 +154,9 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
// the best.
if maxFileSize != 0 {
if fileSize > maxFileSize {
return "", fmt.Errorf("http-upload: file size %s MiB is larger than the maximum file size allowed (%s MiB).",
strconv.FormatInt(fileSize/1024/1024, 10), strconv.FormatInt(maxFileSize/1024/1024, 10))
return "", errors.New("http-upload: file size " + strconv.FormatInt(fileSize/1024/1024, 10) +
" MB is larger than the maximum file size allowed (" +
strconv.FormatInt(maxFileSize/1024/1024, 10) + " MB).")
}
}
@ -176,7 +178,7 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
return "", err
}
if uploadSlot.Type != strResult {
return "", fmt.Errorf("http-upload: error while requesting upload slot.")
return "", errors.New("http-upload: error while requesting upload slot.")
}
iqHTTPUploadSlotXML := etree.NewDocument()
err = iqHTTPUploadSlotXML.ReadFromBytes(uploadSlot.Query)
@ -185,18 +187,18 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
}
iqHTTPUploadSlotXMLSlot := iqHTTPUploadSlotXML.SelectElement("slot")
if iqHTTPUploadSlotXMLSlot == nil {
return "", fmt.Errorf("http-upload: no slot element")
return "", errors.New("http-upload: no slot element")
}
iqHTTPUploadSlotXMLPut := iqHTTPUploadSlotXMLSlot.SelectElement("put")
if iqHTTPUploadSlotXMLPut == nil {
return "", fmt.Errorf("http-upload: no put element")
return "", errors.New("http-upload: no put element")
}
iqHTTPUploadSlotXMLPutURL := iqHTTPUploadSlotXMLPut.SelectAttr("url")
if iqHTTPUploadSlotXMLPutURL == nil {
return "", fmt.Errorf("http-upload: no url attribute")
return "", errors.New("http-upload: no url attribute")
}
if !strings.HasPrefix(iqHTTPUploadSlotXMLPutURL.Value, "https://") {
return "", fmt.Errorf("http-upload: upload slot does not provide https")
return "", errors.New("http-upload: upload slot does not provide https")
}
// Upload file
httpTransport := &http.Transport{
@ -235,17 +237,17 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
}
// Test for http status code "200 OK" or "201 Created"
if resp.StatusCode != 200 && resp.StatusCode != 201 {
return "", fmt.Errorf("http-upload: upload failed.")
return "", errors.New("http-upload: upload failed.")
}
// Return http link
iqHTTPUploadSlotXMLGet := iqHTTPUploadSlotXMLSlot.SelectElement("get")
if iqHTTPUploadSlotXMLGet == nil {
return "", fmt.Errorf("http-upload: no get element")
return "", errors.New("http-upload: no get element")
}
iqHTTPUploadSlotXMLGetURL := iqHTTPUploadSlotXMLGet.SelectAttr("url")
if iqHTTPUploadSlotXMLGetURL == nil {
return "", fmt.Errorf("http-upload: no url attribute")
return "", errors.New("http-upload: no url attribute")
}
err = resp.Body.Close()
if err != nil {

@ -8,7 +8,7 @@
package main
import (
"fmt"
"errors"
"strings"
"unicode/utf8"
)
@ -35,7 +35,7 @@ func MarshalJID(input string) (string, error) {
} else {
// If the resource part exists, make sure it isn't empty.
if sep == len(s)-1 {
return input, fmt.Errorf("invalid JID %s: the resourcepart must be larger than 0 bytes", input)
return input, errors.New("Invalid JID" + input + ": The resourcepart must be larger than 0 bytes")
}
resourcepart = s[sep+1:]
s = s[:sep]
@ -52,7 +52,7 @@ func MarshalJID(input string) (string, error) {
domainpart = s
case sep == 0:
// The JID starts with an @ sign (invalid empty localpart)
err = fmt.Errorf("Invalid JID: %s", input)
err = errors.New("Invalid JID:" + input)
return input, err
default:
domainpart = s[sep+1:]
@ -73,11 +73,11 @@ func MarshalJID(input string) (string, error) {
var jid string
if !utf8.ValidString(localpart) || !utf8.ValidString(domainpart) || !utf8.ValidString(resourcepart) {
return input, fmt.Errorf("invalid JID: %s", input)
return input, errors.New("Invalid JID: " + input)
}
if domainpart == "" {
return input, fmt.Errorf("invalid JID: %s", input)
return input, errors.New("Invalid JID: " + input)
}
if localpart == "" {

@ -8,14 +8,13 @@ import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
osUser "os/user"
"runtime"
"strings"
"time"
@ -32,12 +31,15 @@ type configuration struct {
alias string
}
func closeAndExit(client *xmpp.Client, err error) {
func closeAndExit(client *xmpp.Client, cancel context.CancelFunc, err error) {
// Wait for a short time as some messages are not delivered by the server
// if the connection is closed immediately after sending a message.
time.Sleep(defaultSleepTime * time.Millisecond)
cancel()
client.Close()
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
func readMessage(messageFilePath string) (string, error) {
@ -48,7 +50,7 @@ func readMessage(messageFilePath string) (string, error) {
// Check that message file is existing.
_, err = os.Stat(messageFilePath)
if err != nil {
if os.IsNotExist(err) {
return output, fmt.Errorf("readMessage: %w", err)
}
@ -57,7 +59,6 @@ func readMessage(messageFilePath string) (string, error) {
if err != nil {
return output, fmt.Errorf("readMessage: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
@ -68,13 +69,18 @@ func readMessage(messageFilePath string) (string, error) {
}
}
if err = scanner.Err(); err != nil {
if err := scanner.Err(); err != nil {
if err != io.EOF {
return "", fmt.Errorf("readMessage: %w", err)
}
}
return output, nil
err = file.Close()
if err != nil {
fmt.Println("error while closing file:", err)
}
return output, fmt.Errorf("readMessage: %w", err)
}
func main() {
@ -88,7 +94,6 @@ func main() {
message, user, server, password, alias string
oxPrivKey *crypto.Key
recipients []recipientsType
fast xmpp.Fast
)
// Define command line flags.
@ -130,9 +135,6 @@ func main() {
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.")
flagSSDPOff := getopt.BoolLong("ssdp-off", 0, "Disable XEP-0474: SASL SCRAM Downgrade Protection.")
flagSubject := getopt.StringLong("subject", 's', "", "Set message subject.")
flagFastOff := getopt.BoolLong("fast-off", 0, "Disable XEP-0484: Fast Authentication Streamlining Tokens.")
// Parse command line flags.
getopt.Parse()
@ -144,9 +146,7 @@ func main() {
os.Exit(0)
case *flagVersion:
// If requested, show version and quit.
fmt.Println("Go-sendxmpp", version)
system := runtime.GOOS + "/" + runtime.GOARCH
fmt.Println("System:", system, runtime.Version())
fmt.Println("go-sendxmpp", version)
fmt.Println("License: BSD-2-clause")
os.Exit(0)
// Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like
@ -169,22 +169,6 @@ func main() {
log.Fatal("Can't use message type headline for groupchat messages.")
}
// Print a warning if go-sendxmpp is run by the user root on non-windows systems.
if runtime.GOOS != "windows" {
// Get the current user.
currUser, err := osUser.Current()
if err != nil {
log.Fatal("Failed to get current user: ", err)
}
if currUser.Username == "root" {
fmt.Println("WARNING: It seems you are running go-sendxmpp as root user.\n" +
"This is 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.")
}
}
switch *flagSCRAMPinning {
case "", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS",
"SCRAM-SHA-512", "SCRAM-SHA-512-PLUS":
@ -233,22 +217,6 @@ func main() {
if *flagPassword != "" {
password = *flagPassword
}
// If no server part is specified in the username but a server is specified
// just assume the server is identical to the server part and hope for the
// best. This is for compatibility with the old perl sendxmpp config files.
var serverpart string
if !strings.Contains(user, "@") && server != "" {
// Remove port if server contains it.
if strings.Contains(server, ":") {
serverpart, _, err = net.SplitHostPort(server)
if err != nil {
log.Fatal(err)
}
} else {
serverpart = server
}
user = user + "@" + serverpart
}
switch {
// Use "go-sendxmpp" if no nick is specified via config or command line flag.
@ -262,20 +230,6 @@ func main() {
// Timeout
timeout := time.Duration(*flagTimeout) * time.Second
clientID, err := getClientID(user)
if err != nil {
fmt.Println(err)
}
if !*flagFastOff {
fast, _ = getFastData(user, password)
// Reset FAST token and mechanism if expired.
if time.Now().After(fast.Expiry) {
fast.Token = ""
fast.Mechanism = ""
}
}
// Use ALPN
var tlsConfig tls.Config
tlsConfig.ServerName = user[strings.Index(user, "@")+1:]
@ -296,14 +250,12 @@ func main() {
os.Exit(0)
}
resource := "go-sendxmpp." + getShortID()
// Set XMPP connection options.
options := xmpp.Options{
Host: server,
User: user,
DialTimeout: timeout,
Resource: resource,
Resource: "go-sendxmpp." + getShortID(),
Password: password,
// NoTLS doesn't mean that no TLS is used at all but that instead
// of using an encrypted connection to the server (direct TLS)
@ -311,17 +263,11 @@ func main() {
// set when NoTLS is set go-sendxmpp won't use unencrypted
// client-to-server connections.
// See https://pkg.go.dev/github.com/xmppo/go-xmpp#Options
NoTLS: !*flagDirectTLS,
StartTLS: !*flagDirectTLS,
Debug: *flagDebug,
TLSConfig: &tlsConfig,
Mechanism: *flagSCRAMPinning,
SSDP: !*flagSSDPOff,
UserAgentSW: resource,
UserAgentID: clientID,
Fast: !*flagFastOff,
FastToken: fast.Token,
FastMechanism: fast.Mechanism,
NoTLS: !*flagDirectTLS,
StartTLS: !*flagDirectTLS,
Debug: *flagDebug,
TLSConfig: &tlsConfig,
Mechanism: *flagSCRAMPinning,
}
// Read message from file.
@ -366,41 +312,13 @@ func main() {
// Connect to server.
client, err := connect(options, *flagDirectTLS)
if err != nil {
if fast.Token != "" {
// Reset FAST token and mechanism if FAST login failed.
fast.Token = ""
fast.Mechanism = ""
fast.Expiry = time.Now()
err := writeFastData(user, password, fast)
if err != nil {
fmt.Println(err)
}
options.FastToken = ""
// Try to connect to server without FAST.
client, err = connect(options, *flagDirectTLS)
if err != nil {
log.Fatal(err)
}
} else {
log.Fatal(err)
}
log.Fatal(err)
}
// Update fast token if a new one is received or expiry time is reduced.
if (client.Fast.Token != "" && client.Fast.Token != fast.Token) ||
(client.Fast.Expiry.Before(fast.Expiry) && !client.Fast.Expiry.IsZero()) {
fast.Token = client.Fast.Token
fast.Mechanism = client.Fast.Mechanism
fast.Expiry = client.Fast.Expiry
err := writeFastData(user, password, fast)
if err != nil {
fmt.Println(err)
}
}
iqc := make(chan xmpp.IQ, defaultBufferSize)
msgc := make(chan xmpp.Chat, defaultBufferSize)
ctx, cancel := context.WithCancel(context.Background())
go rcvStanzas(client, ctx, iqc, msgc)
go rcvStanzas(client, iqc, msgc, ctx)
for _, r := range getopt.Args() {
var re recipientsType
re.Jid = r
@ -418,8 +336,7 @@ func main() {
for i, recipient := range recipients {
validatedJid, err := MarshalJID(recipient.Jid)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
recipients[i].Jid = validatedJid
}
@ -428,62 +345,52 @@ func main() {
case *flagOxGenPrivKeyX25519:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "x25519")
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
os.Exit(0)
case *flagOxGenPrivKeyRSA:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "rsa")
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
os.Exit(0)
case *flagOxImportPrivKey != "":
validatedOwnJid, err := MarshalJID(user)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey,
client, iqc)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
os.Exit(0)
case *flagOxDeleteNodes:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
err = oxDeleteNodes(validatedOwnJid, client, iqc)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
os.Exit(0)
case *flagOx:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
}
@ -491,8 +398,7 @@ func main() {
message, err = httpUpload(client, iqc, tlsConfig.ServerName,
*flagHTTPUpload, timeout)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
}
@ -502,8 +408,7 @@ func main() {
// Check if the URI is valid.
uri, err := validURI(message)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
message = uri.String()
}
@ -526,8 +431,7 @@ func main() {
_, err = client.JoinMUCNoHistory(recipient.Jid, alias)
}
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
}
}
@ -539,8 +443,7 @@ func main() {
// Send raw XML
_, err = client.SendOrg(message)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
case *flagInteractive:
// Send in endless loop (for usage with e.g. "tail -f").
@ -551,23 +454,16 @@ func main() {
go func() {
for range c {
cancel()
closeAndExit(client, nil)
client.Close()
os.Exit(0)
}
}()
for {
message, err = reader.ReadString('\n')
message = strings.TrimSuffix(message, "\n")
if err != nil {
select {
case <-ctx.Done():
return
default:
if err != nil {
cancel()
closeAndExit(client, fmt.Errorf("failed to read from stdin"))
}
}
closeAndExit(client, cancel, errors.New("failed to read from stdin"))
}
message = strings.TrimSuffix(message, "\n")
// Remove invalid code points.
message = validUTF8(message)
@ -581,7 +477,7 @@ func main() {
continue
}
oxMessage, err := oxEncrypt(client, oxPrivKey,
recipient.Jid, *recipient.OxKeyRing, message, *flagSubject)
recipient.Jid, recipient.OxKeyRing, message)
if err != nil {
fmt.Println("Ox: couldn't encrypt to",
recipient.Jid)
@ -589,33 +485,21 @@ func main() {
}
_, err = client.SendOrg(oxMessage)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
default:
_, err = client.Send(xmpp.Chat{
Remote: recipient.Jid,
Type: msgType, Text: message,
Subject: *flagSubject,
})
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
}
}
}
case *flagListen:
tz := time.Now().Location()
// Quit if ^C is pressed.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
cancel()
closeAndExit(client, nil)
}
}()
for {
v := <-msgc
switch {
@ -693,7 +577,6 @@ func main() {
_, err = client.Send(xmpp.Chat{
Remote: recipient.Jid,
Type: msgType, Ooburl: message, Text: message,
Subject: *flagSubject,
})
if err != nil {
fmt.Println("Couldn't send message to",
@ -702,15 +585,9 @@ func main() {
// (Hopefully) temporary workaround due to go-xmpp choking on URL encoding.
// Once this is fixed in the lib the http-upload case above can be reused.
case *flagOOBFile != "":
var msg string
if *flagSubject != "" {
msg = fmt.Sprintf("<message to='%s' type='%s'><subject>%s</subject><body>%s</body><x xmlns='jabber:x:oob'><url>%s</url></x></message>",
recipient.Jid, msgType, *flagSubject, message, message)
} else {
msg = fmt.Sprintf("<message to='%s' type='%s'><body>%s</body><x xmlns='jabber:x:oob'><url>%s</url></x></message>",
recipient.Jid, msgType, message, message)
}
_, err = client.SendOrg(msg)
_, err = client.SendOrg("<message to='" + recipient.Jid + "' type='" +
msgType + "'><body>" + message + "</body><x xmlns='jabber:x:oob'><url>" +
message + "</url></x></message>")
if err != nil {
fmt.Println("Couldn't send message to",
recipient.Jid)
@ -720,29 +597,25 @@ func main() {
continue
}
oxMessage, err := oxEncrypt(client, oxPrivKey,
recipient.Jid, *recipient.OxKeyRing, message, *flagSubject)
recipient.Jid, recipient.OxKeyRing, message)
if err != nil {
fmt.Println("Ox: couldn't encrypt to", recipient.Jid)
continue
}
_, err = client.SendOrg(oxMessage)
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
default:
_, err = client.Send(xmpp.Chat{
Remote: recipient.Jid,
Type: msgType, Text: message,
Subject: *flagSubject,
})
if err != nil {
cancel()
closeAndExit(client, err)
closeAndExit(client, cancel, err)
}
}
}
}
cancel()
closeAndExit(client, nil)
closeAndExit(client, cancel, nil)
}

@ -1,12 +1,12 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "GO\-SENDXMPP" "1" "June 2024" ""
.TH "GO\-SENDXMPP" "1" "January 2024" ""
.SH "NAME"
\fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\.
.SH "SYNOPSIS"
\fBgo\-sendxmpp [\-cdilnt] [\-a value] [\-\-fast\-off] [\-f value] [\-\-headline] [\-\-help] [\-h value] [\-j value] [\-m value] [\-\-muc\-password value] [\-\-oob\-file value] [\-\-ox] [\-\-ox\-delete\-nodes] [\-\-ox\-genprivkey\-rsa] [\-\-ox\-genprivkey\-x25519] [\-\-ox\-import\-privkey value] [\-\-ox\-passphrase value] [\-p value] [\-\-raw] [\-\-scram\-mech\-pinning value] [\-\-ssdp\-off] [\-s value] [\-\-timeout value] [\-\-tls\-version value] [\-u value] [\-\-version] [recipients…]\fR
\fBgo\-sendxmpp [\-cdilnt] [\-a value] [\-f value] [\-\-headline] [\-\-help] [\-h value] [\-j value] [\-m value] [\-\-muc\-password value] [\-\-oob\-file value] [\-\-ox] [\-\-ox\-delete\-nodes] [\-\-ox\-genprivkey\-rsa] [\-\-ox\-genprivkey\-x25519] [\-\-ox\-import\-privkey value] [\-\-ox\-passphrase value] [\-p value] [\-\-raw] [\-\-scram\-mech\-pinning value] [\-\-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\.
A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) \fBsendxmpp\fR\.
.br
You can either pipe a programs output to \fBgo\-sendxmpp\fR, write in your terminal (put \fB^D\fR in a new line to finish) or send the input from a file (\fB\-m\fR or \fB\-\-message\fR)\. The account data is expected at \fB~/\.config/go\-sendxmpp/config\fR (preferred), \fB~/\.config/go\-sendxmpp/sendxmpprc\fR (deprecated) \fB~/\.sendxmpprc\fR (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with \fB\-f\fR or \fB\-\-file\fR\.
.SH "OPTIONS"
@ -20,9 +20,6 @@ Send message to a chatroom\.
\fB\-d\fR, \fB\-\-debug\fR
Show debugging info\.
.TP
\fB\-\-fast\-off\fR
Disable XEP\-0484: Fast Authentication Streamlining Tokens\.
.TP
\fB\-f\fR, \fB\-\-file\fR=[\fIvalue\fR]
Set configuration file\. (Default: ~/\.config/go\-sendxmpp/config)
.TP
@ -80,6 +77,9 @@ Import an existing private OpenPGP key\.
\fB\-\-ox\-passphrase\fR=[\fIvalue\fR]
Passphrase for locking and unlocking the private OpenPGP key\.
.TP
\fB\-\-tls\-version\fR=[\fIvalue\fR]
Minimal TLS version\. 10 (TLSv1\.0), 11 (TLSv1\.1), 12 (TLSv1\.2), 13 (TLSv1\.3) (Default: 12)
.TP
\fB\-p\fR, \fB\-\-password\fR=[\fIvalue\fR]
Password for XMPP account\.
.TP
@ -89,21 +89,12 @@ Send raw XML\. To send raw XML to a contact as normal chat message no contact mu
\fB\-\-scram\-mech\-pinning=[<value>]\fR
Enforce the use of a certain SCRAM authentication mechanism\. Currently go\-sendxmpp supports \fBSCRAM\-SHA\-1\fR, \fBSCRAM\-SHA\-1\-PLUS\fR, \fBSCRAM\-SHA\-256\fR, \fBSCRAM\-SHA\-256\-PLUS\fR, \fBSCRAM\-SHA\-512\fR and \fBSCRAM\-SHA\-512\-PLUS\fR\. You should know what you are doing when using this setting and make sure the chosen mechanism is supported by the server\. If not set, go\-sendxmpp will use XEP\-0474 to prevent downgrade attacks (needs server support)\.
.TP
\fB\-\-ssdp\-off\fR
Disable XEP\-0474: SASL SCRAM Downgrade Protection\.
.TP
\fB\-s\fR, \fB\-\-subject\fR=[\fIvalue\fR]
Set message subject\.
.TP
\fB\-\-timeout=\fR[\fIvalue\fR]
Connection timeout in seconds\. (Default: 10)
.TP
\fB\-t\fR, \fB\-\-tls\fR
Use direct TLS\.
.TP
\fB\-\-tls\-version\fR=[\fIvalue\fR]
Minimal TLS version\. 10 (TLSv1\.0), 11 (TLSv1\.1), 12 (TLSv1\.2), 13 (TLSv1\.3) (Default: 12)
.TP
\fB\-u\fR, \fB\-\-username\fR=[\fIvalue\fR]
Username for XMPP account (JID)\.
.TP
@ -114,13 +105,6 @@ Show version information\.
A SOCKS5 proxy can be used by setting the environment variable \fBHTTP_PROXY\fR\. This feature is considered experimental and there is no guarantee that there won't be any connections not using the proxy although it didn't happen during testing\.
.P
\fBHTTP_PROXY="socks5://127\.0\.0\.1:9050" go\-sendxmpp \-\-http\-upload file\.txt user@example\.org\fR
.SH "TIPS"
.SS "USAGE BY ROOT"
In general it's a good advice to only perform commands as root when it is strictly necessary\. To be able to send the output from commands, that need to be performed as root, with go\-sendxmpp without invoking go\-sendxmpp by root sudo can be used\.
.P
In this example there is a user \fBsendxmpp\fR with a go\-sendxmpp config in its \fB$HOME\fR:
.P
\fB# command\-that\-requires\-root|sudo \-H \-u sendxmpp go\-sendxmpp me@example\.org\fR
.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\.
@ -128,10 +112,6 @@ There are no shell completions yet (contributions welcome) but for zsh it is pos
Just place the following in your \fB~/\.zshrc\fR or \fB~/\.zshrc\.local\fR:
.P
\fBcompdef _gnu_generic go\-sendxmpp\fR
.SS "FISH"
There are no shell completions yet, but FISH can generate them from the man page with following command:
.P
\fBfish_update_completions\fR
.SH "CHAT"
Feel free to join \fIhttps://join\.jabber\.network/#go\-sendxmpp@chat\.mdosch\.de?join\fR\.
.SH "AUTHOR"

@ -58,7 +58,6 @@
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#OPTIONS">OPTIONS</a>
<a href="#ENVIRONMENT-VARIABLES">ENVIRONMENT VARIABLES</a>
<a href="#TIPS">TIPS</a>
<a href="#SHELL-COMPLETIONS">SHELL COMPLETIONS</a>
<a href="#CHAT">CHAT</a>
<a href="#AUTHOR">AUTHOR</a>
@ -81,14 +80,14 @@
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>go-sendxmpp [-cdilnt] [-a value] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
<p><code>go-sendxmpp [-cdilnt] [-a value] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
[--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value]
[--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--timeout value]
[--tls-version value] [-u value] [--version] [recipients…]</code></p>
[--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--timeout value] [--tls-version value] [-u value]
[--version] [recipients…]</code></p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>A tool to send messages to an XMPP contact or MUC inspired by <code>sendxmpp</code>. <br>
<p>A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) <code>sendxmpp</code>. <br>
You can either pipe a programs output to <code>go-sendxmpp</code>, write in your terminal (put <code>^D</code> in a new line to
finish) or send the input from a file (<code>-m</code> or <code>--message</code>).
The account data is expected at <code>~/.config/go-sendxmpp/config</code> (preferred), <code>~/.config/go-sendxmpp/sendxmpprc</code>
@ -108,8 +107,6 @@ file location is specified with <code>-f</code> or <code>--file</code>.</p>
<code>-d</code>, <code>--debug</code>
</dt>
<dd>Show debugging info.</dd>
<dt><code>--fast-off</code></dt>
<dd>Disable XEP-0484: Fast Authentication Streamlining Tokens.</dd>
<dt>
<code>-f</code>, <code>--file</code>=[<var>value</var>]</dt>
<dd>Set configuration file. (Default: ~/.config/go-sendxmpp/config)</dd>
@ -180,6 +177,9 @@ it might be imported using <code>--ox-import-privkey</code>.</dd>
<code>--ox-passphrase</code>=[<var>value</var>]</dt>
<dd>Passphrase for locking and unlocking the private OpenPGP key.</dd>
<dt>
<code>--tls-version</code>=[<var>value</var>]</dt>
<dd>Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2), 13 (TLSv1.3) (Default: 12)</dd>
<dt>
<code>-p</code>, <code>--password</code>=[<var>value</var>]</dt>
<dd>Password for XMPP account.</dd>
<dt><code>--raw</code></dt>
@ -191,11 +191,6 @@ To send raw XML to a MUC you have to specify the MUC via <code>-c</code> and go-
and <strong>SCRAM-SHA-512-PLUS</strong>. You should know what you are doing when using this setting and
make sure the chosen mechanism is supported by the server. If not set, go-sendxmpp will use XEP-0474
to prevent downgrade attacks (needs server support).</dd>
<dt><code>--ssdp-off</code></dt>
<dd>Disable XEP-0474: SASL SCRAM Downgrade Protection.</dd>
<dt>
<code>-s</code>, <code>--subject</code>=[<var>value</var>]</dt>
<dd>Set message subject.</dd>
<dt>
<code>--timeout=</code>[<var>value</var>]</dt>
<dd>Connection timeout in seconds. (Default: 10)</dd>
@ -204,9 +199,6 @@ to prevent downgrade attacks (needs server support).</dd>
</dt>
<dd>Use direct TLS.</dd>
<dt>
<code>--tls-version</code>=[<var>value</var>]</dt>
<dd>Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2), 13 (TLSv1.3) (Default: 12)</dd>
<dt>
<code>-u</code>, <code>--username</code>=[<var>value</var>]</dt>
<dd>Username for XMPP account (JID).</dd>
<dt><code>--version</code></dt>
@ -225,20 +217,6 @@ testing.</p>
HTTP_PROXY="socks5://127.0.0.1:9050" go-sendxmpp --http-upload file.txt user@example.org
</code></p>
<h2 id="TIPS">TIPS</h2>
<h3 id="USAGE-BY-ROOT">USAGE BY ROOT</h3>
<p>In general it's a good advice to only perform commands as root when it is strictly necessary. To be able to send
the output from commands, that need to be performed as root, with go-sendxmpp without invoking go-sendxmpp by
root sudo can be used.</p>
<p>In this example there is a user <strong>sendxmpp</strong> with a go-sendxmpp config in its <code>$HOME</code>:</p>
<p><code>
# command-that-requires-root|sudo -H -u sendxmpp go-sendxmpp me@example.org
</code></p>
<h2 id="SHELL-COMPLETIONS">SHELL COMPLETIONS</h2>
<h3 id="ZSH">ZSH</h3>
@ -253,14 +231,6 @@ good enough.</p>
compdef _gnu_generic go-sendxmpp
</code></p>
<h3 id="FISH">FISH</h3>
<p>There are no shell completions yet, but FISH can generate them from the man page with following command:</p>
<p><code>
fish_update_completions
</code></p>
<h2 id="CHAT">CHAT</h2>
<p>Feel free to join <a href="https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join" data-bare-link="true">https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join</a>.</p>
@ -284,7 +254,7 @@ License: BSD 2-clause License</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>June 2024</li>
<li class='tc'>January 2024</li>
<li class='tr'>go-sendxmpp(1)</li>
</ol>

@ -3,14 +3,14 @@ go-sendxmpp(1) -- A tool to send messages to an XMPP contact or MUC.
## SYNOPSIS
`go-sendxmpp [-cdilnt] [-a value] [--fast-off] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
`go-sendxmpp [-cdilnt] [-a value] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
[--oob-file value] [--ox] [--ox-delete-nodes] [--ox-genprivkey-rsa] [--ox-genprivkey-x25519] [--ox-import-privkey value]
[--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--timeout value]
[--tls-version value] [-u value] [--version] [recipients…]`
[--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--timeout value] [--tls-version value] [-u value]
[--version] [recipients…]`
## DESCRIPTION
A tool to send messages to an XMPP contact or MUC inspired by `sendxmpp`.
A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) `sendxmpp`.
You can either pipe a programs output to `go-sendxmpp`, write in your terminal (put `^D` in a new line to
finish) or send the input from a file (`-m` or `--message`).
The account data is expected at `~/.config/go-sendxmpp/config` (preferred), `~/.config/go-sendxmpp/sendxmpprc`
@ -28,9 +28,6 @@ Send message to a chatroom.
* `-d`, `--debug`:
Show debugging info.
* `--fast-off`:
Disable XEP-0484: Fast Authentication Streamlining Tokens.
* `-f`, `--file`=[<value>]:
Set configuration file. (Default: ~/.config/go-sendxmpp/config)
@ -103,6 +100,9 @@ Import an existing private OpenPGP key.
* `--ox-passphrase`=[<value>]:
Passphrase for locking and unlocking the private OpenPGP key.
* `--tls-version`=[<value>]:
Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2), 13 (TLSv1.3) (Default: 12)
* `-p`, `--password`=[<value>]:
Password for XMPP account.
@ -117,21 +117,12 @@ and **SCRAM-SHA-512-PLUS**. You should know what you are doing when using this s
make sure the chosen mechanism is supported by the server. If not set, go-sendxmpp will use XEP-0474
to prevent downgrade attacks (needs server support).
* `--ssdp-off`:
Disable XEP-0474: SASL SCRAM Downgrade Protection.
* `-s`, `--subject`=[<value>]:
Set message subject.
* `--timeout=`[<value>]:
Connection timeout in seconds. (Default: 10)
* `-t`, `--tls`:
Use direct TLS.
* `--tls-version`=[<value>]:
Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2), 13 (TLSv1.3) (Default: 12)
* `-u`, `--username`=[<value>]:
Username for XMPP account (JID).
@ -150,21 +141,6 @@ testing.
HTTP_PROXY="socks5://127.0.0.1:9050" go-sendxmpp --http-upload file.txt user@example.org
```
## TIPS
### USAGE BY ROOT
In general it's a good advice to only perform commands as root when it is strictly necessary. To be able to send
the output from commands, that need to be performed as root, with go-sendxmpp without invoking go-sendxmpp by
root sudo can be used.
In this example there is a user **sendxmpp** with a go-sendxmpp config in its `$HOME`:
```
# command-that-requires-root|sudo -H -u sendxmpp go-sendxmpp me@example.org
```
## SHELL COMPLETIONS
### ZSH
@ -179,14 +155,6 @@ Just place the following in your `~/.zshrc` or `~/.zshrc.local`:
compdef _gnu_generic go-sendxmpp
```
### FISH
There are no shell completions yet, but FISH can generate them from the man page with following command:
```
fish_update_completions
```
## CHAT
Feel free to join [https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join](https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join).

143
ox.go

@ -6,6 +6,7 @@ package main
import (
"encoding/base64"
"errors"
"fmt"
"log"
"os"
@ -38,11 +39,11 @@ func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error {
}
query = nodeListReply.SelectElement("query")
if query == nil {
return fmt.Errorf("error parsing iq reply")
return errors.New("error parsing iq reply")
}
items := query.SelectElements("item")
if items == nil {
return fmt.Errorf("error parsing iq reply")
return errors.New("error parsing iq reply")
}
for _, item := range items {
node := item.SelectAttr("node")
@ -105,26 +106,26 @@ func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string,
}
signcrypt := doc.SelectElement("signcrypt")
if signcrypt == nil {
return strError, time.Now(), fmt.Errorf("ox: no signcrypt element")
return strError, time.Now(), errors.New("ox: no signcrypt element")
}
to := signcrypt.SelectElement("to")
if to == nil {
return strError, time.Now(), fmt.Errorf("ox: no to element")
return strError, time.Now(), errors.New("ox: no to element")
}
jid := to.SelectAttr("jid")
if jid == nil {
return strError, time.Now(), fmt.Errorf("ox: no jid attribute")
return strError, time.Now(), errors.New("ox: no jid attribute")
}
if strings.Split(jid.Value, "/")[0] != user {
return strError, time.Now(), fmt.Errorf("ox: encrypted for wrong user")
return strError, time.Now(), errors.New("ox: encrypted for wrong user")
}
timestamp := signcrypt.SelectElement("time")
if timestamp == nil {
return strError, time.Now(), fmt.Errorf("ox: no time element")
return strError, time.Now(), errors.New("ox: no time element")
}
stamp := timestamp.SelectAttr("stamp")
if stamp == nil {
return strError, time.Now(), fmt.Errorf("ox: no stamp attribute")
return strError, time.Now(), errors.New("ox: no stamp attribute")
}
msgStamp, err := time.Parse("2006-01-02T15:04:05Z0700", stamp.Value)
if err != nil {
@ -132,7 +133,7 @@ func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string,
}
payload := signcrypt.SelectElement("payload")
if payload == nil {
return strError, time.Now(), fmt.Errorf("ox: no payload element")
return strError, time.Now(), errors.New("ox: no payload element")
}
body := payload.SelectElement("body")
if body == nil {
@ -172,7 +173,7 @@ func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iq
}
entity := key.GetEntity()
if entity.Identities[xmppURI] == nil {
return fmt.Errorf("Key identity is not %s", xmppURI)
return errors.New("Key identity is not " + xmppURI)
}
pk, err := key.GetPublicKey()
if err != nil {
@ -261,19 +262,19 @@ func oxPublishPubKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, pubKey *
return fmt.Errorf("oxPublishPubKey: iq failure publishing public key: %w", err)
}
if iqReply.Type != strResult {
return fmt.Errorf("error while publishing public key")
return errors.New("error while publishing public key")
}
ownPubKeyRingFromPubsub, err := oxRecvPublicKeys(client, iqc, jid, fingerprint)
if err != nil {
return fmt.Errorf("couldn't successfully verify public key upload")
return errors.New("couldn't successfully verify public key upload")
}
ownPubKeyFromPubsub := ownPubKeyRingFromPubsub.GetKeys()[0]
ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize()
if err != nil {
return fmt.Errorf("couldn't successfully verify public key upload")
return errors.New("couldn't successfully verify public key upload")
}
if pubKeyBase64 != base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized) {
return fmt.Errorf("couldn't successfully verify public key upload")
return errors.New("couldn't successfully verify public key upload")
}
root = etree.NewDocument()
root.WriteSettings.AttrSingleQuote = true
@ -309,51 +310,72 @@ func oxPublishPubKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, pubKey *
return fmt.Errorf("oxPublishPubKey: iq failure publishing public key list: %w", err)
}
if iqReply.Type != strResult {
return fmt.Errorf("couldn't publish public key list")
return errors.New("couldn't publish public key list")
}
return nil
}
func oxGetPrivKeyLoc(jid string) (string, error) {
dataDir, err := getDataPath(strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1))
if err != nil {
return strError, fmt.Errorf("oxGetPrivKeyLoc: %w", err)
}
oldDataDir, err := getDataPath("oxprivkeys/")
if err != nil {
return strError, fmt.Errorf("oxGetPrivKeyLoc: %w", err)
}
// TODO: Remove handling of oldDataFile in a later version when it's very likely that there are no
// more versions in use using the oldDataFile (<0.8.3).
oldDataFile := oldDataDir + base64.StdEncoding.EncodeToString([]byte(jid))
oldDataFile2 := oldDataDir + strings.Replace(jid, "@", "_at_", -1)
oldDataFile3 := oldDataDir + strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
dataFile := dataDir + "oxprivkey"
if _, err := os.Stat(oldDataFile); err == nil {
err := os.Rename(oldDataFile, dataFile)
var err error
var homeDir, dataDir string
switch {
case os.Getenv("$XDG_DATA_HOME") != "":
dataDir = os.Getenv("$XDG_DATA_HOME")
case os.Getenv("$XDG_HOME") != "":
homeDir = os.Getenv("$XDG_HOME")
dataDir = homeDir + "/.local/share"
case os.Getenv("$HOME") != "":
homeDir = os.Getenv("$HOME")
dataDir = homeDir + "/.local/share"
default:
homeDir, err = os.UserHomeDir()
if err != nil {
return dataFile, err
return strError, fmt.Errorf("oxGetPrivKeyLoc: failed to determine user dir: %w", err)
}
}
if _, err := os.Stat(oldDataFile2); err == nil {
err := os.Rename(oldDataFile2, dataFile)
if err != nil {
return dataFile, err
if homeDir == "" {
return strError, errors.New("oxGetPrivKeyLoc: received empty string for home directory")
}
dataDir = homeDir + "/.local/share"
}
if _, err := os.Stat(oldDataFile3); err == nil {
err := os.Rename(oldDataFile3, dataFile)
dataDir += "/go-sendxmpp/oxprivkeys/"
if _, err = os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, defaultDirRights)
if err != nil {
return dataFile, err
return strError, fmt.Errorf("oxGetPrivKeyLoc: could not create folder for private keys: %w", err)
}
}
dataFile := dataDir + base64.StdEncoding.EncodeToString([]byte(jid))
return dataFile, nil
}
func oxGetPubKeyLoc(fingerprint string) (string, error) {
dataDir, err := getDataPath("oxpubkeys/")
if err != nil {
return strError, fmt.Errorf("oxGetPubKeyLoc: %w", err)
var err error
var homeDir, dataDir string
switch {
case os.Getenv("$XDG_DATA_HOME") != "":
dataDir = os.Getenv("$XDG_DATA_HOME")
case os.Getenv("$XDG_HOME") != "":
homeDir = os.Getenv("$XDG_HOME")
dataDir = homeDir + "/.local/share"
case os.Getenv("$HOME") != "":
homeDir = os.Getenv("$HOME")
dataDir = homeDir + "/.local/share"
default:
homeDir, err = os.UserHomeDir()
if err != nil {
return strError, fmt.Errorf("oxGetPubKeyLoc: failed to determine user dir: %w", err)
}
if homeDir == "" {
return strError, errors.New("oxGetPubKeyLoc: received empty string for home directory")
}
dataDir = homeDir + "/.local/share"
}
dataDir += "/go-sendxmpp/oxpubkeys/"
if _, err = os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, defaultDirRights)
if err != nil {
return strError, fmt.Errorf("oxGetPubKeyLoc: could not create folder for public keys: %w", err)
}
}
dataFile := dataDir + fingerprint
return dataFile, nil
@ -391,7 +413,7 @@ func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) {
log.Fatal("Ox: private key is locked.")
}
if key.IsExpired() {
return nil, fmt.Errorf("Ox: private key is expired: %s", key.GetFingerprint())
return nil, errors.New("Ox: private key is expired: " + key.GetFingerprint())
}
return key, nil
}
@ -477,7 +499,7 @@ func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, f
return nil, fmt.Errorf("oxRecvPublicKeys: iq error requesting public keys: %w", err)
}
if oxPublicKey.Type != strResult {
return nil, fmt.Errorf("error while requesting public key for %s",
return nil, errors.New("error while requesting public key for " +
recipient)
}
oxPublicKeyXML := etree.NewDocument()
@ -491,15 +513,18 @@ func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, f
}
oxPublicKeyXMLPubsub := oxPublicKeyXML.SelectElement("pubsub")
if oxPublicKeyXMLPubsub == nil {
return nil, fmt.Errorf("ox: no pubsub element in reply to public key request")
return nil, errors.New("ox: no pubsub element in reply to public " +
"key request")
}
oxPublicKeyXMLItems := oxPublicKeyXMLPubsub.SelectElement("items")
if oxPublicKeyXMLItems == nil {
return nil, fmt.Errorf("ox: no items element in reply to public key request")
return nil, errors.New("ox: no items element in reply to public " +
"key request")
}
oxPublicKeyXMLItem := oxPublicKeyXMLItems.SelectElement("item")
if oxPublicKeyXMLItem == nil {
return nil, fmt.Errorf("ox: no item element in reply to public key request")
return nil, errors.New("ox: no item element in reply to public " +
"key request")
}
oxPublicKeyXMLPubkeys := oxPublicKeyXMLItem.SelectElements("pubkey")
for _, r := range oxPublicKeyXMLPubkeys {
@ -516,7 +541,7 @@ func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, f
return nil, fmt.Errorf("oxRecvPublicKeys: failed to decode public key: %w", err)
}
if key.IsExpired() {
return nil, fmt.Errorf("Key is expired: %s", fingerprint)
return nil, errors.New("Key is expired: " + fingerprint)
}
err = keyring.AddKey(key)
if err != nil {
@ -548,7 +573,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
log.Fatal(err)
}
if oxPublicKeyList.Type != strResult {
return nil, fmt.Errorf("error while requesting public openpgp keys for %s",
return nil, errors.New("error while requesting public openpgp keys for " +
recipient)
}
oxPubKeyListXML := etree.NewDocument()
@ -565,19 +590,19 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
oxPubKeyListXMLPubsub := oxPubKeyListXML.SelectElement("pubsub")
if oxPubKeyListXMLPubsub == nil {
return nil, fmt.Errorf("ox: no pubsub element in public key list")
return nil, errors.New("ox: no pubsub element in public key list")
}
oxPubKeyListXMLPubsubItems := oxPubKeyListXMLPubsub.SelectElement("items")
if oxPubKeyListXMLPubsubItems == nil {
return nil, fmt.Errorf("ox: no items element in public key list")
return nil, errors.New("ox: no items element in public key list")
}
oxPubKeyListXMLPubsubItemsItem := oxPubKeyListXMLPubsubItems.SelectElement("item")
if oxPubKeyListXMLPubsubItemsItem == nil {
return nil, fmt.Errorf("ox: no item element in public key list")
return nil, errors.New("ox: no item element in public key list")
}
oxPubKeyListXMLPubsubItemsItemPkl := oxPubKeyListXMLPubsubItemsItem.SelectElement("public-keys-list")
if oxPubKeyListXMLPubsubItemsItemPkl == nil {
return nil, fmt.Errorf("ox: no public-keys-list element")
return nil, errors.New("ox: no public-keys-list element")
}
oxPubKeyListXMLPubsubItemsItemPklPm := oxPubKeyListXMLPubsubItemsItemPkl.SelectElements("pubkey-metadata")
for _, r := range oxPubKeyListXMLPubsubItemsItemPklPm {
@ -599,7 +624,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
}
}
if pubKeyRingID == "none" {
return nil, fmt.Errorf("server didn't provide public key fingerprints for %s", recipient)
return nil, errors.New("server didn't provide public key fingerprints for " + recipient)
}
pubKeyRingLocation, err := oxGetPubKeyLoc(pubKeyRingID)
@ -619,7 +644,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
if !savedKeysDate.Before(newestKey) {
pubKeys := pubKeyReadXML.SelectElements("pubkey")
if pubKeys == nil {
return nil, fmt.Errorf("couldn't read public keys from cache")
return nil, errors.New("couldn't read public keys from cache")
}
for _, r := range pubKeys {
keyByte, err := base64.StdEncoding.DecodeString(r.Text())
@ -665,7 +690,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
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) (string, error) {
if message == "" {
return "", nil
}
@ -692,10 +717,6 @@ func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, key
oxCryptMessageScRpad := oxCryptMessageSc.CreateElement("rpad")
oxCryptMessageScRpad.CreateText(getRpad(len(message)))
oxCryptMessageScPayload := oxCryptMessageSc.CreateElement("payload")
if subject != "" {
oxCryptMessageScPayloadSub := oxCryptMessageScPayload.CreateElement("subject")
oxCryptMessageScPayloadSub.CreateText(subject)
}
oxCryptMessageScPayloadBody := oxCryptMessageScPayload.CreateElement("body")
oxCryptMessageScPayloadBody.CreateAttr("xmlns", nsJabberClient)
oxCryptMessageScPayloadBody.CreateText(message)

@ -6,6 +6,7 @@ package main
import (
"bufio"
"errors"
"fmt"
"log"
"os"
@ -25,7 +26,7 @@ func findConfig() (string, error) {
// Get home directory.
home := curUser.HomeDir
if home == "" {
return "", fmt.Errorf("findConfig: no home directory found")
return "", errors.New("no home directory found")
}
osConfigDir := os.Getenv("$XDG_CONFIG_HOME")
if osConfigDir == "" {
@ -44,7 +45,7 @@ func findConfig() (string, error) {
return r, nil
}
}
return "", fmt.Errorf("findConfig: no configuration file found")
return "", errors.New("no configuration file found")
}
// Opens the config file and returns the specified values
@ -76,7 +77,8 @@ func parseConfig(configPath string) (configuration, error) {
perm := info.Mode().Perm()
permissions := strconv.FormatInt(int64(perm), 8)
if permissions != "600" && permissions != "640" && permissions != "440" && permissions != "400" {
return output, fmt.Errorf("parseConfig: wrong permissions for %s: %s instead of 400, 440, 600 or 640.", configPath, permissions)
return output, errors.New("Wrong permissions for " + configPath + ": " +
permissions + " instead of 400, 440, 600 or 640.")
}
}
@ -85,7 +87,6 @@ func parseConfig(configPath string) (configuration, error) {
if err != nil {
return output, fmt.Errorf("parseConfig: failed to open config file: %w", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
@ -139,6 +140,10 @@ func parseConfig(configPath string) (configuration, error) {
}
}
}
err = file.Close()
if err != nil {
fmt.Println("error closing file:", err)
}
// Check if the username is a valid JID
output.username, err = MarshalJID(output.username)
@ -149,7 +154,7 @@ func parseConfig(configPath string) (configuration, error) {
// Check if the username is a valid JID now
output.username, err = MarshalJID(output.username)
if err != nil {
return output, fmt.Errorf("parseConfig: invalid username/JID: %s", output.username)
return output, errors.New("invalid username/JID: " + output.username)
}
}

@ -7,10 +7,8 @@ package main
import (
"context"
"fmt"
"io"
"log"
"runtime"
"time"
"github.com/beevik/etree" // BSD-2-clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
@ -19,18 +17,14 @@ import (
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)
c := make(chan xmpp.IQ)
go getIQ(id, c, iqc)
_, err := client.RawInformation(client.JID(), target, id,
iQtype, content)
if err != nil {
return iq, fmt.Errorf("sendIQ: failed to send iq: %w", err)
}
select {
case iq = <-c:
case <-time.After(60 * time.Second):
return iq, fmt.Errorf("sendIQ: server didn't reply to IQ: %s", content)
}
iq = <-c
return iq, nil
}
@ -44,49 +38,29 @@ func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) {
}
}
func rcvStanzas(client *xmpp.Client, ctx context.Context, iqc chan xmpp.IQ, msgc chan xmpp.Chat) {
var received interface{}
r := make(chan interface{}, defaultBufferSize)
e := make(chan error, defaultBufferSize)
go func() {
for {
select {
case <-ctx.Done():
return
default:
}
rcv, err := client.Recv()
if err != nil {
e <- err
} else {
r <- rcv
}
}
}()
func rcvStanzas(client *xmpp.Client, iqc chan xmpp.IQ, msgc chan xmpp.Chat, ctx context.Context) {
for {
received, err := client.Recv()
// Don't print errors if the program is getting shut down,
// as the errors might be triggered from trying to read from
// a closed connection.
select {
case <-ctx.Done():
return
case err := <-e:
default:
if err != nil {
if err != io.EOF {
closeAndExit(client, err)
return
}
return
log.Println(err)
}
case received = <-r:
}
switch v := received.(type) {
case xmpp.Chat:
msgc <- v
case xmpp.IQ:
switch v.Type {
case "get":
var xmlns *etree.Attr
iq := etree.NewDocument()
err := iq.ReadFromBytes(v.Query)
err = iq.ReadFromBytes(v.Query)
if err != nil {
log.Println("Couldn't parse IQ:",
string(v.Query), err)
@ -95,9 +69,6 @@ func rcvStanzas(client *xmpp.Client, ctx context.Context, iqc chan xmpp.IQ, msgc
if query != nil {
xmlns = query.SelectAttr("xmlns")
}
if xmlns == nil {
break
}
switch xmlns.Value {
case nsDiscoInfo:
root := etree.NewDocument()

Loading…
Cancel
Save