File: dialer.go

package info (click to toggle)
coyim 0.3.7-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 4,064 kB
  • ctags: 4,528
  • sloc: xml: 5,120; sh: 328; python: 286; makefile: 235; ruby: 51
file content (184 lines) | stat: -rw-r--r-- 4,299 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package xmpp

import (
	"encoding/xml"
	"io"
	"net"
	"strings"

	"github.com/twstrike/coyim/tls"
	"github.com/twstrike/coyim/xmpp/data"
	"github.com/twstrike/coyim/xmpp/interfaces"

	"golang.org/x/net/proxy"
)

// A dialer connects and authenticates to an XMPP server
type dialer struct {
	// JID represents the user's "bare JID" as specified in RFC 6120
	JID string

	// password used to authenticate to the server
	password string

	// serverAddress associates a particular FQDN with the origin domain specified by the JID.
	serverAddress string

	// proxy configures a proxy used to connect to the server
	proxy proxy.Dialer

	// config configures the XMPP protocol
	config data.Config

	verifier tls.Verifier
}

// DialerFactory returns a new xmpp dialer
func DialerFactory(verifier tls.Verifier) interfaces.Dialer {
	return &dialer{verifier: verifier}
}

func (d *dialer) SetJID(v string) {
	d.JID = v
}

func (d *dialer) SetServerAddress(v string) {
	d.serverAddress = v
}

func (d *dialer) SetPassword(v string) {
	d.password = v
}

func (d *dialer) SetProxy(v proxy.Dialer) {
	d.proxy = v
}

func (d *dialer) SetConfig(v data.Config) {
	d.config = v
}

func (d *dialer) Config() data.Config {
	return d.config
}

func (d *dialer) ServerAddress() string {
	return d.serverAddress
}

func (d *dialer) hasCustomServer() bool {
	return d.serverAddress != ""
}

func (d *dialer) getJIDLocalpart() string {
	parts := strings.SplitN(d.JID, "@", 2)
	return parts[0]
}

func (d *dialer) getJIDDomainpart() string {
	//TODO: remove any existing resourcepart although our doc says it is a bare JID (without resourcepart) but it would be nice
	parts := strings.SplitN(d.JID, "@", 2)
	return parts[1]
}

// GetServer returns the "hardcoded" server chosen if available, otherwise returns the domainpart from the JID. The server contains port information
func (d *dialer) GetServer() string {
	if d.hasCustomServer() {
		return d.serverAddress
	}

	return d.getFallbackServer()
}

func (d *dialer) getFallbackServer() string {
	return net.JoinHostPort(d.getJIDDomainpart(), "5222")
}

// RegisterAccount registers an account on the server. The formCallback is used to handle XMPP forms.
func (d *dialer) RegisterAccount(formCallback data.FormCallback) (interfaces.Conn, error) {
	//TODO: notify in case the feature is not supported
	d.config.CreateCallback = formCallback
	return d.Dial()
}

// Dial creates a new connection to an XMPP server with the given proxy
// and authenticates as the given user.
func (d *dialer) Dial() (interfaces.Conn, error) {
	// Starting an XMPP connection comprises two parts:
	// - Opening a transport channel (TCP)
	// - Opening an XML stream over the transport channel

	// RFC 6120, section 3
	conn, err := d.newTCPConn()
	if err != nil {
		return nil, err
	}

	// RFC 6120, section 4
	return d.setupStream(conn)
}

// RFC 6120, Section 4.2
func (d *dialer) setupStream(conn net.Conn) (interfaces.Conn, error) {
	c := newConn()
	c.config = d.config
	c.originDomain = d.getJIDDomainpart()
	d.bindTransport(c, conn)

	if err := d.negotiateStreamFeatures(c, conn); err != nil {
		return nil, err
	}

	go c.watchKeepAlive(conn)
	go c.watchPings()

	return c, nil
}

// RFC 6120, section 4.3.2
func (d *dialer) negotiateStreamFeatures(c interfaces.Conn, conn net.Conn) error {
	if err := c.SendInitialStreamHeader(); err != nil {
		return err
	}

	// STARTTLS MUST be the first feature to be negotiated
	if err := d.negotiateSTARTTLS(c, conn); err != nil {
		return err
	}

	if registered, err := d.negotiateInBandRegistration(c); err != nil || registered {
		return err
	}

	// SASL negotiation. RFC 6120, section 6
	if err := d.negotiateSASL(c); err != nil {
		return err
	}

	//TODO: negotiate other features

	return nil
}

func (d *dialer) bindTransport(c interfaces.Conn, conn net.Conn) {
	c.SetInOut(makeInOut(conn, d.config))
	c.SetRawOut(conn)
	c.SetKeepaliveOut(&timeoutableConn{conn, keepaliveTimeout})
	c.SetServerAddress(d.serverAddress)
}

func makeInOut(conn io.ReadWriter, config data.Config) (in *xml.Decoder, out io.Writer) {
	if config.InLog != nil {
		in = xml.NewDecoder(io.TeeReader(conn, config.InLog))
	} else {
		in = xml.NewDecoder(conn)
	}

	if config.OutLog != nil {
		out = io.MultiWriter(conn, config.OutLog)
	} else {
		out = conn
	}

	return
}