Compare commits

...

163 Commits

Author SHA1 Message Date
Martin Dosch d405730032
Use current go-xmpp development version. 4 days ago
Martin Dosch 6bcb4d38b9
Fix ox encryption in interactive mode. 1 week ago
Martin Dosch 5e07e54432
Use gopenpgp v2.8.0-alpha.1. 2 weeks ago
Martin Dosch 49238c9b0d
Update go modules. 2 weeks ago
Martin Dosch cc0cc9f96f
Update go modules. 3 weeks ago
Martin Dosch f3b5fbb2fa
Fix order of commands in manpage. 3 weeks ago
Martin Dosch 50ac109693
Add new example. 4 weeks ago
Martin Dosch f9da054999
Start new development cycle. 4 weeks ago
Martin Dosch 791c2f12e3
Prepare release v0.11.0. 4 weeks ago
Martin Dosch b45c1771ef
Update go modules. 1 month ago
Martin Dosch b73f608aa9
CI: Only build binaries for releases. 1 month ago
Martin Dosch 0701fce91d
CI: Fix windows binary name in upx command. 1 month ago
Martin Dosch 801c4a8004
CI: Compress binaries.
Another try since upx-ucl is now in backports.
1 month ago
Martin Dosch c4236dd164
Use deferred file close. 1 month ago
Martin Dosch 8f2bb80ebe
Use deferred file close. 1 month ago
Martin Dosch d2f758b4f9
Use `fmt.Errorf()` instead of `errors.New()` to create new error messages. 1 month ago
Martin Dosch cbb7beb72b
Switch to latest go-xmpp release. 1 month ago
Martin Dosch d1d1d38e64
Update go modules. 1 month ago
Martin Dosch 95a2f672cf
Add possibility to disable FAST. 2 months ago
Martin Dosch c21991c545
Enable FAST. 2 months ago
Martin Dosch b2720cd707
Use current master of go-xmpp. 2 months ago
Martin Dosch 5cf9f21944
Update go modules. 2 months ago
Martin Dosch b98a071472
Add new parameter subject. 2 months ago
Martin Dosch 953de9c86d
Fix check for valid URIs. 2 months ago
Martin Dosch b9bae7f84b
Update go modules. 2 months ago
Martin Dosch 2329b04dfe
Change short id length. 2 months ago
Martin Dosch c276543729
Also use google/uuid for ShortID. 2 months ago
Martin Dosch 9b30af207d
Improve error message. 2 months ago
Martin Dosch 31ae111c80
New ox priv key location. 2 months ago
Martin Dosch a6eadf1565
Start new development cycle. 2 months ago
Martin Dosch 126890ca22
Prepare release v0.10.0 2 months ago
Martin Dosch bd56592435
Switch to go-xmpp release. 2 months ago
Martin Dosch 4f47e8c713
Update go modules. 2 months ago
Martin Dosch 013756cec9
Update go modules. 2 months ago
Martin Dosch 9aea1e294a
Improve listening mode. 2 months ago
Martin Dosch 93d8bbedaa
FAST: Fix check for expiry. 2 months ago
Martin Dosch 72e517e14d
Update go modules. 2 months ago
Martin Dosch c87f1bdb83
Move stanza receiving goroutine outside of for loop. 2 months ago
Martin Dosch f2b20502c6
FAST: Update token if expiry is reduced. 2 months ago
Martin Dosch 25b8a8e243
Reintroduce context and cancel func
There are still some rare races without.
3 months ago
Martin Dosch c77eae826a
Update go modules. 3 months ago
Martin Dosch be78a2b845
Catch error when listening mode is stopped with Ctrl+C. 3 months ago
Martin Dosch e244f14548
Retry without FAST if FAST authentication fails. 3 months ago
Martin Dosch 79897fd295
Use fast.bin for fast cache file
So it's clear that this is no clear text file and it is not supposed to
be edited by the user.
3 months ago
Martin Dosch 61f5ab705d
Add support for FAST authentication. 3 months ago
Martin Dosch 5ecfe200c1
Revert "Do not use sasl2 uuid attribute."
This reverts commit 557d105238.
3 months ago
Martin Dosch 19812b0863
Reorder changelog. 3 months ago
Martin Dosch 9f9c533941
Add support for SASL2 and BIND2
Squashed commit of the following:

commit 0805b1f06a
Author: Martin Dosch <martin@mdosch.de>
Date:   Tue Apr 9 10:57:29 2024 +0200

    Move to upstream go-xmpp.

commit 557d105238
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 12:45:10 2024 +0200

    Do not use sasl2 uuid attribute.

commit 986aea7957
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 11:29:33 2024 +0200

    Don't print error on io.EOF

commit ef927ce5cc
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 11:25:40 2024 +0200

    Detect stream error while receiving stanzas.

commit 73b00f0612
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 10:00:54 2024 +0200

    Use a client uuid per JID.

commit b2d090a623
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 09:29:50 2024 +0200

    Improve sasl2 user agent id stuff.

commit c7376832ce
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 09:11:03 2024 +0200

    Rework getting data path.

commit 080100486e
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 08:49:49 2024 +0200

    Update go-xmpp.

commit 9c56a79bae
Author: Martin Dosch <martin@mdosch.de>
Date:   Sun Apr 7 00:26:04 2024 +0200

    SASL2: Create per client installation ID.

commit cce36b070a
Author: Martin Dosch <martin@mdosch.de>
Date:   Sat Apr 6 22:20:35 2024 +0200

    Update go-xmpp.

commit 1c4acfd07c
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 12:04:09 2024 +0200

    Update go-xmpp.

commit 0703a7c2d6
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 11:38:28 2024 +0200

    Update go-xmpp.

commit 4f033fc5b9
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 09:39:30 2024 +0200

    Update vendored lib.

commit a44554218d
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 09:32:54 2024 +0200

    Use google/uuid for message IDs.

    It is used for go-xmpp in the sasl2 branch anyway.

commit 3e57ec3603
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 09:12:50 2024 +0200

    Update vendored modules.

commit ead44ef99d
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 09:12:25 2024 +0200

    Add . between go-sendxmpp and short id.

commit b3271a3530
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 08:38:03 2024 +0200

    Update Changelog.

commit 639b9b6657
Author: Martin Dosch <martin@mdosch.de>
Date:   Fri Apr 5 08:22:56 2024 +0200

    Add short ID back to resource string.

    That's still necessary to avoid two instances with the same resource
    when using SASL instead of SASL2.

commit 680593359b
Author: Martin Dosch <martin@mdosch.de>
Date:   Thu Apr 4 23:56:15 2024 +0200

    Experimental sasl2 support.
3 months ago
Martin Dosch 06bbd9fda5
Update go modules. 3 months ago
Martin Dosch c392eeb858
Close receiving of stanzas if io.EOF is received. 3 months ago
Martin Dosch 09e2423da4
Update go modules. 3 months ago
Martin Dosch 2a6ed7b54d
Merge branch 'remove-mutex-and-context' 3 months ago
Martin Dosch 5de4d1f8fe
Regenerate manpage. 3 months ago
Martin Dosch 99689ea2cd
Ignore EOF errors when receiving stanzas. 3 months ago
Martin Dosch 86a2280277 Merge branch 'Menelmacar-master-patch-37014' into 'master'
Update man for FISH - Shell completions

