WebServiceApi.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Auth;
  16. using DnsServerCore.Dns;
  17. using DnsServerCore.Dns.ResourceRecords;
  18. using DnsServerCore.Dns.Zones;
  19. using Microsoft.AspNetCore.Http;
  20. using System;
  21. using System.Collections.Generic;
  22. using System.Net;
  23. using System.Net.Http;
  24. using System.Text.Json;
  25. using System.Threading.Tasks;
  26. using TechnitiumLibrary;
  27. using TechnitiumLibrary.Net;
  28. using TechnitiumLibrary.Net.Dns;
  29. using TechnitiumLibrary.Net.Dns.ResourceRecords;
  30. using TechnitiumLibrary.Net.Http.Client;
  31. using TechnitiumLibrary.Net.Proxy;
  32. namespace DnsServerCore
  33. {
  34. class WebServiceApi
  35. {
  36. #region variables
  37. readonly DnsWebService _dnsWebService;
  38. readonly Uri _updateCheckUri;
  39. string _checkForUpdateJsonData;
  40. DateTime _checkForUpdateJsonDataUpdatedOn;
  41. const int CHECK_FOR_UPDATE_JSON_DATA_CACHE_TIME_SECONDS = 3600;
  42. #endregion
  43. #region constructor
  44. public WebServiceApi(DnsWebService dnsWebService, Uri updateCheckUri)
  45. {
  46. _dnsWebService = dnsWebService;
  47. _updateCheckUri = updateCheckUri;
  48. }
  49. #endregion
  50. #region private
  51. private async Task<string> GetCheckForUpdateJsonData()
  52. {
  53. if ((_checkForUpdateJsonData is null) || (DateTime.UtcNow > _checkForUpdateJsonDataUpdatedOn.AddSeconds(CHECK_FOR_UPDATE_JSON_DATA_CACHE_TIME_SECONDS)))
  54. {
  55. SocketsHttpHandler handler = new SocketsHttpHandler();
  56. handler.Proxy = _dnsWebService.DnsServer.Proxy;
  57. handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null;
  58. handler.AutomaticDecompression = DecompressionMethods.All;
  59. using (HttpClient http = new HttpClient(new HttpClientNetworkHandler(handler, _dnsWebService.DnsServer.PreferIPv6 ? HttpClientNetworkType.PreferIPv6 : HttpClientNetworkType.Default, _dnsWebService.DnsServer)))
  60. {
  61. _checkForUpdateJsonData = await http.GetStringAsync(_updateCheckUri);
  62. _checkForUpdateJsonDataUpdatedOn = DateTime.UtcNow;
  63. }
  64. }
  65. return _checkForUpdateJsonData;
  66. }
  67. #endregion
  68. #region public
  69. public async Task CheckForUpdateAsync(HttpContext context)
  70. {
  71. Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
  72. if (_updateCheckUri is null)
  73. {
  74. jsonWriter.WriteBoolean("updateAvailable", false);
  75. return;
  76. }
  77. try
  78. {
  79. string jsonData = await GetCheckForUpdateJsonData();
  80. using JsonDocument jsonDocument = JsonDocument.Parse(jsonData);
  81. JsonElement jsonResponse = jsonDocument.RootElement;
  82. string updateVersion = jsonResponse.GetProperty("updateVersion").GetString();
  83. string updateTitle = jsonResponse.GetPropertyValue("updateTitle", null);
  84. string updateMessage = jsonResponse.GetPropertyValue("updateMessage", null);
  85. string downloadLink = jsonResponse.GetPropertyValue("downloadLink", null);
  86. string instructionsLink = jsonResponse.GetPropertyValue("instructionsLink", null);
  87. string changeLogLink = jsonResponse.GetPropertyValue("changeLogLink", null);
  88. bool updateAvailable = new Version(updateVersion) > _dnsWebService._currentVersion;
  89. jsonWriter.WriteBoolean("updateAvailable", updateAvailable);
  90. jsonWriter.WriteString("updateVersion", updateVersion);
  91. jsonWriter.WriteString("currentVersion", _dnsWebService.GetServerVersion());
  92. if (updateAvailable)
  93. {
  94. jsonWriter.WriteString("updateTitle", updateTitle);
  95. jsonWriter.WriteString("updateMessage", updateMessage);
  96. jsonWriter.WriteString("downloadLink", downloadLink);
  97. jsonWriter.WriteString("instructionsLink", instructionsLink);
  98. jsonWriter.WriteString("changeLogLink", changeLogLink);
  99. }
  100. string strLog = "Check for update was done {updateAvailable: " + updateAvailable + "; updateVersion: " + updateVersion + ";";
  101. if (!string.IsNullOrEmpty(updateTitle))
  102. strLog += " updateTitle: " + updateTitle + ";";
  103. if (!string.IsNullOrEmpty(updateMessage))
  104. strLog += " updateMessage: " + updateMessage + ";";
  105. if (!string.IsNullOrEmpty(downloadLink))
  106. strLog += " downloadLink: " + downloadLink + ";";
  107. if (!string.IsNullOrEmpty(instructionsLink))
  108. strLog += " instructionsLink: " + instructionsLink + ";";
  109. if (!string.IsNullOrEmpty(changeLogLink))
  110. strLog += " changeLogLink: " + changeLogLink + ";";
  111. strLog += "}";
  112. _dnsWebService._log.Write(context.GetRemoteEndPoint(), strLog);
  113. }
  114. catch (Exception ex)
  115. {
  116. _dnsWebService._log.Write(context.GetRemoteEndPoint(), "Check for update was done {updateAvailable: False;}\r\n" + ex.ToString());
  117. jsonWriter.WriteBoolean("updateAvailable", false);
  118. }
  119. }
  120. public async Task ResolveQueryAsync(HttpContext context)
  121. {
  122. UserSession session = context.GetCurrentSession();
  123. if (!_dnsWebService._authManager.IsPermitted(PermissionSection.DnsClient, session.User, PermissionFlag.View))
  124. throw new DnsWebServiceException("Access was denied.");
  125. HttpRequest request = context.Request;
  126. string server = request.GetQueryOrForm("server");
  127. string domain = request.GetQueryOrForm("domain").Trim(new char[] { '\t', ' ', '.' });
  128. DnsResourceRecordType type = request.GetQueryOrFormEnum<DnsResourceRecordType>("type");
  129. DnsTransportProtocol protocol = request.GetQueryOrFormEnum("protocol", DnsTransportProtocol.Udp);
  130. bool dnssecValidation = request.GetQueryOrForm("dnssec", bool.Parse, false);
  131. bool importResponse = request.GetQueryOrForm("import", bool.Parse, false);
  132. NetProxy proxy = _dnsWebService.DnsServer.Proxy;
  133. bool preferIPv6 = _dnsWebService.DnsServer.PreferIPv6;
  134. ushort udpPayloadSize = _dnsWebService.DnsServer.UdpPayloadSize;
  135. bool randomizeName = false;
  136. bool qnameMinimization = _dnsWebService.DnsServer.QnameMinimization;
  137. const int RETRIES = 1;
  138. const int TIMEOUT = 10000;
  139. DnsDatagram dnsResponse;
  140. string dnssecErrorMessage = null;
  141. if (server.Equals("recursive-resolver", StringComparison.OrdinalIgnoreCase))
  142. {
  143. if (type == DnsResourceRecordType.AXFR)
  144. throw new DnsServerException("Cannot do zone transfer (AXFR) for 'recursive-resolver'.");
  145. DnsQuestionRecord question;
  146. if ((type == DnsResourceRecordType.PTR) && IPAddress.TryParse(domain, out IPAddress address))
  147. question = new DnsQuestionRecord(address, DnsClass.IN);
  148. else
  149. question = new DnsQuestionRecord(domain, type, DnsClass.IN);
  150. DnsCache dnsCache = new DnsCache();
  151. dnsCache.MinimumRecordTtl = 0;
  152. dnsCache.MaximumRecordTtl = 7 * 24 * 60 * 60;
  153. try
  154. {
  155. dnsResponse = await DnsClient.RecursiveResolveAsync(question, dnsCache, proxy, preferIPv6, udpPayloadSize, randomizeName, qnameMinimization, false, dnssecValidation, null, RETRIES, TIMEOUT);
  156. }
  157. catch (DnsClientResponseDnssecValidationException ex)
  158. {
  159. dnsResponse = ex.Response;
  160. dnssecErrorMessage = ex.Message;
  161. importResponse = false;
  162. }
  163. }
  164. else if (server.Equals("system-dns", StringComparison.OrdinalIgnoreCase))
  165. {
  166. DnsClient dnsClient = new DnsClient();
  167. dnsClient.Proxy = proxy;
  168. dnsClient.PreferIPv6 = preferIPv6;
  169. dnsClient.RandomizeName = randomizeName;
  170. dnsClient.Retries = RETRIES;
  171. dnsClient.Timeout = TIMEOUT;
  172. dnsClient.UdpPayloadSize = udpPayloadSize;
  173. dnsClient.DnssecValidation = dnssecValidation;
  174. try
  175. {
  176. dnsResponse = await dnsClient.ResolveAsync(domain, type);
  177. }
  178. catch (DnsClientResponseDnssecValidationException ex)
  179. {
  180. dnsResponse = ex.Response;
  181. dnssecErrorMessage = ex.Message;
  182. importResponse = false;
  183. }
  184. }
  185. else
  186. {
  187. if ((type == DnsResourceRecordType.AXFR) && (protocol == DnsTransportProtocol.Udp))
  188. protocol = DnsTransportProtocol.Tcp;
  189. NameServerAddress nameServer;
  190. if (server.Equals("this-server", StringComparison.OrdinalIgnoreCase))
  191. {
  192. switch (protocol)
  193. {
  194. case DnsTransportProtocol.Udp:
  195. nameServer = _dnsWebService.DnsServer.ThisServer;
  196. break;
  197. case DnsTransportProtocol.Tcp:
  198. nameServer = _dnsWebService.DnsServer.ThisServer.ChangeProtocol(DnsTransportProtocol.Tcp);
  199. break;
  200. case DnsTransportProtocol.Tls:
  201. throw new DnsServerException("Cannot use DNS-over-TLS protocol for 'this-server'. Please use the TLS certificate domain name as the server.");
  202. case DnsTransportProtocol.Https:
  203. throw new DnsServerException("Cannot use DNS-over-HTTPS protocol for 'this-server'. Please use the TLS certificate domain name with a url as the server.");
  204. case DnsTransportProtocol.Quic:
  205. throw new DnsServerException("Cannot use DNS-over-QUIC protocol for 'this-server'. Please use the TLS certificate domain name as the server.");
  206. default:
  207. throw new NotSupportedException("DNS transport protocol is not supported: " + protocol.ToString());
  208. }
  209. proxy = null; //no proxy required for this server
  210. }
  211. else
  212. {
  213. nameServer = NameServerAddress.Parse(server);
  214. if (nameServer.Protocol != protocol)
  215. nameServer = nameServer.ChangeProtocol(protocol);
  216. if (nameServer.IsIPEndPointStale)
  217. {
  218. if (proxy is null)
  219. await nameServer.ResolveIPAddressAsync(_dnsWebService.DnsServer, _dnsWebService.DnsServer.PreferIPv6);
  220. }
  221. else if ((nameServer.DomainEndPoint is null) && ((protocol == DnsTransportProtocol.Udp) || (protocol == DnsTransportProtocol.Tcp)))
  222. {
  223. try
  224. {
  225. await nameServer.ResolveDomainNameAsync(_dnsWebService.DnsServer);
  226. }
  227. catch
  228. { }
  229. }
  230. }
  231. DnsClient dnsClient = new DnsClient(nameServer);
  232. dnsClient.Proxy = proxy;
  233. dnsClient.PreferIPv6 = preferIPv6;
  234. dnsClient.RandomizeName = randomizeName;
  235. dnsClient.Retries = RETRIES;
  236. dnsClient.Timeout = TIMEOUT;
  237. dnsClient.UdpPayloadSize = udpPayloadSize;
  238. dnsClient.DnssecValidation = dnssecValidation;
  239. if (dnssecValidation)
  240. {
  241. if ((type == DnsResourceRecordType.PTR) && IPAddress.TryParse(domain, out IPAddress ptrIp))
  242. domain = ptrIp.GetReverseDomain();
  243. //load trust anchors into dns client if domain is locally hosted
  244. _dnsWebService.DnsServer.AuthZoneManager.LoadTrustAnchorsTo(dnsClient, domain, type);
  245. }
  246. try
  247. {
  248. dnsResponse = await dnsClient.ResolveAsync(domain, type);
  249. }
  250. catch (DnsClientResponseDnssecValidationException ex)
  251. {
  252. dnsResponse = ex.Response;
  253. dnssecErrorMessage = ex.Message;
  254. importResponse = false;
  255. }
  256. if (type == DnsResourceRecordType.AXFR)
  257. dnsResponse = dnsResponse.Join();
  258. }
  259. if (importResponse)
  260. {
  261. AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.FindAuthZoneInfo(domain);
  262. if ((zoneInfo is null) || ((zoneInfo.Type == AuthZoneType.Secondary) && !zoneInfo.Name.Equals(domain, StringComparison.OrdinalIgnoreCase)))
  263. {
  264. if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify))
  265. throw new DnsWebServiceException("Access was denied.");
  266. zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsWebService.DnsServer.ServerDomain, false);
  267. if (zoneInfo is null)
  268. throw new DnsServerException("Cannot import records: failed to create primary zone.");
  269. //set permissions
  270. _dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete);
  271. _dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService._authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  272. _dnsWebService._authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService._authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete);
  273. _dnsWebService._authManager.SaveConfigFile();
  274. }
  275. else
  276. {
  277. if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify))
  278. throw new DnsWebServiceException("Access was denied.");
  279. switch (zoneInfo.Type)
  280. {
  281. case AuthZoneType.Primary:
  282. break;
  283. case AuthZoneType.Forwarder:
  284. if (type == DnsResourceRecordType.AXFR)
  285. throw new DnsServerException("Cannot import records via zone transfer: import zone must be of primary type.");
  286. break;
  287. default:
  288. throw new DnsServerException("Cannot import records: import zone must be of primary or forwarder type.");
  289. }
  290. }
  291. if (type == DnsResourceRecordType.AXFR)
  292. {
  293. _dnsWebService.DnsServer.AuthZoneManager.SyncZoneTransferRecords(zoneInfo.Name, dnsResponse.Answer);
  294. }
  295. else
  296. {
  297. List<DnsResourceRecord> importRecords = new List<DnsResourceRecord>(dnsResponse.Answer.Count + dnsResponse.Authority.Count);
  298. foreach (DnsResourceRecord record in dnsResponse.Answer)
  299. {
  300. if (record.Name.Equals(zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || (zoneInfo.Name.Length == 0))
  301. {
  302. record.RemoveExpiry();
  303. importRecords.Add(record);
  304. if (record.Type == DnsResourceRecordType.NS)
  305. record.SyncGlueRecords(dnsResponse.Additional);
  306. }
  307. }
  308. foreach (DnsResourceRecord record in dnsResponse.Authority)
  309. {
  310. if (record.Name.Equals(zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || record.Name.EndsWith("." + zoneInfo.Name, StringComparison.OrdinalIgnoreCase) || (zoneInfo.Name.Length == 0))
  311. {
  312. record.RemoveExpiry();
  313. importRecords.Add(record);
  314. if (record.Type == DnsResourceRecordType.NS)
  315. record.SyncGlueRecords(dnsResponse.Additional);
  316. }
  317. }
  318. _dnsWebService.DnsServer.AuthZoneManager.ImportRecords(zoneInfo.Name, importRecords);
  319. }
  320. _dnsWebService._log.Write(context.GetRemoteEndPoint(), "[" + session.User.Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; zone: " + zoneInfo.Name + "; type: " + type + ";}");
  321. _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name);
  322. }
  323. Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter();
  324. if (dnssecErrorMessage is not null)
  325. jsonWriter.WriteString("warningMessage", dnssecErrorMessage);
  326. jsonWriter.WritePropertyName("result");
  327. dnsResponse.SerializeTo(jsonWriter);
  328. }
  329. #endregion
  330. }
  331. }