pax_global_header00006660000000000000000000000064145641546060014525gustar00rootroot0000000000000052 comment=ba17366ff527b449b0c1495c0ea587f7c4948d02 go-sendxmpp-v0.8.3/000077500000000000000000000000001456415460600141645ustar00rootroot00000000000000go-sendxmpp-v0.8.3/.gitignore000066400000000000000000000004501456415460600161530ustar00rootroot00000000000000config.json .vscode # Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ go-sendxmpp-v0.8.3/.gitlab-ci.yml000066400000000000000000000077461456415460600166360ustar00rootroot00000000000000# To contribute improvements to CI/CD templates, please follow the Development guide at: # https://docs.gitlab.com/ee/development/cicd/templates.html # This specific template is located at: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml image: golang:latest variables: # Please edit to your GitLab project REPO_NAME: salsa.debian.org/mdosch/go-sendxmpp # The problem is that to be able to use go get, one needs to put # the repository in the $GOPATH. So for example if your gitlab domain # is gitlab.com, and that your repository is namespace/project, and # the default GOPATH being /go, then you'd need to have your # repository in /go/src/gitlab.com/namespace/project # Thus, making a symbolic link corrects this. before_script: - mkdir -p $GOPATH/src/$(dirname $REPO_NAME) - ln -svf $CI_PROJECT_DIR $GOPATH/src/$REPO_NAME - cd $GOPATH/src/$REPO_NAME stages: - test - build - release format: # image: registry.gitlab.com/gitlab-org/gitlab-build-images:golangci-lint-alpine stage: test script: - go fmt $(go list ./... | grep -v /vendor/) - go vet $(go list ./... | grep -v /vendor/) - go test -race $(go list ./... | grep -v /vendor/) # Taken from https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20404/diffs#diff-content-8071267ea32ba69f24a8bd50bcbddf972c295ce3 # Use default .golangci.yml file from the image if one is not present in the project root. #- '[ -e .golangci.yml ] || cp /golangci/.golangci.yml .' #- golangci-lint run #allow_failure: true compile: stage: build only: - tags 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 - 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 - env GOOS=windows GOARCH=amd64 go build -buildmode=pie -ldflags "-s -w -extldflags '-static'" -o $CI_PROJECT_DIR/win64/go-sendxmpp.exe 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 release: stage: release image: registry.gitlab.com/gitlab-org/release-cli:latest only: - tags script: - | 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\"}" go-sendxmpp-v0.8.3/CHANGELOG.md000066400000000000000000000127741456415460600160100ustar00rootroot00000000000000# Changelog ## [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 new parameter `--scram-mech-pinning`. ### Changed - Refuse to upload a file if upload slot doesn't provide https. - Use XEP-0474 instead of SCRAM mechanism pinning to prevent downgrade attacks (requires go-xmpp commit 39f5b80375b6f6f266df37b4a4adcbeb606ffec2). ## [v0.7.0] 2023-11-11 ### Added - Reply to XEP-0092 software version requests. - Add support for PLUS variants of SCRAM authentication mechanisms (requires go-xmpp commit 4c385a334c606e8bc387f0a3d4d84975802b3984). - Add pinning of last used authentication mechanism if a SCRAM mechanism was used. ### Changed - Print every stanza in a new line (requires go-xmpp commit 31c7eb6919b67b18e901dc45a8e5681040ea7f31). ## [v0.6.2] 2023-09-29 ### Changed - Properly close connection to server if ^C is pressed in interactive mode. - Replace invalid characters by UTF8 replacement char. - Add warning that there is no Ox support for messages of type headline. - Suppress warnings about reading from closed connection if go-sendxmpp closes the connection before exiting. - Remove unnecessary newlines after stanzas. - Fix segfault when authentication fails due to invalid username or password. ## Removed - Removed deprecated flag and config option `resource`. ## [v0.6.1] 2023-07-25 ### Changed - Properly close connection to server. ## [v0.6.0] 2023-07-02 ### Added - Add support for sending Out of Band Data (OOB). - Add SCRAM-SHA-1, SCRAM-SHA-256 and SCRAM-SHA512 support (via go-xmpp commit bef3e54). - Add support for messages of type headline. ### Changed - Print `--help` output to stdout instead of stderr. - Print sent stanzas when `--debug` is used (via go-xmpp commit 9129a110df1b). - Allow JIDs without localpart. - Use single quotes for attributes in stanzas created by github.com/beevik/etree (vial etree v1.1.4). - Fix SRV lookup when the domain has a CNAME (via xmppsrv v0.2.5). - Fall back to directTLS on port 5223 (instead of StartTLS on port 5222) if no SRV records are provided and `-t` is set. - Remove trailing newlines in interactive mode. ## [v0.5.6] 2022-11-11 ### Added - Add short option `-h` for http-upload. ### Changed - Fix Ox key generation. ## [v0.5.5] 2022-10-16 ### Changed - Improve interactive mode. ## [v0.5.4] 2022-09-30 ### Changed - Fix http-upload. ## [v0.5.3] 2022-09-29 ### Changed - Don't check for empty message when `--listen` is used. ## [v0.5.2] 2022-09-29 ### Added - Reply to `disco#info` queries. - Send `service-unavailable` errors for all other IQs of type `get` and `set`. - New config option and command line flag `alias` to uncouple MUC nickname/alias from resource. ### Changed - Ox: Improve error messages for failed key requests. - Ox: Do not encrypt empty messages. - Check for empty messages. - Always look up CNAME before doing SRV lookups (via xmppsrv v0.2.3). - Detect CNAME loops (max. 5 CNAMEs) (via xmppsrv >= v0.2.4). - Deprecate resource config option and command line flag. - Improve error handling in XML parsing. - Don't connect to the XMPP server if the message is empty. ## [v0.5.1] 2022-05-22 ### Changed - Improve listening mode for groupchats. - Fix nickname in MUCs if not manually set. ## [v0.5.0] 2022-05-05 ### Added - Add possibility to delete existing OpenPGP for XMPP nodes. ### Changed - Ox: Improve rpad generation. - Rework receiving of stanzas. - Ox: Improve private key import. ## [v0.4.0] 2022-04-30 ### Added - Experimental support for Ox (OpenPGP for XMPP) encryption. ### Changed - Shorter random ID for resource. - Changed to the more flexible "github.com/beevik/etree" instead of encoding/xml for creating and interpreting stanzas. - Changed to use message stamp, if provided, for printing the time stamp of received messages. ## [v0.3.0] 2022-03-21 ### Added - Added support for joining password protected MUCs. ### Changed - Removed invalid code points from input. - Fixed crash when reading a config with wrong syntax. - Fixed crash when a non-existing or non-readable config was supplied by `-f`. - Changed config file location from `~/.config/go-sendxmpp/sendxmpprc` to `~/.config/go-sendxmpp/config`. - Fixed blocking of go-sendxmpp if an IQ reply of type "error" is received (via go-xmpp v0.0.0-20220319135856-e773596ea0b0). ## [v0.2.0] 2022-02-12 ### Added - Added listening function. - Added flag to configure connection timeout. - Added flag to configure minimum TLS version. - Added flag to show version. ### Removed - Removed deprecated option `-x`. ## [v0.1.3] 2022-01-29 ### Changed - Rename files to use a limited character set (alpha numerical and some extra characters) file name before uploading. Workaround for https://github.com/mattn/go-xmpp/issues/132 ## [v0.1.2] 2021-11-18 ### Changed - Use xml.Marshal to safely build HTTP Upload request. - Use salsa.debian.org/mdosch/xmppsrv for SRV lookups. ## [v0.1.1] 2021-09-12 ### Changed - Xml-escape file name in http-upload. - Xml-escape mimetype in http-upload. ## [v0.1.0] 2021-09-11 ### Added - Initial release go-sendxmpp-v0.8.3/LICENSE000066400000000000000000000024341456415460600151740ustar00rootroot00000000000000BSD 2-Clause License Copyright (c) Martin Dosch All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-sendxmpp-v0.8.3/LICENSE-mellium000066400000000000000000000024271456415460600166400ustar00rootroot00000000000000Copyright © 2014 The Mellium Contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-sendxmpp-v0.8.3/README.md000066400000000000000000000136451456415460600154540ustar00rootroot00000000000000# go-sendxmpp ## about A tool to send messages to an XMPP contact or MUC inspired by [sendxmpp](https://sendxmpp.hostname.sk/). You can find other sendxmpp alternatives in the [XSF wiki](https://wiki.xmpp.org/web/User:MDosch/Sendxmpp_incarnations). ## support You might join the [chat](https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join) if you have problems, want to contribute or just want to talk about the project. You might also talk about any of the other sendxmpp incarnations. :) [![Go-sendxmpp MUC badge](https://chat.mdosch.de/muc_badge/go-sendxmpp@chat.mdosch.de)](https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join) ## requirements * [go](https://golang.org/) >= 1.21 ## installation ### repositories including go-sendxmpp [![Packaging status](https://repology.org/badge/vertical-allrepos/go:sendxmpp.svg)](https://repology.org/project/go:sendxmpp/versions) ### manual installation Latest release: ```plain $ go install salsa.debian.org/mdosch/go-sendxmpp@latest ``` Current development version: ```plain $ go install salsa.debian.org/mdosch/go-sendxmpp@master ``` You will find the binary in `$GOPATH/bin` or, if set, `$GOBIN`. ### binaries There are some (automatically built and untested) binaries linked to the [release](https://salsa.debian.org/mdosch/go-sendxmpp/-/releases). ## usage 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` if no other configuration file location is specified with `-f` or `--file`. The configuration file is expected to be in the following format: ```plain username: password: ``` If this is not sufficient to connect you might also specify `jserver` and `port`. It is also possible to use a password manager. In this case the `password` setting should be replaced by the `eval_password` setting: ``` eval_password: ``` You can also configure the alias/nickname for MUCs via the `alias` setting: ``` alias: ``` If no configuration file is present or if the values should be overridden it is possible to define 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] [--scram-mech-pinning value] [--timeout value] [--tls-version value] [-u value] [--version] [recipients…] -a, --alias=value Set alias/nicknamefor chatrooms. -c, --chatroom Send message to a chatroom. -d, --debug Show debugging info. -f, --file=value Set configuration file. (Default: ~/.config/go-sendxmpp/sendxmpprc) --headline Send message as type headline. --help Show help. -h, --http-upload=value Send a file via http-upload. -i, --interactive Interactive mode (for use with e.g. 'tail -f'). -j, --jserver=value XMPP server address. -l, --listen Listen for messages and print them to stdout. -m, --message=value Set file including the message. --muc-password=value Password for password protected MUCs. -n, --no-tls-verify Skip verification of TLS certificates (not recommended). --oob-file=value URL to send a file as out of band data. --ox Use "OpenPGP for XMPP" encryption (experimental). --ox-delete-nodes Delete existing OpenPGP nodes on the server. --ox-genprivkey-rsa Generate a private OpenPGP key (RSA 4096 bit) for the given JID and publish the corresponding public key. --ox-genprivkey-x25519 Generate a private OpenPGP key (x25519) for the given JID and publish the corresponding public key. --ox-import-privkey=value Import an existing private OpenPGP key. --ox-passphrase=value Passphrase for locking and unlocking the private OpenPGP key. -p, --password=value Password for XMPP account. --raw Send raw XML. --scram-mech-pinning=value Enforce the use of a certain SCRAM authentication mechanism. --timeout=value Connection timeout in seconds. [10] -t, --tls Use direct TLS. --tls-version=value Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2) or 13 (TLSv1.3). [12] -u, --username=value Username for XMPP account. --version Show version information. ``` ### examples Send a message to two recipients using a configuration file. ```bash cat message.txt | ./go-sendxmpp -f ./sendxmpp recipient1@example.com recipient2@example.com ``` Send a message to two recipients directly defining account credentials. ```bash cat message.txt | ./go-sendxmpp -u bob@example.com -p swordfish recipient1@example.com recipient2@example.com ``` Send a message to two groupchats (`-c`) using a configuration file. ```bash cat message.txt | ./go-sendxmpp -cf ./sendxmpp chat1@conference.example.com chat2@conference.example.com ``` 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 ``` ### shell completion #### zsh There are no shell completions yet (contributions welcome) but for zsh it is possible to automatically create completions from `--help` which might work *good enough*. Just place the following in your `~/.zshrc` or `~/.zshrc.local`: ``` bash compdef _gnu_generic go-sendxmpp ``` go-sendxmpp-v0.8.3/connect.go000066400000000000000000000034471456415460600161540ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "fmt" "net" "os" "strings" "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") // Look up SRV records if server is not specified manually. if options.Host == "" { server := options.User[strings.Index(options.User, "@")+1:] // Don't do SRV look ups if proxy is set. if proxy == "" { // Look up xmpp-client SRV records. 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. if directTLS { options.NoTLS = false options.StartTLS = false options.Host = net.JoinHostPort(server, "5223") } else { // Try port 5222 if no xmpp-client SRV records are provided and directTLS is not set. options.NoTLS = true options.StartTLS = true options.Host = net.JoinHostPort(server, "5222") } } // Connect to server client, err := options.NewClient() if err == nil { return client, nil } return client, fmt.Errorf("failed to connect to server: %w", err) } go-sendxmpp-v0.8.3/const.go000066400000000000000000000030561456415460600156450ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main const ( version = "0.8.3" // defaults defaultBufferSize = 100 defaultConfigRowSep = 2 defaultDirRights = 0o700 defaultFileRights = 0o600 defaultFileRightsWin = 0o200 defaultIDBytes = 12 defaultLenServerConf = 2 defaultRpadMultiple = 100 defaultRSABits = 4096 defaultShortIDBytes = 4 defaultSleepTime = 100 defaultTimeout = 10 defaultTLSMinVersion = 12 defaultTLS10 = 10 defaultTLS11 = 11 defaultTLS12 = 12 defaultTLS13 = 13 // namespace nsDiscoInfo = "http://jabber.org/protocol/disco#info" nsDiscoItems = "http://jabber.org/protocol/disco#items" nsEme = "urn:xmpp:eme:0" nsHints = "urn:xmpp:hints" nsHTTPUpload = "urn:xmpp:http:upload:0" nsJabberClient = "jabber:client" nsJabberData = "jabber:x:data" nsOx = "urn:xmpp:openpgp:0" nsOxPubKeys = "urn:xmpp:openpgp:0:public-keys" nsPubsub = "http://jabber.org/protocol/pubsub" nsPubsubOwner = "http://jabber.org/protocol/pubsub#owner" nsVersion = "jabber:iq:version" nsXMPPStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas" // strings oxAltBody = "This message is encrypted (XEP-0373: OpenPGP for XMPP)." pubsubPubOptions = "http://jabber.org/protocol/pubsub#publish-options" strChat = "chat" strError = "error" strGroupchat = "groupchat" strHeadline = "headline" strResult = "result" ) go-sendxmpp-v0.8.3/go.mod000066400000000000000000000013111456415460600152660ustar00rootroot00000000000000module salsa.debian.org/mdosch/go-sendxmpp go 1.21.5 require ( github.com/ProtonMail/gopenpgp/v2 v2.7.5 github.com/beevik/etree v1.3.0 github.com/gabriel-vasile/mimetype v1.4.3 github.com/pborman/getopt/v2 v2.1.0 github.com/xmppo/go-xmpp v0.0.2-0.20240201055852-b369b7df1031 salsa.debian.org/mdosch/xmppsrv v0.2.6 ) require ( github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/pkg/errors v0.9.1 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect ) go-sendxmpp-v0.8.3/go.sum000066400000000000000000000167221456415460600153270ustar00rootroot00000000000000github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA= github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g= github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU= github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA= github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xmppo/go-xmpp v0.0.2-0.20240201055852-b369b7df1031 h1:FOObNOvf3Tc3pKHGHp6LZRVBT7tv1KeqFSvOswLTi8E= github.com/xmppo/go-xmpp v0.0.2-0.20240201055852-b369b7df1031/go.mod h1:goIl07FSeu+OGrFpTC8fMUTG4Dc7B6LnOZ+qxe0WVi8= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 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= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= salsa.debian.org/mdosch/xmppsrv v0.2.6 h1:+S7ZxRP7BQwQHGJvYhr408UD0XGAd7+RZaVA/xN4EIc= salsa.debian.org/mdosch/xmppsrv v0.2.6/go.mod h1:udWXnWFa9zkcyN9YSB/u44BCnnRDpeQ0eDy3MVLjHZQ= go-sendxmpp-v0.8.3/helpers.go000066400000000000000000000034241456415460600161600ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "bytes" "crypto/rand" "fmt" "log" "math/big" "net/url" "os" "regexp" "strings" ) func validUTF8(s string) string { // Remove invalid code points. s = strings.ToValidUTF8(s, "�") reg := regexp.MustCompile(`[\x{0000}-\x{0008}\x{000B}\x{000C}\x{000E}-\x{001F}]`) s = reg.ReplaceAllString(s, "�") return s } func validURI(s string) (*url.URL, error) { // Check if URI is valid uri, err := url.ParseRequestURI(s) return uri, fmt.Errorf("validURI: %w", err) } func readFile(path string) (*bytes.Buffer, error) { file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("readFile: %w", err) } buffer := new(bytes.Buffer) _, err = buffer.ReadFrom(file) if err != nil { return nil, fmt.Errorf("readFile: %w", err) } err = file.Close() if err != nil { fmt.Println("error while closing file:", err) } return buffer, nil } func getRpad(messageLength int) string { rpadRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") length := defaultRpadMultiple - messageLength%defaultRpadMultiple max := big.NewInt(int64(len(rpadRunes))) rpad := make([]rune, length) for i := range rpad { randInt, err := rand.Int(rand.Reader, max) if err != nil { log.Fatal(err) } rpad[i] = rpadRunes[randInt.Int64()] } return string(rpad) } 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:]) } func getShortID() string { id := make([]byte, defaultShortIDBytes) _, err := rand.Read(id) if err != nil { log.Fatal(err) } return fmt.Sprintf("%x", id[0:4]) } go-sendxmpp-v0.8.3/httpupload.go000066400000000000000000000170231456415460600167020ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "bytes" "encoding/xml" "errors" "fmt" "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/xmppo/go-xmpp" // BSD-3-Clause ) 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 { return "", err } fileSize := fileInfo.Size() // Read file buffer, err := readFile(filePath) if err != nil { return "", err } // Get mime type mimeType := mimetype.Detect(buffer.Bytes()).String() var mimeTypeEscaped bytes.Buffer xml.Escape(&mimeTypeEscaped, []byte(mimeType)) // Get file name fileName := filepath.Base(filePath) // Just use alphanumerical and some special characters for now // to work around https://github.com/xmppo/go-xmpp/issues/132 reg := regexp.MustCompile(`[^a-zA-Z0-9\+\-\_\.]+`) fileNameEscaped := reg.ReplaceAllString(fileName, "_") // Query server for disco#items iqContent, err := sendIQ(client, iqc, jserver, "get", "") if err != nil { return "", err } iqDiscoItemsXML := etree.NewDocument() err = iqDiscoItemsXML.ReadFromBytes(iqContent.Query) if err != nil { return "", err } iqDiscoItemsXMLQuery = iqDiscoItemsXML.SelectElement("query") if iqDiscoItemsXMLQuery == nil { return "", errors.New("http-upload: no query element in disco items reply") } iqDiscoItemsXMLItems := iqDiscoItemsXMLQuery.SelectElements("item") // Check the services reported by disco#items for the http upload service for _, r := range iqDiscoItemsXMLItems { jid := r.SelectAttr("jid") iqDiscoInfoReqXML := etree.NewDocument() iqDiscoInfoReqXML.WriteSettings.AttrSingleQuote = true iqDiscoInfoReqXMLQuery := iqDiscoInfoReqXML.CreateElement("query") iqDiscoInfoReqXMLQuery.CreateAttr("xmlns", nsDiscoInfo) iqdi, err := iqDiscoInfoReqXML.WriteToString() if err != nil { return "", err } iqDiscoInfo, err := sendIQ(client, iqc, jid.Value, "get", iqdi) if err != nil { return "", err } if iqDiscoInfo.Type != strResult { continue } iqDiscoInfoXML := etree.NewDocument() err = iqDiscoInfoXML.ReadFromBytes(iqDiscoInfo.Query) if err != nil { return "", err } iqDiscoInfoXMLQuery = iqDiscoInfoXML.SelectElement("query") if iqDiscoInfoXMLQuery == nil { continue } iqDiscoInfoXMLIdentity := iqDiscoInfoXMLQuery.SelectElement("identity") if iqDiscoInfoXMLIdentity == nil { continue } iqDiscoInfoXMLType := iqDiscoInfoXMLIdentity.SelectAttr("type") if iqDiscoInfoXMLType == nil { continue } iqDiscoInfoXMLCategory := iqDiscoInfoXMLIdentity.SelectAttr("category") if iqDiscoInfoXMLCategory == nil { continue } if iqDiscoInfoXMLType.Value == "file" && iqDiscoInfoXMLCategory.Value == "store" { uploadComponent = jid.Value break } } if uploadComponent == "" { return "", errors.New("http-upload: no http upload component found.") } iqDiscoInfoXMLX := iqDiscoInfoXMLQuery.SelectElements("x") for _, r := range iqDiscoInfoXMLX { field := r.SelectElements("field") for i, t := range field { varAttr := t.SelectAttr("var") if varAttr == nil { continue } curFieldVal := t.SelectElement("value") if curFieldVal == nil { continue } 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 "", errors.New("http-upload: error while checking server maximum http upload file size.") } } } } } // Check if the file size doesn't exceed the maximum file size of the http upload // component if a maximum file size is reported, if not just continue and hope for // the best. if maxFileSize != 0 { if fileSize > maxFileSize { return "", errors.New("http-upload: file size " + strconv.FormatInt(fileSize/1024/1024, 10) + " MB is larger than the maximum file size allowed (" + strconv.FormatInt(maxFileSize/1024/1024, 10) + " MB).") } } request := etree.NewDocument() request.WriteSettings.AttrSingleQuote = true requestReq := request.CreateElement("request") requestReq.CreateAttr("xmlns", nsHTTPUpload) requestReq.CreateAttr("filename", fileNameEscaped) requestReq.CreateAttr("size", fmt.Sprint(fileSize)) requestReq.CreateAttr("content-type", mimeType) r, err := request.WriteToString() if err != nil { return "", err } // Request http upload slot uploadSlot, err := sendIQ(client, iqc, uploadComponent, "get", r) if err != nil { return "", err } if uploadSlot.Type != strResult { return "", errors.New("http-upload: error while requesting upload slot.") } iqHTTPUploadSlotXML := etree.NewDocument() err = iqHTTPUploadSlotXML.ReadFromBytes(uploadSlot.Query) if err != nil { return "", err } iqHTTPUploadSlotXMLSlot := iqHTTPUploadSlotXML.SelectElement("slot") if iqHTTPUploadSlotXMLSlot == nil { return "", errors.New("http-upload: no slot element") } iqHTTPUploadSlotXMLPut := iqHTTPUploadSlotXMLSlot.SelectElement("put") if iqHTTPUploadSlotXMLPut == nil { return "", errors.New("http-upload: no put element") } iqHTTPUploadSlotXMLPutURL := iqHTTPUploadSlotXMLPut.SelectAttr("url") if iqHTTPUploadSlotXMLPutURL == nil { return "", errors.New("http-upload: no url attribute") } if !strings.HasPrefix(iqHTTPUploadSlotXMLPutURL.Value, "https://") { return "", errors.New("http-upload: upload slot does not provide https") } // Upload file 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 { return "", err } req.Header.Set("Content-Type", mimeTypeEscaped.String()) iqHTTPUploadSlotXMLPutHeaders := iqHTTPUploadSlotXMLPut.SelectElements("header") for _, h := range iqHTTPUploadSlotXMLPutHeaders { name := h.SelectAttr("name") if name == nil { continue } switch name.Value { case "Authorization", "Cookie", "Expires": req.Header.Set(name.Value, h.Text()) } } resp, err := httpClient.Do(req) if err != nil { return "", err } // Test for http status code "200 OK" or "201 Created" if resp.StatusCode != 200 && resp.StatusCode != 201 { return "", errors.New("http-upload: upload failed.") } // Return http link iqHTTPUploadSlotXMLGet := iqHTTPUploadSlotXMLSlot.SelectElement("get") if iqHTTPUploadSlotXMLGet == nil { return "", errors.New("http-upload: no get element") } iqHTTPUploadSlotXMLGetURL := iqHTTPUploadSlotXMLGet.SelectAttr("url") if iqHTTPUploadSlotXMLGetURL == nil { return "", errors.New("http-upload: no url attribute") } err = resp.Body.Close() if err != nil { fmt.Println("http-upload: error while closing http request body:", err) } return iqHTTPUploadSlotXMLGetURL.Value, nil } go-sendxmpp-v0.8.3/jid.go000066400000000000000000000047711456415460600152720ustar00rootroot00000000000000// Copyright 2014 The Mellium Contributors. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE-mellium file. // Original taken from mellium.im/xmpp/jid (BSD-2-Clause) and adjusted for my needs. // Copyright Martin Dosch package main import ( "errors" "strings" "unicode/utf8" ) // MarshalJID checks that JIDs include localpart and serverpart // and return it marshaled. Shamelessly stolen from // mellium.im/xmpp/jid func MarshalJID(input string) (string, error) { var ( err error localpart string domainpart string resourcepart string ) s := input // Remove any portion from the first '/' character to the end of the // string (if there is a '/' character present). sep := strings.Index(s, "/") if sep == -1 { resourcepart = "" } 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") } resourcepart = s[sep+1:] s = s[:sep] } // Remove any portion from the beginning of the string to the first // '@' character (if there is an '@' character present). sep = strings.Index(s, "@") switch { case sep == -1: // There is no @ sign, and therefore no localpart. domainpart = s case sep == 0: // The JID starts with an @ sign (invalid empty localpart) err = errors.New("Invalid JID:" + input) return input, err default: domainpart = s[sep+1:] localpart = s[:sep] } // We'll throw out any trailing dots on domainparts, since they're ignored: // // If the domainpart includes a final character considered to be a label // separator (dot) by [RFC1034], this character MUST be stripped from // the domainpart before the JID of which it is a part is used for the // purpose of routing an XML stanza, comparing against another JID, or // constructing an XMPP URI or IRI [RFC5122]. In particular, such a // character MUST be stripped before any other canonicalization steps // are taken. domainpart = strings.TrimSuffix(domainpart, ".") var jid string if !utf8.ValidString(localpart) || !utf8.ValidString(domainpart) || !utf8.ValidString(resourcepart) { return input, errors.New("Invalid JID: " + input) } if domainpart == "" { return input, errors.New("Invalid JID: " + input) } if localpart == "" { jid = domainpart } else { jid = localpart + "@" + domainpart } if resourcepart == "" { return jid, err } return jid + "/" + resourcepart, err } go-sendxmpp-v0.8.3/main.go000066400000000000000000000433141456415460600154440ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "bufio" "context" "crypto/tls" "errors" "fmt" "io" "log" "net" "os" "os/signal" "strings" "time" "github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License "github.com/pborman/getopt/v2" // BSD-3-Clause "github.com/xmppo/go-xmpp" // BSD-3-Clause ) type configuration struct { username string jserver string port string password string alias string } func closeAndExit(client *xmpp.Client, cancel context.CancelFunc, err error) { // Wait for a short time as some messages are not delivered by the server // if the connection is closed immediately after sending a message. time.Sleep(defaultSleepTime * time.Millisecond) cancel() client.Close() if err != nil { log.Fatal(err) } } func readMessage(messageFilePath string) (string, error) { var ( output string err error ) // Check that message file is existing. _, err = os.Stat(messageFilePath) if err != nil { return output, fmt.Errorf("readMessage: %w", err) } // Open message file. file, err := os.Open(messageFilePath) if err != nil { return output, fmt.Errorf("readMessage: %w", err) } scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) for scanner.Scan() { if output == "" { output = scanner.Text() } else { output = output + "\n" + scanner.Text() } } 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, nil } func main() { type recipientsType struct { Jid string OxKeyRing *crypto.KeyRing } var ( err error message, user, server, password, alias string oxPrivKey *crypto.Key recipients []recipientsType ) // Define command line flags. flagHelp := getopt.BoolLong("help", 0, "Show help.") flagHTTPUpload := getopt.StringLong("http-upload", 'h', "", "Send a file via http-upload.") flagDebug := getopt.BoolLong("debug", 'd', "Show debugging info.") flagServer := getopt.StringLong("jserver", 'j', "", "XMPP server address.") flagUser := getopt.StringLong("username", 'u', "", "Username for XMPP account.") flagPassword := getopt.StringLong("password", 'p', "", "Password for XMPP account.") flagChatroom := getopt.BoolLong("chatroom", 'c', "Send message to a chatroom.") flagDirectTLS := getopt.BoolLong("tls", 't', "Use direct TLS.") flagAlias := getopt.StringLong("alias", 'a', "", "Set alias/nickname"+ "for chatrooms.") flagFile := getopt.StringLong("file", 'f', "", "Set configuration file. (Default: "+ "~/.config/go-sendxmpp/sendxmpprc)") flagMessageFile := getopt.StringLong("message", 'm', "", "Set file including the message.") flagInteractive := getopt.BoolLong("interactive", 'i', "Interactive mode (for use with e.g. 'tail -f').") flagSkipVerify := getopt.BoolLong("no-tls-verify", 'n', "Skip verification of TLS certificates (not recommended).") flagRaw := getopt.BoolLong("raw", 0, "Send raw XML.") flagListen := getopt.BoolLong("listen", 'l', "Listen for messages and print them to stdout.") flagTimeout := getopt.IntLong("timeout", 0, defaultTimeout, "Connection timeout in seconds.") flagTLSMinVersion := getopt.IntLong("tls-version", 0, defaultTLSMinVersion, "Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2) or 13 (TLSv1.3).") flagVersion := getopt.BoolLong("version", 0, "Show version information.") flagMUCPassword := getopt.StringLong("muc-password", 0, "", "Password for password protected MUCs.") flagOx := getopt.BoolLong("ox", 0, "Use \"OpenPGP for XMPP\" encryption (experimental).") flagOxGenPrivKeyRSA := getopt.BoolLong("ox-genprivkey-rsa", 0, "Generate a private OpenPGP key (RSA 4096 bit) for the given JID and publish the "+ "corresponding public key.") flagOxGenPrivKeyX25519 := getopt.BoolLong("ox-genprivkey-x25519", 0, "Generate a private OpenPGP key (x25519) for the given JID and publish the "+ "corresponding public key.") flagOxPassphrase := getopt.StringLong("ox-passphrase", 0, "", "Passphrase for locking and unlocking the private OpenPGP key.") flagOxImportPrivKey := getopt.StringLong("ox-import-privkey", 0, "", "Import an existing private OpenPGP key.") flagOxDeleteNodes := getopt.BoolLong("ox-delete-nodes", 0, "Delete existing OpenPGP nodes on the server.") 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.") // Parse command line flags. getopt.Parse() switch { case *flagHelp: // If requested, show help and quit. getopt.PrintUsage(os.Stdout) os.Exit(0) case *flagVersion: // If requested, show version and quit. fmt.Println("go-sendxmpp", version) fmt.Println("License: BSD-2-clause") os.Exit(0) // Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like // groupchat, http-upload or listening. case *flagOx && *flagHTTPUpload != "": log.Fatal("No Ox support for http-upload available.") case *flagOx && *flagChatroom: log.Fatal("No Ox support for chat rooms available.") case *flagHTTPUpload != "" && *flagInteractive: log.Fatal("Interactive mode and http upload can't" + " be used at the same time.") case *flagHTTPUpload != "" && *flagMessageFile != "": log.Fatal("You can't send a message while using" + " http upload.") case *flagOx && *flagOOBFile != "": log.Fatal("No encryption possible for OOB data.") case *flagOx && *flagHeadline: log.Fatal("No Ox support for headline messages.") case *flagHeadline && *flagChatroom: log.Fatal("Can't use message type headline for groupchat messages.") } switch *flagSCRAMPinning { case "", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS", "SCRAM-SHA-256", "SCRAM-SHA-256-PLUS", "SCRAM-SHA-512", "SCRAM-SHA-512-PLUS": default: log.Fatal("Unknown SCRAM mechanism: ", *flagSCRAMPinning) } // Read recipients from command line and quit if none are specified. // For listening or sending raw XML it's not required to specify a recipient except // when sending raw messages to MUCs (go-sendxmpp will join the MUC automatically). recipientsList := getopt.Args() if (len(recipientsList) == 0 && !*flagRaw && !*flagListen && !*flagOxGenPrivKeyX25519 && !*flagOxGenPrivKeyRSA && *flagOxImportPrivKey == "") && !*flagOxDeleteNodes || (len(recipientsList) == 0 && *flagChatroom) { log.Fatal("No recipient specified.") } // Read configuration file if user or password is not specified. if *flagUser == "" || *flagPassword == "" { // Read configuration from file. config, err := parseConfig(*flagFile) if err != nil { log.Fatal("Error parsing ", *flagFile, ": ", err) } // Set connection options according to config. user = config.username server = config.jserver password = config.password alias = config.alias if config.port != "" { server = net.JoinHostPort(server, fmt.Sprint(config.port)) } } // Overwrite user if specified via command line flag. if *flagUser != "" { user = *flagUser } // Overwrite server if specified via command line flag. if *flagServer != "" { server = *flagServer } // Overwrite password if specified via command line flag. if *flagPassword != "" { password = *flagPassword } switch { // Use "go-sendxmpp" if no nick is specified via config or command line flag. case alias == "" && *flagAlias == "": alias = "go-sendxmpp" // Overwrite configured alias if a nick is specified via command line flag. case *flagAlias != "": alias = *flagAlias } // Timeout timeout := time.Duration(*flagTimeout) * time.Second // 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 case defaultTLS11: tlsConfig.MinVersion = tls.VersionTLS11 case defaultTLS12: tlsConfig.MinVersion = tls.VersionTLS12 case defaultTLS13: tlsConfig.MinVersion = tls.VersionTLS13 default: fmt.Println("Unknown TLS version.") os.Exit(0) } // Set XMPP connection options. options := xmpp.Options{ Host: server, User: user, DialTimeout: timeout, Resource: "go-sendxmpp." + getShortID(), Password: password, // NoTLS doesn't mean that no TLS is used at all but that instead // of using an encrypted connection to the server (direct TLS) // 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/xmppo/go-xmpp#Options NoTLS: !*flagDirectTLS, StartTLS: !*flagDirectTLS, Debug: *flagDebug, TLSConfig: &tlsConfig, Mechanism: *flagSCRAMPinning, } // Read message from file. if *flagMessageFile != "" { message, err = readMessage(*flagMessageFile) if err != nil { log.Fatal(err) } } // Skip reading message if '-i' or '--interactive' is set to work with e.g. 'tail -f'. // Also for listening mode and Ox key handling. if !*flagInteractive && !*flagListen && *flagHTTPUpload == "" && !*flagOxDeleteNodes && *flagOxImportPrivKey == "" && !*flagOxGenPrivKeyX25519 && !*flagOxGenPrivKeyRSA && *flagOOBFile == "" && message == "" { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { if message == "" { message = scanner.Text() } else { message = message + "\n" + scanner.Text() } } if err := scanner.Err(); err != nil { if err != io.EOF { log.Fatal(err) } } } // Remove invalid code points. message = validUTF8(message) // Exit if message is empty. if message == "" && !*flagInteractive && !*flagListen && !*flagOxGenPrivKeyRSA && !*flagOxGenPrivKeyX25519 && *flagOxImportPrivKey == "" && !*flagOxDeleteNodes && *flagHTTPUpload == "" && *flagOOBFile == "" { os.Exit(0) } // Connect to server. client, err := connect(options, *flagDirectTLS) if err != nil { log.Fatal(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) for _, r := range getopt.Args() { var re recipientsType re.Jid = r if *flagOx { re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, r) if err != nil { re.OxKeyRing = nil fmt.Println("ox: error fetching key for", r+":", err) } } recipients = append(recipients, re) } // Check that all recipient JIDs are valid. for i, recipient := range recipients { validatedJid, err := MarshalJID(recipient.Jid) if err != nil { closeAndExit(client, cancel, err) } recipients[i].Jid = validatedJid } switch { case *flagOxGenPrivKeyX25519: validatedOwnJid, err := MarshalJID(user) if err != nil { closeAndExit(client, cancel, err) } err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "x25519") if err != nil { closeAndExit(client, cancel, err) } os.Exit(0) case *flagOxGenPrivKeyRSA: validatedOwnJid, err := MarshalJID(user) if err != nil { closeAndExit(client, cancel, err) } err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "rsa") if err != nil { closeAndExit(client, cancel, err) } os.Exit(0) case *flagOxImportPrivKey != "": validatedOwnJid, err := MarshalJID(user) if err != nil { closeAndExit(client, cancel, err) } err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey, client, iqc) if err != nil { closeAndExit(client, cancel, err) } os.Exit(0) case *flagOxDeleteNodes: validatedOwnJid, err := MarshalJID(user) if err != nil { closeAndExit(client, cancel, err) } err = oxDeleteNodes(validatedOwnJid, client, iqc) if err != nil { closeAndExit(client, cancel, err) } os.Exit(0) case *flagOx: validatedOwnJid, err := MarshalJID(user) if err != nil { closeAndExit(client, cancel, err) } oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase) if err != nil { closeAndExit(client, cancel, err) } } if *flagHTTPUpload != "" { message, err = httpUpload(client, iqc, tlsConfig.ServerName, *flagHTTPUpload, timeout) if err != nil { closeAndExit(client, cancel, err) } } if *flagOOBFile != "" { // Remove invalid UTF8 code points. message = validUTF8(*flagOOBFile) // Check if the URI is valid. uri, err := validURI(message) if err != nil { closeAndExit(client, cancel, err) } message = uri.String() } var msgType string if *flagHeadline { msgType = strHeadline } else { msgType = strChat } if *flagChatroom { msgType = strGroupchat // Join the MUCs. for _, recipient := range recipients { if *flagMUCPassword != "" { dummyTime := time.Now() _, err = client.JoinProtectedMUC(recipient.Jid, alias, *flagMUCPassword, xmpp.NoHistory, 0, &dummyTime) } else { _, err = client.JoinMUCNoHistory(recipient.Jid, alias) } if err != nil { closeAndExit(client, cancel, err) } } } switch { case *flagRaw: if message == "" { break } // Send raw XML _, err = client.SendOrg(message) if err != nil { closeAndExit(client, cancel, err) } case *flagInteractive: // Send in endless loop (for usage with e.g. "tail -f"). reader := bufio.NewReader(os.Stdin) // Quit if ^C is pressed. c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) go func() { for range c { cancel() client.Close() os.Exit(0) } }() for { message, err = reader.ReadString('\n') message = strings.TrimSuffix(message, "\n") if err != nil { closeAndExit(client, cancel, errors.New("failed to read from stdin")) } // Remove invalid code points. message = validUTF8(message) if message == "" { continue } for _, recipient := range recipients { switch { case *flagOx: if recipient.OxKeyRing == nil { continue } oxMessage, err := oxEncrypt(client, oxPrivKey, recipient.Jid, recipient.OxKeyRing, message) if err != nil { fmt.Println("Ox: couldn't encrypt to", recipient.Jid) continue } _, err = client.SendOrg(oxMessage) if err != nil { closeAndExit(client, cancel, err) } default: _, err = client.Send(xmpp.Chat{ Remote: recipient.Jid, Type: msgType, Text: message, }) if err != nil { closeAndExit(client, cancel, err) } } } } case *flagListen: tz := time.Now().Location() for { v := <-msgc switch { case isOxMsg(v) && *flagOx: msg, t, err := oxDecrypt(v, client, iqc, user, oxPrivKey) if err != nil { log.Println(err) continue } if msg == "" { continue } var bareFrom string switch v.Type { case strChat: bareFrom = strings.Split(v.Remote, "/")[0] case strGroupchat: bareFrom = v.Remote default: bareFrom = strings.Split(v.Remote, "/")[0] } // Print any messages if no recipients are specified if len(recipients) == 0 { fmt.Println(t.In(tz).Format(time.RFC3339), "[OX]", bareFrom+":", msg) } else { for _, recipient := range recipients { if strings.Split(v.Remote, "/")[0] == strings.ToLower(recipient.Jid) { fmt.Println(t.In(tz).Format(time.RFC3339), "[OX]", bareFrom+":", msg) } } } default: var t time.Time if v.Text == "" { continue } if v.Stamp.IsZero() { t = time.Now() } else { t = v.Stamp } var bareFrom string switch v.Type { case strChat: bareFrom = strings.Split(v.Remote, "/")[0] case strGroupchat: bareFrom = v.Remote default: bareFrom = strings.Split(v.Remote, "/")[0] } // Print any messages if no recipients are specified if len(recipients) == 0 { fmt.Println(t.In(tz).Format(time.RFC3339), bareFrom+":", v.Text) } else { for _, recipient := range recipients { if strings.Split(v.Remote, "/")[0] == strings.ToLower(recipient.Jid) { fmt.Println(t.In(tz).Format(time.RFC3339), bareFrom+":", v.Text) } } } } } default: for _, recipient := range recipients { if message == "" { break } switch { case *flagHTTPUpload != "": _, err = client.Send(xmpp.Chat{ Remote: recipient.Jid, Type: msgType, Ooburl: message, Text: message, }) if err != nil { fmt.Println("Couldn't send message to", recipient.Jid) } // (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 + "" + message + "") if err != nil { fmt.Println("Couldn't send message to", recipient.Jid) } case *flagOx: if recipient.OxKeyRing == nil { continue } oxMessage, err := oxEncrypt(client, oxPrivKey, recipient.Jid, recipient.OxKeyRing, message) if err != nil { fmt.Println("Ox: couldn't encrypt to", recipient.Jid) continue } _, err = client.SendOrg(oxMessage) if err != nil { closeAndExit(client, cancel, err) } default: _, err = client.Send(xmpp.Chat{ Remote: recipient.Jid, Type: msgType, Text: message, }) if err != nil { closeAndExit(client, cancel, err) } } } } closeAndExit(client, cancel, nil) } go-sendxmpp-v0.8.3/man/000077500000000000000000000000001456415460600147375ustar00rootroot00000000000000go-sendxmpp-v0.8.3/man/go-sendxmpp.1000066400000000000000000000154701456415460600172710ustar00rootroot00000000000000.\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 .TH "GO\-SENDXMPP" "1" "January 2024" "" .SH "NAME" \fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\. .SH "SYNOPSIS" \fBgo\-sendxmpp [\-cdilnt] [\-a value] [\-f value] [\-\-headline] [\-\-help] [\-h value] [\-j value] [\-m value] [\-\-muc\-password value] [\-\-oob\-file value] [\-\-ox] [\-\-ox\-delete\-nodes] [\-\-ox\-genprivkey\-rsa] [\-\-ox\-genprivkey\-x25519] [\-\-ox\-import\-privkey value] [\-\-ox\-passphrase value] [\-p value] [\-\-raw] [\-\-scram\-mech\-pinning value] [\-\-timeout value] [\-\-tls\-version value] [\-u value] [\-\-version] [recipients…]\fR .SH "DESCRIPTION" A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) \fBsendxmpp\fR\. .br You can either pipe a programs output to \fBgo\-sendxmpp\fR, write in your terminal (put \fB^D\fR in a new line to finish) or send the input from a file (\fB\-m\fR or \fB\-\-message\fR)\. The account data is expected at \fB~/\.config/go\-sendxmpp/config\fR (preferred), \fB~/\.config/go\-sendxmpp/sendxmpprc\fR (deprecated) \fB~/\.sendxmpprc\fR (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with \fB\-f\fR or \fB\-\-file\fR\. .SH "OPTIONS" .TP \fB\-a\fR, \fB\-\-alias\fR=[\fIvalue\fR] Set alias/nickname for chatrooms\. .TP \fB\-c\fR, \fB\-\-chatroom\fR=[\fIvalue\fR] Send message to a chatroom\. .TP \fB\-d\fR, \fB\-\-debug\fR Show debugging info\. .TP \fB\-f\fR, \fB\-\-file\fR=[\fIvalue\fR] Set configuration file\. (Default: ~/\.config/go\-sendxmpp/config) .TP \fB\-\-headline\fR Send message as type headline\. .TP \fB\-\-help\fR Show help\. .TP \fB\-h\fR, \fB\-\-http\-upload=\fR[\fIvalue\fR] Send a file via http\-upload\. .TP \fB\-i\fR, \fB\-\-interactive\fR Interactive mode (for use with e\.g\. \fBtail \-f\fR)\. .TP \fB\-j\fR, \fB\-\-jserver\fR=[\fIvalue\fR] XMPP server address\. .TP \fB\-l\fR, \fB\-\-listen\fR Listen for messages and print them to stdout\. If JIDs are specified only messages from those contacts are shown\. If no JIDs are specified all received messages will be shown\. .TP \fB\-m\fR, \fB\-\-message\fR=[\fIvalue\fR] Set file including the message\. .TP \fB\-\-muc\-password\fR=[\fIvalue\fR] Password for password protected MUCs\. .TP \fB\-n\fR, \fB\-\-no\-tls\-verify\fR Skip verification of TLS certificates (not recommended)\. .TP \fB\-\-oob\-file\fR=[\fIvalue\fR] URL to send a file as out of band data\. .TP \fB\-\-ox\fR Use "OpenPGP for XMPP" encryption (experimental)\. .br Ox in go\-sendxmpp only supports sending encrypted 1\-1 messages\. Sending to groupchats and sending encrypted files is not supported\. There is no check whether the recipients key is trusted as there is no local keyring used\. Go\-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired\. As a user facing client a notification would be shown that a new key is available and ask the user whether to use the new key or stick to the old one\. As go\-sendxmpp is usually used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys\. .TP \fB\-\-ox\-delete\-nodes\fR Delete existing OpenPGP nodes on the server\. .TP \fB\-\-ox\-genprivkey\-rsa\fR Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or \fB\-u\fR and \fB\-p\fR) and publish the corresponding public key\. Go\-sendxmpp will save the key in \fB$XDG_DATA_HOME/go\-sendxmpp/oxprivkeys\fR or \fB$HOME/\.local/share/go\-sendxmpp/oxprivkeys\fR\. To protect the key a passphrase might be set using \fB\-\-ox\-passphrase\fR while generating the key\. .br If there is an existing private key for "OpenPGP for XMPP" created by another client (e\.g\. profanity) it might be imported using \fB\-\-ox\-import\-privkey\fR\. .TP \fB\-\-ox\-genprivkey\-x25519\fR Generate a private OpenPGP key (X25519) for the configured account (via config file or \fB\-u\fR and \fB\-p\fR) and publish the corresponding public key\. Go\-sendxmpp will save the key in \fB$XDG_DATA_HOME/go\-sendxmpp/oxprivkeys\fR or \fB$HOME/\.local/share/go\-sendxmpp/oxprivkeys\fR\. To protect the key a passphrase might be set using \fB\-\-ox\-passphrase\fR while generating the key\. .br If there is an existing private key for "OpenPGP for XMPP" created by another client (e\.g\. profanity) it might be imported using \fB\-\-ox\-import\-privkey\fR\. .TP \fB\-\-ox\-import\-privkey\fR=[\fIvalue\fR] Import an existing private OpenPGP key\. .TP \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 \fB\-\-raw\fR 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=[]\fR Enforce the use of a certain SCRAM authentication mechanism\. Currently go\-sendxmpp supports \fBSCRAM\-SHA\-1\fR, \fBSCRAM\-SHA\-1\-PLUS\fR, \fBSCRAM\-SHA\-256\fR, \fBSCRAM\-SHA\-256\-PLUS\fR, \fBSCRAM\-SHA\-512\fR and \fBSCRAM\-SHA\-512\-PLUS\fR\. You should know what you are doing when using this setting and make sure the chosen mechanism is supported by the server\. If not set, go\-sendxmpp will use XEP\-0474 to prevent downgrade attacks (needs server support)\. .TP \fB\-\-timeout=\fR[\fIvalue\fR] Connection timeout in seconds\. (Default: 10) .TP \fB\-t\fR, \fB\-\-tls\fR Use direct TLS\. .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 "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\. .P Just place the following in your \fB~/\.zshrc\fR or \fB~/\.zshrc\.local\fR: .P \fBcompdef _gnu_generic go\-sendxmpp\fR .SH "CHAT" Feel free to join \fIhttps://join\.jabber\.network/#go\-sendxmpp@chat\.mdosch\.de?join\fR\. .SH "AUTHOR" Written by Martin Dosch\. .SH "REPORTING BUGS" Report bugs at \fIhttps://salsa\.debian\.org/mdosch/go\-sendxmpp/issues\fR\. .SH "COPYRIGHT" Copyright (c) Martin Dosch License: BSD 2\-clause License .SH "SEE ALSO" go\-sendxmpp(5), sendxmpp(1) go-sendxmpp-v0.8.3/man/go-sendxmpp.1.html000066400000000000000000000264321456415460600202340ustar00rootroot00000000000000 go-sendxmpp(1) - A tool to send messages to an XMPP contact or MUC.
  1. go-sendxmpp(1)
  2. go-sendxmpp(1)

