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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* $RCSfile: DNSUtil.java,v $
* $Revision: 2867 $
* $Date: 2005-09-22 03:40:04 -0300 (Thu, 22 Sep 2005) $
*
* Copyright (C) 2004-2005 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.wildfire.net;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;
/**
* Utilty class to perform DNS lookups for XMPP services.
*
* @author Matt Tucker
*/
public class DNSUtil {
private static DirContext context;
/**
* Internal DNS that allows to specify target IP addresses and ports to use for domains.
* The internal DNS will be checked up before performing an actual DNS SRV lookup.
*/
private static Map<String, HostAddress> dnsOverride;
static {
try {
Hashtable<String,String> env = new Hashtable<String,String>();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
context = new InitialDirContext(env);
String property = JiveGlobals.getProperty("dnsutil.dnsOverride");
if (property != null) {
dnsOverride = decode(property);
}
}
catch (Exception e) {
Log.error(e);
}
}
/**
* Returns the host name and port that the specified XMPP server can be
* reached at for server-to-server communication. A DNS lookup for a SRV
* record in the form "_xmpp-server._tcp.example.com" is attempted, according
* to section 14.4 of RFC 3920. If that lookup fails, a lookup in the older form
* of "_jabber._tcp.example.com" is attempted since servers that implement an
* older version of the protocol may be listed using that notation. If that
* lookup fails as well, it's assumed that the XMPP server lives at the
* host resolved by a DNS lookup at the specified domain on the specified default port.<p>
*
* As an example, a lookup for "example.com" may return "im.example.com:5269".
*
* @param domain the domain.
* @param defaultPort default port to return if the DNS look up fails.
* @return a HostAddress, which encompasses the hostname and port that the XMPP
* server can be reached at for the specified domain.
*/
public static HostAddress resolveXMPPServerDomain(String domain, int defaultPort) {
// Check if there is an entry in the internal DNS for the specified domain
if (dnsOverride != null) {
HostAddress hostAddress = dnsOverride.get(domain);
if (hostAddress != null) {
return hostAddress;
}
}
if (context == null) {
return new HostAddress(domain, defaultPort);
}
String host = domain;
int port = defaultPort;
try {
Attributes dnsLookup =
context.getAttributes("_xmpp-server._tcp." + domain, new String[]{"SRV"});
String srvRecord = (String)dnsLookup.get("SRV").get();
String [] srvRecordEntries = srvRecord.split(" ");
port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);
host = srvRecordEntries[srvRecordEntries.length-1];
}
catch (Exception e) {
// Attempt lookup with older "jabber" name.
try {
Attributes dnsLookup =
context.getAttributes("_jabber._tcp." + domain, new String[]{"SRV"});
String srvRecord = (String)dnsLookup.get("SRV").get();
String [] srvRecordEntries = srvRecord.split(" ");
port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length-2]);
host = srvRecordEntries[srvRecordEntries.length-1];
}
catch (Exception e2) {
// Do nothing
}
}
// Host entries in DNS should end with a ".".
if (host.endsWith(".")) {
host = host.substring(0, host.length()-1);
}
return new HostAddress(host, port);
}
/**
* Returns the internal DNS that allows to specify target IP addresses and ports
* to use for domains. The internal DNS will be checked up before performing an
* actual DNS SRV lookup.
*
* @return the internal DNS that allows to specify target IP addresses and ports
* to use for domains.
*/
public static Map<String, HostAddress> getDnsOverride() {
return dnsOverride;
}
/**
* Sets the internal DNS that allows to specify target IP addresses and ports
* to use for domains. The internal DNS will be checked up before performing an
* actual DNS SRV lookup.
*
* @param dnsOverride the internal DNS that allows to specify target IP addresses and ports
* to use for domains.
*/
public static void setDnsOverride(Map<String, HostAddress> dnsOverride) {
DNSUtil.dnsOverride = dnsOverride;
JiveGlobals.setProperty("dnsutil.dnsOverride", encode(dnsOverride));
}
private static String encode(Map<String, HostAddress> internalDNS) {
if (internalDNS == null) {
return "";
}
StringBuilder sb = new StringBuilder(100);
for (String key : internalDNS.keySet()) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append("{").append(key).append(",");
sb.append(internalDNS.get(key).getHost()).append(":");
sb.append(internalDNS.get(key).getPort()).append("}");
}
return sb.toString();
}
private static Map<String, HostAddress> decode(String encodedValue) {
Map<String, HostAddress> answer = new HashMap<String, HostAddress>();
StringTokenizer st = new StringTokenizer(encodedValue, "{},:");
while (st.hasMoreElements()) {
String key = st.nextToken();
answer.put(key, new HostAddress(st.nextToken(), Integer.parseInt(st.nextToken())));
}
return answer;
}
/**
* Encapsulates a hostname and port.
*/
public static class HostAddress {
private String host;
private int port;
private HostAddress(String host, int port) {
this.host = host;
this.port = port;
}
/**
* Returns the hostname.
*
* @return the hostname.
*/
public String getHost() {
return host;
}
/**
* Returns the port.
*
* @return the port.
*/
public int getPort() {
return port;
}
public String toString() {
return host + ":" + port;
}
}
}