xmpp: mellium.im/xmpp Index | Examples | Files | Directories

package xmpp

import "mellium.im/xmpp"

Package xmpp provides functionality from the Extensible Messaging and Presence Protocol, sometimes known as "Jabber".

It is subdivided into several packages; this package provides functionality for establishing an XMPP session, feature negotiation (including an API for defining your own stream features), and low-level connection and stream manipulation. The jid package provides an implementation of the XMPP address format.

Session Negotiation

To create an XMPP session, most users will want to call DialClientSession or DialServerSession to create a client-to-server (c2s) or server-to-server (s2s) connection respectively. These methods use sane defaults to dial a TCP connection and perform stream negotiation.

session, err := xmpp.DialClientSession(
    context.TODO(), addr,
    xmpp.BindResource(),
    xmpp.StartTLS(true, nil),
    xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
)

If control over DNS or HTTP-based service discovery is desired, the user can create a Dialer or use DialClient (c2s) or DialServer (s2s). To use the resulting connection, or to use something other than a TCP connection (eg. to communicate over a Unix domain socket, an in-memory pipe, etc.) the connection can be passed to NewClientSession or NewServerSession.

conn, err := xmpp.DialClient(context.TODO(), "tcp", addr)
…
session, err := xmpp.NewClientSession(
    context.TODO(), addr, conn,
    xmpp.BindResource(),
    xmpp.StartTLS(true, nil),
    xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
)

If complete control over the session establishment process is needed the NegotiateSession function can be used to support custom session negotiation protocols or to customize options around the default negotiator by using the NewNegotiator function.

session, err := xmpp.NegotiateSession(
    context.TODO(), addr.Domain(), addr, conn,
    xmpp.NewNegotiator(xmpp.StreamConfig{
        Lang: "en",
        …
    }),
)

The default Negotiator and related functions use a list of StreamFeature's to negotiate the state of the session. Implementations of the most common features (StartTLS, SASL-based authentication, and resource binding) are provided. Custom stream features may be created using the StreamFeature struct. Stream features defined in this module are safe for concurrent use by multiple goroutines and for efficiency should only be created once and re-used.

Handling Stanzas

Unlike HTTP, the XMPP protocol is asynchronous, meaning that both clients and servers can accept and send requests at any time and responses are not required or may be received out of order. This is accomplished with two XML streams: an input stream and an output stream. To receive XML on the input stream, Session implements the xml.TokenReader interface defined in encoding/xml; this allows session to be wrapped with xml.NewTokenDecoder. To send XML on the output stream, Session has an EncodeToken method like the "mellium.im/xmlstream".TokenWriter interface. The session may also buffer writes and has a Flush method which will write any buffered XML to the underlying connection.

However, writing individual XML tokens can be tedious and error prone. The mellium.im/xmpp/stanza package contains functions and structs that aid in the construction of message, presence and IQ elements which have special semantics in XMPP and are known as "stanzas". These can be sent with the Send and SendElement methods.

// Send initial presence to let the server know we want to receive messages.
_, err = session.Send(stanza.WrapPresence(nil, stanza.AvailablePresence, nil))

For Send to correctly handle IQ responses, and to make the common case of polling for incoming XML on the input stream—and possibly writing to the output stream in response—easier, we need a long running goroutine. Session includes the Serve method for starting this processing. Serve provides a Handler with access to the stream but prevents it from advancing the stream beyond the current element and always advances the stream to the end of the element when the handler returns (even if the handler did not consume the entire element).

err := session.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadWriter, start *xml.StartElement) error {
    d := xml.NewTokenDecoder(t)

    // Ignore anything that's not a message.
    if start.Name.Local != "message" {
        return nil
    }

    msg := struct {
        stanza.Message
        Body string `xml:"body"`
    }{}
    err := d.DecodeElement(&msg, start)
    …
    if msg.Body != "" {
        log.Println("Got message: %q", msg.Body)
    }
}))

Be Advised

This API is unstable and subject to change.

Code:

package main