NAME

go-sendxmpp - 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] [--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…]

DESCRIPTION

A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) sendxmpp.
You can either pipe a programs output to go-sendxmpp, write in your terminal (put ^D in a new line to finish) or send the input from a file (-m or --message). The account data is expected at ~/.config/go-sendxmpp/config (preferred), ~/.config/go-sendxmpp/sendxmpprc (deprecated) ~/.sendxmpprc (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with -f or --file.

OPTIONS

-a, --alias=[value]
Set alias/nickname for chatrooms.
-c, --chatroom=[value]
Send message to a chatroom.
-d, --debug
Show debugging info.
-f, --file=[value]
Set configuration file. (Default: ~/.config/go-sendxmpp/config)
--headline
Send message as type headline.
--help
Show help.
-h, --http-upload=[value]
Send a file via http-upload.
-i, --interactive
Interactive mode (for use with e.g. tail -f).
-j, --jserver=[value]
XMPP server address.
-l, --listen
Listen for messages and print them to stdout. If JIDs are specified only messages from those contacts are shown. If no JIDs are specified all received messages will be shown.
-m, --message=[value]
Set file including the message.
--muc-password=[value]
Password for password protected MUCs.
-n, --no-tls-verify
Skip verification of TLS certificates (not recommended).
--oob-file=[value]
URL to send a file as out of band data.
--ox
Use "OpenPGP for XMPP" encryption (experimental).
Ox in go-sendxmpp only supports sending encrypted 1-1 messages. Sending to groupchats and sending encrypted files is not supported. There is no check whether the recipients key is trusted as there is no local keyring used. Go-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired. As a user facing client a notification would be shown that a new key is available and ask the user whether to use the new key or stick to the old one. As go-sendxmpp is usually used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys.
--ox-delete-nodes
Delete existing OpenPGP nodes on the server.
--ox-genprivkey-rsa
Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or -u and -p) and publish the corresponding public key. Go-sendxmpp will save the key in $XDG_DATA_HOME/go-sendxmpp/oxprivkeys or $HOME/.local/share/go-sendxmpp/oxprivkeys. To protect the key a passphrase might be set using --ox-passphrase while generating the key.
If there is an existing private key for "OpenPGP for XMPP" created by another client (e.g. profanity) it might be imported using --ox-import-privkey.
--ox-genprivkey-x25519
Generate a private OpenPGP key (X25519) for the configured account (via config file or -u and -p) and publish the corresponding public key. Go-sendxmpp will save the key in $XDG_DATA_HOME/go-sendxmpp/oxprivkeys or $HOME/.local/share/go-sendxmpp/oxprivkeys. To protect the key a passphrase might be set using --ox-passphrase while generating the key.
If there is an existing private key for "OpenPGP for XMPP" created by another client (e.g. profanity) it might be imported using --ox-import-privkey.
--ox-import-privkey=[value]
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.
--raw
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 -c and go-sendxmpp will join the MUC.
--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 to prevent downgrade attacks (needs server support).
--timeout=[value]
Connection timeout in seconds. (Default: 10)
-t, --tls
Use direct TLS.
-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

