Skip to content

Commit 863039a

Browse files
committed
finalize async update for autodiscover
1 parent 2a5c6bd commit 863039a

File tree

4 files changed

+1345
-1224
lines changed

4 files changed

+1345
-1224
lines changed
Lines changed: 162 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,170 @@
1-
import { DnsSrvRecord } from "../Dns/DnsSrvRecord";
2-
import { AutodiscoverService } from "./AutodiscoverService";
1+
import { AutodiscoverService } from "./AutodiscoverService";
2+
import { EwsLogging } from "../Core/EwsLogging";
3+
import { StringHelper } from "../ExtensionMethods";
4+
import { TraceFlags } from "../Enumerations/TraceFlags";
5+
6+
interface DnsSrvRecord {
7+
priority: number;
8+
weight: number;
9+
port: number;
10+
name: string;
11+
}
312

413
/**
514
* @internal Class that reads AutoDiscover configuration information from DNS.
615
*/
716
export class AutodiscoverDnsClient {
8-
//#region Constants
9-
/**
10-
* SRV DNS prefix to lookup.
11-
*
12-
* @static
13-
*/
14-
private static AutoDiscoverSrvPrefix: string = "_autodiscover._tcp.";
15-
16-
/**
17-
* We are only interested in records that use SSL.
18-
*
19-
* @static
20-
*/
21-
private static SslPort: number = 443;
22-
//#endregion
17+
//#region Constants
18+
/**
19+
* SRV DNS prefix to lookup.
20+
*
21+
* @static
22+
*/
23+
private static AutoDiscoverSrvPrefix: string = "_autodiscover._tcp.";
24+
25+
/**
26+
* We are only interested in records that use SSL.
27+
*
28+
* @static
29+
*/
30+
private static SslPort: number = 443;
31+
//#endregion
32+
33+
/**
34+
* Random selector in the case of ties.
35+
*
36+
* @static
37+
*/
38+
private static randomTieBreakerSelector: any;
39+
40+
/**
41+
* AutodiscoverService using this DNS reader.
42+
*/
43+
private service: AutodiscoverService;
44+
45+
/**
46+
* @internal Initializes a new instance of the **AutodiscoverDnsClient** class.
47+
*
48+
* @param {AutodiscoverService} service The service.
49+
*/
50+
constructor(service: AutodiscoverService) {
51+
this.service = service;
52+
}
2353

24-
/**
25-
* Random selector in the case of ties.
26-
*
27-
* @static
28-
*/
29-
private static randomTieBreakerSelector: any;
30-
31-
/**
32-
* AutodiscoverService using this DNS reader.
33-
*/
34-
private service: AutodiscoverService;
35-
36-
/**
37-
* Initializes a new instance of the **AutodiscoverDnsClient** class.
38-
*
39-
* @param {AutodiscoverService} service The service.
40-
*/
41-
constructor(service: AutodiscoverService) {
42-
this.service = service;
54+
//#region Instance methods
55+
56+
/**
57+
* @internal Finds the Autodiscover host from DNS SRV records.
58+
* @remarks If the domain to lookup is "contoso.com", Autodiscover will use DnsQuery on SRV records for "_autodiscover._tcp.contoso.com". If the query is successful it will return a target domain (e.g. "mail.contoso.com") which will be tried as an Autodiscover endpoint.
59+
* @param {string} domain The domain.
60+
* @return {Promise<string>} Autodiscover hostname (will be null if lookup failed).
61+
*/
62+
async FindAutodiscoverHostFromSrv(domain: string): Promise<string> {
63+
const domainToMatch: string = AutodiscoverDnsClient.AutoDiscoverSrvPrefix + domain;
64+
65+
const dnsSrvRecord: DnsSrvRecord = await this.FindBestMatchingSrvRecord(domainToMatch);
66+
67+
if ((dnsSrvRecord == null) || StringHelper.IsNullOrEmpty(dnsSrvRecord.name)) {
68+
this.service.TraceMessage(
69+
TraceFlags.AutodiscoverConfiguration,
70+
"No appropriate SRV record was found.");
71+
return null;
4372
}
44-
45-
FindAutodiscoverHostFromSrv(domain: string): string { throw new Error("AutodiscoverDnsClient.ts - FindAutodiscoverHostFromSrv : Not implemented."); }
46-
FindBestMatchingSrvRecord(domain: string): DnsSrvRecord { throw new Error("AutodiscoverDnsClient.ts - FindBestMatchingSrvRecord : Not implemented."); }
73+
74+
this.service.TraceMessage(
75+
TraceFlags.AutodiscoverConfiguration,
76+
StringHelper.Format("DNS query for SRV record for domain {0} found {1}", domain, dnsSrvRecord.name));
77+
78+
return dnsSrvRecord.name;
79+
80+
}
81+
82+
/**
83+
* Finds the best matching SRV record.
84+
*
85+
* @param {string} domain The domain.
86+
* @return {Promise<DnsSrvRecord>} DnsSrvRecord(will be null if lookup failed).
87+
*/
88+
private async FindBestMatchingSrvRecord(domain: string): Promise<DnsSrvRecord> {
89+
return new Promise((resolve, reject) => {
90+
91+
let dnsSrvRecordList: DnsSrvRecord[];
92+
let dns = null;
93+
try {
94+
// try to get the dns client, only works on nodejs, not valid in browser\
95+
dns = require("dns");
96+
} catch (error) {
97+
resolve(null);
98+
return;
99+
}
100+
101+
if (!StringHelper.IsNullOrEmpty(this.service.DnsServerAddress)) {
102+
const servers = dns.getServers();
103+
dns.setServers([this.service.DnsServerAddress, ...servers]);
104+
}
105+
106+
dns.resolveSrv(domain, (dnsError, dnsSrvRecordList) => {
107+
if (dnsError) {
108+
const dnsExcMessage = StringHelper.Format(
109+
"DnsQuery returned error error '{0}' error code '{1}'.",
110+
dnsError.message,
111+
dnsError.code || dnsError.errno);
112+
resolve(null);
113+
return;
114+
}
115+
this.service.TraceMessage(
116+
TraceFlags.AutodiscoverConfiguration,
117+
StringHelper.Format("{0} SRV records were returned.", (dnsSrvRecordList || []).length));
118+
119+
if (!dnsSrvRecordList || dnsSrvRecordList.length === 0) {
120+
resolve(null);
121+
return;
122+
}
123+
124+
// filter the addresses with ssl port
125+
dnsSrvRecordList = dnsSrvRecordList.filter(a => a.port === AutodiscoverDnsClient.SslPort);
126+
127+
// Records were returned but nothing matched our criteria.
128+
if (dnsSrvRecordList.length === 0) {
129+
this.service.TraceMessage(
130+
TraceFlags.AutodiscoverConfiguration,
131+
"No appropriate SRV records were found.");
132+
resolve(null);
133+
return;
134+
}
135+
136+
// sort all records with the same (highest) priority and weight.
137+
let bestDnsSrvRecordList = dnsSrvRecordList.sort((a, b) => a.priority === b.priority ? b.weight - a.weight : a.priority - b.priority);
138+
139+
// pick top one which has highest priority and highest weight value
140+
const priority = bestDnsSrvRecordList[0].priority;
141+
const weight = bestDnsSrvRecordList[0].weight;
142+
143+
// filter with highest priority and weight;
144+
bestDnsSrvRecordList = bestDnsSrvRecordList.filter(a => a.priority === priority && a.weight === weight);
145+
146+
// The list must contain at least one matching record since we found one earlier.
147+
EwsLogging.Assert(
148+
dnsSrvRecordList.length > 0,
149+
"AutodiscoverDnsClient.FindBestMatchingSrvRecord",
150+
"At least one DNS SRV record must match the criteria.");
151+
152+
// If we have multiple records with the same priority and weight, randomly pick one.
153+
const recordIndex = bestDnsSrvRecordList.length > 1 ? Math.floor(Math.random() * Math.floor(bestDnsSrvRecordList.length)) : 0;
154+
const bestDnsSrvRecord: DnsSrvRecord = bestDnsSrvRecordList[recordIndex];
155+
const traceMessage = StringHelper.Format(
156+
"Returning SRV record {0} of {1} records. Target: {2}, Priority: {3}, Weight: {4}",
157+
recordIndex,
158+
dnsSrvRecordList.length,
159+
bestDnsSrvRecord.name,
160+
bestDnsSrvRecord.priority,
161+
bestDnsSrvRecord.weight);
162+
this.service.TraceMessage(TraceFlags.AutodiscoverConfiguration, traceMessage);
163+
164+
resolve(bestDnsSrvRecord);
165+
});
166+
});
167+
168+
//#endregion
169+
}
47170
}

0 commit comments

Comments
 (0)