11 Commits

Author SHA1 Message Date
Mickael Remond
8794ea6ed8 Check if we have a connection open before trying to close it
Fixes #22
2019-09-06 10:28:49 +02:00
Martin/Geno
7e596fc33c quick fix for component see #47 2019-09-06 09:27:38 +02:00
Martin/Geno
6f9808fe16 fix(doc): typos 2019-09-05 10:54:28 +02:00
Wichert Akkerman
7b1f83f6b7 Expose Route.AddMatcher
This fixes #102
2019-09-03 14:38:34 +02:00
Martin/Geno
6005a964ba fix stream management for component + add test 2019-09-02 11:50:29 +02:00
Mickael Remond
d3b45b42a5 Fix typo 2019-08-06 18:27:51 +02:00
Martin/Geno
6e65ba2a0b fix naming from fluxxmpp to fluuxmpp 2019-08-06 18:06:34 +02:00
Martin/Geno
76f59be5ed merge commandline tools 2019-08-06 18:06:34 +02:00
Mickael Remond
80d2e0fa1e Remove debug print-out 2019-08-06 16:02:33 +02:00
Mickael Remond
2e864ff7f6 Clean-up: remove test/debug code 2019-08-06 16:02:33 +02:00
Mickael Remond
3de99e0e0e Add initial support for stream management
For now it support enabling SM, replying to ack requests from server,
and trying resuming the session with existing Stream Management state.
2019-08-06 16:02:33 +02:00
21 changed files with 297 additions and 287 deletions

View File