SHELL COMPLETIONS

ZSH

There are no shell completions yet (contributions welcome) but for zsh it is possible to automatically create completions from --help which might work good enough.

Just place the following in your ~/.zshrc or ~/.zshrc.local:

compdef _gnu_generic go-sendxmpp

CHAT

Feel free to join https://join.jabber.network/#go-sendxmpp@chat.mdosch.de?join.

AUTHOR

Written by Martin Dosch.

REPORTING BUGS

Report bugs at https://salsa.debian.org/mdosch/go-sendxmpp/issues.

Copyright (c) Martin Dosch License: BSD 2-clause License

SEE ALSO

go-sendxmpp(5), sendxmpp(1)

  1. January 2024
  2. go-sendxmpp(1)
go-sendxmpp-v0.8.3/man/go-sendxmpp.1.ronn000066400000000000000000000142141456415460600202370ustar00rootroot00000000000000go-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] [--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…]` ## DESCRIPTION A tool to send messages to an XMPP contact or MUC inspired by (but not as powerful as) `sendxmpp`. You can either pipe a programs output to `go-sendxmpp`, write in your terminal (put `^D` in a new line to finish) or send the input from a file (`-m` or `--message`). The account data is expected at `~/.config/go-sendxmpp/config` (preferred), `~/.config/go-sendxmpp/sendxmpprc` (deprecated) `~/.sendxmpprc` (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with `-f` or `--file`. ## OPTIONS * `-a`, `--alias`=[]: Set alias/nickname for chatrooms. * `-c`, `--chatroom`=[]: Send message to a chatroom. * `-d`, `--debug`: Show debugging info. * `-f`, `--file`=[]: Set configuration file. (Default: ~/.config/go-sendxmpp/config) * `--headline`: Send message as type headline. * `--help`: Show help. * `-h`, `--http-upload=`[]: Send a file via http-upload. * `-i`, `--interactive`: Interactive mode (for use with e.g. `tail -f`). * `-j`, `--jserver`=[]: XMPP server address. * `-l`, `--listen`: Listen for messages and print them to stdout. If JIDs are specified only messages from those contacts are shown. If no JIDs are specified all received messages will be shown. * `-m`, `--message`=[]: Set file including the message. * `--muc-password`=[]: Password for password protected MUCs. * `-n`, `--no-tls-verify`: Skip verification of TLS certificates (not recommended). * `--oob-file`=[]: URL to send a file as out of band data. * `--ox`: Use "OpenPGP for XMPP" encryption (experimental). Ox in go-sendxmpp only supports sending encrypted 1-1 messages. Sending to groupchats and sending encrypted files is not supported. There is no check whether the recipients key is trusted as there is no local keyring used. Go-sendxmpp just uses the most recent key that is provided via pubsub and checks that it is not expired. As a user facing client a notification would be shown that a new key is available and ask the user whether to use the new key or stick to the old one. As go-sendxmpp is usually used in scripts it just accepts the new key to prevent the user from missing a new notification due to changed keys. * `--ox-delete-nodes`: Delete existing OpenPGP nodes on the server. * `--ox-genprivkey-rsa`: Generate a private OpenPGP key (RSA 4096 bit) for the configured account (via config file or `-u` and `-p`) and publish the corresponding public key. Go-sendxmpp will save the key in `$XDG_DATA_HOME/go-sendxmpp/oxprivkeys` or `$HOME/.local/share/go-sendxmpp/oxprivkeys`. To protect the key a passphrase might be set using `--ox-passphrase` while generating the key. If there is an existing private key for "OpenPGP for XMPP" created by another client (e.g. profanity) it might be imported using `--ox-import-privkey`. * `--ox-genprivkey-x25519`: Generate a private OpenPGP key (X25519) for the configured account (via config file or `-u` and `-p`) and publish the corresponding public key. Go-sendxmpp will save the key in `$XDG_DATA_HOME/go-sendxmpp/oxprivkeys` or `$HOME/.local/share/go-sendxmpp/oxprivkeys`. To protect the key a passphrase might be set using `--ox-passphrase` while generating the key. If there is an existing private key for "OpenPGP for XMPP" created by another client (e.g. profanity) it might be imported using `--ox-import-privkey`. * `--ox-import-privkey`=[]: Import an existing private OpenPGP key. * `--ox-passphrase`=[]: Passphrase for locking and unlocking the private OpenPGP key. * `--tls-version`=[]: Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2), 13 (TLSv1.3) (Default: 12) * `-p`, `--password`=[]: Password for XMPP account. * `--raw`: 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 `-c` and go-sendxmpp will join the MUC. * `--scram-mech-pinning=[]`: 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 to prevent downgrade attacks (needs server support). * `--timeout=`[]: Connection timeout in seconds. (Default: 10) * `-t`, `--tls`: Use direct TLS. * `-u`, `--username`=[]: 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 ``` ## SHELL COMPLETIONS ### ZSH There are no shell completions yet (contributions welcome) but for zsh it is possible to automatically create completions from `--help` which might work good enough. Just place the following in your `~/.zshrc` or `~/.zshrc.local`: ``` compdef _gnu_generic go-sendxmpp ``` ## 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). ## AUTHOR Written by Martin Dosch. ## REPORTING BUGS Report bugs at [https://salsa.debian.org/mdosch/go-sendxmpp/issues](https://salsa.debian.org/mdosch/go-sendxmpp/issues). ## COPYRIGHT Copyright (c) Martin Dosch License: BSD 2-clause License ## SEE ALSO go-sendxmpp(5), sendxmpp(1) go-sendxmpp-v0.8.3/man/go-sendxmpp.5000066400000000000000000000026541456415460600172750ustar00rootroot00000000000000.\" generated with Ronn-NG/v0.9.1 .\" http://github.com/apjanke/ronn-ng/tree/0.9.1 .TH "GO\-SENDXMPP" "5" "October 2023" "" .SH "NAME" \fBgo\-sendxmpp\fR \- A tool to send messages to an XMPP contact or MUC\. .SH "LOCATION" The account data is expected at \fB~/\.config/go\-sendxmpp/config\fR (preferred), \fB~/\.config/go\-sendxmpp/sendxmpprc\fR (deprecated) or \fB~/\.sendxmpprc\fR (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with \-f or \-\-file\. The configuration file is expected to be in the following format: .SH "FORMAT" username: [\fIyour_jid\fR] .br jserver: [\fIjabber_server\fR] .br port: [\fIjabber_port\fR] .br password: [\fIyour_jabber_password\fR] .br eval_password: [\fIcommand_to_unlock_your_password\fR] .br alias: [\fIyour_alias\fR] .SH "REQUIRED SETTINGS" If all necessary settings are supplied as command line arguments no config file is needed at all\. Setting \fBjserver\fR and \fBport\fR might not be necessary depending on the used server\. .br You should either use a password manager and the setting \fBeval_password\fR or add your password in plaintext to the config file with the setting \fBpassword\fR\. .SH "AUTHOR" Written by Martin Dosch\. .SH "REPORTING BUGS" Report bugs at \fIhttps://salsa\.debian\.org/mdosch/go\-sendxmpp/issues\fR\. .SH "COPYRIGHT" Copyright (c) Martin Dosch License: BSD 2\-clause License .SH "SEE ALSO" go\-sendxmpp(1), sendxmpp(1) go-sendxmpp-v0.8.3/man/go-sendxmpp.5.html000066400000000000000000000115251456415460600202350ustar00rootroot00000000000000 go-sendxmpp(5) - A tool to send messages to an XMPP contact or MUC.
  1. go-sendxmpp(5)
  2. go-sendxmpp(5)