import (
    "context"
    "crypto/tls"
    "encoding/xml"
    "io"
    "log"

    "mellium.im/sasl"
    "mellium.im/xmlstream"
    "mellium.im/xmpp"
    "mellium.im/xmpp/jid"
    "mellium.im/xmpp/stanza"
)

const (
    login = "echo@example.net"
    pass  = "just an example don't hardcode passwords"
)

func main() {
    j := jid.MustParse(login)
    s, err := xmpp.DialClientSession(
        context.TODO(), j,
        xmpp.BindResource(),
        xmpp.StartTLS(true, &tls.Config{
            ServerName: j.Domain().String(),
        }),
        xmpp.SASL("", pass, sasl.ScramSha1Plus, sasl.ScramSha1, sasl.Plain),
    )
    if err != nil {
        log.Printf("Error establishing a session: %q", err)
        return
    }
    defer func() {
        log.Println("Closing session…")
        if err := s.Close(); err != nil {
            log.Printf("Error closing session: %q", err)
        }
        log.Println("Closing conn…")
        if err := s.Conn().Close(); err != nil {
            log.Printf("Error closing connection: %q", err)
        }
    }()

    // Send initial presence to let the server know we want to receive messages.
    _, err = s.Send(context.TODO(), stanza.WrapPresence(nil, stanza.AvailablePresence, nil))
    if err != nil {
        log.Printf("Error sending initial presence: %q", err)
        return
    }

    s.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadWriter, start *xml.StartElement) error {
        d := xml.NewTokenDecoder(t)

        // Ignore anything that's not a message. In a real system we'd want to at
        // least respond to IQs.
        if start.Name.Local != "message" {
            return nil
        }

        msg := struct {
            stanza.Message
            Body string `xml:"body"`
        }{}
        err = d.DecodeElement(&msg, start)
        if err != nil && err != io.EOF {
            log.Printf("Error decoding message: %q", err)
            return nil
        }

        // Don't reflect messages unless they are chat messages and actually have a
        // body.
        if msg.Body == "" || msg.Type != stanza.ChatMessage {
            return nil
        }

        reply := stanza.WrapMessage(
            msg.From.Bare(), stanza.ChatMessage,
            xmlstream.Wrap(xmlstream.ReaderFunc(func() (xml.Token, error) {
                return xml.CharData(msg.Body), io.EOF
            }), xml.StartElement{Name: xml.Name{Local: "body"}}),
        )
        _, err = s.Send(context.TODO(), reply)
        if err != nil {
            log.Printf("Error responding to mid-%s: %q", msg.ID, err)
        }
        return nil
    }))
}

Index

Examples

Package Files

bind.go conn.go dial.go doc.go features.go handler.go negotiator.go sasl.go session.go starttls.go

Variables

var (
    ErrInputStreamClosed  = errors.New("xmpp: attempted to read token from closed stream")
    ErrOutputStreamClosed = errors.New("xmpp: attempted to write token to closed stream")
)

Errors returned by the XMPP package.

func DialClient Uses

func DialClient(ctx context.Context, network string, addr jid.JID) (net.Conn, error)

DialClient discovers and connects to the address on the named network with a client-to-server (c2s) connection.

For more information see the Dialer type.

func DialServer Uses

func DialServer(ctx context.Context, network string, addr jid.JID) (net.Conn, error)

DialServer discovers and connects to the address on the named network with a server-to-server connection (s2s).

For more info see the Dialer type.

type Dialer Uses

type Dialer struct {
    net.Dialer

    // Resolver allows you to change options related to resolving DNS.
    Resolver *net.Resolver

    // NoLookup stops the dialer from looking up SRV or TXT records for the given
    // domain. It also prevents fetching of the host metadata file.
    // Instead, it will try to connect to the domain directly.
    NoLookup bool

    // S2S causes the server to attempt to dial a server-to-server connection.
    S2S bool

    // Disable TLS entirely (eg. when using StartTLS on a server that does not
    // support implicit TLS).
    NoTLS bool

    // Attempt to create a TLS connection by first looking up SRV records (unless
    // NoLookup is set) and then attempting to use the domains A or AAAA record.
    // The nil value is interpreted as a tls.Config with the expected host set to
    // that of the connection addresses domain part.
    TLSConfig *tls.Config
}

