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)
}
|