NAME

go-sendxmpp - A tool to send messages to an XMPP contact or MUC.

LOCATION

The account data is expected at ~/.config/go-sendxmpp/config (preferred), ~/.config/go-sendxmpp/sendxmpprc (deprecated) or ~/.sendxmpprc (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with -f or --file. The configuration file is expected to be in the following format:

FORMAT

username: [your_jid]
jserver: [jabber_server]
port: [jabber_port]
password: [your_jabber_password]
eval_password: [command_to_unlock_your_password]
alias: [your_alias]

REQUIRED SETTINGS

If all necessary settings are supplied as command line arguments no config file is needed at all. Setting jserver and port might not be necessary depending on the used server.
You should either use a password manager and the setting eval_password or add your password in plaintext to the config file with the setting password.

AUTHOR

Written by Martin Dosch.

REPORTING BUGS

Report bugs at https://salsa.debian.org/mdosch/go-sendxmpp/issues.

Copyright (c) Martin Dosch License: BSD 2-clause License

SEE ALSO

go-sendxmpp(1), sendxmpp(1)

  1. October 2023
  2. go-sendxmpp(5)
go-sendxmpp-v0.8.3/man/go-sendxmpp.5.ronn000066400000000000000000000024101456415460600202360ustar00rootroot00000000000000go-sendxmpp(5) -- A tool to send messages to an XMPP contact or MUC. ==== ## LOCATION The account data is expected at `~/.config/go-sendxmpp/config` (preferred), `~/.config/go-sendxmpp/sendxmpprc` (deprecated) or `~/.sendxmpprc` (for compatibility with the original perl sendxmpp) if no other configuration file location is specified with -f or --file. The configuration file is expected to be in the following format: ## FORMAT username: [] jserver: [] port: [] password: [] eval_password: [] alias: [] ## REQUIRED SETTINGS If all necessary settings are supplied as command line arguments no config file is needed at all. Setting `jserver` and `port` might not be necessary depending on the used server. You should either use a password manager and the setting `eval_password` or add your password in plaintext to the config file with the setting `password`. ## AUTHOR Written by Martin Dosch. ## REPORTING BUGS Report bugs at [https://salsa.debian.org/mdosch/go-sendxmpp/issues](https://salsa.debian.org/mdosch/go-sendxmpp/issues). ## COPYRIGHT Copyright (c) Martin Dosch License: BSD 2-clause License ## SEE ALSO go-sendxmpp(1), sendxmpp(1) go-sendxmpp-v0.8.3/ox.go000066400000000000000000000625361456415460600151550ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "encoding/base64" "errors" "fmt" "log" "os" "runtime" "strings" "time" "github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License "github.com/beevik/etree" // BSD-2-clause "github.com/xmppo/go-xmpp" // BSD-3-Clause ) func oxDeleteNodes(jid string, client *xmpp.Client, iqc chan xmpp.IQ) error { nodeListRequest := etree.NewDocument() nodeListRequest.WriteSettings.AttrSingleQuote = true query := nodeListRequest.CreateElement("query") query.CreateAttr("xmlns", nsDiscoItems) nlr, err := nodeListRequest.WriteToString() if err != nil { return fmt.Errorf("oxDeleteNodes: failed to create node list request %w", err) } iqReply, err := sendIQ(client, iqc, jid, "get", nlr) if err != nil { return fmt.Errorf("oxDeleteNodes: failure with node list request iq: %w", err) } nodeListReply := etree.NewDocument() err = nodeListReply.ReadFromBytes(iqReply.Query) if err != nil { return fmt.Errorf("oxDeleteNodes: failed to read node list reply iq: %w", err) } query = nodeListReply.SelectElement("query") if query == nil { return errors.New("error parsing iq reply") } items := query.SelectElements("item") if items == nil { return errors.New("error parsing iq reply") } for _, item := range items { node := item.SelectAttr("node") if node == nil { continue } if !strings.Contains(node.Value, nsOx) { continue } deleteNodeRequest := etree.NewDocument() deleteNodeRequest.WriteSettings.AttrSingleQuote = true pubsub := deleteNodeRequest.CreateElement("pubsub") pubsub.CreateAttr("xmlns", nsPubsubOwner) del := pubsub.CreateElement("delete") del.CreateAttr("node", node.Value) dnr, err := deleteNodeRequest.WriteToString() if err != nil { continue } _, err = sendIQ(client, iqc, jid, "set", dnr) if err != nil { continue } } return nil } func oxDecrypt(m xmpp.Chat, client *xmpp.Client, iqc chan xmpp.IQ, user string, oxPrivKey *crypto.Key) (string, time.Time, error) { var cryptMsgByte []byte var err error sender := strings.Split(m.Remote, "/")[0] for _, r := range m.OtherElem { if r.XMLName.Space == nsOx { cryptMsgByte, err = base64.StdEncoding.DecodeString(r.InnerXML) if err != nil { return strError, time.Now(), err } break } } oxMsg := crypto.NewPGPMessage(cryptMsgByte) keyRing, err := crypto.NewKeyRing(oxPrivKey) if err != nil { return strError, time.Now(), err } senderKeyRing, err := oxGetPublicKeyRing(client, iqc, sender) if err != nil { return strError, time.Now(), err } decryptMsg, err := keyRing.Decrypt(oxMsg, senderKeyRing, crypto.GetUnixTime()) if err != nil { return strError, time.Now(), err } // Remove invalid code points. message := validUTF8(string(decryptMsg.Data)) doc := etree.NewDocument() err = doc.ReadFromString(message) if err != nil { return strError, time.Now(), err } signcrypt := doc.SelectElement("signcrypt") if signcrypt == nil { return strError, time.Now(), errors.New("ox: no signcrypt element") } to := signcrypt.SelectElement("to") if to == nil { return strError, time.Now(), errors.New("ox: no to element") } jid := to.SelectAttr("jid") if jid == nil { return strError, time.Now(), errors.New("ox: no jid attribute") } if strings.Split(jid.Value, "/")[0] != user { return strError, time.Now(), errors.New("ox: encrypted for wrong user") } timestamp := signcrypt.SelectElement("time") if timestamp == nil { return strError, time.Now(), errors.New("ox: no time element") } stamp := timestamp.SelectAttr("stamp") if stamp == nil { return strError, time.Now(), errors.New("ox: no stamp attribute") } msgStamp, err := time.Parse("2006-01-02T15:04:05Z0700", stamp.Value) if err != nil { return strError, time.Now(), err } payload := signcrypt.SelectElement("payload") if payload == nil { return strError, time.Now(), errors.New("ox: no payload element") } body := payload.SelectElement("body") if body == nil { return "", time.Now(), nil } return body.Text(), msgStamp, nil } func isOxMsg(m xmpp.Chat) bool { for _, r := range m.OtherElem { if r.XMLName.Space == nsOx { return true } } return false } func oxImportPrivKey(jid string, privKeyLocation string, client *xmpp.Client, iqc chan xmpp.IQ) error { xmppURI := "xmpp:" + jid buffer, err := readFile(privKeyLocation) if err != nil { return err } key, err := crypto.NewKey(buffer.Bytes()) if err != nil { key, err = crypto.NewKeyFromArmored(buffer.String()) if err != nil { keyDecoded, err := base64.StdEncoding.DecodeString(buffer.String()) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to import private key: %w", err) } key, err = crypto.NewKey(keyDecoded) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to import private key: %w", err) } } } entity := key.GetEntity() if entity.Identities[xmppURI] == nil { return errors.New("Key identity is not " + xmppURI) } pk, err := key.GetPublicKey() if err != nil { return fmt.Errorf("oxImportPrivKey: failed to get public key associated to private key: %w", err) } pubKey, err := crypto.NewKey(pk) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to get public key associated to private key: %w", err) } fingerprint := strings.ToUpper(pubKey.GetFingerprint()) _, err = oxRecvPublicKeys(client, iqc, jid, fingerprint) if err != nil { err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to publish public key: %w", err) } } location, err := oxGetPrivKeyLoc(jid) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to determine private key location: %w", err) } keySerialized, err := key.Serialize() if err != nil { return fmt.Errorf("oxImportPrivKey: failed to serialize private key: %w", err) } err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { log.Fatal(err) } pubKeyRing, err := oxGetPublicKeyRing(client, iqc, jid) if err == nil { pubKeys := pubKeyRing.GetKeys() for _, r := range pubKeys { if strings.ToUpper(r.GetFingerprint()) == fingerprint { return nil } } } err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return fmt.Errorf("oxImportPrivKey: failed to publish public key: %w", err) } return nil } func oxPublishPubKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, pubKey *crypto.Key) error { keyCreated := time.Now().UTC().Format("2006-01-02T15:04:05Z") fingerprint := strings.ToUpper(pubKey.GetFingerprint()) keySerialized, err := pubKey.Serialize() if err != nil { return fmt.Errorf("oxPublishPubKey: failed to serialize pubkey: %w", err) } pubKeyBase64 := base64.StdEncoding.EncodeToString(keySerialized) root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true pubsub := root.CreateElement("pubsub") pubsub.CreateAttr("xmlns", nsPubsub) publish := pubsub.CreateElement("publish") publish.CreateAttr("node", nsOxPubKeys+":"+fingerprint) item := publish.CreateElement("item") item.CreateAttr("id", keyCreated) pubkey := item.CreateElement("pubkey") pubkey.CreateAttr("xmlns", nsOx) data := pubkey.CreateElement("data") data.CreateText(pubKeyBase64) publishoptions := pubsub.CreateElement("publish-options") x := publishoptions.CreateElement("x") x.CreateAttr("xmlns", nsJabberData) x.CreateAttr("type", "submit") field := x.CreateElement("field") field.CreateAttr("var", "FORM_TYPE") field.CreateAttr("type", "hidden") value := field.CreateElement("value") value.CreateText(pubsubPubOptions) field = x.CreateElement("field") field.CreateAttr("var", "pubsub#access_model") value = field.CreateElement("value") value.CreateText("open") xmlstring, err := root.WriteToString() if err != nil { return fmt.Errorf("oxPublishPubKey: failed to create publish public key iq xml: %w", err) } iqReply, err := sendIQ(client, iqc, jid, "set", xmlstring) if err != nil { return fmt.Errorf("oxPublishPubKey: iq failure publishing public key: %w", err) } if iqReply.Type != strResult { return errors.New("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") } ownPubKeyFromPubsub := ownPubKeyRingFromPubsub.GetKeys()[0] ownPubKeyFromPubsubSerialized, err := ownPubKeyFromPubsub.Serialize() if err != nil { return errors.New("couldn't successfully verify public key upload") } if pubKeyBase64 != base64.StdEncoding.EncodeToString(ownPubKeyFromPubsubSerialized) { return errors.New("couldn't successfully verify public key upload") } root = etree.NewDocument() root.WriteSettings.AttrSingleQuote = true pubsub = root.CreateElement("pubsub") pubsub.CreateAttr("xmlns", nsPubsub) publish = pubsub.CreateElement("publish") publish.CreateAttr("node", nsOxPubKeys) item = publish.CreateElement("item") pubkeyslist := item.CreateElement("public-keys-list") pubkeyslist.CreateAttr("xmlns", nsOx) pubkeymeta := pubkeyslist.CreateElement("pubkey-metadata") pubkeymeta.CreateAttr("v4-fingerprint", fingerprint) pubkeymeta.CreateAttr("date", keyCreated) publishoptions = pubsub.CreateElement("publish-options") x = publishoptions.CreateElement("x") x.CreateAttr("xmlns", nsJabberData) x.CreateAttr("type", "submit") field = x.CreateElement("field") field.CreateAttr("var", "FORM_TYPE") field.CreateAttr("type", "hidden") value = field.CreateElement("value") value.CreateText(pubsubPubOptions) field = x.CreateElement("field") field.CreateAttr("var", "pubsub#access_model") value = field.CreateElement("value") value.CreateText("open") xmlstring, err = root.WriteToString() if err != nil { return fmt.Errorf("oxPublishPubKey: failed to create xml for iq to publish public key list: %w", err) } iqReply, err = sendIQ(client, iqc, jid, "set", xmlstring) if err != nil { 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 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() if err != nil { return strError, fmt.Errorf("oxGetPrivKeyLoc: failed to determine user dir: %w", err) } if homeDir == "" { return strError, errors.New("oxGetPrivKeyLoc: received empty string for home directory") } dataDir = homeDir + "/.local/share" } dataDir += "/go-sendxmpp/oxprivkeys/" if _, err = os.Stat(dataDir); os.IsNotExist(err) { err = os.MkdirAll(dataDir, defaultDirRights) if err != nil { return strError, fmt.Errorf("oxGetPrivKeyLoc: could not create folder for private keys: %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 := dataDir + base64.StdEncoding.EncodeToString([]byte(jid)) dataFile := dataDir + strings.Replace(jid, "@", "_at_", -1) if _, err := os.Stat(oldDataFile); err == nil { err := os.Rename(oldDataFile, dataFile) if err != nil { return dataFile, err } } 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) } } dataFile := dataDir + fingerprint return dataFile, nil } func oxGetPrivKey(jid string, passphrase string) (*crypto.Key, error) { dataFile, err := oxGetPrivKeyLoc(jid) if err != nil { log.Fatal(err) } keyBuffer, err := readFile(dataFile) if err != nil { log.Fatal(err) } keyString := keyBuffer.String() decodedPrivKey, err := base64.StdEncoding.DecodeString(keyString) if err != nil { return nil, fmt.Errorf("oxGetPrivKey: failed to decode private key: %w", err) } key, err := crypto.NewKey(decodedPrivKey) if err != nil { return nil, fmt.Errorf("oxGetPrivKey: failed to decode private key: %w", err) } if passphrase != "" { key, err = key.Unlock([]byte(passphrase)) if err != nil { log.Fatal("Ox: couldn't unlock private key.") } } isLocked, err := key.IsLocked() if err != nil { return nil, fmt.Errorf("oxGetPrivKey: failed to check whether private key is locked: %w", err) } if isLocked { log.Fatal("Ox: private key is locked.") } if key.IsExpired() { return nil, errors.New("Ox: private key is expired: " + key.GetFingerprint()) } return key, nil } func oxStoreKey(location string, key string) error { var file *os.File file, err := os.Create(location) if err != nil { return fmt.Errorf("oxStoreKey: failed to create key location: %w", err) } if runtime.GOOS != "windows" { _ = file.Chmod(os.FileMode(defaultFileRights)) } else { _ = file.Chmod(os.FileMode(defaultFileRightsWin)) } _, err = file.Write([]byte(key)) if err != nil { return fmt.Errorf("oxStoreKey: failed to write key to file: %w", err) } err = file.Close() if err != nil { fmt.Println("error while closing file:", err) } return nil } func oxGenPrivKey(jid string, client *xmpp.Client, iqc chan xmpp.IQ, passphrase string, keyType string, ) error { xmppURI := "xmpp:" + jid key, err := crypto.GenerateKey(xmppURI, "", keyType, defaultRSABits) if err != nil { return fmt.Errorf("oxGenPrivKey: failed to generate private key: %w", err) } if passphrase != "" { key, err = key.Lock([]byte(passphrase)) if err != nil { return fmt.Errorf("oxGenPrivKey: failed to lock key with passphrase: %w", err) } } keySerialized, err := key.Serialize() if err != nil { return fmt.Errorf("oxGenPrivKey: failed to serialize private key: %w", err) } location, err := oxGetPrivKeyLoc(jid) if err != nil { return fmt.Errorf("oxGenPrivKey: failed to get private key location: %w", err) } err = oxStoreKey(location, base64.StdEncoding.EncodeToString(keySerialized)) if err != nil { log.Fatal(err) } decodedPubKey, err := key.GetPublicKey() if err != nil { return fmt.Errorf("oxGenPrivKey: failed to decode public key: %w", err) } pubKey, err := crypto.NewKey(decodedPubKey) if err != nil { return fmt.Errorf("oxGenPrivKey: failed to decode public key: %w", err) } err = oxPublishPubKey(jid, client, iqc, pubKey) if err != nil { return fmt.Errorf("oxGenPrivKey: failed to publish public key: %w", err) } return nil } func oxRecvPublicKeys(client *xmpp.Client, iqc chan xmpp.IQ, recipient string, fingerprint string) (*crypto.KeyRing, error) { opkr := etree.NewDocument() opkr.WriteSettings.AttrSingleQuote = true opkrPs := opkr.CreateElement("pubsub") opkrPs.CreateAttr("xmlns", nsPubsub) opkrPsItems := opkrPs.CreateElement("items") opkrPsItems.CreateAttr("node", nsOxPubKeys+":"+fingerprint) opkrPsItems.CreateAttr("max_items", "1") opkrString, err := opkr.WriteToString() if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed to generate xml for public key request: %w", err) } oxPublicKey, err := sendIQ(client, iqc, recipient, "get", opkrString) if err != nil { 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 " + recipient) } oxPublicKeyXML := etree.NewDocument() err = oxPublicKeyXML.ReadFromBytes(oxPublicKey.Query) if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed parsing iq reply to public key request: %w", err) } keyring, err := crypto.NewKeyRing(nil) if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed reading public key: %w", err) } oxPublicKeyXMLPubsub := oxPublicKeyXML.SelectElement("pubsub") if oxPublicKeyXMLPubsub == nil { return nil, errors.New("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") } oxPublicKeyXMLItem := oxPublicKeyXMLItems.SelectElement("item") if oxPublicKeyXMLItem == nil { return nil, errors.New("ox: no item element in reply to public " + "key request") } oxPublicKeyXMLPubkeys := oxPublicKeyXMLItem.SelectElements("pubkey") for _, r := range oxPublicKeyXMLPubkeys { data := r.SelectElement("data") if data == nil { continue } decodedPubKey, err := base64.StdEncoding.DecodeString(data.Text()) if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed to decode public key: %w", err) } key, err := crypto.NewKey(decodedPubKey) if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed to decode public key: %w", err) } if key.IsExpired() { return nil, errors.New("Key is expired: " + fingerprint) } err = keyring.AddKey(key) if err != nil { return nil, fmt.Errorf("oxRecvPublicKeys: failed adding public key to keyring: %w", err) } } return keyring, nil } func oxGetPublicKeyRing(client *xmpp.Client, iqc chan xmpp.IQ, recipient string) (*crypto.KeyRing, error) { publicKeyRing, err := crypto.NewKeyRing(nil) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to create a new keyring: %w", err) } oxPubKeyListReq := etree.NewDocument() oxPubKeyListReq.WriteSettings.AttrSingleQuote = true oxPubKeyListReqPs := oxPubKeyListReq.CreateElement("pubsub") oxPubKeyListReqPs.CreateAttr("xmlns", nsPubsub) oxPubKeyListReqPsItems := oxPubKeyListReqPs.CreateElement("items") oxPubKeyListReqPsItems.CreateAttr("node", nsOxPubKeys) oxPubKeyListReqPsItems.CreateAttr("max_items", "1") opkl, err := oxPubKeyListReq.WriteToString() if err != nil { log.Fatal(err) } oxPublicKeyList, err := sendIQ(client, iqc, recipient, "get", opkl) if err != nil { log.Fatal(err) } if oxPublicKeyList.Type != strResult { return nil, errors.New("error while requesting public openpgp keys for " + recipient) } oxPubKeyListXML := etree.NewDocument() err = oxPubKeyListXML.ReadFromBytes(oxPublicKeyList.Query) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to parse answer to public key list request: %w", err) } pubKeyRingID := "none" newestKey, err := time.Parse(time.RFC3339, "1900-01-01T00:00:00Z") if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to set time for newest key to 1900-01-01: %w", err) } oxPubKeyListXMLPubsub := oxPubKeyListXML.SelectElement("pubsub") if oxPubKeyListXMLPubsub == nil { return nil, errors.New("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") } oxPubKeyListXMLPubsubItemsItem := oxPubKeyListXMLPubsubItems.SelectElement("item") if oxPubKeyListXMLPubsubItemsItem == nil { return nil, errors.New("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") } oxPubKeyListXMLPubsubItemsItemPklPm := oxPubKeyListXMLPubsubItemsItemPkl.SelectElements("pubkey-metadata") for _, r := range oxPubKeyListXMLPubsubItemsItemPklPm { date := r.SelectAttr("date") if date == nil { continue } fingerprint := r.SelectAttr("v4-fingerprint") if fingerprint == nil { continue } keyDate, err := time.Parse(time.RFC3339, date.Value) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to parse time stamp for key: %w", err) } if keyDate.After(newestKey) { newestKey = keyDate pubKeyRingID = fingerprint.Value } } if pubKeyRingID == "none" { return nil, errors.New("server didn't provide public key fingerprints for " + recipient) } pubKeyRingLocation, err := oxGetPubKeyLoc(pubKeyRingID) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to get public key ring location: %w", err) } pubKeyReadXML := etree.NewDocument() err = pubKeyReadXML.ReadFromFile(pubKeyRingLocation) if err == nil { date := pubKeyReadXML.SelectElement("date") if date != nil { savedKeysDate, err := time.Parse(time.RFC3339, date.Text()) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to parse time for saved key: %w", err) } if !savedKeysDate.Before(newestKey) { pubKeys := pubKeyReadXML.SelectElements("pubkey") if pubKeys == nil { return nil, errors.New("couldn't read public keys from cache") } for _, r := range pubKeys { keyByte, err := base64.StdEncoding.DecodeString(r.Text()) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to decode saved key: %w", err) } key, err := crypto.NewKey(keyByte) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to parse saved key: %w", err) } if !key.IsExpired() { err = publicKeyRing.AddKey(key) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to add key to public keyring: %w", err) } } } if publicKeyRing.CanEncrypt() { return publicKeyRing, nil } } } } pubKeyRing, err := oxRecvPublicKeys(client, iqc, recipient, pubKeyRingID) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to get public keyring: %w", err) } pubKeySaveXML := etree.NewDocument() date := pubKeySaveXML.CreateElement("date") date.SetText(newestKey.Format(time.RFC3339)) for _, key := range pubKeyRing.GetKeys() { keySerialized, err := key.Serialize() if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to serialize key: %w", err) } saveKey := pubKeySaveXML.CreateElement("pubkey") saveKey.SetText(base64.StdEncoding.EncodeToString(keySerialized)) } err = pubKeySaveXML.WriteToFile(pubKeyRingLocation) if err != nil { return nil, fmt.Errorf("oxGetPublicKeyRing: failed to create xml for saving public key: %w", err) } return pubKeyRing, nil } func oxEncrypt(client *xmpp.Client, oxPrivKey *crypto.Key, recipient string, keyRing *crypto.KeyRing, message string) (string, error) { if message == "" { return "", nil } privKeyRing, err := crypto.NewKeyRing(oxPrivKey) if err != nil { return strError, fmt.Errorf("oxEncrypt: failed to create private keyring: %w", err) } ownJid := strings.Split(client.JID(), "/")[0] if recipient != ownJid { opk, err := oxPrivKey.GetPublicKey() if err == nil { ownKey, _ := crypto.NewKey(opk) _ = keyRing.AddKey(ownKey) } } oxCryptMessage := etree.NewDocument() oxCryptMessage.WriteSettings.AttrSingleQuote = true oxCryptMessageSc := oxCryptMessage.CreateElement("signcrypt") oxCryptMessageSc.CreateAttr("xmlns", nsOx) oxCryptMessageScTo := oxCryptMessageSc.CreateElement("to") oxCryptMessageScTo.CreateAttr("jid", recipient) oxCryptMessageScTime := oxCryptMessageSc.CreateElement("time") oxCryptMessageScTime.CreateAttr("stamp", time.Now().UTC().Format("2006-01-02T15:04:05Z")) oxCryptMessageScRpad := oxCryptMessageSc.CreateElement("rpad") oxCryptMessageScRpad.CreateText(getRpad(len(message))) oxCryptMessageScPayload := oxCryptMessageSc.CreateElement("payload") oxCryptMessageScPayloadBody := oxCryptMessageScPayload.CreateElement("body") oxCryptMessageScPayloadBody.CreateAttr("xmlns", nsJabberClient) oxCryptMessageScPayloadBody.CreateText(message) ocm, err := oxCryptMessage.WriteToString() if err != nil { return strError, fmt.Errorf("oxEncrypt: failed to create xml for ox crypt message: %w", err) } plainMessage := crypto.NewPlainMessage([]byte(ocm)) pgpMessage, err := keyRing.Encrypt(plainMessage, privKeyRing) if err != nil { return strError, fmt.Errorf("oxEncrypt: failed to create pgp message: %w", err) } om := etree.NewDocument() om.WriteSettings.AttrSingleQuote = true omMessage := om.CreateElement("message") omMessage.CreateAttr("to", recipient) omMessage.CreateAttr("id", getID()) omMessageStore := omMessage.CreateElement("store") omMessageStore.CreateAttr("xmlns", nsHints) omMessageEme := omMessage.CreateElement("encryption") omMessageEme.CreateAttr("xmlns", nsEme) omMessageEme.CreateAttr("namespace", nsOx) omMessageOpgp := omMessage.CreateElement("openpgp") omMessageOpgp.CreateAttr("xmlns", nsOx) omMessageOpgp.CreateText(base64.StdEncoding.EncodeToString(pgpMessage.Data)) omMessageBody := omMessage.CreateElement("body") omMessageBody.CreateText(oxAltBody) oms, err := om.WriteToString() if err != nil { return strError, fmt.Errorf("oxEncrypt: failed to create xml for ox message: %w", err) } return oms, nil } go-sendxmpp-v0.8.3/parseconfig.go000066400000000000000000000101021456415460600170050ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "bufio" "errors" "fmt" "log" "os" "os/exec" "os/user" "runtime" "strconv" "strings" ) func findConfig() (string, error) { // Get the current user. curUser, err := user.Current() if err != nil { return "", fmt.Errorf("findConfig: failed to get current user: %w", err) } // Get home directory. home := curUser.HomeDir if home == "" { return "", errors.New("no home directory found") } osConfigDir := os.Getenv("$XDG_CONFIG_HOME") if osConfigDir == "" { osConfigDir = home + "/.config" } configFiles := [3]string{ osConfigDir + "/go-sendxmpp/config", osConfigDir + "/go-sendxmpp/sendxmpprc", home + "/.sendxmpprc", } for _, r := range configFiles { // Check that the config file is existing. _, err := os.Stat(r) if err == nil { return r, nil } } return "", errors.New("no configuration file found") } // Opens the config file and returns the specified values // for username, server and port. func parseConfig(configPath string) (configuration, error) { var ( output configuration err error ) // Use $XDG_CONFIG_HOME/.config/go-sendxmpp/config, // $XDG_CONFIG_HOME/.config/go-sendxmpp/sendxmpprc or // ~/.sendxmpprc if no config path is specified. // Get systems user config path. if configPath == "" { configPath, err = findConfig() if err != nil { log.Fatal(err) } } // Only check file permissions if we are not running on windows. if runtime.GOOS != "windows" { info, err := os.Stat(configPath) if err != nil { log.Fatal(err) } // Check for file permissions. Must be 600, 640, 440 or 400. 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.") } } // Open config file. file, err := os.Open(configPath) if err != nil { return output, fmt.Errorf("parseConfig: failed to open config file: %w", err) } scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) // Read config file per line. for scanner.Scan() { if strings.HasPrefix(scanner.Text(), "#") { continue } row := strings.SplitN(scanner.Text(), " ", defaultConfigRowSep) switch row[0] { case "username:": output.username = row[1] case "jserver:": output.jserver = row[1] case "password:": output.password = row[1] case "eval_password:": shell := os.Getenv("SHELL") if shell == "" { shell = "/bin/sh" } out, err := exec.Command(shell, "-c", row[1]).Output() if err != nil { log.Fatal(err) } output.password = string(out) if output.password[len(output.password)-1] == '\n' { output.password = output.password[:len(output.password)-1] } case "port:": output.port = row[1] case "alias:": output.alias = row[1] default: if len(row) >= defaultConfigRowSep { if strings.Contains(scanner.Text(), ";") { output.username = strings.Split(row[0], ";")[0] output.jserver = strings.Split(row[0], ";")[1] output.password = row[1] } else { output.username = strings.Split(row[0], ":")[0] jserver := strings.Split(row[0], "@") if len(jserver) < defaultLenServerConf { log.Fatal("Couldn't parse config: ", row[0]) } output.jserver = jserver[0] output.password = row[1] } } } } 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) if err != nil { // Check whether only the local part was used by appending an @ and the // server part. output.username = output.username + "@" + output.jserver // 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, err } go-sendxmpp-v0.8.3/stanzahandling.go000066400000000000000000000111641456415460600175230ustar00rootroot00000000000000// Copyright Martin Dosch. // Use of this source code is governed by the BSD-2-clause // license that can be found in the LICENSE file. package main import ( "context" "fmt" "log" "runtime" "github.com/beevik/etree" // BSD-2-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) 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 return iq, nil } func getIQ(id string, c chan xmpp.IQ, iqc chan xmpp.IQ) { for { iq := <-iqc if iq.ID == id { c <- iq return } } } func rcvStanzas(client *xmpp.Client, iqc chan xmpp.IQ, msgc chan xmpp.Chat, ctx context.Context) { for { received, err := client.Recv() // Don't print errors if the program is getting shut down, // as the errors might be triggered from trying to read from // a closed connection. select { case <-ctx.Done(): return default: if err != nil { log.Println(err) } } 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) if err != nil { log.Println("Couldn't parse IQ:", string(v.Query), err) } query := iq.SelectElement("query") if query != nil { xmlns = query.SelectAttr("xmlns") } switch xmlns.Value { case nsDiscoInfo: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", "result") reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) replyQuery := reply.CreateElement("query") replyQuery.CreateAttr("xmlns", nsDiscoInfo) identity := replyQuery.CreateElement("identity") identity.CreateAttr("category", "client") identity.CreateAttr("type", "bot") identity.CreateAttr("name", "go-sendxmpp") feat := replyQuery.CreateElement("feature") feat.CreateAttr("var", nsDiscoInfo) feat2 := replyQuery.CreateElement("feature") feat2.CreateAttr("var", nsVersion) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } case nsVersion: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", "result") reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) replyQuery := reply.CreateElement("query") replyQuery.CreateAttr("xmlns", nsVersion) rqName := replyQuery.CreateElement("name") rqName.CreateText("go-sendxmpp") rqVersion := replyQuery.CreateElement("version") rqVersion.CreateText(version) rqOS := replyQuery.CreateElement("os") rqOS.CreateText(runtime.GOOS) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } default: root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", strError) reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) errorReply := reply.CreateElement(strError) errorReply.CreateAttr("type", "cancel") su := errorReply.CreateElement("service-unavailable") su.CreateAttr("xmlns", nsXMPPStanzas) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } } case "set": root := etree.NewDocument() root.WriteSettings.AttrSingleQuote = true reply := root.CreateElement("iq") reply.CreateAttr("type", strError) reply.CreateAttr("from", client.JID()) reply.CreateAttr("to", v.From) reply.CreateAttr("id", v.ID) errorReply := reply.CreateElement(strError) errorReply.CreateAttr("type", "cancel") su := errorReply.CreateElement("service-unavailable") su.CreateAttr("xmlns", nsXMPPStanzas) xmlString, err := root.WriteToString() if err == nil { _, err = client.SendOrg(xmlString) if err != nil { log.Println(err) } } case "reply", strError: iqc <- v default: iqc <- v } } } }