A Dialer contains options for connecting to an XMPP address. After a connection is established the Dial method does not attempt to create an XMPP session on the connection.

The zero value for each field is equivalent to dialing without that option. Dialing with the zero value of Dialer is equivalent to calling the DialClient function.

func (*Dialer) Dial Uses

func (d *Dialer) Dial(ctx context.Context, network string, addr jid.JID) (net.Conn, error)

Dial discovers and connects to the address on the named network. It will attempt to look up SRV records for the given JIDs domainpart or connect to the domainpart directly if no such SRV records exist.

If the context expires before the connection is complete, an error is returned. Once successfully connected, any expiration of the context will not affect the connection.

Network may be any of the network types supported by net.Dial, but you most likely want to use one of the tcp connection types ("tcp", "tcp4", or "tcp6").

For more information see the Dialer type.

type Handler Uses

type Handler interface {
    HandleXMPP(t xmlstream.TokenReadWriter, start *xml.StartElement) error
}

A Handler triggers events or responds to incoming elements in an XML stream.

type HandlerFunc Uses

type HandlerFunc func(t xmlstream.TokenReadWriter, start *xml.StartElement) error

The HandlerFunc type is an adapter to allow the use of ordinary functions as XMPP handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.

func (HandlerFunc) HandleXMPP Uses

func (f HandlerFunc) HandleXMPP(t xmlstream.TokenReadWriter, start *xml.StartElement) error

HandleXMPP calls f(t, start).

type Negotiator Uses

type Negotiator func(ctx context.Context, session *Session, data interface{}) (mask SessionState, rw io.ReadWriter, cache interface{}, err error)

Negotiator is a function that can be passed to NegotiateSession to perform custom session negotiation. This can be used for creating custom stream initialization logic that does not use XMPP feature negotiation such as the connection mechanism described in XEP-0114: Jabber Component Protocol. Normally NewClientSession or NewServerSession should be used instead.

If a Negotiator is passed into NegotiateSession it will be called repeatedly until a mask is returned with the Ready bit set. Each time Negotiator is called any bits set in the state mask that it returns will be set on the session state and any cache value that is returned will be passed back in during the next iteration. If a new io.ReadWriter is returned, it is set as the session's underlying io.ReadWriter and the internal session state (encoders, decoders, etc.) will be reset.

func NewNegotiator Uses

func NewNegotiator(cfg StreamConfig) Negotiator

NewNegotiator creates a Negotiator that uses a collection of StreamFeatures to negotiate an XMPP client-to-server (c2s) or server-to-server (s2s) session. If StartTLS is one of the supported stream features, the Negotiator attempts to negotiate it whether the server advertises support or not.

type Session Uses

type Session struct {
    // contains filtered or unexported fields
}

A Session represents an XMPP session comprising an input and an output XML stream.

func DialClientSession Uses

func DialClientSession(ctx context.Context, origin jid.JID, features ...StreamFeature) (*Session, error)

DialClientSession uses a default dialer to create a TCP connection and attempts to negotiate an XMPP session over it.

If the provided context is canceled after stream negotiation is complete it has no effect on the session.

func DialServerSession Uses

func DialServerSession(ctx context.Context, location, origin jid.JID, features ...StreamFeature) (*Session, error)

DialServerSession uses a default dialer to create a TCP connection and attempts to negotiate an XMPP session over it.

If the provided context is canceled after stream negotiation is complete it has no effect on the session.

func NegotiateSession Uses

func NegotiateSession(ctx context.Context, location, origin jid.JID, rw io.ReadWriter, negotiate Negotiator) (*Session, error)

NegotiateSession creates an XMPP session using a custom negotiate function. Calling NegotiateSession with a nil Negotiator panics.

For more information see the Negotiator type.

func NewClientSession Uses