See merge request mdosch/go-sendxmpp!5
3 months ago
Maik Holme fc860dfebb Update man for FISH - Shell completions 3 months ago
Martin Dosch 11a31d6ad0
Remove mutex and context.
Those should hopefully no longer be necessary as the races
have been fixed in the library.
3 months ago
Martin Dosch f1e9bfcdc8
Update README. 3 months ago
Martin Dosch dbed11323d
Remove mutex as the locking is now done in the library. 3 months ago
Martin Dosch c13a0c7016
Update go modules. 3 months ago
Martin Dosch 872eaa847a
Clean up go.sum 3 months ago
Martin Dosch 4483863795
Lock mutex before starting the goroutine. 3 months ago
Martin Dosch 09693ba1bf
Further improvements to make race conditions less likely. 3 months ago
Martin Dosch 46ed540910
Update go-xmpp to latest master. 3 months ago
Martin Dosch 5704798b09
Improve stanza reading. 3 months ago
Martin Dosch 8936ba2acc
Only perform root check on non-windows systems.
Is anyone using go-sendxmpp on windows?
3 months ago
Martin Dosch 1574e2f4df
Add a warning when run by root. 3 months ago
Martin Dosch 606ca8cc18
Fix typo. 3 months ago
Martin Dosch b04196a19f
Add hint about usage is root to the manpage. 3 months ago
Martin Dosch e666f33030
Start new development cycle. 3 months ago
Martin Dosch ffae83f8ea
Prepare release v0.9.0. 3 months ago
Martin Dosch e1708129e2
Switch to latest go-xmpp release. 3 months ago
Martin Dosch 8b4b8105b2
Update go modules. 3 months ago
Martin Dosch 36d6b26fe8
Update go modules. 3 months ago
Martin Dosch ea515401d1
Fixed hanging forever in stream close if server doesn't reply. 3 months ago
Martin Dosch 2ddfcb8383
Add support for XEP-0478: Stream Limits Advertisement. 3 months ago
Martin Dosch 911763b2e5
Update go modules. 3 months ago
Martin Dosch 9a186aeec1
Don't capitalize `--ssdp-off` command line flag. 3 months ago
Martin Dosch ca268e957e
Add command line flag to disable XEP-0474: SASL SCRAM Downgrade Protection. 3 months ago
Martin Dosch b9018ec2d9
Update changelog. 3 months ago
Martin Dosch e5cbe832e3
Properly exit listening mode if Ctrl+C is pressed. 3 months ago
Martin Dosch 27c45a1877
Switch to development branch of go-xmpp 3 months ago
Martin Dosch 92db3d2fb1
Use latest release of go-xmpp. 3 months ago
Martin Dosch 5ca5404418
Make system output look less bloated. 3 months ago
Martin Dosch 2e59f85476
Capitalize go-sendxmpp in version output. 3 months ago
Martin Dosch 4c3e5700e7
Also add go version to system output. 3 months ago
Martin Dosch 54fb96431e
Remove mutex as it might block go-sendxmpp forever. 3 months ago
Martin Dosch 5f6eba4b44
Update go modules. 3 months ago
Martin Dosch 06101c2272
Use go-xmpp master branch.
Get the changes there tested too.
3 months ago
Martin Dosch a02727571b
Show OS and arch combined as system. 3 months ago
Martin Dosch c0493f3cdd
Remove unnecessary passing around of mutex
…it's a global variable anyway…
3 months ago
Martin Dosch 4a54f8419b
Switch to release version of go-xmpp. 3 months ago
Martin Dosch 613a9c8639
Improve closing connection. 3 months ago
Martin Dosch 42e2c83eaf
Print go version for flag `--version`. 4 months ago
Martin Dosch 6a5bdec0e7
Print OS and arch info for `--version`. 4 months ago
Martin Dosch 6dcabd47f4
Properly close stream in interactive mode. 4 months ago
Martin Dosch 15e7664f2a
Update go modules. 4 months ago
Martin Dosch f7e284e331
Use development version of go-xmpp. 4 months ago
Martin Dosch 398170b4b4
You've got the power!
Although the perl sendxmpp has many functions go-sendxmpp doesn't
(yet) have, go-sendxmpp also has functions that sendxmpp is missing
(e.g. http-upload).
4 months ago
Martin Dosch 15b73e9284
Start new development cycle. 4 months ago
Martin Dosch c9e2f54a50
Prepare release v0.8.4. 4 months ago
Martin Dosch 38726460d5
Update changelog. 4 months ago
Martin Dosch 94eb496e61
Update go modules. 4 months ago
Martin Dosch afcfd509f3
Update go modules. 4 months ago
Martin Dosch 1174671f93
CI: Add artifacts expiry. 4 months ago
Martin Dosch 94f29affd2
CI: Don't build 32bit binaries. 4 months ago
Martin Dosch 34a836a41c
Update go modules. 4 months ago
Martin Dosch c5590fe527
Revert "Sleep again for 100ms for debugging purposes."
This reverts commit ad1aeba749.
4 months ago
Martin Dosch 5095297915
Update go modules. 4 months ago
Martin Dosch ad1aeba749
Sleep again for 100ms for debugging purposes. 4 months ago
Martin Dosch 459e40e9a4
More perl sendxmpp config compatibility. 4 months ago
Martin Dosch a3cd1fe0a9
Don't block on unanswered IQs. 4 months ago
Martin Dosch 8c5c0ce508
Update go modules. 4 months ago
Martin Dosch c89045ecff
http-upload: use correct units. 4 months ago
Martin Dosch 6b2c78d2c2
Stanzahandling: Check if xmlns is set. 4 months ago
Martin Dosch e38acf3e92
Improve behavior when no SRV records are set. 4 months ago
Martin Dosch 38bf2d54a8
Remove 100ms sleep before closing connection. 4 months ago
Martin Dosch 6ea4de828f
Improve fallback behavior if no SRV records are provided. 4 months ago
Martin Dosch dff738aeec
Switch to go-xmpp release. 4 months ago
Martin Dosch a2476e39e7
Update go modules. 4 months ago
Martin Dosch a78649b911
Update go modules. 4 months ago
Martin Dosch ce89cd94b9
Improve file name for private ox keys. 4 months ago
Martin Dosch 10feec99c8
Better compatibility with perl sendxmpp config files. 4 months ago
Martin Dosch d74b86c30c
Properly handle lost connection. 4 months ago
Martin Dosch 5c8694d667
CI: Expire artifacts after 2 years. 4 months ago
Martin Dosch 72c415b4d7
Start new development cycle. 4 months ago
Martin Dosch ba17366ff5
Prepare release v0.8.3 4 months ago
Martin Dosch b4613f5cf0
Fix specifying a message via file. 4 months ago
Martin Dosch 55e16b417d
Make comment more precise. 4 months ago
Martin Dosch ae4a727cef
Use human readable file name for private Ox keys. 4 months ago
Martin Dosch 9c981474da
Clean up example in README.
`-j` should usually not be necessary if the server is
correctly configured. So this is removed from the example
where it was used.
5 months ago
Martin Dosch b2e0be8024
Update go modules. 5 months ago
Martin Dosch c6313970f1
Update go modules. 5 months ago
Martin Dosch dcc8ff0052
Start new development cycle. 5 months ago
Martin Dosch 5dfd576cda
Prepare release v0.8.2 5 months ago
Martin Dosch c8ed8df8d2
Update xmppsrv to latest release and mention mtp in changelog. 5 months ago
Martin Dosch abd0bb64b8
Fix issue with SRV look up. 5 months ago
Martin Dosch 90193981be
CI: Fix. 5 months ago
Martin Dosch 954c00746e
CI: (Hopefully) fix syntax error. 5 months ago
Martin Dosch 84444fe1b5
Update go modules. 5 months ago
Martin Dosch d96339d20a
CI: Disable binary compression. 5 months ago
Martin Dosch 006477c559
CI: Compress binaries. 5 months ago
Martin Dosch 87bffa17f8
Wording… 5 months ago
Martin Dosch e52bed4c4f
Start new development cycle. 5 months ago
Martin Dosch 049c45075d
Prepare release v0.8.1 5 months ago
Martin Dosch c6e1a76d90
Http-upload: Fix possible crash. 5 months ago
Martin Dosch 40ee8828b3
Http-upload: fix detection of maximum file size. 5 months ago
Martin Dosch 643842fcb1
http-upload: Improved error handling. 5 months ago
Martin Dosch 2d9bdfa80a
Update go modules. 5 months ago
Martin Dosch 2cc5a7c289
Use bold instead of underlined for SCRAM mechanisms. 5 months ago
Martin Dosch 679d271366
Document HTTP_PROXY. 5 months ago
Martin Dosch 06ef3dc525
Update changelog. 5 months ago
Martin Dosch 7677d2907a
Add experimental support for SOCKS5 proxies. 5 months ago
Martin Dosch e5c2d417e0
Remove duplicate "Usage" info in README. 6 months ago
Martin Dosch 88a82c7113
Reference to commit of improved tls-server-end-point support 6 months ago
Martin Dosch 05e5115c5d
Disable TLS Renegotiation. 6 months ago
Martin Dosch 7ad344c6cb
Update go modules. 6 months ago
Martin Dosch 1db2a3787c
Add support for tls-server-end-point channel binding. 6 months ago
Martin Dosch 3c8ce9423a
Update go modules. 6 months ago
Martin Dosch fc1342ab29
Update go modules. 6 months ago
Martin Dosch 8337bcf82b
Fix typo. 6 months ago
Martin Dosch fdaa694c97
Fix typo. 6 months ago
Martin Dosch f654467e85
Start new development cycle. 6 months ago

