/* Copyright 2018 Martin Dosch Licensed under the "MIT License" */ package main import ( "bufio" "errors" "io" "log" "os" "os/user" "strconv" "strings" "time" "github.com/mattn/go-xmpp" "github.com/pborman/getopt/v2" "mellium.im/xmpp/jid" ) type configuration struct { username string jserver string port string password string } // Check that JIDs include localpart and serverpart // and return it marshalled. func marshalJID(input string) (string, error) { parsedJid, err := jid.Parse(input) if err != nil { return input, err } if parsedJid.Localpart() == "" || parsedJid.Domainpart() == "" { return input, errors.New("Invalid JID: " + input) } return parsedJid.String(), err } // 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 ~/.sendxmpprc if no config path is specified. if configPath == "" { // Get systems user config path. osConfigDir := os.Getenv("$XDG_CONFIG_HOME") if osConfigDir != "" { configPath = osConfigDir + "/.sendxmpprc" } else { // Get the current user. curUser, err := user.Current() if err != nil { return output, err } // Get home directory. home := curUser.HomeDir if home == "" { return output, errors.New("No home directory found.") } configPath = home + "/.sendxmpprc" } } // Check that config file is existing. info, err := os.Stat(configPath) if os.IsNotExist(err) { return output, err } // Check for file permissions. Must be 600 or 400. perm := info.Mode().Perm() permissions := strconv.FormatInt(int64(perm), 8) if permissions != "600" && permissions != "400" { return output, errors.New("Wrong permissions for " + configPath + ": " + permissions + " instead of 600.") } // Open config file. file, err := os.Open(configPath) if err != nil { return output, err } defer file.Close() scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) // Read config file per line. for scanner.Scan() { if strings.HasPrefix(scanner.Text(), "#") == true { continue } row := strings.Split(scanner.Text(), " ") switch row[0] { case "username:": output.username = row[1] case "jserver:": output.jserver = row[1] case "password:": output.password = row[1] case "port:": output.port = row[1] default: if len(row) >= 2 { if strings.Contains(scanner.Text(), ";") == true { 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] output.jserver = strings.Split(row[0], "@")[1] output.password = row[1] } } } } return output, err } func readMessage(messageFilePath string) (string, error) { var ( output string err error ) // Check that message file is existing. _, err = os.Stat(messageFilePath) if os.IsNotExist(err) { return output, err } // Open message file. file, err := os.Open(messageFilePath) if err != nil { return output, err } defer file.Close() 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 "", err } } return output, err } func main() { var ( err error message string ) // Define command line flags. flagHelp := getopt.BoolLong("help", 0, "Show help.") 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.") flagTLS := getopt.BoolLong("tls", 't', "Use TLS.") flagStartTLS := getopt.BoolLong("start-tls", 'x', "Use StartTLS.") flagResource := getopt.StringLong("resource", 'r', "go-sendxmpp", "Set resource. "+ "When sending to a chatroom this is used as 'alias'. (Default: go-sendxmpp)") flagFile := getopt.StringLong("file", 'f', "", "Set configuration file. (Default: ~/.sendxmpprc)") flagMessageFile := getopt.StringLong("message", 'm', "", "Set file including the message.") // Parse command line flags. getopt.Parse() // If requested, show help and quit. if *flagHelp { getopt.Usage() os.Exit(0) } // Read recipients from command line and quit if none are specified. recipients := getopt.Args() if len(recipients) == 0 { log.Fatal("No recipient specified.") } // Quit if unreasonable TLS setting is set. if *flagStartTLS == true && *flagTLS == true { log.Fatal("Use either TLS or StartTLS.") } // Check that all recipient JIDs are valid. for i, recipient := range recipients { validatedJid, err := marshalJID(recipient) if err != nil { log.Fatal(err) } recipients[i] = validatedJid } // Read configuration from file. config, err := parseConfig(*flagFile) if err != nil { log.Println("Error parsing ", *flagFile, ": ", err) } // Set connection options according to config. user := config.username server := config.jserver password := config.password if config.port != "" { server = server + ":" + 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 } // Set XMPP connection options. options := xmpp.Options{ Host: server, User: user, Resource: *flagResource, Password: password, NoTLS: !*flagTLS, StartTLS: *flagStartTLS, Debug: *flagDebug, } // Read message from file. if *flagMessageFile != "" { message, err = readMessage(*flagMessageFile) if err != nil { log.Fatal(err) } } // Connect to server. client, err := options.NewClient() if err != nil { log.Fatal(err) } if 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) } } } // Send message to chatroom(s) if the flag is set. if *flagChatroom { for _, recipient := range recipients { // Join the MUC. mucStatus, err := client.JoinMUCNoHistory(recipient, *flagResource) if err != nil { log.Fatal(err) } // Exit if Status is > 300, see https://xmpp.org/registrar/mucstatus.html if mucStatus > 300 { log.Fatal("Couldn't join MUC. Status:", mucStatus) } // Send the message. _, err = client.Send(xmpp.Chat{Remote: recipient, Type: "groupchat", Text: message}) if err != nil { log.Fatal(err) } // After sending the message, leave the Muc _, err = client.LeaveMUC(recipient) if err != nil { log.Println(err) } } } else { for _, recipient := range recipients { // If the chatroom flag is not set, send message to contact(s). _, err = client.Send(xmpp.Chat{Remote: recipient, Type: "chat", Text: message}) if err != nil { log.Fatal(err) } } } time.Sleep(1 * time.Second) }