/* Technitium DNS Server Copyright (C) 2025 Shreyas Zare (shreyas@technitium.com) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ using DnsServerCore.ApplicationCommon; using DnsServerCore.Auth; using DnsServerCore.Dns.Applications; using Microsoft.AspNetCore.Http; using System; using System.Globalization; using System.IO; using System.Net; using System.Text; using System.Text.Json; using System.Threading.Tasks; using TechnitiumLibrary.Net; using TechnitiumLibrary.Net.Dns; using TechnitiumLibrary.Net.Dns.ResourceRecords; namespace DnsServerCore { public partial class DnsWebService { class WebServiceLogsApi { #region variables readonly DnsWebService _dnsWebService; #endregion #region constructor public WebServiceLogsApi(DnsWebService dnsWebService) { _dnsWebService = dnsWebService; } #endregion #region public public void ListLogs(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) throw new DnsWebServiceException("Access was denied."); string[] logFiles = _dnsWebService._log.ListLogFiles(); Array.Sort(logFiles); Array.Reverse(logFiles); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); jsonWriter.WritePropertyName("logFiles"); jsonWriter.WriteStartArray(); foreach (string logFile in logFiles) { jsonWriter.WriteStartObject(); jsonWriter.WriteString("fileName", Path.GetFileNameWithoutExtension(logFile)); jsonWriter.WriteString("size", WebUtilities.GetFormattedSize(new FileInfo(logFile).Length)); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } public Task DownloadLogAsync(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string fileName = request.GetQueryOrForm("fileName"); int limit = request.GetQueryOrForm("limit", int.Parse, 0); return _dnsWebService._log.DownloadLogAsync(context, fileName, limit * 1024 * 1024); } public void DeleteLog(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string log = request.GetQueryOrForm("log"); _dnsWebService._log.DeleteLog(log); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + session.User.Username + "] Log file was deleted: " + log); } public void DeleteAllLogs(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); _dnsWebService._log.DeleteAllLogs(); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + session.User.Username + "] All log files were deleted."); } public void DeleteAllStats(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Dashboard, session.User, PermissionFlag.Delete)) throw new DnsWebServiceException("Access was denied."); _dnsWebService._dnsServer.StatsManager.DeleteAllStats(); _dnsWebService._log.Write(context.GetRemoteEndPoint(_dnsWebService._webServiceRealIpHeader), "[" + session.User.Username + "] All stats files were deleted."); } public async Task QueryLogsAsync(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string name = request.GetQueryOrForm("name"); string classPath = request.GetQueryOrForm("classPath"); if (!_dnsWebService._dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application)) throw new DnsWebServiceException("DNS application was not found: " + name); if (!application.DnsQueryLogs.TryGetValue(classPath, out IDnsQueryLogs queryLogs)) throw new DnsWebServiceException("DNS application '" + classPath + "' class path was not found: " + name); long pageNumber = request.GetQueryOrForm("pageNumber", long.Parse, 1); int entriesPerPage = request.GetQueryOrForm("entriesPerPage", int.Parse, 25); bool descendingOrder = request.GetQueryOrForm("descendingOrder", bool.Parse, true); DateTime? start = null; string strStart = request.QueryOrForm("start"); if (!string.IsNullOrEmpty(strStart)) start = DateTime.Parse(strStart, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); DateTime? end = null; string strEnd = request.QueryOrForm("end"); if (!string.IsNullOrEmpty(strEnd)) end = DateTime.Parse(strEnd, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); IPAddress clientIpAddress = request.GetQueryOrForm("clientIpAddress", IPAddress.Parse, null); DnsTransportProtocol? protocol = null; string strProtocol = request.QueryOrForm("protocol"); if (!string.IsNullOrEmpty(strProtocol)) protocol = Enum.Parse(strProtocol, true); DnsServerResponseType? responseType = null; string strResponseType = request.QueryOrForm("responseType"); if (!string.IsNullOrEmpty(strResponseType)) responseType = Enum.Parse(strResponseType, true); DnsResponseCode? rcode = null; string strRcode = request.QueryOrForm("rcode"); if (!string.IsNullOrEmpty(strRcode)) rcode = Enum.Parse(strRcode, true); string qname = request.GetQueryOrForm("qname", null); DnsResourceRecordType? qtype = null; string strQtype = request.QueryOrForm("qtype"); if (!string.IsNullOrEmpty(strQtype)) qtype = Enum.Parse(strQtype, true); DnsClass? qclass = null; string strQclass = request.QueryOrForm("qclass"); if (!string.IsNullOrEmpty(strQclass)) qclass = Enum.Parse(strQclass, true); DnsLogPage page = await queryLogs.QueryLogsAsync(pageNumber, entriesPerPage, descendingOrder, start, end, clientIpAddress, protocol, responseType, rcode, qname, qtype, qclass); Utf8JsonWriter jsonWriter = context.GetCurrentJsonWriter(); jsonWriter.WriteNumber("pageNumber", page.PageNumber); jsonWriter.WriteNumber("totalPages", page.TotalPages); jsonWriter.WriteNumber("totalEntries", page.TotalEntries); jsonWriter.WritePropertyName("entries"); jsonWriter.WriteStartArray(); foreach (DnsLogEntry entry in page.Entries) { jsonWriter.WriteStartObject(); jsonWriter.WriteNumber("rowNumber", entry.RowNumber); jsonWriter.WriteString("timestamp", entry.Timestamp); jsonWriter.WriteString("clientIpAddress", entry.ClientIpAddress.ToString()); jsonWriter.WriteString("protocol", entry.Protocol.ToString()); jsonWriter.WriteString("responseType", entry.ResponseType.ToString()); if (entry.ResponseRtt.HasValue) jsonWriter.WriteNumber("responseRtt", entry.ResponseRtt.Value); jsonWriter.WriteString("rcode", entry.RCODE.ToString()); jsonWriter.WriteString("qname", entry.Question?.Name); jsonWriter.WriteString("qtype", entry.Question?.Type.ToString()); jsonWriter.WriteString("qclass", entry.Question?.Class.ToString()); jsonWriter.WriteString("answer", entry.Answer); jsonWriter.WriteEndObject(); } jsonWriter.WriteEndArray(); } public async Task ExportLogsAsync(HttpContext context) { UserSession session = context.GetCurrentSession(); if (!_dnsWebService._authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) throw new DnsWebServiceException("Access was denied."); HttpRequest request = context.Request; string name = request.GetQueryOrForm("name"); string classPath = request.GetQueryOrForm("classPath"); if (!_dnsWebService._dnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application)) throw new DnsWebServiceException("DNS application was not found: " + name); if (!application.DnsQueryLogs.TryGetValue(classPath, out IDnsQueryLogs queryLogs)) throw new DnsWebServiceException("DNS application '" + classPath + "' class path was not found: " + name); DateTime? start = null; string strStart = request.QueryOrForm("start"); if (!string.IsNullOrEmpty(strStart)) start = DateTime.Parse(strStart, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); DateTime? end = null; string strEnd = request.QueryOrForm("end"); if (!string.IsNullOrEmpty(strEnd)) end = DateTime.Parse(strEnd, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); IPAddress clientIpAddress = request.GetQueryOrForm("clientIpAddress", IPAddress.Parse, null); DnsTransportProtocol? protocol = null; string strProtocol = request.QueryOrForm("protocol"); if (!string.IsNullOrEmpty(strProtocol)) protocol = Enum.Parse(strProtocol, true); DnsServerResponseType? responseType = null; string strResponseType = request.QueryOrForm("responseType"); if (!string.IsNullOrEmpty(strResponseType)) responseType = Enum.Parse(strResponseType, true); DnsResponseCode? rcode = null; string strRcode = request.QueryOrForm("rcode"); if (!string.IsNullOrEmpty(strRcode)) rcode = Enum.Parse(strRcode, true); string qname = request.GetQueryOrForm("qname", null); DnsResourceRecordType? qtype = null; string strQtype = request.QueryOrForm("qtype"); if (!string.IsNullOrEmpty(strQtype)) qtype = Enum.Parse(strQtype, true); DnsClass? qclass = null; string strQclass = request.QueryOrForm("qclass"); if (!string.IsNullOrEmpty(strQclass)) qclass = Enum.Parse(strQclass, true); static async Task WriteCsvFieldAsync(StreamWriter sW, string data) { if ((data is null) || (data.Length == 0)) return; if (data.Contains('"', StringComparison.OrdinalIgnoreCase)) { await sW.WriteAsync('"'); await sW.WriteAsync(data.Replace("\"", "\"\"")); await sW.WriteAsync('"'); } else if (data.Contains(',', StringComparison.OrdinalIgnoreCase) || data.Contains(' ', StringComparison.OrdinalIgnoreCase)) { await sW.WriteAsync('"'); await sW.WriteAsync(data); await sW.WriteAsync('"'); } else { await sW.WriteAsync(data); } } DnsLogPage page; long pageNumber = 1; string tmpFile = Path.GetTempFileName(); try { using (FileStream csvFileStream = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) { StreamWriter sW = new StreamWriter(csvFileStream, Encoding.UTF8); await sW.WriteLineAsync("RowNumber,Timestamp,ClientIpAddress,Protocol,ResponseType,ResponseRtt,RCODE,Domain,Type,Class,Answer"); do { page = await queryLogs.QueryLogsAsync(pageNumber, 10000, false, start, end, clientIpAddress, protocol, responseType, rcode, qname, qtype, qclass); foreach (DnsLogEntry entry in page.Entries) { await WriteCsvFieldAsync(sW, entry.RowNumber.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Timestamp.ToString("O")); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.ClientIpAddress.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Protocol.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.ResponseType.ToString()); await sW.WriteAsync(','); if (entry.ResponseRtt.HasValue) await WriteCsvFieldAsync(sW, entry.ResponseRtt.Value.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.RCODE.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Question?.Name.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Question?.Type.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Question?.Class.ToString()); await sW.WriteAsync(','); await WriteCsvFieldAsync(sW, entry.Answer); await sW.WriteLineAsync(); } } while (pageNumber++ < page.TotalPages); await sW.FlushAsync(); //send csv file csvFileStream.Position = 0; HttpResponse response = context.Response; response.ContentType = "text/csv"; response.ContentLength = csvFileStream.Length; response.Headers.ContentDisposition = "attachment;filename=" + _dnsWebService._dnsServer.ServerDomain + DateTime.UtcNow.ToString("_yyyy-MM-dd_HH-mm-ss") + "_query_logs.csv"; using (Stream output = response.Body) { await csvFileStream.CopyToAsync(output); } } } finally { try { File.Delete(tmpFile); } catch (Exception ex) { _dnsWebService._log.Write(ex); } } } #endregion } } }