2023-05-11 18:05:31 +00:00
|
|
|
// Copyright Martin Dosch.
|
2020-04-05 20:02:09 +00:00
|
|
|
// Use of this source code is governed by the BSD-2-clause
|
2020-04-04 07:42:09 +00:00
|
|
|
// license that can be found in the LICENSE file.
|
2018-08-04 10:19:32 +00:00
|
|
|
|
2018-08-04 10:16:28 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2020-04-03 12:35:03 +00:00
|
|
|
"crypto/tls"
|
2024-01-14 20:29:19 +00:00
|
|
|
"errors"
|
2021-10-05 15:11:50 +00:00
|
|
|
"fmt"
|
2018-08-04 10:16:28 +00:00
|
|
|
"io"
|
|
|
|
"log"
|
2021-10-05 15:11:50 +00:00
|
|
|
"net"
|
2018-08-04 10:16:28 +00:00
|
|
|
"os"
|
2023-07-27 17:31:56 +00:00
|
|
|
"os/signal"
|
2024-03-30 13:06:56 +00:00
|
|
|
osUser "os/user"
|
2024-03-11 18:08:42 +00:00
|
|
|
"runtime"
|
2018-08-10 10:14:21 +00:00
|
|
|
"strings"
|
2019-02-20 20:22:31 +00:00
|
|
|
"time"
|
2018-08-04 10:16:28 +00:00
|
|
|
|
2022-04-17 15:16:29 +00:00
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License
|
|
|
|
"github.com/pborman/getopt/v2" // BSD-3-Clause
|
2024-01-13 14:01:42 +00:00
|
|
|
"github.com/xmppo/go-xmpp" // BSD-3-Clause
|
2018-08-04 10:16:28 +00:00
|
|
|
)
|
|
|
|
|
2018-08-10 10:14:21 +00:00
|
|
|
type configuration struct {
|
|
|
|
username string
|
|
|
|
jserver string
|
|
|
|
port string
|
|
|
|
password string
|
2022-08-06 09:52:47 +00:00
|
|
|
alias string
|
2018-08-10 10:14:21 +00:00
|
|
|
}
|
|
|
|
|
2024-04-02 13:36:33 +00:00
|
|
|
func closeAndExit(client *xmpp.Client, err error) {
|
2024-01-14 20:29:19 +00:00
|
|
|
client.Close()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2024-03-26 19:43:30 +00:00
|
|
|
os.Exit(0)
|
2024-01-14 20:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-08-10 11:12:21 +00:00
|
|
|
func readMessage(messageFilePath string) (string, error) {
|
|
|
|
var (
|
|
|
|
output string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
// Check that message file is existing.
|
|
|
|
_, err = os.Stat(messageFilePath)
|
2024-02-17 15:17:19 +00:00
|
|
|
if err != nil {
|
2023-06-06 20:09:59 +00:00
|
|
|
return output, fmt.Errorf("readMessage: %w", err)
|
2018-08-10 11:12:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Open message file.
|
|
|
|
file, err := os.Open(messageFilePath)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2023-06-06 20:09:59 +00:00
|
|
|
return output, fmt.Errorf("readMessage: %w", err)
|
2018-08-10 11:12:21 +00:00
|
|
|
}
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
for scanner.Scan() {
|
|
|
|
if output == "" {
|
|
|
|
output = scanner.Text()
|
|
|
|
} else {
|
|
|
|
output = output + "\n" + scanner.Text()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-17 15:17:19 +00:00
|
|
|
if err = scanner.Err(); err != nil {
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != io.EOF {
|
2023-06-06 20:09:59 +00:00
|
|
|
return "", fmt.Errorf("readMessage: %w", err)
|
2018-08-10 11:12:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-04 14:39:00 +00:00
|
|
|
err = file.Close()
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2023-06-04 14:39:00 +00:00
|
|
|
fmt.Println("error while closing file:", err)
|
|
|
|
}
|
2019-02-21 17:15:46 +00:00
|
|
|
|
2024-02-17 15:17:19 +00:00
|
|
|
return output, nil
|
2018-08-10 11:12:21 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 10:16:28 +00:00
|
|
|
func main() {
|
2022-04-17 15:16:29 +00:00
|
|
|
type recipientsType struct {
|
2022-04-21 15:28:59 +00:00
|
|
|
Jid string
|
|
|
|
OxKeyRing *crypto.KeyRing
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
2018-08-04 10:16:28 +00:00
|
|
|
|
|
|
|
var (
|
2023-09-05 18:39:54 +00:00
|
|
|
err error
|
|
|
|
message, user, server, password, alias string
|
|
|
|
oxPrivKey *crypto.Key
|
|
|
|
recipients []recipientsType
|
2018-08-04 10:16:28 +00:00
|
|
|
)
|
|
|
|
|
2018-08-10 10:14:21 +00:00
|
|
|
// Define command line flags.
|
|
|
|
flagHelp := getopt.BoolLong("help", 0, "Show help.")
|
2022-11-11 16:54:00 +00:00
|
|
|
flagHTTPUpload := getopt.StringLong("http-upload", 'h', "", "Send a file via http-upload.")
|
2018-08-10 10:14:21 +00:00
|
|
|
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.")
|
2022-02-12 08:06:52 +00:00
|
|
|
flagDirectTLS := getopt.BoolLong("tls", 't', "Use direct TLS.")
|
2022-08-06 09:52:47 +00:00
|
|
|
flagAlias := getopt.StringLong("alias", 'a', "", "Set alias/nickname"+
|
|
|
|
"for chatrooms.")
|
2021-02-28 16:58:32 +00:00
|
|
|
flagFile := getopt.StringLong("file", 'f', "", "Set configuration file. (Default: "+
|
|
|
|
"~/.config/go-sendxmpp/sendxmpprc)")
|
2018-08-10 11:12:21 +00:00
|
|
|
flagMessageFile := getopt.StringLong("message", 'm', "", "Set file including the message.")
|
2018-08-10 13:03:04 +00:00
|
|
|
flagInteractive := getopt.BoolLong("interactive", 'i', "Interactive mode (for use with e.g. 'tail -f').")
|
2020-04-05 19:49:17 +00:00
|
|
|
flagSkipVerify := getopt.BoolLong("no-tls-verify", 'n',
|
|
|
|
"Skip verification of TLS certificates (not recommended).")
|
2021-01-30 13:24:54 +00:00
|
|
|
flagRaw := getopt.BoolLong("raw", 0, "Send raw XML.")
|
2022-02-07 15:14:47 +00:00
|
|
|
flagListen := getopt.BoolLong("listen", 'l', "Listen for messages and print them to stdout.")
|
2023-06-06 08:47:41 +00:00
|
|
|
flagTimeout := getopt.IntLong("timeout", 0, defaultTimeout, "Connection timeout in seconds.")
|
|
|
|
flagTLSMinVersion := getopt.IntLong("tls-version", 0, defaultTLSMinVersion,
|
2022-02-23 09:06:06 +00:00
|
|
|
"Minimal TLS version. 10 (TLSv1.0), 11 (TLSv1.1), 12 (TLSv1.2) or 13 (TLSv1.3).")
|
2022-02-12 10:16:45 +00:00
|
|
|
flagVersion := getopt.BoolLong("version", 0, "Show version information.")
|
2022-02-19 07:30:35 +00:00
|
|
|
flagMUCPassword := getopt.StringLong("muc-password", 0, "", "Password for password protected MUCs.")
|
2022-04-17 15:16:29 +00:00
|
|
|
flagOx := getopt.BoolLong("ox", 0, "Use \"OpenPGP for XMPP\" encryption (experimental).")
|
2022-04-26 17:47:22 +00:00
|
|
|
flagOxGenPrivKeyRSA := getopt.BoolLong("ox-genprivkey-rsa", 0,
|
2022-04-27 12:31:44 +00:00
|
|
|
"Generate a private OpenPGP key (RSA 4096 bit) for the given JID and publish the "+
|
2022-04-26 17:47:22 +00:00
|
|
|
"corresponding public key.")
|
|
|
|
flagOxGenPrivKeyX25519 := getopt.BoolLong("ox-genprivkey-x25519", 0,
|
2022-04-27 12:31:44 +00:00
|
|
|
"Generate a private OpenPGP key (x25519) for the given JID and publish the "+
|
2022-04-26 17:47:22 +00:00
|
|
|
"corresponding public key.")
|
2022-04-17 15:16:29 +00:00
|
|
|
flagOxPassphrase := getopt.StringLong("ox-passphrase", 0, "",
|
|
|
|
"Passphrase for locking and unlocking the private OpenPGP key.")
|
2022-04-18 09:16:41 +00:00
|
|
|
flagOxImportPrivKey := getopt.StringLong("ox-import-privkey", 0, "",
|
|
|
|
"Import an existing private OpenPGP key.")
|
2022-05-04 09:18:24 +00:00
|
|
|
flagOxDeleteNodes := getopt.BoolLong("ox-delete-nodes", 0, "Delete existing OpenPGP nodes on the server.")
|
2023-02-18 14:42:04 +00:00
|
|
|
flagOOBFile := getopt.StringLong("oob-file", 0, "", "URL to send a file as out of band data.")
|
2023-06-18 20:21:18 +00:00
|
|
|
flagHeadline := getopt.BoolLong("headline", 0, "Send message as type headline.")
|
2024-01-08 20:11:56 +00:00
|
|
|
flagSCRAMPinning := getopt.StringLong("scram-mech-pinning", 0, "", "Enforce the use of a certain SCRAM authentication mechanism.")
|
2024-03-26 20:47:41 +00:00
|
|
|
flagSSDPOff := getopt.BoolLong("ssdp-off", 0, "Disable XEP-0474: SASL SCRAM Downgrade Protection.")
|
2018-08-10 10:14:21 +00:00
|
|
|
|
|
|
|
// Parse command line flags.
|
|
|
|
getopt.Parse()
|
|
|
|
|
2022-04-25 19:14:40 +00:00
|
|
|
switch {
|
|
|
|
case *flagHelp:
|
|
|
|
// If requested, show help and quit.
|
2023-02-18 17:56:07 +00:00
|
|
|
getopt.PrintUsage(os.Stdout)
|
2018-08-10 10:14:21 +00:00
|
|
|
os.Exit(0)
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagVersion:
|
|
|
|
// If requested, show version and quit.
|
2024-03-16 21:37:54 +00:00
|
|
|
fmt.Println("Go-sendxmpp", version)
|
2024-03-20 17:33:10 +00:00
|
|
|
system := runtime.GOOS + "/" + runtime.GOARCH
|
|
|
|
fmt.Println("System:", system, runtime.Version())
|
2022-02-12 10:16:45 +00:00
|
|
|
fmt.Println("License: BSD-2-clause")
|
|
|
|
os.Exit(0)
|
2022-04-25 19:14:40 +00:00
|
|
|
// Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like
|
|
|
|
// groupchat, http-upload or listening.
|
2022-04-25 19:29:14 +00:00
|
|
|
case *flagOx && *flagHTTPUpload != "":
|
2022-04-25 19:14:40 +00:00
|
|
|
log.Fatal("No Ox support for http-upload available.")
|
|
|
|
case *flagOx && *flagChatroom:
|
|
|
|
log.Fatal("No Ox support for chat rooms available.")
|
2022-04-25 19:29:14 +00:00
|
|
|
case *flagHTTPUpload != "" && *flagInteractive:
|
2022-04-25 19:14:40 +00:00
|
|
|
log.Fatal("Interactive mode and http upload can't" +
|
|
|
|
" be used at the same time.")
|
2022-04-25 19:29:14 +00:00
|
|
|
case *flagHTTPUpload != "" && *flagMessageFile != "":
|
2022-04-25 19:14:40 +00:00
|
|
|
log.Fatal("You can't send a message while using" +
|
|
|
|
" http upload.")
|
2023-02-18 14:42:04 +00:00
|
|
|
case *flagOx && *flagOOBFile != "":
|
|
|
|
log.Fatal("No encryption possible for OOB data.")
|
2023-08-11 13:19:03 +00:00
|
|
|
case *flagOx && *flagHeadline:
|
|
|
|
log.Fatal("No Ox support for headline messages.")
|
2023-06-18 20:21:18 +00:00
|
|
|
case *flagHeadline && *flagChatroom:
|
|
|
|
log.Fatal("Can't use message type headline for groupchat messages.")
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
|
2024-03-30 13:08:54 +00:00
|
|
|
// Print a warning if go-sendxmpp is run by the user root on non-windows systems.
|
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
// Get the current user.
|
|
|
|
currUser, err := osUser.Current()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("Failed to get current user: ", err)
|
|
|
|
}
|
|
|
|
if currUser.Username == "root" {
|
|
|
|
fmt.Println("WARNING: It seems you are running go-sendxmpp as root user.\n" +
|
|
|
|
"This is is not recommended as go-sendxmpp does not require root " +
|
|
|
|
"privileges. Please consider using a less privileged user. For an " +
|
|
|
|
"example how to do this with sudo please consult the manpage chapter " +
|
|
|
|
"TIPS.")
|
|
|
|
}
|
2024-03-30 13:06:56 +00:00
|
|
|
}
|
|
|
|
|
2024-01-08 20:21:24 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-08-10 10:14:21 +00:00
|
|
|
// Read recipients from command line and quit if none are specified.
|
2022-02-06 22:25:00 +00:00
|
|
|
// 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).
|
2022-04-17 15:16:29 +00:00
|
|
|
recipientsList := getopt.Args()
|
2022-04-26 17:47:22 +00:00
|
|
|
if (len(recipientsList) == 0 && !*flagRaw && !*flagListen && !*flagOxGenPrivKeyX25519 &&
|
2022-05-04 09:18:24 +00:00
|
|
|
!*flagOxGenPrivKeyRSA && *flagOxImportPrivKey == "") && !*flagOxDeleteNodes ||
|
2022-04-26 17:47:22 +00:00
|
|
|
(len(recipientsList) == 0 && *flagChatroom) {
|
2018-08-10 10:14:21 +00:00
|
|
|
log.Fatal("No recipient specified.")
|
|
|
|
}
|
2018-08-04 10:16:28 +00:00
|
|
|
|
2021-01-21 09:42:07 +00:00
|
|
|
// Read configuration file if user or password is not specified.
|
|
|
|
if *flagUser == "" || *flagPassword == "" {
|
2019-02-20 21:23:29 +00:00
|
|
|
// Read configuration from file.
|
|
|
|
config, err := parseConfig(*flagFile)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2020-04-03 12:28:08 +00:00
|
|
|
log.Fatal("Error parsing ", *flagFile, ": ", err)
|
2019-02-20 21:23:29 +00:00
|
|
|
}
|
|
|
|
// Set connection options according to config.
|
|
|
|
user = config.username
|
|
|
|
server = config.jserver
|
|
|
|
password = config.password
|
2022-08-06 09:52:47 +00:00
|
|
|
alias = config.alias
|
2019-02-20 21:23:29 +00:00
|
|
|
if config.port != "" {
|
2021-10-05 15:11:50 +00:00
|
|
|
server = net.JoinHostPort(server, fmt.Sprint(config.port))
|
2019-02-20 21:23:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-21 17:14:10 +00:00
|
|
|
// 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
|
|
|
|
}
|
2024-02-20 19:45:43 +00:00
|
|
|
// If no server part is specified in the username but a server is specified
|
|
|
|
// just assume the server is identical to the server part and hope for the
|
|
|
|
// best. This is for compatibility with the old perl sendxmpp config files.
|
2024-02-28 17:24:20 +00:00
|
|
|
var serverpart string
|
2024-02-20 19:45:43 +00:00
|
|
|
if !strings.Contains(user, "@") && server != "" {
|
2024-02-28 17:24:20 +00:00
|
|
|
// Remove port if server contains it.
|
|
|
|
if strings.Contains(server, ":") {
|
|
|
|
serverpart, _, err = net.SplitHostPort(server)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
serverpart = server
|
|
|
|
}
|
|
|
|
user = user + "@" + serverpart
|
2024-02-20 19:45:43 +00:00
|
|
|
}
|
2019-02-21 17:14:10 +00:00
|
|
|
|
2022-08-06 09:52:47 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-02-12 08:18:02 +00:00
|
|
|
// Timeout
|
2023-06-06 08:47:41 +00:00
|
|
|
timeout := time.Duration(*flagTimeout) * time.Second
|
2022-02-12 08:18:02 +00:00
|
|
|
|
2020-04-02 17:35:12 +00:00
|
|
|
// Use ALPN
|
2020-04-03 12:35:03 +00:00
|
|
|
var tlsConfig tls.Config
|
2021-03-03 21:10:45 +00:00
|
|
|
tlsConfig.ServerName = user[strings.Index(user, "@")+1:]
|
2020-04-02 17:35:12 +00:00
|
|
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "xmpp-client")
|
2020-04-05 19:49:17 +00:00
|
|
|
tlsConfig.InsecureSkipVerify = *flagSkipVerify
|
2024-01-12 11:18:00 +00:00
|
|
|
tlsConfig.Renegotiation = tls.RenegotiateNever
|
2022-02-12 08:36:12 +00:00
|
|
|
switch *flagTLSMinVersion {
|
2023-06-06 08:47:41 +00:00
|
|
|
case defaultTLS10:
|
2022-02-12 08:36:12 +00:00
|
|
|
tlsConfig.MinVersion = tls.VersionTLS10
|
2023-06-06 08:47:41 +00:00
|
|
|
case defaultTLS11:
|
2022-02-12 08:36:12 +00:00
|
|
|
tlsConfig.MinVersion = tls.VersionTLS11
|
2023-06-06 08:47:41 +00:00
|
|
|
case defaultTLS12:
|
2022-02-12 08:36:12 +00:00
|
|
|
tlsConfig.MinVersion = tls.VersionTLS12
|
2023-06-06 08:47:41 +00:00
|
|
|
case defaultTLS13:
|
2022-02-12 08:36:12 +00:00
|
|
|
tlsConfig.MinVersion = tls.VersionTLS13
|
|
|
|
default:
|
|
|
|
fmt.Println("Unknown TLS version.")
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2020-04-02 17:35:12 +00:00
|
|
|
|
2018-08-10 10:14:21 +00:00
|
|
|
// Set XMPP connection options.
|
2018-08-04 10:16:28 +00:00
|
|
|
options := xmpp.Options{
|
2022-02-12 08:18:02 +00:00
|
|
|
Host: server,
|
|
|
|
User: user,
|
|
|
|
DialTimeout: timeout,
|
2023-09-05 18:39:54 +00:00
|
|
|
Resource: "go-sendxmpp." + getShortID(),
|
2021-02-28 12:57:06 +00:00
|
|
|
Password: password,
|
2022-02-12 08:11:03 +00:00
|
|
|
// 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.
|
2024-01-10 15:21:39 +00:00
|
|
|
// See https://pkg.go.dev/github.com/xmppo/go-xmpp#Options
|
2022-02-12 08:11:03 +00:00
|
|
|
NoTLS: !*flagDirectTLS,
|
|
|
|
StartTLS: !*flagDirectTLS,
|
|
|
|
Debug: *flagDebug,
|
|
|
|
TLSConfig: &tlsConfig,
|
2024-01-08 20:11:56 +00:00
|
|
|
Mechanism: *flagSCRAMPinning,
|
2024-03-26 20:41:45 +00:00
|
|
|
SSDP: !*flagSSDPOff,
|
2018-08-04 10:16:28 +00:00
|
|
|
}
|
|
|
|
|
2018-08-10 11:12:21 +00:00
|
|
|
// Read message from file.
|
|
|
|
if *flagMessageFile != "" {
|
|
|
|
message, err = readMessage(*flagMessageFile)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2018-08-10 11:12:21 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-29 12:38:33 +00:00
|
|
|
// Skip reading message if '-i' or '--interactive' is set to work with e.g. 'tail -f'.
|
2022-11-06 13:30:22 +00:00
|
|
|
// Also for listening mode and Ox key handling.
|
|
|
|
if !*flagInteractive && !*flagListen && *flagHTTPUpload == "" &&
|
|
|
|
!*flagOxDeleteNodes && *flagOxImportPrivKey == "" &&
|
2023-02-18 14:42:04 +00:00
|
|
|
!*flagOxGenPrivKeyX25519 && !*flagOxGenPrivKeyRSA && *flagOOBFile == "" &&
|
2022-11-06 13:38:31 +00:00
|
|
|
message == "" {
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
for scanner.Scan() {
|
|
|
|
if message == "" {
|
|
|
|
message = scanner.Text()
|
|
|
|
} else {
|
|
|
|
message = message + "\n" + scanner.Text()
|
2022-09-29 12:38:33 +00:00
|
|
|
}
|
2022-11-06 13:38:31 +00:00
|
|
|
}
|
2022-09-29 12:38:33 +00:00
|
|
|
|
2022-11-06 13:38:31 +00:00
|
|
|
if err := scanner.Err(); err != nil {
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != io.EOF {
|
2022-11-06 13:38:31 +00:00
|
|
|
log.Fatal(err)
|
2022-09-29 12:38:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-05 12:22:35 +00:00
|
|
|
// Remove invalid code points.
|
2022-11-05 11:40:40 +00:00
|
|
|
message = validUTF8(message)
|
2022-09-29 12:38:33 +00:00
|
|
|
// Exit if message is empty.
|
2022-11-06 13:30:22 +00:00
|
|
|
if message == "" && !*flagInteractive && !*flagListen && !*flagOxGenPrivKeyRSA &&
|
|
|
|
!*flagOxGenPrivKeyX25519 && *flagOxImportPrivKey == "" &&
|
2023-02-18 14:42:04 +00:00
|
|
|
!*flagOxDeleteNodes && *flagHTTPUpload == "" && *flagOOBFile == "" {
|
2022-09-29 12:38:33 +00:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2018-08-04 10:16:28 +00:00
|
|
|
// Connect to server.
|
2022-02-12 08:06:52 +00:00
|
|
|
client, err := connect(options, *flagDirectTLS)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2018-08-04 10:16:28 +00:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2023-06-06 08:47:41 +00:00
|
|
|
iqc := make(chan xmpp.IQ, defaultBufferSize)
|
|
|
|
msgc := make(chan xmpp.Chat, defaultBufferSize)
|
2024-04-02 13:36:33 +00:00
|
|
|
go rcvStanzas(client, iqc, msgc)
|
2022-04-17 15:16:29 +00:00
|
|
|
for _, r := range getopt.Args() {
|
|
|
|
var re recipientsType
|
|
|
|
re.Jid = r
|
2022-04-18 12:59:29 +00:00
|
|
|
if *flagOx {
|
2022-05-03 10:25:43 +00:00
|
|
|
re.OxKeyRing, err = oxGetPublicKeyRing(client, iqc, r)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2022-04-21 16:54:55 +00:00
|
|
|
re.OxKeyRing = nil
|
2022-06-28 16:31:03 +00:00
|
|
|
fmt.Println("ox: error fetching key for", r+":", err)
|
2022-04-18 12:59:29 +00:00
|
|
|
}
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
recipients = append(recipients, re)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that all recipient JIDs are valid.
|
|
|
|
for i, recipient := range recipients {
|
|
|
|
validatedJid, err := MarshalJID(recipient.Jid)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
recipients[i].Jid = validatedJid
|
|
|
|
}
|
|
|
|
|
2022-04-25 19:14:40 +00:00
|
|
|
switch {
|
2022-04-26 17:47:22 +00:00
|
|
|
case *flagOxGenPrivKeyX25519:
|
2022-04-17 15:16:29 +00:00
|
|
|
validatedOwnJid, err := MarshalJID(user)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
2022-05-03 10:25:43 +00:00
|
|
|
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "x25519")
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-26 17:47:22 +00:00
|
|
|
}
|
|
|
|
os.Exit(0)
|
|
|
|
case *flagOxGenPrivKeyRSA:
|
|
|
|
validatedOwnJid, err := MarshalJID(user)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-26 17:47:22 +00:00
|
|
|
}
|
2022-05-03 10:25:43 +00:00
|
|
|
err = oxGenPrivKey(validatedOwnJid, client, iqc, *flagOxPassphrase, "rsa")
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
os.Exit(0)
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagOxImportPrivKey != "":
|
2022-04-18 09:16:41 +00:00
|
|
|
validatedOwnJid, err := MarshalJID(user)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-18 09:16:41 +00:00
|
|
|
}
|
|
|
|
err = oxImportPrivKey(validatedOwnJid, *flagOxImportPrivKey,
|
2022-05-03 10:25:43 +00:00
|
|
|
client, iqc)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-18 09:16:41 +00:00
|
|
|
}
|
|
|
|
os.Exit(0)
|
2022-05-04 09:18:24 +00:00
|
|
|
case *flagOxDeleteNodes:
|
|
|
|
validatedOwnJid, err := MarshalJID(user)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-05-04 09:18:24 +00:00
|
|
|
}
|
|
|
|
err = oxDeleteNodes(validatedOwnJid, client, iqc)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-05-04 09:18:24 +00:00
|
|
|
}
|
|
|
|
os.Exit(0)
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagOx:
|
2022-04-17 15:16:29 +00:00
|
|
|
validatedOwnJid, err := MarshalJID(user)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
oxPrivKey, err = oxGetPrivKey(validatedOwnJid, *flagOxPassphrase)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-17 15:16:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-25 19:29:14 +00:00
|
|
|
if *flagHTTPUpload != "" {
|
2024-01-14 20:29:19 +00:00
|
|
|
message, err = httpUpload(client, iqc, tlsConfig.ServerName,
|
2024-01-13 14:01:42 +00:00
|
|
|
*flagHTTPUpload, timeout)
|
2024-01-14 20:29:19 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2024-01-14 20:29:19 +00:00
|
|
|
}
|
2020-04-09 12:58:03 +00:00
|
|
|
}
|
|
|
|
|
2023-02-18 14:42:04 +00:00
|
|
|
if *flagOOBFile != "" {
|
|
|
|
// Remove invalid UTF8 code points.
|
|
|
|
message = validUTF8(*flagOOBFile)
|
|
|
|
// Check if the URI is valid.
|
2023-02-18 15:24:46 +00:00
|
|
|
uri, err := validURI(message)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2023-02-18 14:42:04 +00:00
|
|
|
}
|
2023-02-18 15:24:46 +00:00
|
|
|
message = uri.String()
|
2023-02-18 14:42:04 +00:00
|
|
|
}
|
|
|
|
|
2022-04-25 19:14:40 +00:00
|
|
|
var msgType string
|
2023-06-18 20:21:18 +00:00
|
|
|
if *flagHeadline {
|
|
|
|
msgType = strHeadline
|
|
|
|
} else {
|
|
|
|
msgType = strChat
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
if *flagChatroom {
|
2023-06-04 13:23:40 +00:00
|
|
|
msgType = strGroupchat
|
2021-01-31 11:35:24 +00:00
|
|
|
// Join the MUCs.
|
|
|
|
for _, recipient := range recipients {
|
2022-02-19 07:30:35 +00:00
|
|
|
if *flagMUCPassword != "" {
|
|
|
|
dummyTime := time.Now()
|
2022-08-06 09:52:47 +00:00
|
|
|
_, err = client.JoinProtectedMUC(recipient.Jid, alias,
|
2022-02-19 08:19:53 +00:00
|
|
|
*flagMUCPassword, xmpp.NoHistory, 0, &dummyTime)
|
2022-02-19 07:30:35 +00:00
|
|
|
} else {
|
2022-08-06 09:52:47 +00:00
|
|
|
_, err = client.JoinMUCNoHistory(recipient.Jid, alias)
|
2022-02-19 07:30:35 +00:00
|
|
|
}
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2021-01-31 11:35:24 +00:00
|
|
|
}
|
2021-01-30 13:24:54 +00:00
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case *flagRaw:
|
2022-07-04 15:16:40 +00:00
|
|
|
if message == "" {
|
|
|
|
break
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
// Send raw XML
|
2023-08-14 08:34:21 +00:00
|
|
|
_, err = client.SendOrg(message)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2021-01-30 13:24:54 +00:00
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagInteractive:
|
|
|
|
// Send in endless loop (for usage with e.g. "tail -f").
|
2023-06-06 08:47:40 +00:00
|
|
|
reader := bufio.NewReader(os.Stdin)
|
2023-07-27 17:31:56 +00:00
|
|
|
// Quit if ^C is pressed.
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(c, os.Interrupt)
|
|
|
|
go func() {
|
|
|
|
for range c {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, nil)
|
2023-07-27 17:31:56 +00:00
|
|
|
}
|
|
|
|
}()
|
2022-04-25 19:14:40 +00:00
|
|
|
for {
|
2022-10-16 08:57:35 +00:00
|
|
|
message, err = reader.ReadString('\n')
|
2024-04-02 13:36:33 +00:00
|
|
|
if err != nil {
|
|
|
|
closeAndExit(client, errors.New("failed to read from stdin"))
|
2022-10-16 08:57:35 +00:00
|
|
|
}
|
2024-03-11 18:08:42 +00:00
|
|
|
message = strings.TrimSuffix(message, "\n")
|
2022-10-16 08:57:35 +00:00
|
|
|
|
|
|
|
// Remove invalid code points.
|
2022-11-05 11:40:40 +00:00
|
|
|
message = validUTF8(message)
|
2022-07-04 15:16:40 +00:00
|
|
|
if message == "" {
|
|
|
|
continue
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
for _, recipient := range recipients {
|
|
|
|
switch {
|
|
|
|
case *flagOx:
|
|
|
|
if recipient.OxKeyRing == nil {
|
|
|
|
continue
|
|
|
|
}
|
2023-06-06 08:47:41 +00:00
|
|
|
oxMessage, err := oxEncrypt(client, oxPrivKey,
|
2022-04-25 19:14:40 +00:00
|
|
|
recipient.Jid, recipient.OxKeyRing, message)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2022-04-25 19:14:40 +00:00
|
|
|
fmt.Println("Ox: couldn't encrypt to",
|
|
|
|
recipient.Jid)
|
|
|
|
continue
|
|
|
|
}
|
2023-08-14 08:34:21 +00:00
|
|
|
_, err = client.SendOrg(oxMessage)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-25 19:14:40 +00:00
|
|
|
}
|
|
|
|
default:
|
2023-06-06 08:47:40 +00:00
|
|
|
_, err = client.Send(xmpp.Chat{
|
|
|
|
Remote: recipient.Jid,
|
|
|
|
Type: msgType, Text: message,
|
|
|
|
})
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2022-04-25 19:14:40 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-31 11:35:24 +00:00
|
|
|
}
|
2021-01-30 13:24:54 +00:00
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagListen:
|
2022-04-30 11:06:04 +00:00
|
|
|
tz := time.Now().Location()
|
2024-03-26 19:43:30 +00:00
|
|
|
// Quit if ^C is pressed.
|
|
|
|
c := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(c, os.Interrupt)
|
|
|
|
go func() {
|
|
|
|
for range c {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, nil)
|
2024-03-26 19:43:30 +00:00
|
|
|
}
|
|
|
|
}()
|
2022-02-06 22:25:00 +00:00
|
|
|
for {
|
2022-05-03 10:25:43 +00:00
|
|
|
v := <-msgc
|
|
|
|
switch {
|
|
|
|
case isOxMsg(v) && *flagOx:
|
|
|
|
msg, t, err := oxDecrypt(v, client, iqc, user, oxPrivKey)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2022-05-03 10:25:43 +00:00
|
|
|
log.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if msg == "" {
|
|
|
|
continue
|
|
|
|
}
|
2022-05-05 18:01:35 +00:00
|
|
|
var bareFrom string
|
|
|
|
switch v.Type {
|
2023-06-04 13:25:15 +00:00
|
|
|
case strChat:
|
2022-05-05 18:01:35 +00:00
|
|
|
bareFrom = strings.Split(v.Remote, "/")[0]
|
2023-06-04 13:23:40 +00:00
|
|
|
case strGroupchat:
|
2022-05-05 18:01:35 +00:00
|
|
|
bareFrom = v.Remote
|
|
|
|
default:
|
|
|
|
bareFrom = strings.Split(v.Remote, "/")[0]
|
|
|
|
}
|
2022-05-03 10:25:43 +00:00
|
|
|
// 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 {
|
2022-05-05 18:01:35 +00:00
|
|
|
if strings.Split(v.Remote, "/")[0] ==
|
|
|
|
strings.ToLower(recipient.Jid) {
|
2022-05-03 10:25:43 +00:00
|
|
|
fmt.Println(t.In(tz).Format(time.RFC3339),
|
|
|
|
"[OX]", bareFrom+":", msg)
|
2022-04-30 11:06:04 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-03 10:25:43 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
var t time.Time
|
|
|
|
if v.Text == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if v.Stamp.IsZero() {
|
|
|
|
t = time.Now()
|
|
|
|
} else {
|
|
|
|
t = v.Stamp
|
|
|
|
}
|
2022-05-05 18:01:35 +00:00
|
|
|
var bareFrom string
|
|
|
|
switch v.Type {
|
2023-06-04 13:25:15 +00:00
|
|
|
case strChat:
|
2022-05-05 18:01:35 +00:00
|
|
|
bareFrom = strings.Split(v.Remote, "/")[0]
|
2023-06-04 13:23:40 +00:00
|
|
|
case strGroupchat:
|
2022-05-05 18:01:35 +00:00
|
|
|
bareFrom = v.Remote
|
|
|
|
default:
|
|
|
|
bareFrom = strings.Split(v.Remote, "/")[0]
|
|
|
|
}
|
2022-05-03 10:25:43 +00:00
|
|
|
// 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 {
|
2022-05-05 18:01:35 +00:00
|
|
|
if strings.Split(v.Remote, "/")[0] ==
|
|
|
|
strings.ToLower(recipient.Jid) {
|
2022-05-03 10:25:43 +00:00
|
|
|
fmt.Println(t.In(tz).Format(time.RFC3339),
|
|
|
|
bareFrom+":", v.Text)
|
2022-02-07 15:17:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-06 22:25:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
default:
|
2018-08-10 10:14:21 +00:00
|
|
|
for _, recipient := range recipients {
|
2022-07-04 15:16:40 +00:00
|
|
|
if message == "" {
|
|
|
|
break
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
switch {
|
2023-02-18 15:24:46 +00:00
|
|
|
case *flagHTTPUpload != "":
|
2023-06-06 08:47:40 +00:00
|
|
|
_, err = client.Send(xmpp.Chat{
|
|
|
|
Remote: recipient.Jid,
|
|
|
|
Type: msgType, Ooburl: message, Text: message,
|
|
|
|
})
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2022-04-25 19:14:40 +00:00
|
|
|
fmt.Println("Couldn't send message to",
|
|
|
|
recipient.Jid)
|
2018-08-10 13:03:04 +00:00
|
|
|
}
|
2023-02-18 15:24:46 +00:00
|
|
|
// (Hopefully) temporary workaround due to go-xmpp choking on URL encoding.
|
|
|
|
// Once this is fixed in the lib the http-upload case above can be reused.
|
|
|
|
case *flagOOBFile != "":
|
|
|
|
_, err = client.SendOrg("<message to='" + recipient.Jid + "' type='" +
|
|
|
|
msgType + "'><body>" + message + "</body><x xmlns='jabber:x:oob'><url>" +
|
|
|
|
message + "</url></x></message>")
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2023-02-18 15:24:46 +00:00
|
|
|
fmt.Println("Couldn't send message to",
|
|
|
|
recipient.Jid)
|
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
case *flagOx:
|
|
|
|
if recipient.OxKeyRing == nil {
|
|
|
|
continue
|
2020-04-09 14:49:27 +00:00
|
|
|
}
|
2023-06-06 08:47:41 +00:00
|
|
|
oxMessage, err := oxEncrypt(client, oxPrivKey,
|
2022-04-25 19:14:40 +00:00
|
|
|
recipient.Jid, recipient.OxKeyRing, message)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2022-04-25 19:14:40 +00:00
|
|
|
fmt.Println("Ox: couldn't encrypt to", recipient.Jid)
|
|
|
|
continue
|
2018-08-10 13:03:04 +00:00
|
|
|
}
|
2023-08-14 08:34:21 +00:00
|
|
|
_, err = client.SendOrg(oxMessage)
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2020-04-09 14:49:27 +00:00
|
|
|
}
|
2022-04-25 19:14:40 +00:00
|
|
|
default:
|
2023-06-06 08:47:40 +00:00
|
|
|
_, err = client.Send(xmpp.Chat{
|
|
|
|
Remote: recipient.Jid,
|
|
|
|
Type: msgType, Text: message,
|
|
|
|
})
|
2023-08-16 06:49:43 +00:00
|
|
|
if err != nil {
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, err)
|
2018-08-10 13:03:04 +00:00
|
|
|
}
|
2018-08-10 10:14:21 +00:00
|
|
|
}
|
2018-08-04 10:16:28 +00:00
|
|
|
}
|
|
|
|
}
|
2024-04-02 13:36:33 +00:00
|
|
|
closeAndExit(client, nil)
|
2018-08-04 10:16:28 +00:00
|
|
|
}
|