forked from jshiffer/go-xmpp
commit
c0f9b41b3d
69
xmpp.go
69
xmpp.go
@ -38,11 +38,13 @@ const (
|
||||
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
|
||||
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
|
||||
nsClient = "jabber:client"
|
||||
NsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
||||
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
|
||||
)
|
||||
|
||||
// Default TLS configuration options
|
||||
var DefaultConfig tls.Config
|
||||
|
||||
// Cookie is a unique XMPP session identifier
|
||||
type Cookie uint64
|
||||
|
||||
func getCookie() Cookie {
|
||||
@ -53,6 +55,7 @@ func getCookie() Cookie {
|
||||
return Cookie(binary.LittleEndian.Uint64(buf[:]))
|
||||
}
|
||||
|
||||
// Client holds XMPP connection opitons
|
||||
type Client struct {
|
||||
conn net.Conn // connection to server
|
||||
jid string // Jabber ID for our connection
|
||||
@ -211,6 +214,7 @@ func NewClient(host, user, passwd string, debug bool) (*Client, error) {
|
||||
return opts.NewClient()
|
||||
}
|
||||
|
||||
// NewClientNoTLS creates a new client without TLS
|
||||
func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
|
||||
opts := Options{
|
||||
Host: host,
|
||||
@ -223,16 +227,15 @@ func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
|
||||
return opts.NewClient()
|
||||
}
|
||||
|
||||
// Close closes the XMPP connection
|
||||
func (c *Client) Close() error {
|
||||
if c.conn != (*tls.Conn)(nil) {
|
||||
return c.conn.Close()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr,
|
||||
authenticate, digestUri, nonceCountStr string) string {
|
||||
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
|
||||
h := func(text string) []byte {
|
||||
h := md5.New()
|
||||
h.Write([]byte(text))
|
||||
@ -245,12 +248,9 @@ func saslDigestResponse(username, realm, passwd, nonce, cnonceStr,
|
||||
return h(secret + ":" + data)
|
||||
}
|
||||
|
||||
a1 := string(h(username+":"+realm+":"+passwd)) + ":" +
|
||||
nonce + ":" + cnonceStr
|
||||
a2 := authenticate + ":" + digestUri
|
||||
response := hex(kd(hex(h(a1)), nonce+":"+
|
||||
nonceCountStr+":"+cnonceStr+":auth:"+
|
||||
hex(h(a2))))
|
||||
a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
|
||||
a2 := authenticate + ":" + digestURI
|
||||
response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
|
||||
return response
|
||||
}
|
||||
|
||||
@ -265,10 +265,11 @@ func cnonce() string {
|
||||
}
|
||||
|
||||
func (c *Client) init(o *Options) error {
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
// For debugging: the following causes the plaintext of the connection to be duplicated to stdout.
|
||||
if o.Debug {
|
||||
c.p = xml.NewDecoder(tee{c.conn, os.Stdout})
|
||||
// For debugging: the following causes the plaintext of the connection to be duplicated to stderr.
|
||||
c.p = xml.NewDecoder(tee{c.conn, os.Stderr})
|
||||
} else {
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
}
|
||||
|
||||
a := strings.SplitN(o.User, "@", 2)
|
||||
@ -284,7 +285,7 @@ func (c *Client) init(o *Options) error {
|
||||
}
|
||||
|
||||
// If the server requires we STARTTLS, attempt to do so.
|
||||
if f, err = c.startTlsIfRequired(f, o, domain); err != nil {
|
||||
if f, err = c.startTLSIfRequired(f, o, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -316,15 +317,13 @@ func (c *Client) init(o *Options) error {
|
||||
raw := "\x00" + user + "\x00" + o.Password
|
||||
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
||||
base64.StdEncoding.Encode(enc, []byte(raw))
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n",
|
||||
nsSASL, enc)
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc)
|
||||
break
|
||||
}
|
||||
if m == "DIGEST-MD5" {
|
||||
mechanism = m
|
||||
// Digest-MD5 authentication
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n",
|
||||
nsSASL)
|
||||
fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
|
||||
var ch saslChallenge
|
||||
if err = c.p.DecodeElement(&ch, nil); err != nil {
|
||||
return errors.New("unmarshal <challenge>: " + err.Error())
|
||||
@ -348,10 +347,11 @@ func (c *Client) init(o *Options) error {
|
||||
qop, _ := tokens["qop"]
|
||||
charset, _ := tokens["charset"]
|
||||
cnonceStr := cnonce()
|
||||
digestUri := "xmpp/" + domain
|
||||
digestURI := "xmpp/" + domain
|
||||
nonceCount := fmt.Sprintf("%08x", 1)
|
||||
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestUri, nonceCount)
|
||||
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr + "\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestUri + "\", response=" + digest + ", charset=" + charset
|
||||
digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
|
||||
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
|
||||
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
|
||||
|
||||
fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
|
||||
|
||||
@ -368,7 +368,7 @@ func (c *Client) init(o *Options) error {
|
||||
}
|
||||
}
|
||||
if mechanism == "" {
|
||||
return errors.New(fmt.Sprintf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism))
|
||||
return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
|
||||
}
|
||||
|
||||
// Next message should be either success or failure.
|
||||
@ -392,7 +392,7 @@ func (c *Client) init(o *Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//Generate uniq cookie
|
||||
// Generate a uniqe cookie
|
||||
cookie := getCookie()
|
||||
|
||||
// Send IQ message asking to bind to the local user name.
|
||||
@ -412,7 +412,7 @@ func (c *Client) init(o *Options) error {
|
||||
|
||||
if o.Session {
|
||||
//if server support session, open it
|
||||
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, NsSession)
|
||||
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
|
||||
}
|
||||
|
||||
// We're connected and can now receive and send messages.
|
||||
@ -423,7 +423,7 @@ func (c *Client) init(o *Options) error {
|
||||
|
||||
// startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake.
|
||||
// f will be updated if the handshake completes, as the new stream's features are typically different from the original.
|
||||
func (c *Client) startTlsIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
|
||||
func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
|
||||
// whether we start tls is a matter of opinion: the server's and the user's.
|
||||
switch {
|
||||
case f.StartTLS == nil:
|
||||
@ -465,12 +465,13 @@ func (c *Client) startTlsIfRequired(f *streamFeatures, o *Options, domain string
|
||||
}
|
||||
|
||||
// startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has
|
||||
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stdout. The features advertised by the server
|
||||
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr. The features advertised by the server
|
||||
// will be returned.
|
||||
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
if o.Debug {
|
||||
c.p = xml.NewDecoder(tee{c.conn, os.Stdout})
|
||||
c.p = xml.NewDecoder(tee{c.conn, os.Stderr})
|
||||
} else {
|
||||
c.p = xml.NewDecoder(c.conn)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+
|
||||
@ -508,6 +509,7 @@ func (c *Client) IsEncrypted() bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Chat is an incoming or outgoing XMPP chat message
|
||||
type Chat struct {
|
||||
Remote string
|
||||
Type string
|
||||
@ -515,6 +517,7 @@ type Chat struct {
|
||||
Other []string
|
||||
}
|
||||
|
||||
// Presence is an XMPP presence message
|
||||
type Presence struct {
|
||||
From string
|
||||
To string
|
||||
@ -546,7 +549,7 @@ func (c *Client) Send(chat Chat) (n int, err error) {
|
||||
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
||||
}
|
||||
|
||||
// Send origin
|
||||
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
|
||||
func (c *Client) SendOrg(org string) (n int, err error) {
|
||||
return fmt.Fprint(c.conn, org)
|
||||
}
|
||||
@ -625,7 +628,7 @@ type bindBind struct {
|
||||
type clientMessage struct {
|
||||
XMLName xml.Name `xml:"jabber:client message"`
|
||||
From string `xml:"from,attr"`
|
||||
Id string `xml:"id,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
To string `xml:"to,attr"`
|
||||
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal
|
||||
|
||||
@ -647,7 +650,7 @@ type clientText struct {
|
||||
type clientPresence struct {
|
||||
XMLName xml.Name `xml:"jabber:client presence"`
|
||||
From string `xml:"from,attr"`
|
||||
Id string `xml:"id,attr"`
|
||||
ID string `xml:"id,attr"`
|
||||
To string `xml:"to,attr"`
|
||||
Type string `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
|
||||
Lang string `xml:"lang,attr"`
|
||||
@ -661,7 +664,7 @@ type clientPresence struct {
|
||||
type clientIQ struct { // info/query
|
||||
XMLName xml.Name `xml:"jabber:client iq"`
|
||||
From string `xml:",attr"`
|
||||
Id string `xml:",attr"`
|
||||
ID string `xml:",attr"`
|
||||
To string `xml:",attr"`
|
||||
Type string `xml:",attr"` // error, get, result, set
|
||||
Error clientError
|
||||
|
Loading…
Reference in New Issue
Block a user