mirror of
				https://github.com/FluuxIO/go-xmpp.git
				synced 2025-10-31 08:23:44 -07:00 
			
		
		
		
	Initial working version of go XMPP library
This commit is contained in:
		
							
								
								
									
										79
									
								
								xmpp/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								xmpp/auth.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f streamFeatures, user string, password string) (err error) { | ||||
| 	// TODO: Implement other type of SASL Authentication | ||||
| 	havePlain := false | ||||
| 	for _, m := range f.Mechanisms.Mechanism { | ||||
| 		if m == "PLAIN" { | ||||
| 			havePlain = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !havePlain { | ||||
| 		return errors.New(fmt.Sprintf("PLAIN authentication is not supported by server: %v", f.Mechanisms.Mechanism)) | ||||
| 	} | ||||
|  | ||||
| 	return authPlain(socket, decoder, user, password) | ||||
| } | ||||
|  | ||||
| // Plain authentication: send base64-encoded \x00 user \x00 password | ||||
| func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password string) error { | ||||
| 	raw := "\x00" + user + "\x00" + password | ||||
| 	enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||
| 	base64.StdEncoding.Encode(enc, []byte(raw)) | ||||
| 	fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc) | ||||
|  | ||||
| 	// Next message should be either success or failure. | ||||
| 	name, val, err := next(decoder) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	switch v := val.(type) { | ||||
| 	case *saslSuccess: | ||||
| 	case *saslFailure: | ||||
| 		// v.Any is type of sub-element in failure, which gives a description of what failed. | ||||
| 		return errors.New("auth failure: " + v.Any.Local) | ||||
| 	default: | ||||
| 		return errors.New("expected success or failure, got " + name.Local + " in " + name.Space) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // XMPP Packet Parsing | ||||
| type saslMechanisms struct { | ||||
| 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` | ||||
| 	Mechanism []string `xml:"mechanism"` | ||||
| } | ||||
|  | ||||
| type saslSuccess struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"` | ||||
| } | ||||
|  | ||||
| type saslFailure struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"` | ||||
| 	Any     xml.Name // error reason is a subelement | ||||
|  | ||||
| } | ||||
|  | ||||
| type bindBind struct { | ||||
| 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` | ||||
| 	Resource string | ||||
| 	Jid      string | ||||
| } | ||||
|  | ||||
| // Session is obsolete in RFC 6121. | ||||
| // Added for compliance with RFC 3121. | ||||
| // Remove when ejabberd purely conforms to RFC 6121. | ||||
| type sessionSession struct { | ||||
| 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"` | ||||
| 	optional xml.Name // If it does exist, it mean we are not required to open session | ||||
| } | ||||
							
								
								
									
										116
									
								
								xmpp/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								xmpp/client.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Client struct { | ||||
| 	// Store user defined options | ||||
| 	options Options | ||||
| 	// Session gather data that can be accessed by users of this library | ||||
| 	Session *Session | ||||
| 	// TCP level connection / can be replace by a TLS session after starttls | ||||
| 	conn net.Conn | ||||
| } | ||||
|  | ||||
| /* | ||||
| Setting up the client / Checking the parameters | ||||
| */ | ||||
|  | ||||
| // TODO: better options check | ||||
| func NewClient(options Options) (c *Client, err error) { | ||||
| 	// TODO: If option address is nil, use the Jid domain to compose the address | ||||
| 	if options.Address, err = checkAddress(options.Address); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if options.Password == "" { | ||||
| 		err = errors.New("missing password") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	c = new(Client) | ||||
| 	c.options = options | ||||
|  | ||||
| 	// Parse JID | ||||
| 	if c.options.parsedJid, err = NewJid(c.options.Jid); err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func checkAddress(addr string) (string, error) { | ||||
| 	var err error | ||||
| 	hostport := strings.Split(addr, ":") | ||||
| 	if len(hostport) > 2 { | ||||
| 		err = errors.New("too many colons in xmpp server address") | ||||
| 		return addr, err | ||||
| 	} | ||||
|  | ||||
| 	// Address is composed of two parts, we are good | ||||
| 	if len(hostport) == 2 && hostport[1] != "" { | ||||
| 		return addr, err | ||||
| 	} | ||||
|  | ||||
| 	// Port was not passed, we append XMPP default port: | ||||
| 	return strings.Join([]string{hostport[0], "5222"}, ":"), err | ||||
| } | ||||
|  | ||||
| // NewClient creates a new connection to a host given as "hostname" or "hostname:port". | ||||
| // If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID. | ||||
| // Default the port to 5222. | ||||
| func (c *Client) Connect() (*Session, error) { | ||||
| 	var tcpconn net.Conn | ||||
| 	var err error | ||||
| 	if tcpconn, err = net.Dial("tcp", c.options.Address); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	c.conn = tcpconn | ||||
| 	if c.conn, c.Session, err = NewSession(c.conn, c.options); err != nil { | ||||
| 		return c.Session, err | ||||
| 	} | ||||
|  | ||||
| 	// We're connected and can now receive and send messages. | ||||
| 	//fmt.Fprintf(client.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", "chat", "Online") | ||||
| 	fmt.Fprintf(c.Session.socketProxy, "<presence/>") | ||||
|  | ||||
| 	return c.Session, err | ||||
| } | ||||
|  | ||||
| func (c *Client) recv(receiver chan<- interface{}) (err error) { | ||||
| 	for { | ||||
| 		_, val, err := next(c.Session.decoder) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		receiver <- val | ||||
| 		val = nil | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| // Channel allow client to receive / dispatch packets in for range loop | ||||
| func (c *Client) Recv() <-chan interface{} { | ||||
| 	ch := make(chan interface{}) | ||||
| 	go c.recv(ch) | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| // Send sends message text. | ||||
| func (c *Client) Send(packet string) error { | ||||
| 	fmt.Fprintf(c.Session.socketProxy, packet) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func xmlEscape(s string) string { | ||||
| 	var b bytes.Buffer | ||||
| 	xml.Escape(&b, []byte(s)) | ||||
| 	return b.String() | ||||
| } | ||||
							
								
								
									
										11
									
								
								xmpp/iq.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								xmpp/iq.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package xmpp | ||||
|  | ||||
| import "encoding/xml" | ||||
|  | ||||
| type clientIQ struct { // info/query | ||||
| 	XMLName xml.Name `xml:"jabber:client iq"` | ||||
| 	Packet | ||||
| 	Bind bindBind | ||||
| 	// TODO We need to support detecting the IQ namespace / Query packet | ||||
| 	// 	Error   clientError | ||||
| } | ||||
							
								
								
									
										34
									
								
								xmpp/jid.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								xmpp/jid.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Jid struct { | ||||
| 	username string | ||||
| 	domain   string | ||||
| 	resource string | ||||
| } | ||||
|  | ||||
| func NewJid(sjid string) (jid *Jid, err error) { | ||||
| 	s1 := strings.Split(sjid, "@") | ||||
| 	if len(s1) != 2 { | ||||
| 		err = errors.New("invalid JID: " + sjid) | ||||
| 		return | ||||
| 	} | ||||
| 	jid = new(Jid) | ||||
| 	jid.username = s1[0] | ||||
|  | ||||
| 	s2 := strings.Split(s1[1], "/") | ||||
| 	if len(s2) > 2 { | ||||
| 		err = errors.New("invalid JID: " + sjid) | ||||
| 		return | ||||
| 	} | ||||
| 	jid.domain = s2[0] | ||||
| 	if len(s2) == 2 { | ||||
| 		jid.resource = s2[1] | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										24
									
								
								xmpp/message.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								xmpp/message.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // XMPP Packet Parsing | ||||
| type ClientMessage struct { | ||||
| 	XMLName xml.Name `xml:"jabber:client message"` | ||||
| 	Packet | ||||
| 	Subject string `xml:"subject,omitempty"` | ||||
| 	Body    string `xml:"body,omitempty"` | ||||
| 	Thread  string `xml:"thread,omitempty"` | ||||
| } | ||||
|  | ||||
| // TODO: Func new message to create an empty message structure without the XML tag matching elements | ||||
|  | ||||
| func (message *ClientMessage) XMPPFormat() string { | ||||
| 	return fmt.Sprintf("<message to='%s' type='chat' xml:lang='en'>"+ | ||||
| 		"<body>%s</body></message>", | ||||
| 		message.To, | ||||
| 		xmlEscape(message.Body)) | ||||
| } | ||||
							
								
								
									
										10
									
								
								xmpp/ns.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								xmpp/ns.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package xmpp | ||||
|  | ||||
| const ( | ||||
| 	nsStream  = "http://etherx.jabber.org/streams" | ||||
| 	nsTLS     = "urn:ietf:params:xml:ns:xmpp-tls" | ||||
| 	nsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl" | ||||
| 	nsBind    = "urn:ietf:params:xml:ns:xmpp-bind" | ||||
| 	nsSession = "urn:ietf:params:xml:ns:xmpp-session" | ||||
| 	nsClient  = "jabber:client" | ||||
| ) | ||||
							
								
								
									
										12
									
								
								xmpp/options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								xmpp/options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package xmpp | ||||
|  | ||||
| import "os" | ||||
|  | ||||
| type Options struct { | ||||
| 	Address      string | ||||
| 	Jid          string | ||||
| 	parsedJid    *Jid // For easier manipulation | ||||
| 	Password     string | ||||
| 	PacketLogger *os.File // Used for debugging | ||||
| 	Lang         string   // TODO: should default to 'en' | ||||
| } | ||||
							
								
								
									
										13
									
								
								xmpp/packet.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								xmpp/packet.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| package xmpp | ||||
|  | ||||
| type Packet struct { | ||||
| 	Id   string `xml:"id,attr,omitempty"` | ||||
| 	From string `xml:"from,attr,omitempty"` | ||||
| 	To   string `xml:"to,attr,omitempty"` | ||||
| 	Type string `xml:"type,attr,omitempty"` | ||||
| 	Lang string `xml:"lang,attr,omitempty"` | ||||
| } | ||||
|  | ||||
| type packetFormatter interface { | ||||
| 	XMPPFormat() string | ||||
| } | ||||
							
								
								
									
										96
									
								
								xmpp/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								xmpp/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"log" | ||||
| ) | ||||
|  | ||||
| // Reads and checks the opening XMPP stream element. | ||||
| // It returns a stream structure containing: | ||||
| // - Host: You can check the host against the host you were expecting to connect to | ||||
| // - Id: the Stream ID is a temporary shared secret used for some hash calculation. It is also used by ProcessOne | ||||
| //       reattach features (allowing to resume an existing stream at the point the connection was interrupted, without | ||||
| //       getting through the authentication process. | ||||
| func initDecoder(p *xml.Decoder) (sessionID string, err error) { | ||||
| 	for { | ||||
| 		var t xml.Token | ||||
| 		t, err = p.Token() | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		switch elem := t.(type) { | ||||
| 		case xml.StartElement: | ||||
| 			if elem.Name.Space != nsStream || elem.Name.Local != "stream" { | ||||
| 				err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space) | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			// Parse Stream attributes | ||||
| 			for _, attrs := range elem.Attr { | ||||
| 				switch attrs.Name.Local { | ||||
| 				case "id": | ||||
| 					sessionID = attrs.Value | ||||
| 				} | ||||
| 			} | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| // Scan XML token stream to find next StartElement. | ||||
| func nextStart(p *xml.Decoder) (xml.StartElement, error) { | ||||
| 	for { | ||||
| 		t, err := p.Token() | ||||
| 		if err == io.EOF { | ||||
| 			return xml.StartElement{}, nil | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			log.Fatal("token:", err) | ||||
| 		} | ||||
| 		switch t := t.(type) { | ||||
| 		case xml.StartElement: | ||||
| 			return t, nil | ||||
| 		} | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| // Scan XML token stream for next element and save into val. | ||||
| // If val == nil, allocate new element based on proto map. | ||||
| // Either way, return val. | ||||
| func next(p *xml.Decoder) (xml.Name, interface{}, error) { | ||||
| 	// Read start element to find out what type we want. | ||||
| 	se, err := nextStart(p) | ||||
| 	if err != nil { | ||||
| 		return xml.Name{}, nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Put it in an interface and allocate one. | ||||
| 	var nv interface{} | ||||
| 	switch se.Name.Space + " " + se.Name.Local { | ||||
| 	// TODO: general case = Parse IQ / presence / message => split SASL case | ||||
| 	case nsSASL + " success": | ||||
| 		nv = &saslSuccess{} | ||||
| 	case nsSASL + " failure": | ||||
| 		nv = &saslFailure{} | ||||
| 	case nsClient + " message": | ||||
| 		nv = &ClientMessage{} | ||||
| 	case nsClient + " presence": | ||||
| 		nv = &clientPresence{} | ||||
| 	case nsClient + " iq": | ||||
| 		nv = &clientIQ{} | ||||
| 	default: | ||||
| 		return xml.Name{}, nil, errors.New("unexpected XMPP message " + | ||||
| 			se.Name.Space + " <" + se.Name.Local + "/>") | ||||
| 	} | ||||
|  | ||||
| 	// Decode element into pointer storage | ||||
| 	if err = p.DecodeElement(nv, &se); err != nil { | ||||
| 		return xml.Name{}, nil, err | ||||
| 	} | ||||
| 	return se.Name, nv, err | ||||
| } | ||||
							
								
								
									
										13
									
								
								xmpp/presence.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								xmpp/presence.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| package xmpp | ||||
|  | ||||
| import "encoding/xml" | ||||
|  | ||||
| // XMPP Packet Parsing | ||||
| type clientPresence struct { | ||||
| 	XMLName xml.Name `xml:"jabber:client presence"` | ||||
| 	Packet | ||||
| 	Show     string `xml:"show,attr,omitempty"` // away, chat, dnd, xa | ||||
| 	Status   string `xml:"status,attr,omitempty"` | ||||
| 	Priority string `xml:"priority,attr,omitempty"` | ||||
| 	//Error    *clientError | ||||
| } | ||||
							
								
								
									
										178
									
								
								xmpp/session.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								xmpp/session.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| ) | ||||
|  | ||||
| const xmppStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s' version='1.0'>" | ||||
|  | ||||
| type Session struct { | ||||
| 	// Session info | ||||
| 	BindJid      string // Jabber ID as provided by XMPP server | ||||
| 	StreamId     string | ||||
| 	Features     streamFeatures | ||||
| 	TlsEnabled   bool | ||||
| 	lastPacketId int | ||||
|  | ||||
| 	// Session interface | ||||
| 	In  chan interface{} | ||||
| 	Out chan interface{} | ||||
|  | ||||
| 	// read / write | ||||
| 	socketProxy io.ReadWriter | ||||
| 	decoder     *xml.Decoder | ||||
|  | ||||
| 	// error management | ||||
| 	err error | ||||
| } | ||||
|  | ||||
| func NewSession(conn net.Conn, o Options) (net.Conn, *Session, error) { | ||||
| 	s := new(Session) | ||||
| 	s.init(conn, o) | ||||
|  | ||||
| 	// starttls | ||||
| 	var tlsConn net.Conn | ||||
| 	tlsConn = s.startTlsIfSupported(conn, o.parsedJid.domain) | ||||
| 	s.reset(conn, tlsConn, o) | ||||
|  | ||||
| 	// auth | ||||
| 	s.auth(o) | ||||
| 	s.reset(tlsConn, tlsConn, o) | ||||
|  | ||||
| 	// bind resource and 'start' XMPP session | ||||
| 	s.bind(o) | ||||
| 	s.rfc3921Session(o) | ||||
|  | ||||
| 	return tlsConn, s, s.err | ||||
| } | ||||
|  | ||||
| func (s *Session) PacketId() string { | ||||
| 	s.lastPacketId++ | ||||
| 	return fmt.Sprintf("%x", s.lastPacketId) | ||||
| } | ||||
|  | ||||
| func (s *Session) init(conn net.Conn, o Options) { | ||||
| 	s.setProxy(nil, conn, o) | ||||
| 	s.Features = s.open(o.parsedJid.domain) | ||||
| } | ||||
|  | ||||
| func (s *Session) reset(conn net.Conn, newConn net.Conn, o Options) { | ||||
| 	if s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s.setProxy(conn, newConn, o) | ||||
| 	s.Features = s.open(o.parsedJid.domain) | ||||
| } | ||||
|  | ||||
| // TODO: setProxyLogger ? better name ? This is not a TCP / HTTP proxy | ||||
| func (s *Session) setProxy(conn net.Conn, newConn net.Conn, o Options) { | ||||
| 	if newConn != conn { | ||||
| 		s.socketProxy = newSocketProxy(newConn, o.PacketLogger) | ||||
| 	} | ||||
| 	s.decoder = xml.NewDecoder(s.socketProxy) | ||||
| } | ||||
|  | ||||
| func (s *Session) open(domain string) (f streamFeatures) { | ||||
| 	// Send stream open tag | ||||
| 	if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, nsClient, nsStream); s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Set xml decoder and extract streamID from reply | ||||
| 	s.StreamId, s.err = initDecoder(s.decoder) // TODO refactor / rename | ||||
| 	if s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// extract stream features | ||||
| 	if s.err = s.decoder.Decode(&f); s.err != nil { | ||||
| 		s.err = errors.New("stream open decode features: " + s.err.Error()) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn { | ||||
| 	if s.err != nil { | ||||
| 		return conn | ||||
| 	} | ||||
|  | ||||
| 	if s.Features.StartTLS.XMLName.Space+" "+s.Features.StartTLS.XMLName.Local == nsTLS+" starttls" { | ||||
| 		fmt.Fprintf(s.socketProxy, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>") | ||||
|  | ||||
| 		var k tlsProceed | ||||
| 		if s.err = s.decoder.DecodeElement(&k, nil); s.err != nil { | ||||
| 			s.err = errors.New("expecting starttls proceed: " + s.err.Error()) | ||||
| 			return conn | ||||
| 		} | ||||
| 		s.TlsEnabled = true | ||||
|  | ||||
| 		// TODO: add option to accept all TLS certificates: insecureSkipTlsVerify (DefaultTlsConfig.InsecureSkipVerify) | ||||
| 		DefaultTlsConfig.ServerName = domain | ||||
| 		var tlsConn *tls.Conn = tls.Client(conn, &DefaultTlsConfig) | ||||
| 		// We convert existing connection to TLS | ||||
| 		if s.err = tlsConn.Handshake(); s.err != nil { | ||||
| 			return tlsConn | ||||
| 		} | ||||
|  | ||||
| 		// We check that cert matches hostname | ||||
| 		s.err = tlsConn.VerifyHostname(domain) | ||||
| 		return tlsConn | ||||
| 	} | ||||
|  | ||||
| 	// starttls is not supported => we do not upgrade the connection: | ||||
| 	return conn | ||||
| } | ||||
|  | ||||
| func (s *Session) auth(o Options) { | ||||
| 	if s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s.err = authSASL(s.socketProxy, s.decoder, s.Features, o.parsedJid.username, o.Password) | ||||
| } | ||||
|  | ||||
| func (s *Session) bind(o Options) { | ||||
| 	if s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Send IQ message asking to bind to the local user name. | ||||
| 	var resource = o.parsedJid.resource | ||||
| 	if resource != "" { | ||||
| 		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'><resource>%s</resource></bind></iq>", | ||||
| 			s.PacketId(), nsBind, resource) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), nsBind) | ||||
| 	} | ||||
|  | ||||
| 	var iq clientIQ | ||||
| 	if s.err = s.decoder.Decode(&iq); s.err != nil || &iq.Bind == nil { | ||||
| 		s.err = errors.New("iq bind result missing: " + s.err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	s.BindJid = iq.Bind.Jid // our local id (with possibly randomly generated resource | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // TODO: remove when ejabberd is fixed: https://github.com/processone/ejabberd/issues/869 | ||||
| // After the bind, if the session is required (as per old RFC 3921), we send the session open iq | ||||
| func (s *Session) rfc3921Session(o Options) { | ||||
| 	if s.err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var iq clientIQ | ||||
|  | ||||
| 	// TODO: Do no send unconditionally, check if session is optional and omit it | ||||
| 	fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), nsSession) | ||||
| 	if s.err = s.decoder.Decode(&iq); s.err != nil { | ||||
| 		s.err = errors.New("expecting iq result after session open: " + s.err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										49
									
								
								xmpp/socket_proxy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								xmpp/socket_proxy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // Mediated Read / Write on socket | ||||
| // Used if logFile from Options is not nil | ||||
| type socketProxy struct { | ||||
| 	socket  io.ReadWriter // Actual connection | ||||
| 	logFile *os.File | ||||
| } | ||||
|  | ||||
| func newSocketProxy(conn io.ReadWriter, logFile *os.File) io.ReadWriter { | ||||
| 	if logFile == nil { | ||||
| 		return conn | ||||
| 	} else { | ||||
| 		return &socketProxy{conn, logFile} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pl *socketProxy) Read(p []byte) (n int, err error) { | ||||
| 	n, err = pl.socket.Read(p) | ||||
| 	if n > 0 { | ||||
| 		pl.logFile.Write([]byte("RECV:\n")) // Prefix | ||||
| 		if n, err := pl.logFile.Write(p[:n]); err != nil { | ||||
| 			return n, err | ||||
| 		} | ||||
| 		pl.logFile.Write([]byte("\n\n")) // Separator | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (pl *socketProxy) Write(p []byte) (n int, err error) { | ||||
| 	pl.logFile.Write([]byte("SEND:\n")) // Prefix | ||||
| 	for _, w := range []io.Writer{pl.socket, pl.logFile} { | ||||
| 		n, err = w.Write(p) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		if n != len(p) { | ||||
| 			err = io.ErrShortWrite | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	pl.logFile.Write([]byte("\n\n")) // Separator | ||||
| 	return len(p), nil | ||||
| } | ||||
							
								
								
									
										22
									
								
								xmpp/starttls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								xmpp/starttls.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/xml" | ||||
| ) | ||||
|  | ||||
| var DefaultTlsConfig tls.Config | ||||
|  | ||||
| // XMPP Packet Parsing | ||||
| type tlsStartTLS struct { | ||||
| 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` | ||||
| 	Required bool | ||||
| } | ||||
|  | ||||
| type tlsProceed struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` | ||||
| } | ||||
|  | ||||
| type tlsFailure struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"` | ||||
| } | ||||
							
								
								
									
										22
									
								
								xmpp/stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								xmpp/stream.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package xmpp | ||||
|  | ||||
| import "encoding/xml" | ||||
|  | ||||
| // XMPP Packet Parsing | ||||
| type streamFeatures struct { | ||||
| 	XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"` | ||||
| 	StartTLS   tlsStartTLS | ||||
| 	Caps       Caps | ||||
| 	Mechanisms saslMechanisms | ||||
| 	Bind       bindBind | ||||
| 	Session    sessionSession | ||||
| 	Any        []xml.Name `xml:",any"` | ||||
| } | ||||
|  | ||||
| type Caps struct { | ||||
| 	XMLName xml.Name `xml:"http://jabber.org/protocol/caps c"` | ||||
| 	Hash    string   `xml:"hash,attr"` | ||||
| 	Node    string   `xml:"node,attr"` | ||||
| 	Ver     string   `xml:"ver,attr"` | ||||
| 	Ext     string   `xml:"ext,attr,omitempty"` | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Mickael Remond
					Mickael Remond