File: connect.go

package info (click to toggle)
go-sendxmpp 0.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 332 kB
  • sloc: makefile: 11
file content (144 lines) | stat: -rw-r--r-- 5,038 bytes parent folder | download | duplicates (2)
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
// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"log/slog"
	"net"
	"os"
	"strings"
	"time"

	"github.com/xmppo/go-xmpp"        // BSD-3-Clause
	"golang.org/x/net/idna"           // BSD-3-Clause
	"salsa.debian.org/mdosch/xmppsrv" // BSD-2-Clause
)

func connect(options xmpp.Options, directTLS bool, anon bool, retryWait int, retryMax int) (client *xmpp.Client, err error) {
	var server string
	proxy := os.Getenv("HTTP_PROXY")
	slog.Info("getting environment variable:", "HTTP_PROXY", proxy)
	if !anon {
		server = options.User[strings.Index(options.User, "@")+1:]
	} else {
		server = options.User
	}
	retry := true
	for i := 0; retry; i++ {
		// Look up SRV records or hostmeta2 if server is not specified manually
		// or if anon authentication is used.
		if options.Host == "" || anon {
			// Don't do SRV or hostmeta2 look ups if proxy is set.
			// TODO: 2024-10-30: Support hostmeta2 look ups using proxy.
			if proxy == "" {
				// Look up xmpp-client SRV records.
				slog.Info("looking up client SRV records for", "server", server)
				serverASCII, err := idna.ToASCII(server)
				if err != nil {
					return client, err
				}
				srvMixed, err := xmppsrv.LookupClient(serverASCII)
				if len(srvMixed) > 0 && err == nil {
					for _, adr := range srvMixed {
						switch {
						case directTLS && adr.Type == "xmpp-client":
							slog.Info("skipping as direct TLS is requested:", "type", adr.Type,
								"target", adr.Target, "port", adr.Port)
							continue
						case adr.Type == "xmpp-client":
							// Use StartTLS
							options.NoTLS = true
							slog.Info("received SRV record:", "type", adr.Type, "target", adr.Target, "port", adr.Port)
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
							options.StartTLS = true
						case adr.Type == "xmpps-client":
							// Use direct TLS
							options.NoTLS = false
							options.StartTLS = false
							slog.Info("received SRV record::", "type", adr.Type, "target", adr.Target, "port", adr.Port)
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
						default:
							continue
						}
						options.Host = net.JoinHostPort(adr.Target, fmt.Sprint(adr.Port))
						// Connect to server
						slog.Info("connecting to", "host", options.Host)
						client, err = options.NewClient()
						if err == nil || strings.Contains(err.Error(), "not-authorized") {
							// Don't try to connect on other port if error contains
							// "not-authorized".
							return client, err
						}
						slog.Info("connecting failed")
					}
				}
				// Look up hostmeta2 file.
				slog.Info("looking up host meta 2 for", "server", server)
				hm2, httpStatus, err := xmppsrv.LookupHostmeta2(serverASCII)
				if httpStatus != 404 && err != nil {
					for _, link := range hm2.Links {
						if link.Rel == nsC2SdTLS {
							options.NoTLS = false
							options.StartTLS = false
							slog.Info("setting xmpp connection option:", "NoTLS", options.NoTLS)
							slog.Info("setting xmpp connection option:", "StartTLS", options.StartTLS)
							options.Host = net.JoinHostPort(link.Sni, fmt.Sprint(link.Port))
							// Connect to server
							slog.Info("connecting to", "host", options.Host)
							client, err = options.NewClient()
							if err == nil || strings.Contains(err.Error(), "not-authorized") {
								// Don't try to connect on other port if error contains
								// "not-authorized".
								return client, err
							}
						}
					}
				}
			}
		}
		_, port, _ := net.SplitHostPort(options.Host)
		if port == "" {
			if options.Host == "" {
				options.Host = server
			}
			// Try port 5223 if directTLS is set and no port is provided.
			if directTLS {
				options.NoTLS = false
				options.StartTLS = false
				options.Host = net.JoinHostPort(options.Host, "5223")
				slog.Info("trying direct TLS fallback on port 5223")
			} else {
				// Try port 5222 if no port is provided and directTLS is not set.
				options.NoTLS = true
				options.StartTLS = true
				options.Host = net.JoinHostPort(options.Host, "5222")
				slog.Info("trying StartTLS fallback on port 5222")
			}
		}
		// Connect to server
		slog.Info("connecting to", "host", options.Host)
		client, err = options.NewClient()
		if err == nil {
			return
		}
		if retryWait == 0 {
			slog.Info("giving up trying to connect…")
			break
		}
		if retryMax != 0 && retryMax == i {
			retry = false
		}
		// Reset options.Host as otherwise DNS look up is skipped.
		options.Host = ""
		sleepDuration := time.Duration(retryWait) * time.Second
		slog.Info("connecting failed, retry after", "sleep", sleepDuration)
		time.Sleep(sleepDuration)
		slog.Info("connecting to server:", "retry", i)
	}
	return client, fmt.Errorf("connect: failed to connect to server: %v", err)
}