1/* Copyright 2016-2017 Vector Creations Ltd
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package gomatrixserverlib
17
18import (
19	"fmt"
20	"net"
21	"strconv"
22)
23
24// ResolutionResult is a result of looking up a Matrix homeserver according to
25// the federation specification.
26type ResolutionResult struct {
27	Destination   string     // The hostname and port to send federation requests to.
28	Host          ServerName // The value of the Host headers.
29	TLSServerName string     // The TLS server name to request a certificate for.
30}
31
32// ResolveServer implements the server name resolution algorithm described at
33// https://matrix.org/docs/spec/server_server/r0.1.1.html#resolving-server-names
34// Returns a slice of ResolutionResult that can be used to send a federation
35// request to the server using a given server name.
36// Returns an error if the server name isn't valid.
37func ResolveServer(serverName ServerName) (results []ResolutionResult, err error) {
38	return resolveServer(serverName, true)
39}
40
41// resolveServer does the same thing as ResolveServer, except it also requires
42// the checkWellKnown parameter, which indicates whether a .well-known file
43// should be looked up.
44func resolveServer(serverName ServerName, checkWellKnown bool) (results []ResolutionResult, err error) {
45	host, port, valid := ParseAndValidateServerName(serverName)
46	if !valid {
47		err = fmt.Errorf("Invalid server name")
48		return
49	}
50
51	// 1. If the hostname is an IP literal
52	// Check if we're dealing with an IPv6 literal with square brackets. If so,
53	// remove the brackets.
54	if host[0] == '[' && host[len(host)-1] == ']' {
55		host = host[1 : len(host)-1]
56	}
57	if net.ParseIP(host) != nil {
58		var destination string
59
60		if port == -1 {
61			destination = net.JoinHostPort(host, strconv.Itoa(8448))
62		} else {
63			destination = string(serverName)
64		}
65
66		results = []ResolutionResult{
67			ResolutionResult{
68				Destination:   destination,
69				Host:          serverName,
70				TLSServerName: host,
71			},
72		}
73
74		return
75	}
76
77	// 2. If the hostname is not an IP literal, and the server name includes an
78	// explicit port
79	if port != -1 {
80		results = []ResolutionResult{
81			ResolutionResult{
82				Destination:   string(serverName),
83				Host:          serverName,
84				TLSServerName: host,
85			},
86		}
87
88		return
89	}
90
91	if checkWellKnown {
92		// 3. If the hostname is not an IP literal
93		var result *WellKnownResult
94		result, err = LookupWellKnown(serverName)
95		if err == nil {
96			// We don't want to check .well-known on the result
97			return resolveServer(result.NewAddress, false)
98		}
99	}
100
101	return handleNoWellKnown(serverName), nil
102}
103
104// handleNoWellKnown implements steps 4 and 5 of the resolution algorithm (as
105// well as 3.3 and 3.4)
106func handleNoWellKnown(serverName ServerName) (results []ResolutionResult) {
107	// 4. If the /.well-known request resulted in an error response
108	_, records, err := net.LookupSRV("matrix", "tcp", string(serverName))
109	if err == nil && len(records) > 0 {
110		for _, rec := range records {
111			// If the domain is a FQDN, remove the trailing dot at the end. This
112			// isn't critical to send the request, as Go's HTTP client and most
113			// servers understand FQDNs quite well, but it makes automated
114			// testing easier.
115			target := rec.Target
116			if target[len(target)-1] == '.' {
117				target = target[:len(target)-1]
118			}
119
120			results = append(results, ResolutionResult{
121				Destination:   fmt.Sprintf("%s:%d", target, rec.Port),
122				Host:          serverName,
123				TLSServerName: string(serverName),
124			})
125		}
126
127		return
128	}
129
130	// 5. If the /.well-known request returned an error response, and the SRV
131	// record was not found
132	results = []ResolutionResult{
133		ResolutionResult{
134			Destination:   fmt.Sprintf("%s:%d", serverName, 8448),
135			Host:          serverName,
136			TLSServerName: string(serverName),
137		},
138	}
139
140	return
141}
142