2019-06-18 14:28:30 +00:00
|
|
|
package xmpp
|
2016-01-06 15:51:12 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2019-06-26 15:14:52 +00:00
|
|
|
|
|
|
|
"gosrc.io/xmpp/stanza"
|
2016-01-06 15:51:12 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Session struct {
|
|
|
|
// Session info
|
|
|
|
BindJid string // Jabber ID as provided by XMPP server
|
|
|
|
StreamId string
|
2019-07-31 16:47:30 +00:00
|
|
|
SMState SMState
|
2019-06-26 15:14:52 +00:00
|
|
|
Features stanza.StreamFeatures
|
2016-01-06 15:51:12 +00:00
|
|
|
TlsEnabled bool
|
|
|
|
lastPacketId int
|
|
|
|
|
|
|
|
// read / write
|
2019-10-18 18:29:54 +00:00
|
|
|
transport Transport
|
2016-01-06 15:51:12 +00:00
|
|
|
|
|
|
|
// error management
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2019-10-06 17:37:56 +00:00
|
|
|
func NewSession(transport Transport, o Config, state SMState) (*Session, error) {
|
2016-01-06 15:51:12 +00:00
|
|
|
s := new(Session)
|
2019-10-18 18:29:54 +00:00
|
|
|
s.transport = transport
|
2019-07-31 16:47:30 +00:00
|
|
|
s.SMState = state
|
2019-10-18 18:29:54 +00:00
|
|
|
s.init(o)
|
2016-01-06 15:51:12 +00:00
|
|
|
|
2019-07-31 09:43:54 +00:00
|
|
|
if s.err != nil {
|
2019-10-06 17:37:56 +00:00
|
|
|
return nil, NewConnError(s.err, true)
|
2019-07-31 09:43:54 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 13:46:13 +00:00
|
|
|
if !transport.IsSecure() {
|
|
|
|
s.startTlsIfSupported(o)
|
|
|
|
}
|
|
|
|
|
2019-10-11 05:15:47 +00:00
|
|
|
if !transport.IsSecure() && !o.Insecure {
|
2019-07-15 10:18:35 +00:00
|
|
|
err := fmt.Errorf("failed to negotiate TLS session : %s", s.err)
|
2019-10-06 17:37:56 +00:00
|
|
|
return nil, NewConnError(err, true)
|
2019-07-15 10:18:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if s.TlsEnabled {
|
2019-10-18 18:29:54 +00:00
|
|
|
s.reset(o)
|
2017-10-21 12:49:25 +00:00
|
|
|
}
|
|
|
|
|
2016-01-06 15:51:12 +00:00
|
|
|
// auth
|
|
|
|
s.auth(o)
|
2019-10-18 18:29:54 +00:00
|
|
|
s.reset(o)
|
2016-01-06 15:51:12 +00:00
|
|
|
|
2019-07-31 16:47:30 +00:00
|
|
|
// attempt resumption
|
|
|
|
if s.resume(o) {
|
2019-10-06 17:37:56 +00:00
|
|
|
return s, s.err
|
2019-07-31 16:47:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise, bind resource and 'start' XMPP session
|
2016-01-06 15:51:12 +00:00
|
|
|
s.bind(o)
|
|
|
|
s.rfc3921Session(o)
|
|
|
|
|
2019-07-31 16:47:30 +00:00
|
|
|
// Enable stream management if supported
|
|
|
|
s.EnableStreamManagement(o)
|
|
|
|
|
2019-10-06 17:37:56 +00:00
|
|
|
return s, s.err
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Session) PacketId() string {
|
|
|
|
s.lastPacketId++
|
|
|
|
return fmt.Sprintf("%x", s.lastPacketId)
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
func (s *Session) init(o Config) {
|
2019-06-07 13:56:41 +00:00
|
|
|
s.Features = s.open(o.parsedJid.Domain)
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
func (s *Session) reset(o Config) {
|
2019-10-25 13:55:27 +00:00
|
|
|
if s.StreamId, s.err = s.transport.StartStream(); s.err != nil {
|
2016-01-06 15:51:12 +00:00
|
|
|
return
|
|
|
|
}
|
2019-10-25 13:55:27 +00:00
|
|
|
|
2019-06-07 13:56:41 +00:00
|
|
|
s.Features = s.open(o.parsedJid.Domain)
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
func (s *Session) open(domain string) (f stanza.StreamFeatures) {
|
2016-01-06 15:51:12 +00:00
|
|
|
// extract stream features
|
2019-10-18 18:29:54 +00:00
|
|
|
if s.err = s.transport.GetDecoder().Decode(&f); s.err != nil {
|
2016-01-06 15:51:12 +00:00
|
|
|
s.err = errors.New("stream open decode features: " + s.err.Error())
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
func (s *Session) startTlsIfSupported(o Config) {
|
2016-01-06 15:51:12 +00:00
|
|
|
if s.err != nil {
|
2019-10-06 17:37:56 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
if !s.transport.DoesStartTLS() {
|
2019-10-06 17:37:56 +00:00
|
|
|
if !o.Insecure {
|
2020-01-31 10:48:03 +00:00
|
|
|
s.err = errors.New("transport does not support starttls")
|
2019-10-06 17:37:56 +00:00
|
|
|
}
|
|
|
|
return
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-10 14:27:52 +00:00
|
|
|
if _, ok := s.Features.DoesStartTLS(); ok {
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
|
2016-01-06 15:51:12 +00:00
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
var k stanza.TLSProceed
|
2019-10-18 18:29:54 +00:00
|
|
|
if s.err = s.transport.GetDecoder().DecodeElement(&k, nil); s.err != nil {
|
2016-01-06 15:51:12 +00:00
|
|
|
s.err = errors.New("expecting starttls proceed: " + s.err.Error())
|
2019-10-06 17:37:56 +00:00
|
|
|
return
|
2019-07-15 22:26:21 +00:00
|
|
|
}
|
|
|
|
|
2019-10-25 13:55:27 +00:00
|
|
|
s.err = s.transport.StartTLS()
|
2019-07-15 10:18:35 +00:00
|
|
|
|
|
|
|
if s.err == nil {
|
|
|
|
s.TlsEnabled = true
|
|
|
|
}
|
2019-10-06 17:37:56 +00:00
|
|
|
return
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-12-10 16:15:16 +00:00
|
|
|
// If we do not allow cleartext serverConnections, make it explicit that server do not support starttls
|
2019-07-15 10:18:35 +00:00
|
|
|
if !o.Insecure {
|
|
|
|
s.err = errors.New("XMPP server does not advertise support for starttls")
|
|
|
|
}
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2018-09-26 14:25:04 +00:00
|
|
|
func (s *Session) auth(o Config) {
|
2016-01-06 15:51:12 +00:00
|
|
|
if s.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
s.err = authSASL(s.transport, s.transport.GetDecoder(), s.Features, o.parsedJid.Node, o.Credential)
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-07-31 16:47:30 +00:00
|
|
|
// Attempt to resume session using stream management
|
|
|
|
func (s *Session) resume(o Config) bool {
|
|
|
|
if !s.Features.DoesStreamManagement() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if s.SMState.Id == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<resume xmlns='%s' h='%d' previd='%s'/>",
|
2019-07-31 16:47:30 +00:00
|
|
|
stanza.NSStreamManagement, s.SMState.Inbound, s.SMState.Id)
|
|
|
|
|
|
|
|
var packet stanza.Packet
|
2019-10-18 18:29:54 +00:00
|
|
|
packet, s.err = stanza.NextPacket(s.transport.GetDecoder())
|
2019-07-31 16:47:30 +00:00
|
|
|
if s.err == nil {
|
|
|
|
switch p := packet.(type) {
|
|
|
|
case stanza.SMResumed:
|
|
|
|
if p.PrevId != s.SMState.Id {
|
|
|
|
s.err = errors.New("session resumption: mismatched id")
|
|
|
|
s.SMState = SMState{}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
case stanza.SMFailed:
|
|
|
|
default:
|
|
|
|
s.err = errors.New("unexpected reply to SM resume")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.SMState = SMState{}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-09-26 14:25:04 +00:00
|
|
|
func (s *Session) bind(o Config) {
|
2016-01-06 15:51:12 +00:00
|
|
|
if s.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send IQ message asking to bind to the local user name.
|
2019-06-07 13:56:41 +00:00
|
|
|
var resource = o.parsedJid.Resource
|
2016-01-06 15:51:12 +00:00
|
|
|
if resource != "" {
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<iq type='set' id='%s'><bind xmlns='%s'><resource>%s</resource></bind></iq>",
|
2019-06-26 15:14:52 +00:00
|
|
|
s.PacketId(), stanza.NSBind, resource)
|
2016-01-06 15:51:12 +00:00
|
|
|
} else {
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), stanza.NSBind)
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
var iq stanza.IQ
|
2019-10-18 18:29:54 +00:00
|
|
|
if s.err = s.transport.GetDecoder().Decode(&iq); s.err != nil {
|
2016-02-15 14:22:51 +00:00
|
|
|
s.err = errors.New("error decoding iq bind result: " + s.err.Error())
|
2016-01-06 15:51:12 +00:00
|
|
|
return
|
|
|
|
}
|
2016-02-15 14:22:51 +00:00
|
|
|
|
2018-01-15 11:28:34 +00:00
|
|
|
// TODO Check all elements
|
2019-06-19 08:21:57 +00:00
|
|
|
switch payload := iq.Payload.(type) {
|
2019-06-29 14:49:54 +00:00
|
|
|
case *stanza.Bind:
|
2016-02-15 14:22:51 +00:00
|
|
|
s.BindJid = payload.Jid // our local id (with possibly randomly generated resource
|
|
|
|
default:
|
|
|
|
s.err = errors.New("iq bind result missing")
|
|
|
|
}
|
|
|
|
|
2016-01-06 15:51:12 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-29 14:49:54 +00:00
|
|
|
// After the bind, if the session is not optional (as per old RFC 3921), we send the session open iq.
|
2018-09-26 14:25:04 +00:00
|
|
|
func (s *Session) rfc3921Session(o Config) {
|
2016-01-06 15:51:12 +00:00
|
|
|
if s.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:14:52 +00:00
|
|
|
var iq stanza.IQ
|
2019-06-29 15:48:38 +00:00
|
|
|
// We only negotiate session binding if it is mandatory, we skip it when optional.
|
2019-06-29 15:39:19 +00:00
|
|
|
if !s.Features.Session.IsOptional() {
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), stanza.NSSession)
|
|
|
|
if s.err = s.transport.GetDecoder().Decode(&iq); s.err != nil {
|
2017-10-04 23:27:35 +00:00
|
|
|
s.err = errors.New("expecting iq result after session open: " + s.err.Error())
|
|
|
|
return
|
|
|
|
}
|
2016-01-06 15:51:12 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-31 16:47:30 +00:00
|
|
|
|
|
|
|
// Enable stream management, with session resumption, if supported.
|
|
|
|
func (s *Session) EnableStreamManagement(o Config) {
|
|
|
|
if s.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !s.Features.DoesStreamManagement() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:29:54 +00:00
|
|
|
fmt.Fprintf(s.transport, "<enable xmlns='%s' resume='true'/>", stanza.NSStreamManagement)
|
2019-07-31 16:47:30 +00:00
|
|
|
|
|
|
|
var packet stanza.Packet
|
2019-10-18 18:29:54 +00:00
|
|
|
packet, s.err = stanza.NextPacket(s.transport.GetDecoder())
|
2019-07-31 16:47:30 +00:00
|
|
|
if s.err == nil {
|
|
|
|
switch p := packet.(type) {
|
|
|
|
case stanza.SMEnabled:
|
|
|
|
s.SMState = SMState{Id: p.Id}
|
|
|
|
case stanza.SMFailed:
|
2019-07-31 16:51:16 +00:00
|
|
|
// TODO: Store error in SMState, for later inspection
|
2019-07-31 16:47:30 +00:00
|
|
|
default:
|
|
|
|
s.err = errors.New("unexpected reply to SM enable")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|