2018-02-07 19:32:11 +01:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/xml"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"strings"
|
2018-02-11 09:07:07 +01:00
|
|
|
"time"
|
2018-02-07 19:32:11 +01:00
|
|
|
|
|
|
|
"dev.sum7.eu/genofire/yaja/messages"
|
|
|
|
"dev.sum7.eu/genofire/yaja/model"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client holds XMPP connection opitons
|
|
|
|
type Client struct {
|
|
|
|
conn net.Conn // connection to server
|
|
|
|
Out *xml.Encoder
|
|
|
|
In *xml.Decoder
|
|
|
|
|
|
|
|
JID *model.JID
|
|
|
|
}
|
|
|
|
|
2018-02-10 13:34:42 +01:00
|
|
|
func NewClient(jid *model.JID, password string) (*Client, error) {
|
2018-02-11 19:35:32 +01:00
|
|
|
return NewClientProtocolDuration(jid, password, "tcp", 0)
|
2018-02-11 09:07:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewClientProtocolDuration(jid *model.JID, password string, proto string, timeout time.Duration) (*Client, error) {
|
2018-02-10 13:34:42 +01:00
|
|
|
_, srvEntries, err := net.LookupSRV("xmpp-client", "tcp", jid.Domain)
|
|
|
|
addr := jid.Domain + ":5222"
|
|
|
|
if err == nil && len(srvEntries) > 0 {
|
|
|
|
bestSrv := srvEntries[0]
|
|
|
|
for _, srv := range srvEntries {
|
|
|
|
if srv.Priority <= bestSrv.Priority && srv.Weight >= bestSrv.Weight {
|
|
|
|
bestSrv = srv
|
|
|
|
addr = fmt.Sprintf("%s:%d", srv.Target, srv.Port)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a := strings.SplitN(addr, ":", 2)
|
|
|
|
if len(a) == 1 {
|
|
|
|
addr += ":5222"
|
|
|
|
}
|
2018-02-11 19:35:32 +01:00
|
|
|
conn, err := net.DialTimeout(proto, addr, timeout)
|
2018-02-07 19:32:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
client := &Client{
|
|
|
|
conn: conn,
|
|
|
|
In: xml.NewDecoder(conn),
|
|
|
|
Out: xml.NewEncoder(conn),
|
|
|
|
|
2018-02-10 13:34:42 +01:00
|
|
|
JID: jid,
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err = client.connect(password); err != nil {
|
|
|
|
client.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the XMPP connection
|
|
|
|
func (c *Client) Close() error {
|
|
|
|
if c.conn != (*tls.Conn)(nil) {
|
|
|
|
return c.conn.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-10 13:34:42 +01:00
|
|
|
func (client *Client) startStream() (*messages.StreamFeatures, error) {
|
2018-02-07 19:32:11 +01:00
|
|
|
// XMPP-Connection
|
|
|
|
_, err := fmt.Fprintf(client.conn, "<?xml version='1.0'?>\n"+
|
|
|
|
"<stream:stream to='%s' xmlns='%s'\n"+
|
|
|
|
" xmlns:stream='%s' version='1.0'>\n",
|
|
|
|
model.XMLEscape(client.JID.Domain), messages.NSClient, messages.NSStream)
|
|
|
|
if err != nil {
|
2018-02-10 13:34:42 +01:00
|
|
|
return nil, err
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
|
|
|
element, err := client.Read()
|
|
|
|
if err != nil {
|
2018-02-10 13:34:42 +01:00
|
|
|
return nil, err
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
|
|
|
if element.Name.Space != messages.NSStream || element.Name.Local != "stream" {
|
2018-02-10 13:34:42 +01:00
|
|
|
return nil, errors.New("is not stream")
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
2018-02-10 13:34:42 +01:00
|
|
|
f := &messages.StreamFeatures{}
|
|
|
|
if err := client.ReadElement(f); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return f, nil
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
2018-02-10 13:34:42 +01:00
|
|
|
|
2018-02-07 19:32:11 +01:00
|
|
|
func (client *Client) connect(password string) error {
|
2018-02-10 13:34:42 +01:00
|
|
|
if _, err := client.startStream(); err != nil {
|
2018-02-07 19:32:11 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := client.Out.Encode(&messages.TLSStartTLS{}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var p messages.TLSProceed
|
|
|
|
if err := client.ReadElement(&p); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Change tcp to tls
|
|
|
|
tlsconn := tls.Client(client.conn, &tls.Config{
|
|
|
|
ServerName: client.JID.Domain,
|
|
|
|
})
|
|
|
|
client.conn = tlsconn
|
|
|
|
client.In = xml.NewDecoder(client.conn)
|
|
|
|
client.Out = xml.NewEncoder(client.conn)
|
|
|
|
|
|
|
|
if err := tlsconn.Handshake(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := tlsconn.VerifyHostname(client.JID.Domain); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-11 19:35:32 +01:00
|
|
|
if err := client.auth(password); err != nil {
|
2018-02-07 19:32:11 +01:00
|
|
|
return err
|
|
|
|
}
|
2018-02-11 19:35:32 +01:00
|
|
|
|
|
|
|
if _, err := client.startStream(); err != nil {
|
2018-02-07 19:32:11 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// bind to resource
|
2018-02-10 13:34:42 +01:00
|
|
|
bind := &messages.Bind{}
|
|
|
|
if client.JID.Resource != "" {
|
|
|
|
bind.Resource = client.JID.Resource
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
2018-02-11 19:35:32 +01:00
|
|
|
if err := client.Out.Encode(&messages.IQClient{
|
2018-02-07 19:32:11 +01:00
|
|
|
Type: messages.IQTypeSet,
|
2018-02-10 13:34:42 +01:00
|
|
|
From: client.JID,
|
2018-02-11 19:35:32 +01:00
|
|
|
To: model.NewJID(client.JID.Domain),
|
2018-02-10 13:34:42 +01:00
|
|
|
Bind: bind,
|
2018-02-11 19:35:32 +01:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-07 19:32:11 +01:00
|
|
|
|
|
|
|
var iq messages.IQClient
|
|
|
|
if err := client.ReadElement(&iq); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-02-11 15:11:52 +01:00
|
|
|
if iq.Error != nil {
|
2018-02-11 19:35:32 +01:00
|
|
|
if iq.Error.ServiceUnavailable == nil {
|
|
|
|
return errors.New(fmt.Sprintf("recv error on iq>bind: %s[%s]: %s -> %s -> %s", iq.Error.Code, iq.Error.Type, iq.Error.Text, messages.XMLChildrenString(iq.Error.StanzaErrorGroup), messages.XMLChildrenString(iq.Error.Other)))
|
2018-02-11 15:11:52 +01:00
|
|
|
}
|
|
|
|
} else if iq.Bind == nil {
|
2018-02-07 19:32:11 +01:00
|
|
|
return errors.New("<iq> result missing <bind>")
|
2018-02-11 15:11:52 +01:00
|
|
|
} else if iq.Bind.JID != nil {
|
2018-02-07 19:32:11 +01:00
|
|
|
client.JID.Local = iq.Bind.JID.Local
|
|
|
|
client.JID.Domain = iq.Bind.JID.Domain
|
|
|
|
client.JID.Resource = iq.Bind.JID.Resource
|
|
|
|
} else {
|
2018-02-11 19:35:32 +01:00
|
|
|
return errors.New(messages.XMLChildrenString(iq.Other))
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|
|
|
|
// set status
|
2018-02-11 19:35:32 +01:00
|
|
|
return client.Send(&messages.PresenceClient{Show: messages.PresenceShowXA, Status: "online"})
|
2018-02-07 19:32:11 +01:00
|
|
|
}
|