@@ -189,7 +189,10 @@ func (c *Client) Resume(state SMState) error {
func (c *Client) Disconnect() {
_ = c.SendRaw("</stream:stream>")
// TODO: Add a way to wait for stream close acknowledgement from the server for clean disconnect
_ = c.conn.Close()
conn := c.conn
if conn != nil {
_ = conn.Close()
}
}
func (c *Client) SetHandler(handler EventHandler) {

198
cmd/fluuxmpp/README.md Normal file
View File

@@ -0,0 +1,198 @@
# fluuxmpp
fluuxIO's XMPP command-line tool
## Installation
To install `fluuxmpp` in your Go path:
```
$ go get -u gosrc.io/xmpp/cmd/fluuxmpp
```
## Usage
```
$ fluuxmpp --help
fluuxIO's xmpp comandline tool
Usage:
fluuxmpp [command]
Available Commands:
check is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires
help Help about any command
send is a command-line tool to send to send XMPP messages to users
Flags:
-h, --help help for fluuxmpp
Use "fluuxmpp [command] --help" for more information about a command.
```
### check tls
```
$ fluuxmpp check --help
is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires
Usage:
fluuxmpp check <host[:port]> [flags]
Examples:
fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de
Flags:
-d, --domain string domain if host handle multiple domains
-h, --help help for check
```
### sending messages
```
$ fluuxmpp send --help
is a command-line tool to send to send XMPP messages to users
Usage:
fluuxmpp send <recipient,> [message] [flags]
Examples:
fluuxmpp send to@chat.sum7.eu "Hello World!"
Flags:
--addr string host[:port]
--config string config file (default is ~/.config/fluuxmpp.yml)
-h, --help help for send
--jid string using jid (required)
-m, --muc recipient is a muc (join it before sending messages)
--password string using password for your jid (required)
```
## Examples
### check tls
If you server is on standard port and XMPP domains matches the hostname you can simply use:
```
$ fluuxmpp check chat.sum7.eu
info All checks passed
⇢ address="chat.sum7.eu" domain=""
⇢ main.go:43 main.runCheck
⇢ 2019-07-16T22:01:39.765+02:00
```
You can also pass the port and the XMPP domain if different from the server hostname:
```
$ fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de
info All checks passed
⇢ address="chat.sum7.eu:5222" domain="meckerspace.de"
⇢ main.go:43 main.runCheck
⇢ 2019-07-16T22:01:33.270+02:00
```
Error code will be non-zero in case of error. You can thus use it directly with your usual
monitoring scripts.
### sending messages
Message from arguments:
```bash
$ fluuxmpp send to@example.org "Hello World!"
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:42:43.310+02:00
info send message
muc=false text="Hello World!" to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:42:43.310+02:00
```
Message from STDIN:
```bash
$ journalctl -f | fluuxmpp send to@example.org -
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:40:03.177+02:00
info send message
muc=false text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:40:03.178+02:00
info send message
muc=false text="Jul 17 23:36:46 RECHNERNAME systemd[755]: Started Fetch mails." to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:40:03.178+02:00
^C
```
Multiple recipients:
```bash
$ fluuxmpp send to1@example.org,to2@example.org "Multiple recipient"
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:47:57.650+02:00
info send message
muc=false text="Multiple recipient" to="to1@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:47:57.651+02:00
info send message
muc=false text="Multiple recipient" to="to2@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:47:57.652+02:00
```
Send to MUC:
```bash
journalctl -f | fluuxmpp send testit@conference.chat.sum7.eu - --muc
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:52:56.269+02:00
info send message
muc=true text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="testit@conference.chat.sum7.eu"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:52:56.270+02:00
info send message
muc=true text="Jul 17 23:48:58 RECHNERNAME systemd[755]: mail.service: Succeeded." to="testit@conference.chat.sum7.eu"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:52:56.277+02:00
^C
```
## Authentification
### Configuration file
In `/etc/`, `~/.config` and `.` (here).
You could create the file name `fluuxmpp` with you favorite file extension (e.g. `toml`, `yml`).
e.g. ~/.config/fluuxmpp.toml
```toml
jid = "bot@example.org"
password = "secret"
addr = "example.com:5222"
```
### Environment variables
```bash
export FLUXXMPP_JID='bot@example.org';
export FLUXXMPP_PASSWORD='secret';
export FLUXXMPP_ADDR='example.com:5222';
fluuxmpp send to@example.org "Hello Welt";
```
### Parameters
Warning: This should not be used for production systems, as all users on the system
can read the running processes, and their parameters (and thus the password).
```bash
fluuxmpp send to@example.org "Hello World!" --jid bot@example.org --password secret --addr example.com:5222;
```

View File

@@ -1,11 +1,18 @@
# TODO
## Issues
## check
### Features
- Use a config file to define the checks to perform as client on an XMPP server.
## send
### Issues
- Remove global variable (like mucToleave)
- Does not report error when trying to connect to a non open port (for example localhost with no server running).
## Features
### Features
- configuration
- allow unencrypted

View File

@@ -6,15 +6,11 @@ import (
"gosrc.io/xmpp"
)
func main() {
log.AddHook(&hook{})
cmd.Execute()
}
var domain = ""
var cmd = &cobra.Command{
Use: "xmpp-check <host[:port]>",
Example: "xmpp-check chat.sum7.eu:5222 --domain meckerspace.de",
var cmdCheck = &cobra.Command{
Use: "check <host[:port]>",
Short: "is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires",
Example: "fluuxmpp check chat.sum7.eu:5222 --domain meckerspace.de",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
runCheck(args[0], domain)
@@ -22,7 +18,8 @@ var cmd = &cobra.Command{
}
func init() {
cmd.Flags().StringVarP(&domain, "domain", "d", "", "domain if host handle multiple domains")
cmdRoot.AddCommand(cmdCheck)
cmdCheck.Flags().StringVarP(&domain, "domain", "d", "", "domain if host handle multiple domains")
}
func runCheck(address, domain string) {

5
cmd/fluuxmpp/doc.go Normal file
View File

@@ -0,0 +1,5 @@
/*
fluuxmpp: fluuxIO's xmpp comandline tool
*/
package main

19
cmd/fluuxmpp/main.go Normal file
View File

@@ -0,0 +1,19 @@
package main
import (
"github.com/bdlm/log"
"github.com/spf13/cobra"
)
// cmdRoot represents the base command when called without any subcommands
var cmdRoot = &cobra.Command{
Use: "fluuxmpp",
Short: "fluuxIO's xmpp comandline tool",
}
func main() {
log.AddHook(&hook{})
if err := cmdRoot.Execute(); err != nil {
log.Fatal(err)
}
}

View File

@@ -18,9 +18,10 @@ var configFile = ""
// FIXME: Remove global variables
var isMUCRecipient = false
var cmd = &cobra.Command{
Use: "sendxmpp <recipient,> [message]",
Example: `sendxmpp to@chat.sum7.eu "Hello World!"`,
var cmdSend = &cobra.Command{
Use: "send <recipient,> [message]",
Short: "is a command-line tool to send to send XMPP messages to users",
Example: `fluuxmpp send to@chat.sum7.eu "Hello World!"`,
Args: cobra.ExactArgs(2),
Run: sendxmpp,
}
@@ -95,28 +96,30 @@ func sendxmpp(cmd *cobra.Command, args []string) {
}
func init() {
cobra.OnInitialize(initConfig)
cmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ~/.config/fluxxmpp.yml)")
cmdRoot.AddCommand(cmdSend)
cmd.Flags().StringP("jid", "", "", "using jid (required)")
viper.BindPFlag("jid", cmd.Flags().Lookup("jid"))
cobra.OnInitialize(initConfigFile)
cmdSend.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is ~/.config/fluuxmpp.yml)")
cmd.Flags().StringP("password", "", "", "using password for your jid (required)")
viper.BindPFlag("password", cmd.Flags().Lookup("password"))
cmdSend.Flags().StringP("jid", "", "", "using jid (required)")
viper.BindPFlag("jid", cmdSend.Flags().Lookup("jid"))
cmd.Flags().StringP("addr", "", "", "host[:port]")
viper.BindPFlag("addr", cmd.Flags().Lookup("addr"))
cmdSend.Flags().StringP("password", "", "", "using password for your jid (required)")
viper.BindPFlag("password", cmdSend.Flags().Lookup("password"))
cmd.Flags().BoolVarP(&isMUCRecipient, "muc", "m", false, "recipient is a muc (join it before sending messages)")
cmdSend.Flags().StringP("addr", "", "", "host[:port]")
viper.BindPFlag("addr", cmdSend.Flags().Lookup("addr"))
cmdSend.Flags().BoolVarP(&isMUCRecipient, "muc", "m", false, "recipient is a muc (join it before sending messages)")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
func initConfigFile() {
if configFile != "" {
viper.SetConfigFile(configFile)
}
viper.SetConfigName("fluxxmpp")
viper.SetConfigName("fluuxmpp")
viper.AddConfigPath("/etc/")
viper.AddConfigPath("$HOME/.config")
viper.AddConfigPath(".")

View File

@@ -1,131 +0,0 @@
# sendXMPP
sendxmpp is a tool to send messages from command-line.
## Installation
To install `sendxmpp` in your Go path:
```
$ go get -u gosrc.io/xmpp/cmd/sendxmpp
```
## Usage
```
$ sendxmpp --help
Usage:
sendxmpp <recipient,> [message] [flags]
Examples:
sendxmpp to@chat.sum7.eu "Hello World!"
Flags:
--addr string host[:port]
--config string config file (default is ~/.config/fluxxmpp.yml)
-h, --help help for sendxmpp
--jid string using jid (required)
-m, --muc recipient is a muc (join it before sending messages)
--password string using password for your jid (required)
```
## Examples
Message from arguments:
```bash
$ sendxmpp to@example.org "Hello World!"
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:42:43.310+02:00
info send message
muc=false text="Hello World!" to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:42:43.310+02:00
```
Message from STDIN:
```bash
$ journalctl -f | sendxmpp to@example.org -
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:40:03.177+02:00
info send message
muc=false text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:40:03.178+02:00
info send message
muc=false text="Jul 17 23:36:46 RECHNERNAME systemd[755]: Started Fetch mails." to="to@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:40:03.178+02:00
^C
```
Multiple recipients:
```bash
$ sendxmpp to1@example.org,to2@example.org "Multiple recipient"
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:47:57.650+02:00
info send message
muc=false text="Multiple recipient" to="to1@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:47:57.651+02:00
info send message
muc=false text="Multiple recipient" to="to2@example.org"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:47:57.652+02:00
```
Send to MUC:
```bash
journalctl -f | sendxmpp testit@conference.chat.sum7.eu - --muc
info client connected
⇢ cmd.go:56 main.glob..func1.1
⇢ 2019-07-17T23:52:56.269+02:00
info send message
muc=true text="-- Logs begin at Mon 2019-07-08 22:16:54 CEST. --" to="testit@conference.chat.sum7.eu"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:52:56.270+02:00
info send message
muc=true text="Jul 17 23:48:58 RECHNERNAME systemd[755]: mail.service: Succeeded." to="testit@conference.chat.sum7.eu"
⇢ send.go:31 main.send
⇢ 2019-07-17T23:52:56.277+02:00
^C
```
### Authentification
#### Configuration file
In `/etc/`, `~/.config` and `.` (here).
You could create the file name `fluxxmpp` with you favorite file extenion (e.g. `toml`, `yml`).
e.g. ~/.config/fluxxmpp.toml
```toml
jid = "bot@example.org"
password = "secret"
addr = "example.com:5222"
```
#### Environment variables
```bash
export FLUXXMPP_JID='bot@example.org';
export FLUXXMPP_PASSWORD='secret';
export FLUXXMPP_ADDR='example.com:5222';
sendxmpp to@example.org "Hello Welt";
```
#### Parameters
Warning: This should not be used for production systems, as all users on the system
can read the running processes, and their parameters (and thus the password).
```bash
sendxmpp to@example.org "Hello World!" --jid bot@example.org --password secret --addr example.com:5222;
```

View File

@@ -1,6 +0,0 @@
/*
sendxmpp is a command-line tool to send to send XMPP messages to users
*/
package main

View File

@@ -1,12 +0,0 @@
package main
import (
"github.com/bdlm/log"
)
func main() {
log.AddHook(&hook{})
if err := cmd.Execute(); err != nil {
log.Fatal(err)
}
}

View File

@@ -1,49 +0,0 @@
# XMPP Check
XMPP check is a tool to check TLS certificate on a remote server.
## Installation
To install `xmpp-check` in your Go path:
```
$ go get -u gosrc.io/xmpp/cmd/xmpp-check
```
## Usage
```
$ xmpp-check --help
Usage:
xmpp-check <host[:port]> [flags]
Examples:
xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
Flags:
-d, --domain string domain if host handle multiple domains
-h, --help help for xmpp-check
```
If you server is on standard port and XMPP domains matches the hostname you can simply use:
```
$ xmpp-check chat.sum7.eu
info All checks passed
⇢ address="chat.sum7.eu" domain=""
⇢ main.go:43 main.runCheck
⇢ 2019-07-16T22:01:39.765+02:00
```
You can also pass the port and the XMPP domain if different from the server hostname:
```
$ xmpp-check chat.sum7.eu:5222 --domain meckerspace.de
info All checks passed
⇢ address="chat.sum7.eu:5222" domain="meckerspace.de"
⇢ main.go:43 main.runCheck
⇢ 2019-07-16T22:01:33.270+02:00
```
Error code will be non-zero in case of error. You can thus use it directly with your usual
monitoring scripts.

View File

@@ -1,3 +0,0 @@
# TODO
- Use a config file to define the checks to perform as client on an XMPP server.

View File

@@ -1,6 +0,0 @@
/*
xmpp-check is a command-line to check if you XMPP TLS certificate is valid and warn you before it expires.
*/
package main

View File

@@ -1,34 +0,0 @@
package main
import (
"os"
"github.com/bdlm/log"
stdLogger "github.com/bdlm/std/logger"
)
type hook struct{}
func (h *hook) Fire(entry *log.Entry) error {
switch entry.Level {
case log.PanicLevel:
entry.Logger.Out = os.Stderr
case log.FatalLevel:
entry.Logger.Out = os.Stderr
case log.ErrorLevel:
entry.Logger.Out = os.Stderr
case log.WarnLevel:
entry.Logger.Out = os.Stdout
case log.InfoLevel:
entry.Logger.Out = os.Stdout
case log.DebugLevel:
entry.Logger.Out = os.Stdout
default:
}
return nil
}
func (h *hook) Levels() []stdLogger.Level {
return log.AllLevels
}

View File

@@ -66,56 +66,67 @@ func NewComponent(opts ComponentOptions, r *Router) (*Component, error) {
// Connect triggers component connection to XMPP server component port.
// TODO: Failed handshake should be a permanent error
func (c *Component) Connect() error {
var state SMState
return c.Resume(state)
}
func (c *Component) Resume(sm SMState) error {
var conn net.Conn
var err error
if conn, err = net.DialTimeout("tcp", c.Address, time.Duration(5)*time.Second); err != nil {
return err
}
c.conn = conn
c.updateState(StateConnected)
// 1. Send stream open tag
if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, stanza.NSComponent, stanza.NSStream); err != nil {
return errors.New("cannot send stream open " + err.Error())
c.updateState(StateStreamError)
return NewConnError(errors.New("cannot send stream open "+err.Error()), false)
}
c.decoder = xml.NewDecoder(conn)
// 2. Initialize xml decoder and extract streamID from reply
streamId, err := stanza.InitStream(c.decoder)
if err != nil {
return errors.New("cannot init decoder " + err.Error())
c.updateState(StateStreamError)
return NewConnError(errors.New("cannot init decoder "+err.Error()), false)
}
// 3. Authentication
if _, err := fmt.Fprintf(conn, "<handshake>%s</handshake>", c.handshake(streamId)); err != nil {
return errors.New("cannot send handshake " + err.Error())
c.updateState(StateStreamError)
return NewConnError(errors.New("cannot send handshake "+err.Error()), false)
}
// 4. Check server response for authentication
val, err := stanza.NextPacket(c.decoder)
if err != nil {
return err
c.updateState(StateDisconnected)
return NewConnError(err, true)
}
switch v := val.(type) {
case stanza.StreamError:
return errors.New("handshake failed " + v.Error.Local)
c.streamError("conflict", "no auth loop")
return NewConnError(errors.New("handshake failed "+v.Error.Local), true)
case stanza.Handshake:
// Start the receiver go routine
c.updateState(StateSessionEstablished)
go c.recv()
return nil
default:
return errors.New("expecting handshake result, got " + v.Name())
c.updateState(StateStreamError)
return NewConnError(errors.New("expecting handshake result, got "+v.Name()), true)
}
}
func (c *Component) Resume() error {
return errors.New("components do not support stream management")
}
func (c *Component) Disconnect() {
_ = c.SendRaw("</stream:stream>")
// TODO: Add a way to wait for stream close acknowledgement from the server for clean disconnect
_ = c.conn.Close()
conn := c.conn
if conn != nil {
_ = conn.Close()
}
}
func (c *Component) SetHandler(handler EventHandler) {

View File

@@ -23,3 +23,10 @@ func TestHandshake(t *testing.T) {
func TestGenerateHandshake(t *testing.T) {
// TODO
}
// Test that NewStreamManager can accept a Component.
//
// This validates that Component conforms to StreamClient interface.
func TestStreamManager(t *testing.T) {
NewStreamManager(&Component{}, nil)
}

View File

@@ -98,7 +98,7 @@ type Handler interface {
type Route struct {
handler Handler
// Matchers are used to "specialize" routes and focus on specific packet features
matchers []matcher
matchers []Matcher
}
func (r *Route) Handler(handler Handler) *Route {
@@ -122,8 +122,8 @@ func (r *Route) HandlerFunc(f HandlerFunc) *Route {
return r.Handler(f)
}
// addMatcher adds a matcher to the route
func (r *Route) addMatcher(m matcher) *Route {
// AddMatcher adds a matcher to the route
func (r *Route) AddMatcher(m Matcher) *Route {
r.matchers = append(r.matchers, m)
return r
}
@@ -170,7 +170,7 @@ func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
// It matches on the Local part of the xml.Name
func (r *Route) Packet(name string) *Route {
name = strings.ToLower(name)
return r.addMatcher(nameMatcher(name))
return r.AddMatcher(nameMatcher(name))
}
// -------------------------
@@ -204,7 +204,7 @@ func (r *Route) StanzaType(types ...string) *Route {
for k, v := range types {
types[k] = strings.ToLower(v)
}
return r.addMatcher(nsTypeMatcher(types))
return r.AddMatcher(nsTypeMatcher(types))
}
// -------------------------
@@ -229,14 +229,15 @@ func (r *Route) IQNamespaces(namespaces ...string) *Route {
for k, v := range namespaces {
namespaces[k] = strings.ToLower(v)
}
return r.addMatcher(nsIQMatcher(namespaces))
return r.AddMatcher(nsIQMatcher(namespaces))
}
// ============================================================================
// Matchers
// Matchers are used to "specialize" routes and focus on specific packet features
type matcher interface {
// Matchers are used to "specialize" routes and focus on specific packet features.
// You can register attach them to a route via the AddMatcher method.
type Matcher interface {
Match(stanza.Packet, *RouteMatch) bool
}

View File

@@ -152,7 +152,7 @@ type Metrics struct {
ConnectTime time.Duration
// LoginTime returns the between client initiation of the TCP/IP
// connection to the server and the return of the login result.
// This includes ConnectTime, but also XMPP level protocol negociation
// This includes ConnectTime, but also XMPP level protocol negotiation
// like starttls.
LoginTime time.Duration
}