@ -8,22 +8,19 @@ import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
osUser "os/user"
"runtime"
"strings"
"time"
gopenpgpConst "github.com/ProtonMail/gopenpgp/v2/constants" // MIT License
"github.com/ProtonMail/gopenpgp/v2/crypto" // MIT License
"github.com/pborman/getopt/v2" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
"salsa.debian.org/mdosch/xmppsrv" // BSD-2-Clause
"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 {
@ -34,12 +31,15 @@ type configuration struct {
alias string
}
func closeAndExit ( client * xmpp . Client , err error ) {
func closeAndExit ( client * xmpp . Client , cancel context . CancelFunc , err error ) {
// Wait for a short time as some messages are not delivered by the server
// if the connection is closed immediately after sending a message.
time . Sleep ( defaultSleepTime * time . Millisecond )
cancel ( )
client . Close ( )
if err != nil {
log . Fatal ( err )
}
os . Exit ( 0 )
}
func readMessage ( messageFilePath string ) ( string , error ) {
@ -59,7 +59,6 @@ func readMessage(messageFilePath string) (string, error) {
if err != nil {
return output , fmt . Errorf ( "readMessage: %w" , err )
}
defer file . Close ( )
scanner := bufio . NewScanner ( file )
scanner . Split ( bufio . ScanLines )
for scanner . Scan ( ) {
@ -76,6 +75,11 @@ func readMessage(messageFilePath string) (string, error) {
}
}
err = file . Close ( )
if err != nil {
fmt . Println ( "error while closing file:" , err )
}
return output , nil
}
@ -90,11 +94,6 @@ func main() {
message , user , server , password , alias string
oxPrivKey * crypto . Key
recipients [ ] recipientsType
fast xmpp . Fast
// There are some errors that we ignore as we do not want to
// stop the execution. Failure is used to track those to exit
// with a non-success return value.
failure error
)
// Define command line flags.
@ -136,9 +135,6 @@ func main() {
flagOOBFile := getopt . StringLong ( "oob-file" , 0 , "" , "URL to send a file as out of band data." )
flagHeadline := getopt . BoolLong ( "headline" , 0 , "Send message as type headline." )
flagSCRAMPinning := getopt . StringLong ( "scram-mech-pinning" , 0 , "" , "Enforce the use of a certain SCRAM authentication mechanism." )
flagSSDPOff := getopt . BoolLong ( "ssdp-off" , 0 , "Disable XEP-0474: SASL SCRAM Downgrade Protection." )
flagSubject := getopt . StringLong ( "subject" , 's' , "" , "Set message subject." )
flagFastOff := getopt . BoolLong ( "fast-off" , 0 , "Disable XEP-0484: Fast Authentication Streamlining Tokens." )
// Parse command line flags.
getopt . Parse ( )
@ -150,11 +146,7 @@ func main() {
os . Exit ( 0 )
case * flagVersion :
// If requested, show version and quit.
fmt . Println ( "Go-sendxmpp" , version )
fmt . Println ( "Xmppsrv library version:" , xmppsrv . Version )
fmt . Println ( "Gopenpgp library version:" , gopenpgpConst . Version )
system := runtime . GOOS + "/" + runtime . GOARCH
fmt . Println ( "System:" , system , runtime . Version ( ) )
fmt . Println ( "go-sendxmpp" , version )
fmt . Println ( "License: BSD-2-clause" )
os . Exit ( 0 )
// Quit if Ox (OpenPGP for XMPP) is requested for unsupported operations like
@ -177,22 +169,6 @@ func main() {
log . Fatal ( "Can't use message type headline for groupchat messages." )
}
// Print a warning if go-sendxmpp is run by the user root on non-windows systems.
if runtime . GOOS != "windows" {
// Get the current user.
currUser , err := osUser . Current ( )
if err != nil {
log . Fatal ( "Failed to get current user: " , err )
}
if currUser . Username == "root" {
fmt . Println ( "WARNING: It seems you are running go-sendxmpp as root user.\n" +
"This is is not recommended as go-sendxmpp does not require root " +
"privileges. Please consider using a less privileged user. For an " +
"example how to do this with sudo please consult the manpage chapter " +
"TIPS." )
}
}
switch * flagSCRAMPinning {
case "" , "SCRAM-SHA-1" , "SCRAM-SHA-1-PLUS" , "SCRAM-SHA-256" , "SCRAM-SHA-256-PLUS" ,
"SCRAM-SHA-512" , "SCRAM-SHA-512-PLUS" :
@ -241,22 +217,6 @@ func main() {
if * flagPassword != "" {
password = * flagPassword
}
// If no server part is specified in the username but a server is specified
// just assume the server is identical to the server part and hope for the
// best. This is for compatibility with the old perl sendxmpp config files.
var serverpart string
if ! strings . Contains ( user , "@" ) && server != "" {
// Remove port if server contains it.
if strings . Contains ( server , ":" ) {
serverpart , _ , err = net . SplitHostPort ( server )
if err != nil {
log . Fatal ( err )
}
} else {
serverpart = server
}
user = user + "@" + serverpart
}
switch {
// Use "go-sendxmpp" if no nick is specified via config or command line flag.
@ -270,20 +230,6 @@ func main() {
// Timeout
timeout := time . Duration ( * flagTimeout ) * time . Second
clientID , err := getClientID ( user )
if err != nil {
fmt . Println ( err )
}
if ! * flagFastOff {
fast , _ = getFastData ( user , password )
// Reset FAST token and mechanism if expired.
if time . Now ( ) . After ( fast . Expiry ) {
fast . Token = ""
fast . Mechanism = ""
}
}
// Use ALPN
var tlsConfig tls . Config
tlsConfig . ServerName = user [ strings . Index ( user , "@" ) + 1 : ]
@ -304,14 +250,12 @@ func main() {
os . Exit ( 0 )
}
resource := "go-sendxmpp." + getShortID ( )
// Set XMPP connection options.
options := xmpp . Options {
Host : server ,
User : user ,
DialTimeout : timeout ,
Resource : resource ,
Resource : "go-sendxmpp." + getShortID ( ) ,
Password : password ,
// NoTLS doesn't mean that no TLS is used at all but that instead
// of using an encrypted connection to the server (direct TLS)
@ -319,17 +263,11 @@ func main() {
// set when NoTLS is set go-sendxmpp won't use unencrypted
// client-to-server connections.
// See https://pkg.go.dev/github.com/xmppo/go-xmpp#Options
NoTLS : ! * flagDirectTLS ,
StartTLS : ! * flagDirectTLS ,
Debug : * flagDebug ,
TLSConfig : & tlsConfig ,
Mechanism : * flagSCRAMPinning ,
SSDP : ! * flagSSDPOff ,
UserAgentSW : resource ,
UserAgentID : clientID ,
Fast : ! * flagFastOff ,
FastToken : fast . Token ,
FastMechanism : fast . Mechanism ,
NoTLS : ! * flagDirectTLS ,
StartTLS : ! * flagDirectTLS ,
Debug : * flagDebug ,
TLSConfig : & tlsConfig ,
Mechanism : * flagSCRAMPinning ,
}
// Read message from file.
@ -374,41 +312,13 @@ func main() {
// Connect to server.
client , err := connect ( options , * flagDirectTLS )
if err != nil {
if fast . Token != "" {
// Reset FAST token and mechanism if FAST login failed.
fast . Token = ""
fast . Mechanism = ""
fast . Expiry = time . Now ( )
err := writeFastData ( user , password , fast )
if err != nil {
fmt . Println ( err )
}
options . FastToken = ""
// Try to connect to server without FAST.
client , err = connect ( options , * flagDirectTLS )
if err != nil {
log . Fatal ( err )
}
} else {
log . Fatal ( err )
}
log . Fatal ( err )
}
// Update fast token if a new one is received or expiry time is reduced.
if ( client . Fast . Token != "" && client . Fast . Token != fast . Token ) ||
( client . Fast . Expiry . Before ( fast . Expiry ) && ! client . Fast . Expiry . IsZero ( ) ) {
fast . Token = client . Fast . Token
fast . Mechanism = client . Fast . Mechanism
fast . Expiry = client . Fast . Expiry
err := writeFastData ( user , password , fast )
if err != nil {
fmt . Println ( err )
}
}
iqc := make ( chan xmpp . IQ , defaultBufferSize )
msgc := make ( chan xmpp . Chat , defaultBufferSize )
ctx , cancel := context . WithCancel ( context . Background ( ) )
go rcvStanzas ( client , ctx, iqc, msgc )
go rcvStanzas ( client , iqc , msgc , ctx )
for _ , r := range getopt . Args ( ) {
var re recipientsType
re . Jid = r
@ -416,8 +326,7 @@ func main() {
re . OxKeyRing , err = oxGetPublicKeyRing ( client , iqc , r )
if err != nil {
re . OxKeyRing = nil
fmt . Printf ( "Ox: error fetching key for %s: %v\n" , r , err )
failure = err
fmt . Println ( "ox: error fetching key for" , r + ":" , err )
}
}
recipients = append ( recipients , re )
@ -427,8 +336,7 @@ func main() {
for i , recipient := range recipients {
validatedJid , err := MarshalJID ( recipient . Jid )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
recipients [ i ] . Jid = validatedJid
}
@ -437,62 +345,52 @@ func main() {
case * flagOxGenPrivKeyX25519 :
validatedOwnJid , err := MarshalJID ( user )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
err = oxGenPrivKey ( validatedOwnJid , client , iqc , * flagOxPassphrase , "x25519" )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
os . Exit ( 0 )
case * flagOxGenPrivKeyRSA :
validatedOwnJid , err := MarshalJID ( user )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
err = oxGenPrivKey ( validatedOwnJid , client , iqc , * flagOxPassphrase , "rsa" )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
os . Exit ( 0 )
case * flagOxImportPrivKey != "" :
validatedOwnJid , err := MarshalJID ( user )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
err = oxImportPrivKey ( validatedOwnJid , * flagOxImportPrivKey ,
client , iqc )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
os . Exit ( 0 )
case * flagOxDeleteNodes :
validatedOwnJid , err := MarshalJID ( user )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
err = oxDeleteNodes ( validatedOwnJid , client , iqc )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
os . Exit ( 0 )
case * flagOx :
validatedOwnJid , err := MarshalJID ( user )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
oxPrivKey , err = oxGetPrivKey ( validatedOwnJid , * flagOxPassphrase )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
}
@ -500,8 +398,7 @@ func main() {
message , err = httpUpload ( client , iqc , tlsConfig . ServerName ,
* flagHTTPUpload , timeout )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
}
@ -511,8 +408,7 @@ func main() {
// Check if the URI is valid.
uri , err := validURI ( message )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
message = uri . String ( )
}
@ -535,8 +431,7 @@ func main() {
_ , err = client . JoinMUCNoHistory ( recipient . Jid , alias )
}
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
}
}
@ -548,8 +443,7 @@ func main() {
// Send raw XML
_ , err = client . SendOrg ( message )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
case * flagInteractive :
// Send in endless loop (for usage with e.g. "tail -f").
@ -560,26 +454,16 @@ func main() {
go func ( ) {
for range c {
cancel ( )
if failure != nil {
closeAndExit ( client , failure )
}
closeAndExit ( client , nil )
client . Close ( )
os . Exit ( 0 )
}
} ( )
for {
message , err = reader . ReadString ( '\n' )
message = strings . TrimSuffix ( message , "\n" )
if err != nil {
select {
case <- ctx . Done ( ) :
return
default :
if err != nil {
cancel ( )
closeAndExit ( client , fmt . Errorf ( "failed to read from stdin" ) )
}
}
closeAndExit ( client , cancel , errors . New ( "failed to read from stdin" ) )
}
message = strings . TrimSuffix ( message , "\n" )
// Remove invalid code points.
message = validUTF8 ( message )
@ -593,45 +477,29 @@ func main() {
continue
}
oxMessage , err := oxEncrypt ( client , oxPrivKey ,
recipient . Jid , * recipient . OxKeyRing , message , * flagSubject )
recipient . Jid , recipient . OxKeyRing , message )
if err != nil {
fmt . Printf ( "Ox: couldn't encrypt to %s: %v\n" ,
recipient . Jid , err )
failure = err
fmt . Println ( "Ox: couldn't encrypt to" ,
recipient . Jid )
continue
}
_ , err = client . SendOrg ( oxMessage )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
default :
_ , err = client . Send ( xmpp . Chat {
Remote : recipient . Jid ,
Type : msgType , Text : message ,
Subject : * flagSubject ,
} )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
}
}
}
case * flagListen :
tz := time . Now ( ) . Location ( )
// Quit if ^C is pressed.
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
for range c {
cancel ( )
if failure != nil {
closeAndExit ( client , failure )
}
closeAndExit ( client , nil )
}
} ( )
for {
v := <- msgc
switch {
@ -709,7 +577,6 @@ func main() {
_ , err = client . Send ( xmpp . Chat {
Remote : recipient . Jid ,
Type : msgType , Ooburl : message , Text : message ,
Subject : * flagSubject ,
} )
if err != nil {
fmt . Println ( "Couldn't send message to" ,
@ -718,15 +585,9 @@ func main() {
// (Hopefully) temporary workaround due to go-xmpp choking on URL encoding.
// Once this is fixed in the lib the http-upload case above can be reused.
case * flagOOBFile != "" :
var msg string
if * flagSubject != "" {
msg = fmt . Sprintf ( "<message to='%s' type='%s'><subject>%s</subject><body>%s</body><x xmlns='jabber:x:oob'><url>%s</url></x></message>" ,
recipient . Jid , msgType , * flagSubject , message , message )
} else {
msg = fmt . Sprintf ( "<message to='%s' type='%s'><body>%s</body><x xmlns='jabber:x:oob'><url>%s</url></x></message>" ,
recipient . Jid , msgType , message , message )
}
_ , err = client . SendOrg ( msg )
_ , err = client . SendOrg ( "<message to='" + recipient . Jid + "' type='" +
msgType + "'><body>" + message + "</body><x xmlns='jabber:x:oob'><url>" +
message + "</url></x></message>" )
if err != nil {
fmt . Println ( "Couldn't send message to" ,
recipient . Jid )
@ -736,34 +597,25 @@ func main() {
continue
}
oxMessage , err := oxEncrypt ( client , oxPrivKey ,
recipient . Jid , * recipient . OxKeyRing , message , * flagSubject )
recipient . Jid , recipient . OxKeyRing , message )
if err != nil {
fmt . Printf ( "Ox: couldn't encrypt to %s: %v\n" ,
recipient . Jid , err )
failure = err
fmt . Println ( "Ox: couldn't encrypt to" , recipient . Jid )
continue
}
_ , err = client . SendOrg ( oxMessage )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
default :
_ , err = client . Send ( xmpp . Chat {
Remote : recipient . Jid ,
Type : msgType , Text : message ,
Subject : * flagSubject ,
} )
if err != nil {
cancel ( )
closeAndExit ( client , err )
closeAndExit ( client , cancel , err )
}
}
}
}
cancel ( )
if failure != nil {
closeAndExit ( client , failure )
}
closeAndExit ( client , nil )
closeAndExit ( client , cancel , nil )
}