func NewClientSession(ctx context.Context, origin jid.JID, rw io.ReadWriter, features ...StreamFeature) (*Session, error)

NewClientSession attempts to use an existing connection (or any io.ReadWriter) to negotiate an XMPP client-to-server session. If the provided context is canceled before stream negotiation is complete an error is returned. After stream negotiation if the context is canceled it has no effect.

func NewServerSession Uses

func NewServerSession(ctx context.Context, location, origin jid.JID, rw io.ReadWriter, features ...StreamFeature) (*Session, error)

NewServerSession attempts to use an existing connection (or any io.ReadWriter) to negotiate an XMPP server-to-server session. If the provided context is canceled before stream negotiation is complete an error is returned. After stream negotiation if the context is canceled it has no effect.

func (*Session) Close Uses

func (s *Session) Close() error

Close ends the output stream (by sending a closing </stream:stream> token). It does not close the underlying connection. Calling Close() multiple times will only result in one closing </stream:stream> being sent.

func (*Session) Conn Uses

func (s *Session) Conn() net.Conn

Conn returns the Session's backing connection.

This should almost never be read from or written to, but is useful during stream negotiation for wrapping the existing connection in a new layer (eg. compression or TLS).

func (*Session) EncodeToken Uses

func (s *Session) EncodeToken(t xml.Token) error

EncodeToken satisfies the xmlstream.TokenWriter interface.

func (*Session) Feature Uses

func (s *Session) Feature(namespace string) (data interface{}, ok bool)

Feature checks if a feature with the given namespace was advertised by the server for the current stream. If it was data will be the canonical representation of the feature as returned by the feature's Parse function.

func (*Session) Flush Uses

func (s *Session) Flush() error

Flush satisfies the xmlstream.TokenWriter interface.

func (*Session) LocalAddr Uses

func (s *Session) LocalAddr() jid.JID

LocalAddr returns the Origin address for initiated connections, or the Location for received connections.

func (*Session) RemoteAddr Uses

func (s *Session) RemoteAddr() jid.JID

RemoteAddr returns the Location address for initiated connections, or the Origin address for received connections.

func (*Session) Send Uses

func (s *Session) Send(ctx context.Context, r xml.TokenReader) (xmlstream.TokenReadCloser, error)

Send transmits the first element read from the provided token reader.

For more information, see SendElement.

func (*Session) SendElement Uses

func (s *Session) SendElement(ctx context.Context, r xml.TokenReader, start xml.StartElement) (xmlstream.TokenReadCloser, error)

SendElement transmits the first element read from the provided token reader using start as the outermost tag in the encoding.

If the element is an IQ stanza, Send blocks until a response is received and then returns a reader from which it can be read. If the provided context is closed before the response is received SendElement immediately returns an error and any response received at a later time must be handled separately. The response does not need to be consumed in its entirety, but it must be closed before stream processing will resume. If an error is returned, xml.TokenReader will be nil; the converse is not necessarily true. If the input stream is not being processed (a call to Serve is not running), SendElement may block forever.

func (*Session) Serve Uses

func (s *Session) Serve(h Handler) error

Serve decodes incoming XML tokens from the connection and delegates handling them to h. If an error is returned from the handler and it is of type stanza.Error or stream.Error, the error is marshaled and sent over the XML stream. If any other error type is returned, it is marshaled as an undefined-condition StreamError. If a stream error is received while serving it is not passed to the handler. Instead, Serve unmarshals the error, closes the session, and returns it (h handles stanza level errors, the session handles stream level errors). If serve handles an incoming IQ stanza and the handler does not write a response (an IQ with the same ID and type "result" or "error"), Serve writes an error IQ with a service-unavailable payload.

If the user closes the output stream by calling Close, Serve continues until the input stream is closed by the remote entity as above, or the deadline set by SetCloseDeadline is reached in which case a timeout error is returned.

func (*Session) SetCloseDeadline Uses

func (s *Session) SetCloseDeadline(t time.Time) error