@ -38,28 +38,31 @@ 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
- 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
- upx $CI_PROJECT_DIR/linux-arm64/go-sendxmpp || true
- 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
@ -71,7 +74,6 @@ 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,8 +1,69 @@
# 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.
## [v0.8.1] 2024-01-16
### Added
- Add support for `tls-server-end-point` channel binding (via go-xmpp commit 3f0cbac30767faa562ad198ee69f36055f5924bc).
- Add experimental support for SOCKS5 proxies using the `HTTP_PROXY` environment variable (requires go-xmpp commit 685570cbd85c31ea3b426bea34dd4af404aac8cf).
### Changed
- http-upload: Improve error handling.
## [v0.8.0] 2024-01-09
### Added
- Add no parameter `--scram-mech-pinning`.
- Add new parameter `--scram-mech-pinning`.
### Changed
- Refuse to upload a file if upload slot doesn't provide https.

@ -15,7 +15,7 @@ sendxmpp incarnations. :)
## requirements
* [go](https://golang.org/) >= 1.17
* [go](https://golang.org/) >= 1.21
## installation
@ -75,11 +75,11 @@ 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] [-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] [--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…]
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…]
-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,6 +117,9 @@ Usage: go-sendxmpp [-cdilnt] [-a value] [-f value] [--headline] [--help] [-h val
--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.
@ -139,7 +142,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 -j example.com -p swordfish recipient1@example.com recipient2@example.com
cat message.txt | ./go-sendxmpp -u bob@example.com -p swordfish recipient1@example.com recipient2@example.com
```
Send a message to two groupchats (`-c`) using a configuration file.
@ -153,6 +156,11 @@ 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

@ -7,47 +7,58 @@ package main
import (
"fmt"
"net"
"os"
"strings"
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
"salsa.debian.org/mdosch/xmppsrv" // BSD-2-Clause
)
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:]
// Look up xmpp-client SRV records.
srvMixed, err := xmppsrv.LookupClient(server)
if len(srvMixed) > 0 && err == nil {
for _, adr := range srvMixed {
if !directTLS && adr.Type != "xmpps-client" {
// Use StartTLS
options.NoTLS = true
options.StartTLS = true
} else if adr.Type == "xmpps-client" {
// Use direct TLS
options.NoTLS = false
options.StartTLS = false
}
options.Host = net.JoinHostPort(adr.Target, fmt.Sprint(adr.Port))
// Connect to server
client, err := options.NewClient()
if err == nil {
return client, nil
// Don't do SRV look ups if proxy is set.
if proxy == "" {
// Look up xmpp-client SRV records.
srvMixed, err := xmppsrv.LookupClient(server)
if len(srvMixed) > 0 && err == nil {
for _, adr := range srvMixed {
if !directTLS && adr.Type != "xmpps-client" {
// Use StartTLS
options.NoTLS = true
options.StartTLS = true
} else if adr.Type == "xmpps-client" {
// Use direct TLS
options.NoTLS = false
options.StartTLS = false
}
options.Host = net.JoinHostPort(adr.Target, fmt.Sprint(adr.Port))
// Connect to server
client, err := options.NewClient()
if err == nil {
return client, nil
}
}
}
}
// Try port 5223 if directTLS is set and no xmpp-client SRV records are provided.
}
_, 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.
if directTLS {
options.NoTLS = false
options.StartTLS = false
options.Host = net.JoinHostPort(server, "5223")
options.Host = net.JoinHostPort(options.Host, "5223")
} else {
// Try port 5222 if no xmpp-client SRV records are provided and directTLS is not set.
// Try port 5222 if no port is provided and directTLS is not set.
options.NoTLS = true
options.StartTLS = true
options.Host = net.JoinHostPort(server, "5222")
options.Host = net.JoinHostPort(options.Host, "5222")
}
}
// Connect to server

@ -5,7 +5,7 @@
package main
const (
version = "0.8.0"
version = "0.11.1-dev"
// defaults
defaultBufferSize = 100
defaultConfigRowSep = 2
@ -17,7 +17,6 @@ const (
defaultRpadMultiple = 100
defaultRSABits = 4096
defaultShortIDBytes = 4
defaultSleepTime = 100
defaultTimeout = 10
defaultTLSMinVersion = 12
defaultTLS10 = 10
@ -42,6 +41,7 @@ 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"

@ -1,23 +1,24 @@
module salsa.debian.org/mdosch/go-sendxmpp
go 1.17
go 1.21.5
require (
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/mattn/go-xmpp v0.0.2-0.20240109092456-39f5b80375b6
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/pborman/getopt/v2 v2.1.0
salsa.debian.org/mdosch/xmppsrv v0.2.5
github.com/xmppo/go-xmpp v0.2.2-0.20240621203446-ca6fa9ced5fa
golang.org/x/crypto v0.24.0
salsa.debian.org/mdosch/xmppsrv v0.2.6
)
require (
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/pkg/errors v0.9.1 // 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
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
)

@ -1,54 +1,44 @@
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
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-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.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/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/mattn/go-xmpp v0.0.2-0.20240109092456-39f5b80375b6 h1:ks0S9C3JZYldBbuk5TEvbH4H6I1dV1ZannTY3A33EkI=
github.com/mattn/go-xmpp v0.0.2-0.20240109092456-39f5b80375b6/go.mod h1:HBSVZzomYzxh5UNQq6sXImCsgh01O55Pvb6fopW/u74=
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/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/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.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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
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=
@ -57,43 +47,25 @@ 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
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.5 h1:ACPk8EhmCAUMl59TnGe5kvvwSnW065CJrhN7uvt25xY=
salsa.debian.org/mdosch/xmppsrv v0.2.5/go.mod h1:udWXnWFa9zkcyN9YSB/u44BCnnRDpeQ0eDy3MVLjHZQ=
salsa.debian.org/mdosch/xmppsrv v0.2.6 h1:+S7ZxRP7BQwQHGJvYhr408UD0XGAd7+RZaVA/xN4EIc=
salsa.debian.org/mdosch/xmppsrv v0.2.6/go.mod h1:udWXnWFa9zkcyN9YSB/u44BCnnRDpeQ0eDy3MVLjHZQ=

@ -6,14 +6,22 @@ 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 {
@ -28,7 +36,10 @@ func validUTF8(s string) string {
func validURI(s string) (*url.URL, error) {
// Check if URI is valid
uri, err := url.ParseRequestURI(s)
return uri, fmt.Errorf("validURI: %w", err)
if err != nil {
return uri, fmt.Errorf("validURI: %w", err)
}
return uri, nil
}
func readFile(path string) (*bytes.Buffer, error) {
@ -36,16 +47,179 @@ 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)
}
err = file.Close()
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 {
fmt.Println("error while closing file:", err)
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache folder: %w", err)
}
return buffer, nil
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)
if err != nil {
return fmt.Errorf("writeFastData: failed to create fast token file: %w", 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
}
func getRpad(messageLength int) string {
@ -64,19 +238,9 @@ func getRpad(messageLength int) string {
}
func getID() string {
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:])
return uuid.NewString()
}
func getShortID() string {
id := make([]byte, defaultShortIDBytes)
_, err := rand.Read(id)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x", id[0:4])
return uuid.NewString()[:6]
}

@ -8,34 +8,38 @@ import (
"bytes"
"encoding/xml"
"fmt"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/beevik/etree" // BSD-2-clause
"github.com/gabriel-vasile/mimetype" // MIT License
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
)
func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath string) string {
func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath string,
timeout time.Duration,
) (string, error) {
var uploadComponent string
var maxFileSize int64
var iqDiscoItemsXMLQuery, iqDiscoInfoXMLQuery *etree.Element
// Get file size
fileInfo, err := os.Stat(filePath)
if err != nil {
log.Fatal(err)
return "", err
}
fileSize := fileInfo.Size()
// Read file
buffer, err := readFile(filePath)
if err != nil {
log.Fatal(err)
return "", err
}
// Get mime type
@ -46,7 +50,7 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
// Get file name
fileName := filepath.Base(filePath)
// Just use alphanumerical and some special characters for now
// to work around https://github.com/mattn/go-xmpp/issues/132
// to work around https://github.com/xmppo/go-xmpp/issues/132
reg := regexp.MustCompile(`[^a-zA-Z0-9\+\-\_\.]+`)
fileNameEscaped := reg.ReplaceAllString(fileName, "_")
@ -54,16 +58,16 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
iqContent, err := sendIQ(client, iqc, jserver, "get",
"<query xmlns='http://jabber.org/protocol/disco#items'/>")
if err != nil {
log.Fatal(err)
return "", err
}
iqDiscoItemsXML := etree.NewDocument()
err = iqDiscoItemsXML.ReadFromBytes(iqContent.Query)
if err != nil {
log.Fatal(err)
return "", err
}
iqDiscoItemsXMLQuery := iqDiscoItemsXML.SelectElement("query")
iqDiscoItemsXMLQuery = iqDiscoItemsXML.SelectElement("query")
if iqDiscoItemsXMLQuery == nil {
log.Fatal("no query element in disco items reply")
return "", fmt.Errorf("http-upload: no query element in disco items reply")
}
iqDiscoItemsXMLItems := iqDiscoItemsXMLQuery.SelectElements("item")
@ -76,11 +80,11 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
iqDiscoInfoReqXMLQuery.CreateAttr("xmlns", nsDiscoInfo)
iqdi, err := iqDiscoInfoReqXML.WriteToString()
if err != nil {
log.Fatal(err)
return "", err
}
iqDiscoInfo, err := sendIQ(client, iqc, jid.Value, "get", iqdi)
if err != nil {
log.Fatal(err)
return "", err
}
if iqDiscoInfo.Type != strResult {
continue
@ -88,9 +92,9 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
iqDiscoInfoXML := etree.NewDocument()
err = iqDiscoInfoXML.ReadFromBytes(iqDiscoInfo.Query)
if err != nil {
log.Fatal(err)
return "", err
}
iqDiscoInfoXMLQuery := iqDiscoInfoXML.SelectElement("query")
iqDiscoInfoXMLQuery = iqDiscoInfoXML.SelectElement("query")
if iqDiscoInfoXMLQuery == nil {
continue
}
@ -109,12 +113,13 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
if iqDiscoInfoXMLType.Value == "file" &&
iqDiscoInfoXMLCategory.Value == "store" {
uploadComponent = jid.Value
break
}
}
if uploadComponent == "" {
log.Fatal("No http upload component found.")
return "", fmt.Errorf("http-upload: no http upload component found.")
}
iqDiscoInfoXMLX := iqDiscoItemsXMLQuery.SelectElements("x")
iqDiscoInfoXMLX := iqDiscoInfoXMLQuery.SelectElements("x")
for _, r := range iqDiscoInfoXMLX {
field := r.SelectElements("field")
for i, t := range field {
@ -122,18 +127,23 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
if varAttr == nil {
continue
}
prevFieldVal := field[i-1].SelectElement("value")
if prevFieldVal == nil {
continue
}
curFieldVal := t.SelectElement("value")
if curFieldVal == nil {
continue
}
if varAttr.Value == "max-file-size" && prevFieldVal.Text() == nsHTTPUpload {
maxFileSize, err = strconv.ParseInt(curFieldVal.Text(), 10, 64)
if err != nil {
log.Fatal("error while checking server maximum http upload file size.")
if varAttr.Value == "max-file-size" {
var prevFieldVal *etree.Element
if i > 0 {
prevFieldVal = field[i-1].SelectElement("value")
if prevFieldVal == nil {
continue
}
}
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.")
}
}
}
}
@ -143,9 +153,8 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
// the best.
if maxFileSize != 0 {
if fileSize > maxFileSize {
log.Fatal("File size " + strconv.FormatInt(fileSize/1024/1024, 10) +
" MB is larger than the maximum file size allowed (" +
strconv.FormatInt(maxFileSize/1024/1024, 10) + " MB).")
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))
}
}
@ -158,43 +167,55 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
requestReq.CreateAttr("content-type", mimeType)
r, err := request.WriteToString()
if err != nil {
log.Fatal(err)
return "", err
}
// Request http upload slot
uploadSlot, err := sendIQ(client, iqc, uploadComponent, "get", r)
if err != nil {
log.Fatal(err)
return "", err
}
if uploadSlot.Type != strResult {
log.Fatal("error while requesting upload slot.")
return "", fmt.Errorf("http-upload: error while requesting upload slot.")
}
iqHTTPUploadSlotXML := etree.NewDocument()
err = iqHTTPUploadSlotXML.ReadFromBytes(uploadSlot.Query)
if err != nil {
log.Fatal(err)
return "", err
}
iqHTTPUploadSlotXMLSlot := iqHTTPUploadSlotXML.SelectElement("slot")
if iqHTTPUploadSlotXMLSlot == nil {
log.Fatal("http-upload: no slot element")
return "", fmt.Errorf("http-upload: no slot element")
}
iqHTTPUploadSlotXMLPut := iqHTTPUploadSlotXMLSlot.SelectElement("put")
if iqHTTPUploadSlotXMLPut == nil {
log.Fatal("http-upload: no put element")
return "", fmt.Errorf("http-upload: no put element")
}
iqHTTPUploadSlotXMLPutURL := iqHTTPUploadSlotXMLPut.SelectAttr("url")
if iqHTTPUploadSlotXMLPutURL == nil {
log.Fatal("http-upload: no url attribute")
return "", fmt.Errorf("http-upload: no url attribute")
}
if !strings.HasPrefix(iqHTTPUploadSlotXMLPutURL.Value, "https://") {
log.Fatal("http-upload: upload slot does not provide https")
return "", fmt.Errorf("http-upload: upload slot does not provide https")
}
// Upload file
httpClient := &http.Client{}
httpTransport := &http.Transport{
IdleConnTimeout: timeout,
TLSHandshakeTimeout: timeout,
}
proxyEnv := os.Getenv("HTTP_PROXY")
if proxyEnv != "" {
proxyURL, err := url.Parse(proxyEnv)
if err != nil {
return "", err
}
httpTransport.Proxy = http.ProxyURL(proxyURL)
}
httpClient := &http.Client{Transport: httpTransport}
req, err := http.NewRequest(http.MethodPut, iqHTTPUploadSlotXMLPutURL.Value,
buffer)
if err != nil {
log.Fatal(err)
return "", err
}
req.Header.Set("Content-Type", mimeTypeEscaped.String())
iqHTTPUploadSlotXMLPutHeaders := iqHTTPUploadSlotXMLPut.SelectElements("header")
@ -210,25 +231,25 @@ func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath
}
resp, err := httpClient.Do(req)
if err != nil {
log.Fatal(err)
return "", err
}
// Test for http status code "200 OK" or "201 Created"
if resp.StatusCode != 200 && resp.StatusCode != 201 {
log.Fatal("Http upload failed.")
return "", fmt.Errorf("http-upload: upload failed.")
}
// Return http link
iqHTTPUploadSlotXMLGet := iqHTTPUploadSlotXMLSlot.SelectElement("get")
if iqHTTPUploadSlotXMLGet == nil {
log.Fatal("http-upload: no get element")
return "", fmt.Errorf("http-upload: no get element")
}
iqHTTPUploadSlotXMLGetURL := iqHTTPUploadSlotXMLGet.SelectAttr("url")
if iqHTTPUploadSlotXMLGetURL == nil {
log.Fatal("http-upload: no url attribute")
return "", fmt.Errorf("http-upload: no url attribute")
}
err = resp.Body.Close()
if err != nil {
fmt.Println("error while closing http request body:", err)
fmt.Println("http-upload: error while closing http request body:", err)
}
return iqHTTPUploadSlotXMLGetURL.Value
return iqHTTPUploadSlotXMLGetURL.Value, nil
}

@ -8,7 +8,7 @@
package main
import (
"errors"
"fmt"
"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, errors.New("Invalid JID" + input + ": The resourcepart must be larger than 0 bytes")
return input, fmt.Errorf("invalid JID %s: the resourcepart must be larger than 0 bytes", input)
}
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 = errors.New("Invalid JID:" + input)
err = fmt.Errorf("Invalid JID: %s", 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, errors.New("Invalid JID: " + input)
return input, fmt.Errorf("invalid JID: %s", input)
}
if domainpart == "" {
return input, errors.New("Invalid JID: " + input)
return input, fmt.Errorf("invalid JID: %s", input)
}
if localpart == "" {

@ -14,12 +14,14 @@ import (
"net"
"os"
"os/signal"
osUser "os/user"
"runtime"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/pborman/getopt/v2" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
)
type configuration struct {
@ -30,6 +32,14 @@ type configuration struct {
alias string
}
func closeAndExit(client *xmpp.Client, err error) {
client.Close()
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
func readMessage(messageFilePath string) (string, error) {
var (
output string
@ -38,7 +48,7 @@ func readMessage(messageFilePath string) (string, error) {
// Check that message file is existing.
_, err = os.Stat(messageFilePath)
if os.IsNotExist(err) {
if err != nil {
return output, fmt.Errorf("readMessage: %w", err)
}
@ -47,6 +57,7 @@ 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() {
@ -57,18 +68,13 @@ 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)
}
}
err = file.Close()
if err != nil {
fmt.Println("error while closing file:", err)
}
return output, fmt.Errorf("readMessage: %w", err)
return output, nil
}
func main() {
@ -82,6 +88,7 @@ func main() {
message, user, server, password, alias string
oxPrivKey *crypto.Key
recipients []recipientsType
fast xmpp.Fast
)
// Define command line flags.
@ -123,6 +130,9 @@ 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()
@ -134,7 +144,9 @@ func main() {
os.Exit(0)
case *flagVersion:
// If requested, show version and quit.
fmt.Println("go-sendxmpp", version)
fmt.Println("Go-sendxmpp", version)
system := runtime.GOOS + "/" + runtime.GOARCH
fmt.Println("System:", system, runtime.Version())
fmt.Println("License: BSD-2-clause")
os.Exit(0)
// Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like
@ -157,6 +169,22 @@ 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":
@ -205,6 +233,22 @@ 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.
@ -218,11 +262,26 @@ 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:]
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "xmpp-client")
tlsConfig.InsecureSkipVerify = *flagSkipVerify
tlsConfig.Renegotiation = tls.RenegotiateNever
switch *flagTLSMinVersion {
case defaultTLS10:
tlsConfig.MinVersion = tls.VersionTLS10
@ -237,24 +296,32 @@ func main() {
os.Exit(0)
}
resource := "go-sendxmpp." + getShortID()
// Set XMPP connection options.
options := xmpp.Options{
Host: server,
User: user,
DialTimeout: timeout,
Resource: "go-sendxmpp." + getShortID(),
Resource: resource,
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)
// an unencrypted connection is established. As StartTLS is
// set when NoTLS is set go-sendxmpp won't use unencrypted
// client-to-server connections.
// See https://pkg.go.dev/github.com/mattn/go-xmpp#Options
NoTLS: !*flagDirectTLS,
StartTLS: !*flagDirectTLS,
Debug: *flagDebug,
TLSConfig: &tlsConfig,
Mechanism: *flagSCRAMPinning,
// 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,
}
// Read message from file.
@ -299,13 +366,41 @@ func main() {
// Connect to server.
client, err := connect(options, *flagDirectTLS)
if err != nil {
log.Fatal(err)
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)
}
}
// 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, iqc, msgc, ctx)
go rcvStanzas(client, ctx, iqc, msgc)
for _, r := range getopt.Args() {
var re recipientsType
re.Jid = r
@ -323,7 +418,8 @@ func main() {
for i, recipient := range recipients {
validatedJid, err := MarshalJID(recipient.Jid)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
recipients[i].Jid = validatedJid
}
@ -332,58 +428,72 @@ func main() {
case *flagOxGenPrivKeyX25519:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "x25519")
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
os.Exit(0)
case *flagOxGenPrivKeyRSA:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "rsa")
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
os.Exit(0)
case *flagOxImportPrivKey != "":
validatedOwnJid, err := MarshalJID(user)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey,
client, iqc)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
os.Exit(0)
case *flagOxDeleteNodes:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
err = oxDeleteNodes(validatedOwnJid, client, iqc)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
os.Exit(0)
case *flagOx:
validatedOwnJid, err := MarshalJID(user)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
}
if *flagHTTPUpload != "" {
message = httpUpload(client, iqc, tlsConfig.ServerName,
*flagHTTPUpload)
message, err = httpUpload(client, iqc, tlsConfig.ServerName,
*flagHTTPUpload, timeout)
if err != nil {
cancel()
closeAndExit(client, err)
}
}
if *flagOOBFile != "" {
@ -392,7 +502,8 @@ func main() {
// Check if the URI is valid.
uri, err := validURI(message)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
message = uri.String()
}
@ -415,7 +526,8 @@ func main() {
_, err = client.JoinMUCNoHistory(recipient.Jid, alias)
}
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
}
}
@ -427,7 +539,8 @@ func main() {
// Send raw XML
_, err = client.SendOrg(message)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
case *flagInteractive:
// Send in endless loop (for usage with e.g. "tail -f").
@ -438,16 +551,23 @@ func main() {
go func() {
for range c {
cancel()
client.Close()
os.Exit(0)
closeAndExit(client, nil)
}
}()
for {
message, err = reader.ReadString('\n')
message = strings.TrimSuffix(message, "\n")
if err != nil {
log.Fatal("failed to read from stdin")
select {
case <-ctx.Done():
return
default:
if err != nil {
cancel()
closeAndExit(client, fmt.Errorf("failed to read from stdin"))
}
}
}
message = strings.TrimSuffix(message, "\n")
// Remove invalid code points.
message = validUTF8(message)
@ -461,7 +581,7 @@ func main() {
continue
}
oxMessage, err := oxEncrypt(client, oxPrivKey,
recipient.Jid, recipient.OxKeyRing, message)
recipient.Jid, *recipient.OxKeyRing, message, *flagSubject)
if err != nil {
fmt.Println("Ox: couldn't encrypt to",
recipient.Jid)
@ -469,21 +589,33 @@ func main() {
}
_, err = client.SendOrg(oxMessage)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
default:
_, err = client.Send(xmpp.Chat{
Remote: recipient.Jid,
Type: msgType, Text: message,
Subject: *flagSubject,
})
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, 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 {
@ -561,6 +693,7 @@ 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",
@ -569,9 +702,15 @@ 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 != "":
_, err = client.SendOrg("<message to='" + recipient.Jid + "' type='" +
msgType + "'><body>" + message + "</body><x xmlns='jabber:x:oob'><url>" +
message + "</url></x></message>")
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)
if err != nil {
fmt.Println("Couldn't send message to",
recipient.Jid)
@ -581,30 +720,29 @@ func main() {
continue
}
oxMessage, err := oxEncrypt(client, oxPrivKey,
recipient.Jid, recipient.OxKeyRing, message)
recipient.Jid, *recipient.OxKeyRing, message, *flagSubject)
if err != nil {
fmt.Println("Ox: couldn't encrypt to", recipient.Jid)
continue
}
_, err = client.SendOrg(oxMessage)
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
default:
_, err = client.Send(xmpp.Chat{
Remote: recipient.Jid,
Type: msgType, Text: message,
Subject: *flagSubject,
})
if err != nil {
log.Fatal(err)
cancel()
closeAndExit(client, err)
}
}
}
}
// 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()
closeAndExit(client, 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" "January 2024" ""
.TH "GO\-SENDXMPP" "1" "June 2024" ""
.SH "NAME"
\fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\.
.SH "SYNOPSIS"
\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
\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
.SH "DESCRIPTION"
A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) \fBsendxmpp\fR\.
A tool to send messages to an XMPP contact or MUC inspired by \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,6 +20,9 @@ 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
@ -77,9 +80,6 @@ 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
@ -87,7 +87,13 @@ Password for XMPP account\.
Send raw XML\. To send raw XML to a contact as normal chat message no contact must be specified\. To send raw XML to a MUC you have to specify the MUC via \fB\-c\fR and go\-sendxmpp will join the MUC\.
.TP
\fB\-\-scram\-mech\-pinning=[<value>]\fR
Enforce the use of a certain SCRAM authentication mechanism\. Currently go\-sendxmpp supports \fISCRAM\-SHA\-1\fR, \fISCRAM\-SHA\-1\-PLUS\fR, \fISCRAM\-SHA\-256\fR, \fISCRAM\-SHA\-256\-PLUS\fR, \fISCRAM\-SHA\-512\fR and \fISCRAM\-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)\.
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)
@ -95,11 +101,26 @@ Connection timeout in seconds\. (Default: 10)
\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
\fB\-\-version\fR
Show version information\.
.SH "ENVIRONMENT VARIABLES"
.SS "HTTP_PROXY"
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\.
@ -107,6 +128,10 @@ 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"

@ -57,6 +57,8 @@
<a href="#SYNOPSIS">SYNOPSIS</a>
<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>
@ -79,14 +81,14 @@
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>go-sendxmpp [-cdilnt] [-a value] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
<p><code>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] [--timeout value] [--tls-version value] [-u value]
[--version] [recipients…]</code></p>
[--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>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) <code>sendxmpp</code>. <br>
<p>A tool to send messages to an XMPP contact or MUC inspired by <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>
@ -106,6 +108,8 @@ 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>
@ -176,9 +180,6 @@ 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>
@ -186,10 +187,15 @@ it might be imported using <code>--ox-import-privkey</code>.</dd>
To send raw XML to a MUC you have to specify the MUC via <code>-c</code> and go-sendxmpp will join the MUC.</dd>
<dt><code>--scram-mech-pinning=[&lt;value&gt;]</code></dt>
<dd>Enforce the use of a certain SCRAM authentication mechanism. Currently go-sendxmpp supports
<em>SCRAM-SHA-1</em>, <em>SCRAM-SHA-1-PLUS</em>, <em>SCRAM-SHA-256</em>, <em>SCRAM-SHA-256-PLUS</em>, <em>SCRAM-SHA-512</em>
and <em>SCRAM-SHA-512-PLUS</em>. 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
<strong>SCRAM-SHA-1</strong>, <strong>SCRAM-SHA-1-PLUS</strong>, <strong>SCRAM-SHA-256</strong>, <strong>SCRAM-SHA-256-PLUS</strong>, <strong>SCRAM-SHA-512</strong>
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>
@ -198,12 +204,41 @@ 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>
<dd>Show version information.</dd>
</dl>
<h2 id="ENVIRONMENT-VARIABLES">ENVIRONMENT VARIABLES</h2>
<h3 id="HTTP_PROXY">HTTP_PROXY</h3>
<p>A SOCKS5 proxy can be used by setting the environment variable <code>HTTP_PROXY</code>. 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>
<p><code>
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>
@ -218,6 +253,14 @@ 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>
@ -241,7 +284,7 @@ License: BSD 2-clause License</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>January 2024</li>
<li class='tc'>June 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] [-f value] [--headline] [--help] [-h value] [-j value] [-m value] [--muc-password value]
`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] [--timeout value] [--tls-version value] [-u value]
[--version] [recipients…]`
[--ox-passphrase value] [-p value] [--raw] [--scram-mech-pinning value] [--ssdp-off] [-s value] [--timeout value]
[--tls-version value] [-u value] [--version] [recipients…]`
## DESCRIPTION
A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) `sendxmpp`.
A tool to send messages to an XMPP contact or MUC inspired by `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,6 +28,9 @@ 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)
@ -100,9 +103,6 @@ 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.
@ -112,23 +112,59 @@ To send raw XML to a MUC you have to specify the MUC via `-c` and go-sendxmpp wi
* `--scram-mech-pinning=[<value>]`:
Enforce the use of a certain SCRAM authentication mechanism. Currently go-sendxmpp supports
*SCRAM-SHA-1*, *SCRAM-SHA-1-PLUS*, *SCRAM-SHA-256*, *SCRAM-SHA-256-PLUS*, *SCRAM-SHA-512*
and *SCRAM-SHA-512-PLUS*. 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
**SCRAM-SHA-1**, **SCRAM-SHA-1-PLUS**, **SCRAM-SHA-256**, **SCRAM-SHA-256-PLUS**, **SCRAM-SHA-512**
and **SCRAM-SHA-512-PLUS**. 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).
* `--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).
* `--version`:
Show version information.
## ENVIRONMENT VARIABLES
### HTTP_PROXY
A SOCKS5 proxy can be used by setting the environment variable `HTTP_PROXY`. 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.
```
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
@ -143,6 +179,14 @@ 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).

145
ox.go

@ -6,7 +6,6 @@ package main
import (
"encoding/base64"
"errors"
"fmt"
"log"
"os"
@ -16,7 +15,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License
"github.com/beevik/etree" // BSD-2-clause
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
)
func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error {
@ -39,11 +38,11 @@ func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error {
}
query = nodeListReply.SelectElement("query")
if query == nil {
return errors.New("error parsing iq reply")
return fmt.Errorf("error parsing iq reply")
}
items := query.SelectElements("item")
if items == nil {
return errors.New("error parsing iq reply")
return fmt.Errorf("error parsing iq reply")
}
for _, item := range items {
node := item.SelectAttr("node")
@ -106,26 +105,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(), errors.New("ox: no signcrypt element")
return strError, time.Now(), fmt.Errorf("ox: no signcrypt element")
}
to := signcrypt.SelectElement("to")
if to == nil {
return strError, time.Now(), errors.New("ox: no to element")
return strError, time.Now(), fmt.Errorf("ox: no to element")
}
jid := to.SelectAttr("jid")
if jid == nil {
return strError, time.Now(), errors.New("ox: no jid attribute")
return strError, time.Now(), fmt.Errorf("ox: no jid attribute")
}
if strings.Split(jid.Value, "/")[0] != user {
return strError, time.Now(), errors.New("ox: encrypted for wrong user")
return strError, time.Now(), fmt.Errorf("ox: encrypted for wrong user")
}
timestamp := signcrypt.SelectElement("time")
if timestamp == nil {
return strError, time.Now(), errors.New("ox: no time element")
return strError, time.Now(), fmt.Errorf("ox: no time element")
}
stamp := timestamp.SelectAttr("stamp")
if stamp == nil {
return strError, time.Now(), errors.New("ox: no stamp attribute")
return strError, time.Now(), fmt.Errorf("ox: no stamp attribute")
}
msgStamp, err := time.Parse("2006-01-02T15:04:05Z0700", stamp.Value)
if err != nil {
@ -133,7 +132,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(), errors.New("ox: no payload element")
return strError, time.Now(), fmt.Errorf("ox: no payload element")
}
body := payload.SelectElement("body")
if body == nil {
@ -173,7 +172,7 @@ func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iq
}
entity := key.GetEntity()
if entity.Identities[xmppURI] == nil {
return errors.New("Key identity is not " + xmppURI)
return fmt.Errorf("Key identity is not %s", xmppURI)
}
pk, err := key.GetPublicKey()
if err != nil {
@ -262,19 +261,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 errors.New("error while publishing public key")
return fmt.Errorf("error while publishing public key")
}
ownPubKeyRingFromPubsub, err := oxRecvPublicKeys(client, iqc, jid, fingerprint)
if err != nil {
return errors.New("couldn't successfully verify public key upload")
return fmt.Errorf("couldn't successfully verify public key upload")
}
ownPubKeyFromPubsub := ownPubKeyRingFromPubsub.GetKeys()[0]
ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize()
if err != nil {
return errors.New("couldn't successfully verify public key upload")
return fmt.Errorf("couldn't successfully verify public key upload")
}
if pubKeyBase64 != base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized) {
return errors.New("couldn't successfully verify public key upload")
return fmt.Errorf("couldn't successfully verify public key upload")
}
root = etree.NewDocument()
root.WriteSettings.AttrSingleQuote = true
@ -310,72 +309,51 @@ 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 errors.New("couldn't publish public key list")
return fmt.Errorf("couldn't publish public key list")
}
return nil
}
func oxGetPrivKeyLoc(jid 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()
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)
if err != nil {
return strError, fmt.Errorf("oxGetPrivKeyLoc: failed to determine user dir: %w", err)
return dataFile, err
}
if homeDir == "" {
return strError, errors.New("oxGetPrivKeyLoc: received empty string for home directory")
}
if _, err := os.Stat(oldDataFile2); err == nil {
err := os.Rename(oldDataFile2, dataFile)
if err != nil {
return dataFile, err
}
dataDir = homeDir + "/.local/share"
}
dataDir += "/go-sendxmpp/oxprivkeys/"
if _, err = os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, defaultDirRights)
if _, err := os.Stat(oldDataFile3); err == nil {
err := os.Rename(oldDataFile3, dataFile)
if err != nil {
return strError, fmt.Errorf("oxGetPrivKeyLoc: could not create folder for private keys: %w", err)
return dataFile, err
}
}
dataFile := dataDir + base64.StdEncoding.EncodeToString([]byte(jid))
return dataFile, nil
}
func oxGetPubKeyLoc(fingerprint 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("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)
}
dataDir, err := getDataPath("oxpubkeys/")
if err != nil {
return strError, fmt.Errorf("oxGetPubKeyLoc: %w", err)
}
dataFile := dataDir + fingerprint
return dataFile, nil
@ -413,7 +391,7 @@ func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) {
log.Fatal("Ox: private key is locked.")
}
if key.IsExpired() {
return nil, errors.New("Ox: private key is expired: " + key.GetFingerprint())
return nil, fmt.Errorf("Ox: private key is expired: %s", key.GetFingerprint())
}
return key, nil
}
@ -499,7 +477,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, errors.New("error while requesting public key for " +
return nil, fmt.Errorf("error while requesting public key for %s",
recipient)
}
oxPublicKeyXML := etree.NewDocument()
@ -513,18 +491,15 @@ func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, f
}
oxPublicKeyXMLPubsub := oxPublicKeyXML.SelectElement("pubsub")
if oxPublicKeyXMLPubsub == nil {
return nil, errors.New("ox: no pubsub element in reply to public " +
"key request")
return nil, fmt.Errorf("ox: no pubsub element in reply to public key request")
}
oxPublicKeyXMLItems := oxPublicKeyXMLPubsub.SelectElement("items")
if oxPublicKeyXMLItems == nil {
return nil, errors.New("ox: no items element in reply to public " +
"key request")
return nil, fmt.Errorf("ox: no items element in reply to public key request")
}
oxPublicKeyXMLItem := oxPublicKeyXMLItems.SelectElement("item")
if oxPublicKeyXMLItem == nil {
return nil, errors.New("ox: no item element in reply to public " +
"key request")
return nil, fmt.Errorf("ox: no item element in reply to public key request")
}
oxPublicKeyXMLPubkeys := oxPublicKeyXMLItem.SelectElements("pubkey")
for _, r := range oxPublicKeyXMLPubkeys {
@ -541,7 +516,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, errors.New("Key is expired: " + fingerprint)
return nil, fmt.Errorf("Key is expired: %s", fingerprint)
}
err = keyring.AddKey(key)
if err != nil {
@ -573,7 +548,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
log.Fatal(err)
}
if oxPublicKeyList.Type != strResult {
return nil, errors.New("error while requesting public openpgp keys for " +
return nil, fmt.Errorf("error while requesting public openpgp keys for %s",
recipient)
}
oxPubKeyListXML := etree.NewDocument()
@ -590,19 +565,19 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
oxPubKeyListXMLPubsub := oxPubKeyListXML.SelectElement("pubsub")
if oxPubKeyListXMLPubsub == nil {
return nil, errors.New("ox: no pubsub element in public key list")
return nil, fmt.Errorf("ox: no pubsub element in public key list")
}
oxPubKeyListXMLPubsubItems := oxPubKeyListXMLPubsub.SelectElement("items")
if oxPubKeyListXMLPubsubItems == nil {
return nil, errors.New("ox: no items element in public key list")
return nil, fmt.Errorf("ox: no items element in public key list")
}
oxPubKeyListXMLPubsubItemsItem := oxPubKeyListXMLPubsubItems.SelectElement("item")
if oxPubKeyListXMLPubsubItemsItem == nil {
return nil, errors.New("ox: no item element in public key list")
return nil, fmt.Errorf("ox: no item element in public key list")
}
oxPubKeyListXMLPubsubItemsItemPkl := oxPubKeyListXMLPubsubItemsItem.SelectElement("public-keys-list")
if oxPubKeyListXMLPubsubItemsItemPkl == nil {
return nil, errors.New("ox: no public-keys-list element")
return nil, fmt.Errorf("ox: no public-keys-list element")
}
oxPubKeyListXMLPubsubItemsItemPklPm := oxPubKeyListXMLPubsubItemsItemPkl.SelectElements("pubkey-metadata")
for _, r := range oxPubKeyListXMLPubsubItemsItemPklPm {
@ -624,7 +599,7 @@ func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string)
}
}
if pubKeyRingID == "none" {
return nil, errors.New("server didn't provide public key fingerprints for " + recipient)
return nil, fmt.Errorf("server didn't provide public key fingerprints for %s", recipient)
}
pubKeyRingLocation, err := oxGetPubKeyLoc(pubKeyRingID)
@ -644,7 +619,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, errors.New("couldn't read public keys from cache")
return nil, fmt.Errorf("couldn't read public keys from cache")
}
for _, r := range pubKeys {
keyByte, err := base64.StdEncoding.DecodeString(r.Text())
@ -690,7 +665,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) (string, error) {
func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing crypto.KeyRing, message string, subject string) (string, error) {
if message == "" {
return "", nil
}
@ -717,6 +692,10 @@ 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,7 +6,6 @@ package main
import (
"bufio"
"errors"
"fmt"
"log"
"os"
@ -26,7 +25,7 @@ func findConfig() (string, error) {
// Get home directory.
home := curUser.HomeDir
if home == "" {
return "", errors.New("no home directory found")
return "", fmt.Errorf("findConfig: no home directory found")
}
osConfigDir := os.Getenv("$XDG_CONFIG_HOME")
if osConfigDir == "" {
@ -45,7 +44,7 @@ func findConfig() (string, error) {
return r, nil
}
}
return "", errors.New("no configuration file found")
return "", fmt.Errorf("findConfig: no configuration file found")
}
// Opens the config file and returns the specified values
@ -77,8 +76,7 @@ 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, errors.New("Wrong permissions for " + configPath + ": " +
permissions + " instead of 400, 440, 600 or 640.")
return output, fmt.Errorf("parseConfig: wrong permissions for %s: %s instead of 400, 440, 600 or 640.", configPath, permissions)
}
}
@ -87,6 +85,7 @@ 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)
@ -140,10 +139,6 @@ 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)
@ -154,7 +149,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, errors.New("invalid username/JID: " + output.username)
return output, fmt.Errorf("parseConfig: invalid username/JID: %s", output.username)
}
}

@ -7,24 +7,30 @@ package main
import (
"context"
"fmt"
"io"
"log"
"runtime"
"time"
"github.com/beevik/etree" // BSD-2-clause
"github.com/mattn/go-xmpp" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
)
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)
c := make(chan xmpp.IQ, defaultBufferSize)
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)
}
iq = <-c
select {
case iq = <-c:
case <-time.After(60 * time.Second):
return iq, fmt.Errorf("sendIQ: server didn't reply to IQ: %s", content)
}
return iq, nil
}
@ -38,29 +44,49 @@ func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) {
}
}
func rcvStanzas(client *xmpp.Client, iqc chan xmpp.IQ, msgc chan xmpp.Chat, ctx context.Context) {
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
}
}
}()
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
default:
case err := <-e:
if err != nil {
log.Println(err)
if err != io.EOF {
closeAndExit(client, err)
return
}
return
}
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)
@ -69,6 +95,9 @@ func rcvStanzas(client *xmpp.Client, iqc chan xmpp.IQ, msgc chan xmpp.Chat, ctx
if query != nil {
xmlns = query.SelectAttr("xmlns")
}
if xmlns == nil {
break
}
switch xmlns.Value {
case nsDiscoInfo:
root := etree.NewDocument()

Loading…
Cancel
Save