1package xmpp
2
3import (
4	"log"
5	"net"
6	"time"
7
8	ourNet "github.com/coyim/coyim/net"
9	"github.com/coyim/coyim/xmpp/errors"
10	"golang.org/x/net/proxy"
11)
12
13const defaultDialTimeout = 60 * time.Second
14
15func (d *dialer) newTCPConn() (net.Conn, error) {
16	if d.proxy == nil {
17		d.proxy = proxy.Direct
18	}
19
20	//libpurple and xmpp-client are strict to section 3.2.3 and skip SRV lookup
21	//whenever the user has configured a custom server address.
22	//This is necessary to keep imported accounts from Adium/Pidgin/xmpp-client
23	//working as expected.
24	if d.hasCustomServer() {
25		d.config.SkipSRVLookup = true
26	}
27
28	//RFC 6120, Section 3.2.3
29	//See: https://xmpp.org/rfcs/rfc6120.html#tcp-resolution-srvnot
30	if d.config.SkipSRVLookup {
31		log.Println("Skipping SRV lookup")
32		return connectWithProxy(d.GetServer(), d.proxy)
33	}
34
35	return d.srvLookupAndFallback()
36}
37
38func (d *dialer) srvLookupAndFallback() (net.Conn, error) {
39	host := d.getJIDDomainpart()
40	log.Println("Make SRV lookup to:", host)
41	xmppAddrs, err := ResolveSRVWithProxy(d.proxy, host)
42
43	//Every other error means
44	//"the initiating entity [did] not receive a response to its SRV query" and
45	//we should use the fallback method
46	//See RFC 6120, Section 3.2.1, item 9
47	if err == ErrServiceNotAvailable {
48		return nil, err
49	}
50
51	//RFC 6120, Section 3.2.1, item 9
52	//If the SRV has no response, we fallback to use the origin domain
53	//at default port.
54	if len(xmppAddrs) == 0 {
55		err = errors.ErrTCPBindingFailed
56
57		//TODO: in this case, a failure to connect might be recovered using HTTP binding
58		//See: RFC 6120, Section 3.2.2
59		xmppAddrs = []string{d.getFallbackServer()}
60	} else {
61		//The SRV lookup succeeded but we failed to connect
62		err = errors.ErrConnectionFailed
63	}
64
65	conn, _, e := connectToFirstAvailable(xmppAddrs, d.proxy)
66	if e != nil {
67		return nil, err
68	}
69
70	return conn, nil
71}
72
73func connectToFirstAvailable(xmppAddrs []string, dialer proxy.Dialer) (net.Conn, string, error) {
74	for _, addr := range xmppAddrs {
75		conn, err := connectWithProxy(addr, dialer)
76		if err == nil {
77			return conn, addr, nil
78		}
79	}
80
81	return nil, "", errors.ErrConnectionFailed
82}
83
84func dialTimeout(network, addr string, dialer proxy.Dialer, t time.Duration) (c net.Conn, err error) {
85	result := make(chan bool, 1)
86
87	go func() {
88		c, err = dialer.Dial(network, addr)
89		result <- true
90	}()
91
92	select {
93	case <-time.After(t):
94		log.Println("tcp: dial timed out")
95		return nil, ourNet.ErrTimeout
96	case <-result:
97		return
98	}
99}
100
101func connectWithProxy(addr string, dialer proxy.Dialer) (conn net.Conn, err error) {
102	log.Printf("Connecting to %s\n", addr)
103
104	//TODO: It is not clear to me if this follows
105	//RFC 6120, Section 3.2.1, item 6
106	//See: https://xmpp.org/rfcs/rfc6120.html#tcp-resolution
107	conn, err = dialTimeout("tcp", addr, dialer, defaultDialTimeout)
108	if err != nil {
109		if err == ourNet.ErrTimeout {
110			return nil, err
111		}
112
113		return nil, errors.CreateErrFailedToConnect(addr, err)
114	}
115
116	return
117}
118