SetCloseDeadline sets a deadline for the input stream to be closed by the other side. If the input stream is not closed by the deadline, the input stream is marked as closed and any blocking calls to Serve will return an error. This is normally called just before a call to Close.

func (*Session) State Uses

func (s *Session) State() SessionState

State returns the current state of the session. For more information, see the SessionState type.

func (*Session) Token Uses

func (s *Session) Token() (xml.Token, error)

Token satisfies the xml.TokenReader interface for Session.

type SessionState Uses

type SessionState uint8

SessionState is a bitmask that represents the current state of an XMPP session. For a description of each bit, see the various SessionState typed constants.

const (
    // Secure indicates that the underlying connection has been secured. For
    // instance, after STARTTLS has been performed or if a pre-secured connection
    // is being used such as websockets over HTTPS.
    Secure SessionState = 1 << iota

    // Authn indicates that the session has been authenticated (probably with
    // SASL).
    Authn

    // Ready indicates that the session is fully negotiated and that XMPP stanzas
    // may be sent and received.
    Ready

    // Received indicates that the session was initiated by a foreign entity.
    Received

    // OutputStreamClosed indicates that the output stream has been closed with a
    // stream end tag.  When set all write operations will return an error even if
    // the underlying TCP connection is still open.
    OutputStreamClosed

    // InputStreamClosed indicates that the input stream has been closed with a
    // stream end tag. When set all read operations will return an error.
    InputStreamClosed
)

type StreamConfig Uses

type StreamConfig struct {
    // The native language of the stream.
    Lang string

    // S2S causes the negotiator to negotiate a server-to-server (s2s) connection.
    S2S bool

    // A list of stream features to attempt to negotiate.
    Features []StreamFeature

    // If set a copy of any reads from the session will be written to TeeIn and
    // any writes to the session will be written to TeeOut (similar to the tee(1)
    // command).
    // This can be used to build an "XML console", but users should be careful
    // since this bypasses TLS and could expose passwords and other sensitve data.
    TeeIn, TeeOut io.Writer
}

StreamConfig contains options for configuring the default Negotiator.

type StreamFeature Uses

type StreamFeature struct {
    // The XML name of the feature in the <stream:feature/> list. If a start
    // element with this name is seen while the connection is reading the features
    // list, it will trigger this StreamFeature's Parse function as a callback.
    Name xml.Name

    // Bits that are required before this feature is advertised. For instance, if
    // this feature should only be advertised after the user is authenticated we
    // might set this to "Authn" or if it should be advertised only after the
    // feature is authenticated and encrypted we might set this to "Authn|Secure".
    Necessary SessionState

    // Bits that must be off for this feature to be advertised. For instance, if
    // this feature should only be advertised before the connection is
    // authenticated (eg. if the feature performs authentication itself), we might
    // set this to "Authn".
    Prohibited SessionState

    // Used to send the feature in a features list for server connections. The
    // start element will have a name that matches the features name and should be
    // used as the outermost tag in the stream (but also may be ignored). List
    // implementations that call e.EncodeToken directly need to call e.Flush when
    // finished to ensure that the XML is written to the underlying writer.
    List func(ctx context.Context, e xmlstream.TokenWriter, start xml.StartElement) (req bool, err error)

    // Used to parse the feature that begins with the given xml start element
    // (which should have a Name that matches this stream feature's Name).
    // Returns whether or not the feature is required, and any data that will be
    // needed if the feature is selected for negotiation (eg. the list of
    // mechanisms if the feature was SASL authentication).
    Parse func(ctx context.Context, r xml.TokenReader, start *xml.StartElement) (req bool, data interface{}, err error)

    // A function that will take over the session temporarily while negotiating
    // the feature. The "mask" SessionState represents the state bits that should
    // be flipped after negotiation of the feature is complete. For instance, if
    // this feature creates a security layer (such as TLS) and performs
    // authentication, mask would be set to Authn|Secure, but if it does not
    // authenticate the connection it would just return Secure. If negotiate
    // returns a new io.ReadWriter (probably wrapping the old session.Conn()) the
    // stream will be restarted automatically after Negotiate returns using the
    // new ReadWriter. If this is an initiated connection and the features List
    // call returned a value, that value is passed to the data parameter when
    // Negotiate is called. For instance, in the case of compression this data
    // parameter might be the list of supported algorithms as a slice of strings
    // (or in whatever format the feature implementation has decided upon).
    Negotiate func(ctx context.Context, session *Session, data interface{}) (mask SessionState, rw io.ReadWriter, err error)
}

A StreamFeature represents a feature that may be selected during stream negotiation, eg. STARTTLS, compression, and SASL authentication are all stream features. Features should be stateless as they may be reused between connection attempts, however, a method for passing state between features exists on the Parse and Negotiate functions.

func BindCustom Uses

func BindCustom(server func(jid.JID, string) (jid.JID, error)) StreamFeature

BindCustom is identical to BindResource when used on a client session, but for server sessions the server function is called to generate the JID that should be returned to the client. If server is nil, BindCustom is identical to BindResource.

The server function is passed the current client JID and the resource requested by the client (or an empty string if a specific resource was not requested). Resources generated by the server function should be random to prevent certain security issues related to guessing resourceparts.

func BindResource Uses

func BindResource() StreamFeature

BindResource is a stream feature that can be used for binding a resource (the name by which an individual client can be addressed) to the stream.

Resource binding is the final feature negotiated when setting up a new session and is required to allow communication with other clients and servers in the network. Resource binding is mandatory-to-negotiate.

If used on a server connection, BindResource generates and assigns random resourceparts, however this default is subject to change.

func SASL Uses

func SASL(identity, password string, mechanisms ...sasl.Mechanism) StreamFeature

SASL returns a stream feature for performing authentication using the Simple Authentication and Security Layer (SASL) as defined in RFC 4422. It panics if no mechanisms are specified. The order in which mechanisms are specified will be the preferred order, so stronger mechanisms should be listed first.

Identity is used when a user wants to act on behalf of another user. For instance, an admin might want to log in as another user to help them troubleshoot an issue. Normally it is left blank and the localpart of the Origin JID is used.

func StartTLS Uses

func StartTLS(required bool, cfg *tls.Config) StreamFeature

StartTLS returns a new stream feature that can be used for negotiating TLS. If cfg is nil, a default configuration is used that uses the domainpart of the sessions local address as the ServerName.

Bugs

SASL feature does not have security layer byte precision.

STARTTLS feature does not have security layer byte precision.

Directories

PathSynopsis
colorPackage color implements XEP-0392: Consistent Color Generation v0.4.
componentPackage component is used to establish XEP-0114: Jabber Component Protocol connections.
compressPackage compress implements XEP-0138: Stream Compression and XEP-0229: Stream Compression with LZW.
formPackage form is an implementation of XEP-0004: Data Forms.
ibr2Package ibr2 implements the Extensible In-Band Registration ProtoXEP.
internalPackage internal provides non-exported functionality used by xmpp and its child packages.
internal/nsPackage ns provides namespace constants that are used by the xmpp package and other internal packages.
internal/saslerrPackage saslerr provides error conditions for the XMPP profile of SASL as defined by RFC 6120 §6.5.
internal/xmpptestPackage xmpptest provides utilities for XMPP testing.
jidPackage jid implements the XMPP address format.
muxPackage mux implements an XMPP multiplexer.
oobPackage oob implements XEP-0066: Out of Band Data.
pingPackage ping implements XEP-0199: XMPP Ping.
rosterPackage roster implements contact list functionality.
sasl2Package sasl2 is an experimental implementation of XEP-0388: Extensible SASL Profile.
stanzaPackage stanza contains functionality for dealing with XMPP stanzas and stanza level errors.
streamPackage stream contains XMPP stream errors as defined by RFC 6120 §4.9.
x509Package x509 parses X.509-encoded keys and certificates.

Package xmpp imports 19 packages (graph) and is imported by 6 packages. Updated 2019-01-31. Refresh now. Tools for package owners.