diff --git a/APIDOCS.md b/APIDOCS.md index 060e2fd5..251bacb6 100644 --- a/APIDOCS.md +++ b/APIDOCS.md @@ -43,28 +43,127 @@ The DNS server uses a specific text format to define the name server address to - A combination of DNS-over-HTTPS URL and IP address together is as shown: `https://cloudflare-dns.com/dns-query (1.1.1.1)`. Here, the IP address of the domain name in the URL is specified in the round brackets. This allows the DNS server to use the specified IP address instead of trying to resolve it separately. - IPv6 addresses must always be enclosed in square brackets as shown: `cloudflare-dns.com ([2606:4700:4700::1111]:853)` or `[2606:4700:4700::1111]` -## Authentication API Calls +## User API Calls -These API calls allow to login or logout using credentials. Once logged in, a session token is returned which MUST be used with all other API calls. +These API calls allow to a user to login, logout, perform account management, etc. Once logged in, a session token is returned which MUST be used with all other API calls. ### Login -This call authenticates with the server and generates a session token to be used for subsequent API calls. The session token expires in 30 minutes from the last API call. +This call authenticates with the server and generates a session token to be used for subsequent API calls. The session token expires as per the user's session expiry timeout value (default 30 minutes) from the last API call. URL: -`http://localhost:5380/api/login?user=admin&pass=admin` +`http://localhost:5380/api/user/login?user=admin&pass=admin&includeInfo=true` + +OBSOLETE PATH: +`/api/login` + +PERMISSIONS: +None WHERE: -- `user`: The username. The built-in administrator username on the DNS server is `admin`. -- `pass`: The password for the user. The default password for `admin` user is `admin`. +- `user`: The username for the user account. The built-in administrator username on the DNS server is `admin`. +- `pass`: The password for the user account. The default password for `admin` user is `admin`. +- `includeInfo`: Includes basic info relevant for the user in response. WARNING: It is highly recommended to change the password on first use to avoid security related issues. RESPONSE: ``` { - "status": "ok", - "token": "932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9" + "displayName": "Administrator", + "username": "admin", + "token": "932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9", + "info": { + "version": "9.0", + "dnsServerDomain": "server1", + "defaultRecordTtl": 3600, + "permissions": { + "Dashboard": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Zones": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Cache": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Allowed": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Blocked": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Apps": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "DnsClient": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Settings": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "DhcpServer": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Administration": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Logs": { + "canView": true, + "canModify": true, + "canDelete": true + } + } + }, + "status": "ok" +} +``` + +WHERE: +- `token`: Is the session token generated that MUST be used with all subsequent API calls. + +### Create API Token + +Allows creating a non-expiring API token that can be used with automation scripts to make API calls. The token allows access to API calls with the same privileges as that of the user and thus its advised to create a separate user with limited permissions required for creating the API token. The token cannot be used to change the user's password, or update the user profile details. + +URL: +`http://localhost:5380/api/user/createToken?user=admin&pass=admin&tokenName=MyToken1&includeInfo=true` + +PERMISSIONS: +None + +WHERE: +- `user`: The username for the user account for which to generate the API token. +- `pass`: The password for the user account. +- `tokenName`: The name of the created token to identify its session. + +RESPONSE: +``` +{ + "username": "admin", + "tokenName": "MyToken1", + "token": "932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9", + "status": "ok" } ``` @@ -73,13 +172,19 @@ WHERE: ### Logout -This call ends the session generated by the `login` call. The `token` generated by the `login` call would no longer be valid after calling `logout`. +This call ends the session generated by the `login` or the `createToken` call. The `token` would no longer be valid after calling the `logout` API. URL: -`http://localhost:5380/api/logout?token=932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9` +`http://localhost:5380/api/user/logout?token=932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9` + +OBSOLETE PATH: +`/api/logout` + +PERMISSIONS: +None WHERE: -- `token`: The session token generated by the `login` call. +- `token`: The session token generated by the `login` or the `createToken` call. RESPONSE: ``` @@ -88,14 +193,127 @@ RESPONSE: } ``` +### Get Session Info + +Returns the same info as that of the `login` or the `createToken` calls for the session specified by the token. + +URL: +`http://localhost:5380/api/user/session/get?token=932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9` + +PERMISSIONS: +None + +WHERE: +- `token`: The session token generated by the `login` or the `createToken` call. + +RESPONSE: +``` +{ + "displayName": "Administrator", + "username": "admin", + "token": "932b2a3495852c15af01598f62563ae534460388b6a370bfbbb8bb6094b698e9", + "info": { + "version": "9.0", + "dnsServerDomain": "server1", + "defaultRecordTtl": 3600, + "permissions": { + "Dashboard": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Zones": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Cache": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Allowed": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Blocked": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Apps": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "DnsClient": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Settings": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "DhcpServer": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Administration": { + "canView": true, + "canModify": true, + "canDelete": true + }, + "Logs": { + "canView": true, + "canModify": true, + "canDelete": true + } + } + }, + "status": "ok" +} +``` + +### Delete User Session + +Allows deleting a session for the current user. + +URL: +`http://localhost:5380/api/user/session/delete?token=x&partialToken=620c3bfcd09d0a07` + +PERMISSIONS: +None + +WHERE: +- `token`: The session token generated by the `login` call. +- `partialToken`: The partial token as returned by the user profile details API call. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + ### Change Password -This call allows changing the password for the current logged in user. +Allows changing the password for the current logged in user account. NOTE: It is highly recommended to change the `admin` user password on first use to avoid security related issues. URL: -`http://localhost:5380/api/changePassword?token=x&pass=password` +`http://localhost:5380/api/user/changePassword?token=x&pass=password` + +OBSOLETE PATH: +`/api/changePassword` + +PERMISSIONS: +None WHERE: - `token`: The session token generated by the `login` call. @@ -108,14 +326,110 @@ RESPONSE: } ``` -## General DNS API Calls +### Get User Profile Details + +Gets the user account profile details. + +URL: +`http://localhost:5380/api/user/profile/get?token=x` + +PERMISSIONS: +None + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "displayName": "Administrator", + "username": "admin", + "disabled": false, + "previousSessionLoggedOn": "2022-09-15T12:59:05.944Z", + "previousSessionRemoteAddress": "127.0.0.1", + "recentSessionLoggedOn": "2022-09-15T13:57:50.1843973Z", + "recentSessionRemoteAddress": "127.0.0.1", + "sessionTimeoutSeconds": 1800, + "memberOfGroups": [ + "Administrators" + ], + "sessions": [ + { + "username": "admin", + "isCurrentSession": true, + "partialToken": "620c3bfcd09d0a07", + "type": "Standard", + "tokenName": null, + "lastSeen": "2022-09-15T13:58:02.4728Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + } + ] + }, + "status": "ok" +} +``` + +### Set User Profile Details + +Allows changing user account profile values. + +URL: +`http://localhost:5380/api/user/profile/set?token=x&displayName=Administrator&sessionTimeoutSeconds=1800` + +PERMISSIONS: +None + +WHERE: +- `token`: The session token generated by the `login` call. +- `displayName` (optional): The display name to set for the user account. +- `sessionTimeoutSeconds` (optional): The session timeout value to set in seconds for the user account. + +RESPONSE: +``` +{ + "response": { + "displayName": "Administrator", + "username": "admin", + "disabled": false, + "previousSessionLoggedOn": "2022-09-15T12:59:05.944Z", + "previousSessionRemoteAddress": "127.0.0.1", + "recentSessionLoggedOn": "2022-09-15T13:57:50.1843973Z", + "recentSessionRemoteAddress": "127.0.0.1", + "sessionTimeoutSeconds": 1800, + "memberOfGroups": [ + "Administrators" + ], + "sessions": [ + { + "username": "admin", + "isCurrentSession": true, + "partialToken": "620c3bfcd09d0a07", + "type": "Standard", + "tokenName": null, + "lastSeen": "2022-09-15T14:00:50.288738Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + } + ] + }, + "status": "ok" +} +``` ### Check For Update This call requests the server to check for software update. URL: -`http://localhost:5380/api/checkForUpdate?token=x` +`http://localhost:5380/api/user/checkForUpdate?token=x` + +OBSOLETE PATH: +`/api/checkForUpdate` + +PERMISSIONS: +None WHERE: - `token`: The session token generated by the `login` call. @@ -125,8 +439,8 @@ RESPONSE: { "response": { "updateAvailable": true, - "updateVersion": "7.0", - "currentVersion": "7.0", + "updateVersion": "9.0", + "currentVersion": "8.1.4", "updateTitle": "New Update Available!", "updateMessage": "Follow the instructions from the link below to update the DNS server to the latest version. Read the change logs before installing the update to know if there are any breaking changes.", "downloadLink": "https://download.technitium.com/dns/DnsServerSetup.zip", @@ -137,285 +451,22 @@ RESPONSE: } ``` -### Get DNS Settings +## Dashboard API Calls -This call returns all the DNS server settings. - -URL: -`http://localhost:5380/api/getDnsSettings?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "response": { - "version": "8.0", - "dnsServerDomain": "server1", - "dnsServerLocalEndPoints": [ - "0.0.0.0:53", - "[::]:53" - ], - "webServiceLocalAddresses": [ - "0.0.0.0", - "[::]" - ], - "webServiceHttpPort": 5380, - "webServiceEnableTls": false, - "webServiceHttpToTlsRedirect": false, - "webServiceTlsPort": 53443, - "webServiceUseSelfSignedTlsCertificate": false, - "webServiceTlsCertificatePath": null, - "webServiceTlsCertificatePassword": "************", - "enableDnsOverHttp": false, - "enableDnsOverTls": false, - "enableDnsOverHttps": false, - "dnsTlsCertificatePath": null, - "dnsTlsCertificatePassword": "************", - "tsigKeys": [ - { - "keyName": "key.example.com", - "sharedSecret": "AQ==", - "algorithmName": "hmac-sha256" - } - ], - "defaultRecordTtl": 3600, - "preferIPv6": false, - "udpPayloadSize": 1232, - "dnssecValidation": false, - "enableLogging": true, - "logQueries": true, - "useLocalTime": false, - "logFolder": "logs", - "maxLogFileDays": 365, - "maxStatFileDays": 365, - "recursion": "AllowOnlyForPrivateNetworks", - "recursionDeniedNetworks": [], - "recursionAllowedNetworks": [ - "192.168.1.0/24" - ], - "randomizeName": true, - "qnameMinimization": true, - "nsRevalidation": true, - "qpmLimitRequests", 0, - "qpmLimitErrors", 0, - "qpmLimitSampleMinutes": 5, - "qpmLimitIPv4PrefixLength": 24, - "qpmLimitIPv6PrefixLength": 56, - "serveStale": true, - "serveStaleTtl": 259200, - "temporaryDisableBlockingTill": "2021-10-10T01:14:27.1106773Z", - "cacheMinimumRecordTtl": 10, - "cacheMaximumRecordTtl": 86400, - "cacheNegativeRecordTtl": 300, - "cacheFailureRecordTtl": 60, - "cachePrefetchEligibility": 2, - "cachePrefetchTrigger": 9, - "cachePrefetchSampleIntervalInMinutes": 5, - "cachePrefetchSampleEligibilityHitsPerHour": 30, - "proxy": { - "type": "Socks5", - "address": "192.168.10.2", - "port": 9050, - "username": "username", - "password": "password", - "bypass": [ - "127.0.0.0/8", - "169.254.0.0/16", - "fe80::/10", - "::1", - "localhost" - ] - }, - "forwarders": [ - "https://cloudflare-dns.com/dns-query (1.1.1.1)", - "https://cloudflare-dns.com/dns-query (1.0.0.1)" - ], - "forwarderProtocol": "Https", - "enableBlocking": true, - "allowTxtBlockingReport": true, - "blockingType": "AnyAddress", - "customBlockingAddresses": [], - "blockListUrls": [ - "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" - ], - "blockListUpdateIntervalHours": 24, - "blockListNextUpdatedOn": "2021-09-11T12:09:22.244Z" - }, - "status": "ok" -} -``` - -### Set DNS Settings - -This call allows to change the DNS server settings. - -URL: -`http://localhost:5380/api/setDnsSettings?token=x&dnsServerDomain=server1&dnsServerLocalEndPoints=0.0.0.0:53,[::]:53&webServiceLocalAddresses=0.0.0.0,[::]&webServiceHttpPort=5380&webServiceEnableTls=false&webServiceTlsPort=53443&webServiceTlsCertificatePath=&webServiceTlsCertificatePassword=&enableDnsOverHttp=false&enableDnsOverTls=false&enableDnsOverHttps=false&dnsTlsCertificatePath=&dnsTlsCertificatePassword=&preferIPv6=false&logQueries=true&allowRecursion=true&allowRecursionOnlyForPrivateNetworks=true&randomizeName=true&cachePrefetchEligibility=2&cachePrefetchTrigger=9&cachePrefetchSampleIntervalInMinutes=5&cachePrefetchSampleEligibilityHitsPerHour=30&proxyType=socks5&proxyAddress=192.168.10.2&proxyPort=9050&proxyUsername=username&proxyPassword=password&proxyBypass=127.0.0.0/8,169.254.0.0/16,fe80::/10,::1,localhost&forwarders=192.168.10.2&forwarderProtocol=Udp&useNxDomainForBlocking=false&blockListUrls=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts,https://mirror1.malwaredomains.com/files/justdomains,https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt,https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt` - -WHERE: -- `token`: The session token generated by the `login` call. -- `dnsServerDomain` (optional): The primary domain name used by this DNS Server to identify itself. -- `dnsServerLocalEndPoints` (optional): Local end points are the network interface IP addresses and ports you want the DNS Server to listen for requests. -- `webServiceLocalAddresses` (optional): Local addresses are the network interface IP addresses you want the web service to listen for requests. -- `webServiceHttpPort` (optional): Specify the TCP port number for the web console and this API web service. Default value is `5380`. -- `webServiceEnableTls` (optional): Set this to `true` to start the HTTPS service to acccess web service. -- `webServiceTlsPort` (optional): Specified the TCP port number for the web console for HTTPS access. -- `webServiceUseSelfSignedTlsCertificate` (optional): Set `true` for the web service to use an automatically generated self signed certificate when TLS certificate path is not specified. -- `webServiceTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the web console for HTTPS access. -- `webServiceTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any. -- `enableDnsOverHttp` (optional): Enable this option to accept DNS-over-HTTP requests for both wire and json response formats. It must be used with a TLS terminating reverse proxy like nginx and will work only on private networks. -- `enableDnsOverTls` (optional): Enable this option to accept DNS-over-TLS requests. -- `enableDnsOverHttps` (optional): Enable this option to accept DNS-over-HTTPS requests for both wire and json response formats. -- `dnsTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the DNS-over-TLS and DNS-over-HTTPS optional protocols. -- `dnsTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any. -- `tsigKeys` (optional): A pipe `|` separated multi row list of TSIG key name, shared secret, and algorithm. Set this parameter to `false` to remove all existing keys. Supported algorithms are [`hmac-md5.sig-alg.reg.int`, `hmac-sha1`, `hmac-sha256`, `hmac-sha256-128`, `hmac-sha384`, `hmac-sha384-192`, `hmac-sha512`, `hmac-sha512-256`]. -- `defaultRecordTtl` (optional): The default TTL value to use if not specified when adding or updating records in a Zone. -- `preferIPv6` (optional): DNS Server will use IPv6 for querying whenever possible with this option enabled. Default value is `false`. -- `udpPayloadSize` (optional): The maximum EDNS UDP payload size that can be used to avoid IP fragmentation. Valid range is 512-4096 bytes. Default value is `1232`. -- `dnssecValidation` (optional): Set this to `true` to enable DNSSEC validation. DNS Server will validate all responses from name servers or forwarders when this option is enabled. -- `enableLogging` (optional): Enable this option to log error and audit logs into the log file. Default value is `true`. -- `logQueries` (optional): Enable this option to log every query received by this DNS Server and the corresponding response answers into the log file. Default value is `false`. -- `useLocalTime` (optional): Enable this option to use local time instead of UTC for logging. Default value is `false`. -- `logFolder` (optional): The folder path on the server where the log files should be saved. The path can be relative to the DNS server config folder. Default value is `logs`. -- `maxLogFileDays` (optional): Max number of days to keep the log files. Log files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete. -- `maxStatFileDays` (optional): Max number of days to keep the dashboard stats. Stat files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete. -- `recursion` (optional): Sets the recursion policy for the DNS server. Valid values are [`Deny`, `Allow`, `AllowOnlyForPrivateNetworks`, `UseSpecifiedNetworks`]. -- `recursionDeniedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be denied recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`. -- `recursionAllowedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be allowed recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`. -- `randomizeName` (optional): Enables QNAME randomization [draft-vixie-dnsext-dns0x20-00](https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00) when using UDP as the transport protocol. Default value is `true`. -- `qnameMinimization` (optional): Enables QNAME minimization [draft-ietf-dnsop-rfc7816bis-04](https://tools.ietf.org/html/draft-ietf-dnsop-rfc7816bis-04) when doing recursive resolution. Default value is `true`. -- `nsRevalidation` (optional): Enables [draft-ietf-dnsop-ns-revalidation](https://datatracker.ietf.org/doc/draft-ietf-dnsop-ns-revalidation/) for recursive resolution. Default value is `true`. -- `qpmLimitRequests` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests that is enforces per client subnet. Set value to `0` to disable the feature. -- `qpmLimitErrors` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests which generates an error response that is enforces per client subnet. Set value to `0` to disable the feature. Response with an RCODE of FormatError, ServerFailure, or Refused is considered as an error response. -- `qpmLimitSampleMinutes` (optional): Sets the client query stats sample size in minutes for QPM limit feature. Default value is `5`. -- `qpmLimitIPv4PrefixLength` (optional): Sets the client subnet IPv4 prefix length used to define the subnet. Default value is `24`. -- `qpmLimitIPv6PrefixLength` (optional): Sets the client subnet IPv6 prefix length used to define the subnet. Default value is `56`. -- `serveStale` (optional): Enable the serve stale feature to improve resiliency by using expired or stale records in cache when the DNS server is unable to reach the upstream or authoritative name servers. Default value is `true`. -- `serveStaleTtl` (optional): The TTL value in seconds which should be used for cached records that are expired. When the serve stale TTL too expires for a stale record, it gets removed from the cache. Recommended value is between 1-3 days and maximum supported value is 7 days. Default value is `259200`. -- `temporaryDisableBlockingTill` (read only): An ISO 8601 String with the Date and Time when the Temporary Blocking will end. -- `cacheMinimumRecordTtl` (optional): The minimum TTL value that a record can have in cache. Set a value to make sure that the records with TTL value than it stays in cache for a minimum duration. Default value is `10`. -- `cacheMaximumRecordTtl` (optional): The maximum TTL value that a record can have in cache. Set a lower value to allow the records to expire early. Default value is `86400`. -- `cacheNegativeRecordTtl` (optional): The negative TTL value to use when there is no SOA MINIMUM value available. Default value is `300`. -- `cacheFailureRecordTtl` (optional): The failure TTL value to used for caching failure responses. This allows storing failure record in cache and prevent frequent recursive resolution to name servers that are responding with `ServerFailure`. Default value is `60`. -- `cachePrefetchEligibility` (optional): The minimum initial TTL value of a record needed to be eligible for prefetching. -- `cachePrefetchTrigger` (optional): A record with TTL value less than trigger value will initiate prefetch operation immediately for itself. Set `0` to disable prefetching & auto prefetching. -- `cachePrefetchSampleIntervalInMinutes` (optional): The interval to sample eligible domain names from last hour stats for auto prefetch. -- `cachePrefetchSampleEligibilityHitsPerHour` (optional): Minimum required hits per hour for a domain name to be eligible for auto prefetch. -- `proxyType` (optional): The type of proxy protocol to be used. Valid values are [`None`, `Http`, `Socks5`]. -- `proxyAddress` (optional): The proxy server hostname or IP address. -- `proxyPort` (optional): The proxy server port. -- `proxyUsername` (optional): The proxy server username. -- `proxyPassword` (optional): The proxy server password. -- `proxyBypass` (optional): A comma separated bypass list consisting of IP addresses, network addresses in CIDR format, or host/domain names to never use proxy for. -- `forwarders` (optional): A comma separated list of forwarders to be used by this DNS server. Set this parameter to `false` string to remove existing forwarders so that the DNS server does recursive resolution by itself. -- `forwarderProtocol` (optional): The forwarder DNS transport protocol to be used. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`]. -- `enableBlocking` (optional): Sets the DNS server to block domain names using Blocked Zone and Block List Zone. -- `allowTxtBlockingReport` (optional): Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. -- `blockingType` (optional): Sets how the DNS server should respond to a blocked domain request. Valid values are [`AnyAddress`, `NxDomain`, `CustomAddress`] where `AnyAddress` is default which response with `0.0.0.0` and `::` IP addresses for blocked domains. Using `NxDomain` will respond with `NX Domain` response. `CustomAddress` will return the specified custom blocking addresses. -- `blockListNextUpdatedOn` (read only): An ISO 8601 String with the Date and Time when the blocklist will next be updated. -- `customBlockingAddresses` (optional): Set the custom blocking addresses to be used for blocked domain response. These addresses are returned only when `blockingType` is set to `CustomAddress`. -- `blockListUrls` (optional): A comma separated list of block list URLs that this server must automatically download and use with the block lists zone. DNS Server will use the data returned by the block list URLs to update the block list zone automatically every 24 hours. The expected file format is standard hosts file format or plain text file containing list of domains to block. Set this parameter to `false` to remove existing values. -- `blockListUpdateIntervalHours` (optional): The interval in hours to automatically download and update the block lists. Default value is `24`. - -RESPONSE: -This call returns the newly updated settings in the same format as that of the `getDnsSettings` call. - -### Force Update Block Lists - -This call allows to reset the next update schedule and force download and update of the block lists. - -URL: -`http://localhost:5380/api/forceUpdateBlockLists?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Temporarily Disable Block Lists - -This call temporarily disables the block lists and block list zones. - -URL: -`http://localhost:5380/api/temporaryDisableBlocking?token=x&minutes=5` - -WHERE: -- `token`: The session token generated by the `login` call. -- `minutes`: The time in minutes to disable the blocklist for. - -RESPONSE: -``` -{ - "status": "ok", - "response": { - "temporaryDisableBlockingTill": "2021-10-10T01:14:27.1106773Z" - } -} -``` - -### Backup Settings - -This call returns a zip file containing copies of all the items that were requested to be backed up. - -URL: -`http://localhost:5380/api/backupSettings?token=x&blockLists=true&logs=true&scopes=true&stats=true&zones=true&allowedZones=true&blockedZones=true&dnsSettings=true&logSettings=true` - -WHERE: -- `token`: The session token generated by the `login` call. -- `blockLists` (optional): Set to `true` to backup block lists cache files. Default value is `false`. -- `logs` (optional): Set to `true` to backup log files. Default value is `false`. -- `scopes` (optional): Set to `true` to backup DHCP scope files. Default value is `false`. -- `apps` (optional): Set to `true` to backup the installed DNS apps. Default value is `false`. -- `stats` (optional): Set to `true` to backup dashboard stats files. Default value is `false`. -- `zones` (optional): Set to `true` to backup DNS zone files. Default value is `false`. -- `allowedZones` (optional): Set to `true` to backup allowed zones file. Default value is `false`. -- `blockedZones` (optional): Set to `true` to backup blocked zones file. Default value is `false`. -- `dnsSettings` (optional): Set to `true` to backup DNS settings file. Default value is `false`. -- `logSettings` (optional): Set to `true` to backup log settings file. Default value is `false`. - -RESPONSE: -A zip file with content type `application/zip` and content disposition set to `attachment`. - -### Restore Settings - -This call restores selected items from a given backup zip file. - -URL: -`http://localhost:5380/api/restoreSettings?token=x&blockLists=true&logs=true&scopes=true&stats=true&zones=true&allowedZones=true&blockedZones=true&dnsSettings=true&logSettings=true&deleteExistingFiles=true` - -WHERE: -- `token`: The session token generated by the `login` call. -- `blockLists` (optional): Set to `true` to restore block lists cache files. Default value is `false`. -- `logs` (optional): Set to `true` to restore log files. Default value is `false`. -- `scopes` (optional): Set to `true` to restore DHCP scope files. Default value is `false`. -- `apps` (optional): Set to `true` to restore the DNS apps. Default value is `false`. -- `stats` (optional): Set to `true` to restore dashboard stats files. Default value is `false`. -- `zones` (optional): Set to `true` to restore DNS zone files. Default value is `false`. -- `allowedZones` (optional): Set to `true` to restore allowed zones file. Default value is `false`. -- `blockedZones` (optional): Set to `true` to restore blocked zones file. Default value is `false`. -- `dnsSettings` (optional): Set to `true` to restore DNS settings file. Default value is `false`. -- `logSettings` (optional): Set to `true` to restore log settings file. Default value is `false`. -- `deleteExistingFiles` (optional). Set to `true` to delete existing files for selected items. Default value is `false`. - -REQUEST: -This is a `POST` request call where the request must be multi-part form data with the backup zip file data in binary format. - -RESPONSE: -This call returns the newly updated settings in the same format as that of the `getDnsSettings` call. +These API calls provide access to dashboard stats and allow deleting stat files. ### Get Stats -This call returns the stats that are shown on the web console's dashboard. +Returns the DNS stats that are displayed on the web console dashboard. URL: -`http://localhost:5380/api/getStats?token=x&type=lastHour` +`http://localhost:5380/api/dashboard/stats/get?token=x&type=lastHour` + +OBSOLETE PATH: +`api/getStats` + +PERMISSIONS: +Dashboard: View WHERE: - `token`: The session token generated by the `login` call. @@ -1383,10 +1434,16 @@ RESPONSE: ### Get Top Stats -This call returns the top stats data for specified stats type. +Returns the top stats data for specified stats type. URL: -`http://localhost:5380/api/getTopStats?token=x&type=lastHour&statsType=TopClients&limit=1000` +`http://localhost:5380/api/dashboard/stats/getTop?token=x&type=lastHour&statsType=TopClients&limit=1000` + +OBSOLETE PATH: +`/api/getTopStats` + +PERMISSIONS: +Dashboard: View WHERE: - `token`: The session token generated by the `login` call. @@ -1448,14 +1505,18 @@ The response json will include the object with definition same in the `getStats` } ``` -## DNS Cache API Calls +### Delete All Stats -### Flush DNS Cache - -This call clears all the DNS cache from the server forcing the DNS server to make recursive queries again to populate the cache. +Permanently delete all hourly and daily stats files from the disk and clears all stats stored in memory. This call will clear all stats from the Dashboard. URL: -`http://localhost:5380/api/flushDnsCache?token=x` +`http://localhost:5380/api/dashboard/stats/deleteAll?token=x` + +OBSOLETE PATH: +`/api/deleteAllStats` + +PERMISSIONS: +Dashboard: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -1463,353 +1524,29 @@ WHERE: RESPONSE: ``` { - "status": "ok" -} -``` - -### List Cached Zones - -List all cached zones. - -URL: -`http://localhost:5380/api/listCachedZones?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. -- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. - -RESPONSE: -``` -{ - "response": { - "domain": "google.com", - "zones": [], - "records": [ - { - "name": "google.com", - "type": "A", - "ttl": "283 (4 mins 43 sec)", - "rData": { - "value": "216.58.199.174" - } - } - ] - }, - "status": "ok" -} -``` - -### Delete Cached Zone - -Deletes a specific zone from the DNS cache. - -URL: -`http://localhost:5380/api/deleteCachedZone?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name to delete cached records from. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -## Allowed Zones API Calls - -### List Allowed Zones - -List all allowed zones. - -URL: -`http://localhost:5380/api/listAllowedZones?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. -- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. - -RESPONSE: -``` -{ - "response": { - "domain": "google.com", - "zones": [], - "records": [ - { - "name": "google.com", - "type": "NS", - "ttl": "14400 (4 hours)", - "rData": { - "value": "server1" - } - }, - { - "name": "google.com", - "type": "SOA", - "ttl": "14400 (4 hours)", - "rData": { - "primaryNameServer": "server1", - "responsiblePerson": "hostadmin.server1", - "serial": 1, - "refresh": 14400, - "retry": 3600, - "expire": 604800, - "minimum": 900 - } - } - ] - }, - "status": "ok" -} -``` - -### Import Allowed Zones - -Imports domain names into the Allowed Zones. - -URL: -`http://localhost:5380/api/importAllowedZones?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -REQUEST: -This is a `POST` request call where the content type of the request must be `application/x-www-form-urlencoded` and the content must be as shown below: - -``` -allowedZones=google.com,twitter.com -``` - -WHERE: -- `allowedZones`: A list of comma separated domain names that are to be imported. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Export Allowed Zones - -Allows exporting all the zones from the Allowed Zones as a text file. - -URL: -`http://localhost:5380/api/exportAllowedZones?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -Response is a downloadable text file with `Content-Type: text/plain` and `Content-Disposition: attachment`. - -### Delete Allowed Zone - -Allows deleting a zone from the Allowed Zones. - -URL: -`http://localhost:5380/api/deleteAllowedZone?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name for the zone to be deleted. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Flush Allowed Zone - -Flushes the Allowed zone to clear all records. - -URL: -`http://localhost:5380/api/flushAllowedZone?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Allow Zone - -Adds a domain name into the Allowed Zones. - -URL: -`http://localhost:5380/api/allowZone?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name for the zone to be added. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -## Blocked Zones API Calls - -### List Blocked Zones - -List all blocked zones. - -URL: -`http://localhost:5380/api/listBlockedZones?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. -- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. - -RESPONSE: -``` -{ - "response": { - "domain": "google.com", - "zones": [], - "records": [ - { - "name": "google.com", - "type": "NS", - "ttl": "14400 (4 hours)", - "rData": { - "value": "server1" - } - }, - { - "name": "google.com", - "type": "SOA", - "ttl": "14400 (4 hours)", - "rData": { - "primaryNameServer": "server1", - "responsiblePerson": "hostadmin.server1", - "serial": 1, - "refresh": 14400, - "retry": 3600, - "expire": 604800, - "minimum": 900 - } - } - ] - }, - "status": "ok" -} -``` - -### Import Blocked Zones - -Imports domain names into Blocked Zones. - -URL: -`http://localhost:5380/api/importBlockedZones?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -REQUEST: -This is a `POST` request call where the content type of the request must be `application/x-www-form-urlencoded` and the content must be as shown below: - -``` -blockedZones=google.com,twitter.com -``` - -WHERE: -- `blockedZones`: A list of comma separated domain names that are to be imported. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Export Blocked Zones - -Allows exporting all the zones from the Blocked Zones as a text file. - -URL: -`http://localhost:5380/api/exportBlockedZones?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -Response is a downloadable text file with `Content-Type: text/plain` and `Content-Disposition: attachment`. - -### Delete Blocked Zone - -Allows deleting a zone from the Blocked Zones. - -URL: -`http://localhost:5380/api/deleteBlockedZone?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name for the zone to be deleted. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Flush Blocked Zone - -Flushes the Blocked zone to clear all records. - -URL: -`http://localhost:5380/api/flushBlockedZone?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Block Zone - -Adds a domain name into the Blocked Zones. - -URL: -`http://localhost:5380/api/blockZone?token=x&domain=google.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name for the zone to be added. - -RESPONSE: -``` -{ + "response": {}, "status": "ok" } ``` ## Authoritative Zone API Calls +These API calls allow managing all hosted zones on the DNS server. + ### List Zones -List all authoritative zones hosted on this DNS server. +List all authoritative zones hosted on this DNS server. The list contains only the zones that the user has View permissions for. These API calls requires permission for both the Zones section as well as the individual permission for each zone. URL: -`http://localhost:5380/api/zone/list?token=x` +`http://localhost:5380/api/zones/list?token=x` + +OBSOLETE PATH: +`/api/zone/list` +`/api/listZones` + +PERMISSIONS: +Zones: View +Zone: View WHERE: - `token`: The session token generated by the `login` call. @@ -1882,7 +1619,14 @@ RESPONSE: Creates a new authoritative zone. URL: -`http://localhost:5380/api/zone/create?token=x&zone=example.com&type=Primary` +`http://localhost:5380/api/zones/create?token=x&zone=example.com&type=Primary` + +OBSOLETE PATH: +`/api/zone/create` +`/api/createZone` + +PERMISSIONS: +Zones: Modify WHERE: - `token`: The session token generated by the `login` call. @@ -1913,12 +1657,303 @@ RESPONSE: WHERE: - `domain`: Will contain the zone that was created. This is specifically useful to know the reverse zone that was created. +### Enable Zone + +Enables an authoritative zone. + +URL: +`http://localhost:5380/api/zones/enable?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/enable` +`/api/enableZone` + +PERMISSIONS: +Zones: Modify +Zone: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to be enabled. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Disable Zone + +Disables an authoritative zone. This will prevent the DNS server from responding for queries to this zone. + +URL: +`http://localhost:5380/api/zones/disable?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/disable` +`/api/disableZone` + +PERMISSIONS: +Zones: Modify +Zone: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to be disabled. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Delete Zone + +Deletes an authoritative zone. + +URL: +`http://localhost:5380/api/zones/delete?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/delete` +`/api/deleteZone` + +PERMISSIONS: +Zones: Delete +Zone: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to be deleted. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Resync Zone + +Allows resyncing a Secondary or Stub zone. This process will re-fetch all the records from the primary name server for the zone. + +URL: +`http://localhost:5380/api/zones/resync?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/resync` + +PERMISSIONS: +Zones: Modify +Zone: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to resync. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Get Zone Options + +Gets the zone specific options. + +URL: +`http://localhost:5380/api/zones/options/get?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/options` + +PERMISSIONS: +Zones: Modify +Zone: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to get options. + +RESPONSE: +``` +{ + "response": { + "name": "example.com", + "type": "Primary", + "internal": false, + "disabled": false, + "zoneTransfer": "AllowOnlyZoneNameServers", + "zoneTransferNameServers": [], + "notify": "ZoneNameServers", + "notifyNameServers": [], + "zoneTransferTsigKeyNames": [ + "key.example.com" + ], + "availableTsigKeyNames": [ + "key.example.com" + ] + }, + "status": "ok" +} +``` + +### Set Zone Options + +Sets the zone specific options. + +URL: +`http://localhost:5380/api/zones/options/set?token=x&zone=example.com&disabled=false&zoneTransfer=Allow&zoneTransferNameServers=¬ify=ZoneNameServers¬ifyNameServers=` + +OBSOLETE PATH: +`/api/zone/options` + +PERMISSIONS: +Zones: Modify +Zone: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to set options. +- `disabled` (optional): Sets if the zone is enabled or disabled. +- `zoneTransfer` (optional): Sets if the zone allows zone transfer. Valid options are [`Deny`, `Allow`, `AllowOnlyZoneNameServers`, `AllowOnlySpecifiedNameServers`]. +- `zoneTransferNameServers` (optional): A list of comma separated IP addresses which should be allowed to perform zone transfer. This list is enabled only when `zoneTransfer` option is set to `AllowOnlySpecifiedNameServers`. +- `notify` (optional): Sets if the DNS server should notify other DNS servers for zone updates. Valid options are [`None`, `ZoneNameServers`, `SpecifiedNameServers`]. +- `notifyNameServers` (optional): A list of comma separated IP addresses which should be notified by the DNS server for zone updates. This list is used only when `notify` option is set to `SpecifiedNameServers`. +- `zoneTransferTsigKeyNames` (optional): A list of comma separated TSIG keys names that are authorized to perform a zone transfer. Set this option to `false` to remove all keys. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Get Zone Permissions + +Gets the zone specific permissions. + +URL: +`http://localhost:5380/api/zones/permissions/get?token=x&zone=example.com&includeUsersAndGroups=true` + +PERMISSIONS: +Zones: Modify +Zone: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to get the permissions for. +- `includeUsersAndGroups`: Set to true to get a list of users and groups in the response. + +RESPONSE: +``` +{ + "response": { + "section": "Zones", + "subItem": "example.com", + "userPermissions": [ + { + "username": "admin", + "canView": true, + "canModify": true, + "canDelete": true + } + ], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + } + ], + "users": [ + "admin", + "shreyas" + ], + "groups": [ + "Administrators", + "DHCP Administrators", + "DNS Administrators", + "Everyone" + ] + }, + "status": "ok" +} +``` + +### Set Zone Permissions + +Sets the zone specific permissions. + +URL: +`http://localhost:5380/api/zones/permissions/set?token=x&zone=example.com&userPermissions=admin|true|true|true&groupPermissions=Administrators|true|true|true|DNS%20Administrators|true|true|true` + +PERMISSIONS: +Zones: Modify +Zone: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `zone`: The domain name of the zone to get the permissions for. +- `userPermissions` (optional): A pipe `|` separated table data with each row containing username and boolean values for the view, modify and delete permissions. For example: user1|true|true|true|user2|true|false|false +- `groupPermissions` (optional): A pipe `|` separated table data with each row containing the group name and boolean values for the view, modify and delete permissions. For example: group1|true|true|true|group2|true|true|false + +RESPONSE: +``` +{ + "response": { + "section": "Zones", + "subItem": "example.com", + "userPermissions": [ + { + "username": "admin", + "canView": true, + "canModify": true, + "canDelete": true + } + ], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + } + ] + }, + "status": "ok" +} +``` + ### Sign Zone Signs the primary zone (DNSSEC). URL: -`http://localhost:5380/api/zone/dnssec/sign?token=x&zone=example.com&algorithm=ECDSA&dnsKeyTtl=86400&zskRolloverDays=90&nxProof=NSEC3&iterations=0&saltLength=0&curve=P256` +`http://localhost:5380/api/zones/dnssec/sign?token=x&zone=example.com&algorithm=ECDSA&dnsKeyTtl=86400&zskRolloverDays=90&nxProof=NSEC3&iterations=0&saltLength=0&curve=P256` + +OBSOLETE PATH: +`/api/zone/dnssec/sign` + +PERMISSONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -1946,7 +1981,14 @@ RESPONSE: Unsigns the primary zone (DNSSEC). URL: -`http://localhost:5380/api/zone/dnssec/unsign?token=x&zone=example.com +`http://localhost:5380/api/zones/dnssec/unsign?token=x&zone=example.com + +OBSOLETE PATH: +`/api/zone/dnssec/unsign` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -1964,7 +2006,14 @@ RESPONSE: Get the DNSSEC properties for the primary zone. URL: -`http://localhost:5380/api/zone/dnssec/getProperties?token=x&zone=example.com` +`http://localhost:5380/api/zones/dnssec/properties/get?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/dnssec/getProperties` + +PERMISSIONS: +Zones: Modify +Zone: View WHERE: - `token`: The session token generated by the `login` call. @@ -2010,7 +2059,14 @@ RESPONSE: Converts a primary zone from NSEC3 to NSEC for proof of non-existence. URL: -`http://localhost:5380/api/zone/dnssec/convertToNSEC?token=x&zone=example.com` +`http://localhost:5380/api/zones/dnssec/properties/convertToNSEC?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/dnssec/convertToNSEC` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2028,7 +2084,14 @@ RESPONSE: Converts a primary zone from NSEC to NSEC3 for proof of non-existence. URL: -`http://localhost:5380/api/zone/dnssec/convertToNSEC3?token=x&zone=example.com` +`http://localhost:5380/api/zones/dnssec/properties/convertToNSEC3?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/dnssec/convertToNSEC3` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2046,7 +2109,14 @@ RESPONSE: Updates the iteration and salt length parameters for NSEC3. URL: -`http://localhost:5380/api/zone/dnssec/updateNSEC3Params?token=x&zone=example.com&iterations=0&saltLength=0` +`http://localhost:5380/api/zones/dnssec/properties/updateNSEC3Params?token=x&zone=example.com&iterations=0&saltLength=0` + +OBSOLETE PATH: +`/api/zone/dnssec/updateNSEC3Params` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2066,7 +2136,14 @@ RESPONSE: Updates the TTL value for DNSKEY resource record set. The value can be updated only when all the DNSKEYs are in ready or active state. URL: -`http://localhost:5380/api/zone/dnssec/updateDnsKeyTtl?token=x&zone=example.com&ttl=86400` +`http://localhost:5380/api/zones/dnssec/properties/updateDnsKeyTtl?token=x&zone=example.com&ttl=86400` + +OBSOLETE PATH: +`/api/zone/dnssec/updateDnsKeyTtl` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2085,7 +2162,14 @@ RESPONSE: Generates a private key to be used for signing the zone with DNSSEC. URL: -`http://localhost:5380/api/zone/dnssec/generatePrivateKey?token=x&zone=example.com&keyType=KeySigningKey&algorithm=ECDSA&curve=P256` +`http://localhost:5380/api/zones/dnssec/properties/generatePrivateKey?token=x&zone=example.com&keyType=KeySigningKey&algorithm=ECDSA&curve=P256` + +OBSOLETE PATH: +`/api/zone/dnssec/generatePrivateKey` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2093,7 +2177,7 @@ WHERE: - `keyType`: The type of key for which the private key is to be generated. Valid values are [`KeySigningKey`, `ZoneSigningKey`]. - `rolloverDays` (optional): The frequency in days that the DNS server must automatically rollover the private key in the zone. Valid range is 0-365 days where 0 disables rollover. Default value is 90 days for Zone Signing Key (ZSK) and 0 days for Key Signing Key (KSK). - `algorithm`: The algorithm to be used for signing. Valid values are [`RSA`, `ECDSA`]. -- `hashAlgorithm` (optional): The hash algorithm to be used when using `RSA` algorithm. Valid values are [`MD5`, `SHA1, `SHA256`, `SHA512`]. This optional parameter is required when using `RSA` algorithm. +- `hashAlgorithm` (optional): The hash algorithm to be used when using `RSA` algorithm. Valid values are [`MD5`, `SHA1`, `SHA256`, `SHA512`]. This optional parameter is required when using `RSA` algorithm. - `keySize` (optional): The size of the generated private key in bits to be used when using `RSA` algorithm. This optional parameter is required when using `RSA` algorithm. - `curve` (optional): The name of the curve to be used when using `ECDSA` algorithm. Valid values are [`P256`, `P384`]. This optional parameter is required when using `ECDSA` algorithm. @@ -2109,7 +2193,14 @@ RESPONSE: Updates the DNSSEC private key properties. URL: -`http://localhost:5380/api/zone/dnssec/updatePrivateKey?token=x&zone=example.com&keyTag=1234&rolloverDays=90` +`http://localhost:5380/api/zones/dnssec/properties/updatePrivateKey?token=x&zone=example.com&keyTag=1234&rolloverDays=90` + +OBSOLETE PATH: +`/api/zone/dnssec/updatePrivateKey` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2129,7 +2220,14 @@ RESPONSE: Deletes a private key that has state set as `Generated`. Private keys with any other state cannot be delete. URL: -`http://localhost:5380/api/zone/dnssec/deletePrivateKey?token=x&zone=example.com&keyTag=12345` +`http://localhost:5380/api/zones/dnssec/properties/deletePrivateKey?token=x&zone=example.com&keyTag=12345` + +OBSOLETE PATH: +`/api/zone/dnssec/deletePrivateKey` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2148,7 +2246,14 @@ RESPONSE: Publishes all private keys that have state set as `Generated` by adding associated DNSKEY records for them. Once published, the keys will be automatically activated. For Key Signing Keys (KSK), once the state is set to `Ready` you can then safely replace the old DS record from the parent zone with a new DS key record for the KSK associated DNSKEY record. Once the new DS record is published at the parent zone, the DNS server will automatically detect and set the KSK state to `Active`. URL: -`http://localhost:5380/api/zone/dnssec/publishAllPrivateKeys?token=x&zone=example.com` +`http://localhost:5380/api/zones/dnssec/properties/publishAllPrivateKeys?token=x&zone=example.com` + +OBSOLETE PATH: +`/api/zone/dnssec/publishAllPrivateKeys` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2166,7 +2271,14 @@ RESPONSE: Generates and publishes a new private key for the given key that has to be rolled over. The old private key and its associated DNSKEY record will be automatically retired and removed safely once the new key is active. URL: -`http://localhost:5380/api/zone/dnssec/rolloverDnsKey?token=x&zone=example.com&keyTag=12345` +`http://localhost:5380/api/zones/dnssec/properties/rolloverDnsKey?token=x&zone=example.com&keyTag=12345` + +OBSOLETE PATH: +`/api/zone/dnssec/rolloverDnsKey` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2185,7 +2297,14 @@ RESPONSE: Retires the specified private key and its associated DNSKEY record and removes it safely. To retire an existing DNSKEY, there must be at least one active key available. URL: -`http://localhost:5380/api/zone/dnssec/retireDnsKey?token=x&zone=example.com&keyTag=12345` +`http://localhost:5380/api/zones/dnssec/properties/retireDnsKey?token=x&zone=example.com&keyTag=12345` + +OBSOLETE PATH: +`/api/zone/dnssec/retireDnsKey` + +PERMISSIONS: +Zones: Modify +Zone: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -2199,124 +2318,20 @@ RESPONSE: } ``` -### Delete Zone - -Deletes an authoritative zone. - -URL: -`http://localhost:5380/api/zone/delete?token=x&zone=example.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `zone`: The domain name of the zone to be deleted. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Enable Zone - -Enables an authoritative zone. - -URL: -`http://localhost:5380/api/zone/enable?token=x&zone=example.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `zone`: The domain name of the zone to be enabled. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Disable Zone - -Disables an authoritative zone. This will prevent the DNS server from responding for queries to this zone. - -URL: -`http://localhost:5380/api/zone/disable?token=x&zone=example.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `zone`: The domain name of the zone to be disabled. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - -### Get Zone Options - -Gets the zone specific options. - -URL: -`http://localhost:5380/api/zone/options/get?token=x&zone=example.com` - -WHERE: -- `token`: The session token generated by the `login` call. -- `zone`: The domain name of the zone to get options. - -RESPONSE: -``` -{ - "response": { - "name": "example.com", - "type": "Primary", - "internal": false, - "disabled": false, - "zoneTransfer": "AllowOnlyZoneNameServers", - "zoneTransferNameServers": [], - "notify": "ZoneNameServers", - "notifyNameServers": [], - "zoneTransferTsigKeyNames": [ - "key.example.com" - ], - "availableTsigKeyNames": [ - "key.example.com" - ] - }, - "status": "ok" -} -``` - -### Set Zone Options - -Sets the zone specific options. - -URL: -`http://localhost:5380/api/zone/options/set?token=x&zone=example.com&disabled=false&zoneTransfer=Allow&zoneTransferNameServers=¬ify=ZoneNameServers¬ifyNameServers=` - -WHERE: -- `token`: The session token generated by the `login` call. -- `zone`: The domain name of the zone to set options. -- `disabled` (optional): Sets if the zone is enabled or disabled. -- `zoneTransfer` (optional): Sets if the zone allows zone transfer. Valid options are [`Deny`, `Allow`, `AllowOnlyZoneNameServers`, `AllowOnlySpecifiedNameServers`]. -- `zoneTransferNameServers` (optional): A list of comma separated IP addresses which should be allowed to perform zone transfer. This list is enabled only when `zoneTransfer` option is set to `AllowOnlySpecifiedNameServers`. -- `notify` (optional): Sets if the DNS server should notify other DNS servers for zone updates. Valid options are [`None`, `ZoneNameServers`, `SpecifiedNameServers`]. -- `notifyNameServers` (optional): A list of comma separated IP addresses which should be notified by the DNS server for zone updates. This list is used only when `notify` option is set to `SpecifiedNameServers`. -- `zoneTransferTsigKeyNames` (optional): A list of comma separated TSIG keys names that are authorized to perform a zone transfer. Set this option to `false` to remove all keys. - -RESPONSE: -``` -{ - "status": "ok" -} -``` - ### Add Record Adds an resource record for an authoritative zone. URL: -`http://localhost:5380/api/zone/addRecord?token=x&domain=example.com&zone=example.com` +`http://localhost:5380/api/zones/records/add?token=x&domain=example.com&zone=example.com` + +OBSOLETE PATH: +`/api/zone/addRecord` +`/api/addRecord` + +PERMISSIONS: +Zones: None +Zone: Modify WHERE: - `token`: The session token generated by the `login` call. @@ -2392,7 +2407,15 @@ RESPONSE: Gets all records for a given authoritative zone. URL: -`http://localhost:5380/api/zone/getRecords?token=x&domain=example.com&zone=example.com` +`http://localhost:5380/api/zones/records/get?token=x&domain=example.com&zone=example.com` + +OBSOLETE PATH: +`/api/zone/getRecords` +`/api/getRecords` + +PERMISSIONS: +Zones: None +Zone: View WHERE: - `token`: The session token generated by the `login` call. @@ -2922,53 +2945,20 @@ RESPONSE: } ``` -### Delete Record - -Deletes a record from an authoritative zone. - -URL: -`http://localhost:5380/api/zone/deleteRecord?token=x&domain=example.com&zone=example.com&type=A&value=127.0.0.1` - -WHERE: -- `token`: The session token generated by the `login` call. -- `domain`: The domain name of the zone to delete the record. -- `zone` (optional): The name of the authoritative zone into which the `domain` exists. When unspecified, the closest authoritative zone will be used. -- `type`: The type of the resource record to delete. -- `ipAddress` (optional): This parameter is required when deleting `A` or `AAAA` record. -- `nameServer` (optional): This parameter is required when deleting `NS` record. -- `ptrName` (optional): This parameter is required when deleting `PTR` record. -- `preference` (optional): This parameter is required when deleting `MX` record. -- `exchange` (optional): This parameter is required when deleting `MX` record. -- `text` (optional): This parameter is required when deleting `TXT` record. -- `priority` (optional): This parameter is required when deleting the `SRV` record. -- `weight` (optional): This parameter is required when deleting the `SRV` record. -- `port` (optional): This parameter is required when deleting the `SRV` record. -- `target` (optional): This parameter is required when deleting the `SRV` record. -- `keyTag` (optional): This parameter is required when deleting `DS` record. -- `algorithm` (optional): This parameter is required when deleting `DS` record. -- `digestType` (optional): This parameter is required when deleting `DS` record. -- `digest` (optional): This parameter is required when deleting `DS` record. -- `flags` (optional): This is the flags parameter in the CAA record. This parameter is required when deleting the `CAA` record. -- `tag` (optional): This is the tag parameter in the CAA record. This parameter is required when deleting the `CAA` record. -- `value` (optional): This parameter is required when deleting the `CAA` record. -- `aname` (optional): This parameter is required when deleting the `ANAME` record. -- `protocol` (optional): This is the protocol parameter in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. This parameter is optional and default value `Udp` will be used when deleting the `FWD` record. -- `forwarder` (optional): This parameter is required when deleting the `FWD` record. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - ### Update Record Updates an existing record in an authoritative zone. URL: -`http://localhost:5380/api/zone/updateRecord?token=x&domain=mail.example.com&zone=example.com&type=A&value=127.0.0.1&newValue=127.0.0.2&ptr=false` +`http://localhost:5380/api/zones/records/update?token=x&domain=mail.example.com&zone=example.com&type=A&value=127.0.0.1&newValue=127.0.0.2&ptr=false` + +OBSOLETE PATH: +`/api/zone/updateRecord` +`/api/updateRecord` + +PERMISSIONS: +Zones: None +Zone: Modify WHERE: - `token`: The session token generated by the `login` call. @@ -3076,8 +3066,509 @@ RESPONSE: } ``` +### Delete Record + +Deletes a record from an authoritative zone. + +URL: +`http://localhost:5380/api/zones/records/delete?token=x&domain=example.com&zone=example.com&type=A&value=127.0.0.1` + +OBSOLETE PATH: +`/api/zone/deleteRecord` +`/api/deleteRecord` + +PERMISSIONS: +Zones: None +Zone: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name of the zone to delete the record. +- `zone` (optional): The name of the authoritative zone into which the `domain` exists. When unspecified, the closest authoritative zone will be used. +- `type`: The type of the resource record to delete. +- `ipAddress` (optional): This parameter is required when deleting `A` or `AAAA` record. +- `nameServer` (optional): This parameter is required when deleting `NS` record. +- `ptrName` (optional): This parameter is required when deleting `PTR` record. +- `preference` (optional): This parameter is required when deleting `MX` record. +- `exchange` (optional): This parameter is required when deleting `MX` record. +- `text` (optional): This parameter is required when deleting `TXT` record. +- `priority` (optional): This parameter is required when deleting the `SRV` record. +- `weight` (optional): This parameter is required when deleting the `SRV` record. +- `port` (optional): This parameter is required when deleting the `SRV` record. +- `target` (optional): This parameter is required when deleting the `SRV` record. +- `keyTag` (optional): This parameter is required when deleting `DS` record. +- `algorithm` (optional): This parameter is required when deleting `DS` record. +- `digestType` (optional): This parameter is required when deleting `DS` record. +- `digest` (optional): This parameter is required when deleting `DS` record. +- `flags` (optional): This is the flags parameter in the CAA record. This parameter is required when deleting the `CAA` record. +- `tag` (optional): This is the tag parameter in the CAA record. This parameter is required when deleting the `CAA` record. +- `value` (optional): This parameter is required when deleting the `CAA` record. +- `aname` (optional): This parameter is required when deleting the `ANAME` record. +- `protocol` (optional): This is the protocol parameter in the FWD record. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. This parameter is optional and default value `Udp` will be used when deleting the `FWD` record. +- `forwarder` (optional): This parameter is required when deleting the `FWD` record. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +## DNS Cache API Calls + +These API calls allow managing the DNS server cache. + +### List Cached Zones + +List all cached zones. + +URL: +`http://localhost:5380/api/cache/list?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/listCachedZones` + +PERMISSIONS: +Cache: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. +- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. + +RESPONSE: +``` +{ + "response": { + "domain": "google.com", + "zones": [], + "records": [ + { + "name": "google.com", + "type": "A", + "ttl": "283 (4 mins 43 sec)", + "rData": { + "value": "216.58.199.174" + } + } + ] + }, + "status": "ok" +} +``` + +### Delete Cached Zone + +Deletes a specific zone from the DNS cache. + +URL: +`http://localhost:5380/api/cache/delete?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/deleteCachedZone` + +PERMISSIONS: +Cache: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name to delete cached records from. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Flush DNS Cache + +This call clears all the DNS cache from the server forcing the DNS server to make recursive queries again to populate the cache. + +URL: +`http://localhost:5380/api/cache/flush?token=x` + +OBSOLETE PATH: +`/api/flushDnsCache` + +PERMISSIONS: +Cache: Delete + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +## Allowed Zones API Calls + +These API calls allow managing the Allowed zones. + +### List Allowed Zones + +List all allowed zones. + +URL: +`http://localhost:5380/api/allowed/list?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/listAllowedZones` + +PERMISSIONS: +Allowed: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. +- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. + +RESPONSE: +``` +{ + "response": { + "domain": "google.com", + "zones": [], + "records": [ + { + "name": "google.com", + "type": "NS", + "ttl": "14400 (4 hours)", + "rData": { + "value": "server1" + } + }, + { + "name": "google.com", + "type": "SOA", + "ttl": "14400 (4 hours)", + "rData": { + "primaryNameServer": "server1", + "responsiblePerson": "hostadmin.server1", + "serial": 1, + "refresh": 14400, + "retry": 3600, + "expire": 604800, + "minimum": 900 + } + } + ] + }, + "status": "ok" +} +``` + +### Allow Zone + +Adds a domain name into the Allowed Zones. + +URL: +`http://localhost:5380/api/allowed/add?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/allowZone` + +PERMISSIONS: +Allowed: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name for the zone to be added. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Delete Allowed Zone + +Allows deleting a zone from the Allowed Zones. + +URL: +`http://localhost:5380/api/allowed/delete?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/deleteAllowedZone` + +PERMISSIONS: +Allowed: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name for the zone to be deleted. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Flush Allowed Zone + +Flushes the Allowed zone to clear all records. + +URL: +`http://localhost:5380/api/allowed/flush?token=x` + +OBSOLETE PATH: +`/api/flushAllowedZone` + +PERMISSIONS: +Allowed: Delete + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Import Allowed Zones + +Imports domain names into the Allowed Zones. + +URL: +`http://localhost:5380/api/allowed/import?token=x` + +OBSOLETE PATH: +`/api/importAllowedZones` + +PERMISSIONS: +Allowed: Modify + +WHERE: +- `token`: The session token generated by the `login` call. + +REQUEST: +This is a `POST` request call where the content type of the request must be `application/x-www-form-urlencoded` and the content must be as shown below: + +``` +allowedZones=google.com,twitter.com +``` + +WHERE: +- `allowedZones`: A list of comma separated domain names that are to be imported. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Export Allowed Zones + +Allows exporting all the zones from the Allowed Zones as a text file. + +URL: +`http://localhost:5380/api/allowed/export?token=x` + +OBSOLETE PATH: +`/api/exportAllowedZones` + +PERMISSIONS: +Allowed: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +Response is a downloadable text file with `Content-Type: text/plain` and `Content-Disposition: attachment`. + +## Blocked Zones API Calls + +These API calls allow managing the Blocked zones. + +### List Blocked Zones + +List all blocked zones. + +URL: +`http://localhost:5380/api/blocked/list?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/listBlockedZones` + +PERMISSIONS: +Blocked: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain` (Optional): The domain name to list records. If not passed, the domain is set to empty string which corresponds to the zone root. +- `direction` (Optional): Allows specifying the direction of browsing the zone. Valid values are [`up`, `down`] and the default value is `down` when parameter is missing. This option allows the server to skip empty labels in the domain name when browsing up or down. + +RESPONSE: +``` +{ + "response": { + "domain": "google.com", + "zones": [], + "records": [ + { + "name": "google.com", + "type": "NS", + "ttl": "14400 (4 hours)", + "rData": { + "value": "server1" + } + }, + { + "name": "google.com", + "type": "SOA", + "ttl": "14400 (4 hours)", + "rData": { + "primaryNameServer": "server1", + "responsiblePerson": "hostadmin.server1", + "serial": 1, + "refresh": 14400, + "retry": 3600, + "expire": 604800, + "minimum": 900 + } + } + ] + }, + "status": "ok" +} +``` + +### Block Zone + +Adds a domain name into the Blocked Zones. + +URL: +`http://localhost:5380/api/blocked/add?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/blockZone` + +PERMISSIONS: +Blocked: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name for the zone to be added. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Delete Blocked Zone + +Allows deleting a zone from the Blocked Zones. + +URL: +`http://localhost:5380/api/blocked/delete?token=x&domain=google.com` + +OBSOLETE PATH: +`/api/deleteBlockedZone` + +PERMISSIONS: +Blocked: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `domain`: The domain name for the zone to be deleted. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Flush Blocked Zone + +Flushes the Blocked zone to clear all records. + +URL: +`http://localhost:5380/api/blocked/flush?token=x` + +OBSOLETE PATH: +`/api/flushBlockedZone` + +PERMISSIONS: +Blocked: Delete + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Import Blocked Zones + +Imports domain names into Blocked Zones. + +URL: +`http://localhost:5380/api/blocked/import?token=x` + +OBSOLETE PATH: +`/api/importBlockedZones` + +PERMISSIONS: +Blocked: Modify + +WHERE: +- `token`: The session token generated by the `login` call. + +REQUEST: +This is a `POST` request call where the content type of the request must be `application/x-www-form-urlencoded` and the content must be as shown below: + +``` +blockedZones=google.com,twitter.com +``` + +WHERE: +- `blockedZones`: A list of comma separated domain names that are to be imported. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Export Blocked Zones + +Allows exporting all the zones from the Blocked Zones as a text file. + +URL: +`http://localhost:5380/api/blocked/export?token=x` + +OBSOLETE PATH: +`/api/exportBlockedZones` + +PERMISSIONS: +Blocked: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +Response is a downloadable text file with `Content-Type: text/plain` and `Content-Disposition: attachment`. + ## DNS Apps API Calls +These API calls allows managing DNS Apps. + ### List Apps Lists all installed apps on the DNS server. If the DNS server has Internet access and is able to retrieve data from DNS App Store, the API call will also return if a store App has updates available. @@ -3085,6 +3576,9 @@ Lists all installed apps on the DNS server. If the DNS server has Internet acces URL: `http://localhost:5380/api/apps/list?token=x` +PERMISSIONS: +Apps/Zones/Logs: View + WHERE: - `token`: The session token generated by the `login` call. @@ -3135,6 +3629,9 @@ Lists all available apps on the DNS App Store. URL: `http://localhost:5380/api/apps/listStoreApps?token=x` +PERMISSIONS: +Apps: View + WHERE: - `token`: The session token generated by the `login` call. @@ -3200,6 +3697,9 @@ Download an app zip file from given URL and installs it on the DNS Server. URL: `http://localhost:5380/api/apps/downloadAndInstall?token=x&name=app-name&url=https://example.com/app.zip` +PERMISSIONS: +Apps: Delete + WHERE: - `token`: The session token generated by the `login` call. - `name`: The name of the app to install. @@ -3220,6 +3720,9 @@ Download an app zip file from given URL and updates an existing app installed on URL: `http://localhost:5380/api/apps/downloadAndUpdate?token=x&name=app-name&url=https://example.com/app.zip` +PERMISSIONS: +Apps: Delete + WHERE: - `token`: The session token generated by the `login` call. - `name`: The name of the app to install. @@ -3240,6 +3743,9 @@ Installs a DNS application on the DNS server. URL: `http://localhost:5380/api/apps/install?token=x&name=app-name` +PERMISSIONS: +Apps: Delete + WHERE: - `token`: The session token generated by the `login` call. - `name`: The name of the app to install. @@ -3261,6 +3767,9 @@ Allows to manually update an installed app using a provided app zip file. URL: `http://localhost:5380/api/apps/update?token=x&name=app-name` +PERMISSIONS: +Apps: Delete + WHERE: - `token`: The session token generated by the `login` call. - `name`: The name of the app to update. @@ -3282,6 +3791,9 @@ Uninstall an app from the DNS server. This does not remove any APP records that URL: `http://localhost:5380/api/apps/uninstall?token=x&name=app-name` +PERMISSIONS: +Apps: Delete + WHERE: - `token`: The session token generated by the `login` call. - `name`: The name of the app to uninstall. @@ -3299,7 +3811,13 @@ RESPONSE: Retrieve the DNS application config from the `dnsApp.config` file in the application folder. URL: -`http://localhost:5380/api/apps/getConfig?token=x&name=app-name` +`http://localhost:5380/api/apps/config/get?token=x&name=app-name` + +OBSOLETE PATH: +`/api/apps/getConfig` + +PERMISSIONS: +Apps: View WHERE: - `token`: The session token generated by the `login` call. @@ -3320,7 +3838,13 @@ RESPONSE: Saves the provided DNS application config into the `dnsApp.config` file in the application folder. URL: -`http://localhost:5380/api/apps/setConfig?token=x&name=app-name` +`http://localhost:5380/api/apps/config/set?token=x&name=app-name` + +OBSOLETE PATH: +`/api/apps/setConfig` + +PERMISSIONS: +Apps: Modify WHERE: - `token`: The session token generated by the `login` call. @@ -3341,10 +3865,18 @@ RESPONSE: ## DNS Client API Calls +These API calls allow interacting with the DNS Client section. + ### Resolve Query URL: -`http://localhost:5380/api/resolveQuery?token=x&server=this-server&domain=example.com&type=A&protocol=UDP` +`http://localhost:5380/api/dnsClient/resolve?token=x&server=this-server&domain=example.com&type=A&protocol=UDP` + +OBSOLETE PATH: +`/api/resolveQuery` + +PERMISSIONS: +DnsClient: View WHERE: - `token`: The session token generated by the `login` call. @@ -3353,7 +3885,7 @@ WHERE: - `type`: The type of the query. - `protocol` (optional): The DNS transport protocol to be used to query. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`, `HttpsJson`]. The default value of `Udp` is used when the parameter is missing. - `dnssec` (optional): Set to `true` to enable DNSSEC validation. -- `import` (optional): This parameter when set to `true` indicates that the response of the DNS query should be imported in the an authoritative zone on this DNS server. Default value is `false` when this parameter is missing. If a zone does not exists, a primary zone for the `domain` name is created and the records from the response are set into the zone. Import can be done only for primary and forwarder type of zones. When `type` is set to AXFR, then the import feature will work as if a zone transfer was requested and the complete zone will be updated as per the zone transfer response. Note that any existing record type for the given `type` will be overwritten when syncing the records. It is recommended to use `recursive-resolver` or the actual name server address for the `server` parameter when importing records. +- `import` (optional): This parameter when set to `true` indicates that the response of the DNS query should be imported in the an authoritative zone on this DNS server. Default value is `false` when this parameter is missing. If a zone does not exists, a primary zone for the `domain` name is created and the records from the response are set into the zone. Import can be done only for primary and forwarder type of zones. When `type` is set to AXFR, then the import feature will work as if a zone transfer was requested and the complete zone will be updated as per the zone transfer response. Note that any existing record type for the given `type` will be overwritten when syncing the records. It is recommended to use `recursive-resolver` or the actual name server address for the `server` parameter when importing records. You must have Zones Modify permission to create a zone or Zone Modify permission to import records into an existing zone. RESPONSE: ``` @@ -3408,6 +3940,1599 @@ RESPONSE: } ``` +## Settings API Calls + +These API calls allow managing the DNS server settings. + +### Get DNS Settings + +This call returns all the DNS server settings. + +URL: +`http://localhost:5380/api/settings/get?token=x` + +OBSOLETE PATH: +`/api/getDnsSettings` + +PERMISSIONS: +Settings: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "version": "9.0", + "dnsServerDomain": "server1", + "dnsServerLocalEndPoints": [ + "0.0.0.0:53", + "[::]:53" + ], + "webServiceLocalAddresses": [ + "0.0.0.0", + "[::]" + ], + "webServiceHttpPort": 5380, + "webServiceEnableTls": false, + "webServiceHttpToTlsRedirect": false, + "webServiceTlsPort": 53443, + "webServiceUseSelfSignedTlsCertificate": false, + "webServiceTlsCertificatePath": "", + "webServiceTlsCertificatePassword": "************", + "enableDnsOverHttp": false, + "enableDnsOverTls": false, + "enableDnsOverHttps": false, + "dnsTlsCertificatePath": null, + "dnsTlsCertificatePassword": "************", + "tsigKeys": [], + "defaultRecordTtl": 3600, + "dnsAppsEnableAutomaticUpdate": true, + "preferIPv6": false, + "udpPayloadSize": 1232, + "dnssecValidation": true, + "resolverRetries": 2, + "resolverTimeout": 2000, + "resolverMaxStackCount": 16, + "forwarderRetries": 3, + "forwarderTimeout": 2000, + "forwarderConcurrency": 2, + "clientTimeout": 4000, + "tcpSendTimeout": 10000, + "tcpReceiveTimeout": 10000, + "enableLogging": true, + "logQueries": false, + "useLocalTime": false, + "logFolder": "logs", + "maxLogFileDays": 0, + "maxStatFileDays": 0, + "recursion": "AllowOnlyForPrivateNetworks", + "recursionDeniedNetworks": [], + "recursionAllowedNetworks": [], + "randomizeName": true, + "qnameMinimization": true, + "nsRevalidation": true, + "qpmLimitRequests": 0, + "qpmLimitErrors": 0, + "qpmLimitSampleMinutes": 5, + "qpmLimitIPv4PrefixLength": 24, + "qpmLimitIPv6PrefixLength": 56, + "serveStale": true, + "serveStaleTtl": 259200, + "cacheMaximumEntries": 10000, + "cacheMinimumRecordTtl": 10, + "cacheMaximumRecordTtl": 604800, + "cacheNegativeRecordTtl": 300, + "cacheFailureRecordTtl": 60, + "cachePrefetchEligibility": 2, + "cachePrefetchTrigger": 9, + "cachePrefetchSampleIntervalInMinutes": 5, + "cachePrefetchSampleEligibilityHitsPerHour": 30, + "proxy": null, + "forwarders": null, + "forwarderProtocol": "Udp", + "enableBlocking": true, + "allowTxtBlockingReport": true, + "blockingType": "AnyAddress", + "customBlockingAddresses": [], + "blockListUrls": null, + "blockListUpdateIntervalHours": 24 + }, + "status": "ok" +} +``` + +### Set DNS Settings + +This call allows to change the DNS server settings. + +URL: +`http://localhost:5380/api/settings/set?token=x&dnsServerDomain=server1&dnsServerLocalEndPoints=0.0.0.0:53,[::]:53&webServiceLocalAddresses=0.0.0.0,[::]&webServiceHttpPort=5380&webServiceEnableTls=false&webServiceTlsPort=53443&webServiceTlsCertificatePath=&webServiceTlsCertificatePassword=&enableDnsOverHttp=false&enableDnsOverTls=false&enableDnsOverHttps=false&dnsTlsCertificatePath=&dnsTlsCertificatePassword=&preferIPv6=false&logQueries=true&allowRecursion=true&allowRecursionOnlyForPrivateNetworks=true&randomizeName=true&cachePrefetchEligibility=2&cachePrefetchTrigger=9&cachePrefetchSampleIntervalInMinutes=5&cachePrefetchSampleEligibilityHitsPerHour=30&proxyType=socks5&proxyAddress=192.168.10.2&proxyPort=9050&proxyUsername=username&proxyPassword=password&proxyBypass=127.0.0.0/8,169.254.0.0/16,fe80::/10,::1,localhost&forwarders=192.168.10.2&forwarderProtocol=Udp&useNxDomainForBlocking=false&blockListUrls=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts,https://mirror1.malwaredomains.com/files/justdomains,https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt,https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt` + +OBSOLETE PATH: +`/api/setDnsSettings` + +PERMISSIONS: +Settings: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `dnsServerDomain` (optional): The primary domain name used by this DNS Server to identify itself. +- `dnsServerLocalEndPoints` (optional): Local end points are the network interface IP addresses and ports you want the DNS Server to listen for requests. +- `webServiceLocalAddresses` (optional): Local addresses are the network interface IP addresses you want the web service to listen for requests. +- `webServiceHttpPort` (optional): Specify the TCP port number for the web console and this API web service. Default value is `5380`. +- `webServiceEnableTls` (optional): Set this to `true` to start the HTTPS service to acccess web service. +- `webServiceTlsPort` (optional): Specified the TCP port number for the web console for HTTPS access. +- `webServiceUseSelfSignedTlsCertificate` (optional): Set `true` for the web service to use an automatically generated self signed certificate when TLS certificate path is not specified. +- `webServiceTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the web console for HTTPS access. +- `webServiceTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any. +- `enableDnsOverHttp` (optional): Enable this option to accept DNS-over-HTTP requests for both wire and json response formats. It must be used with a TLS terminating reverse proxy like nginx and will work only on private networks. +- `enableDnsOverTls` (optional): Enable this option to accept DNS-over-TLS requests. +- `enableDnsOverHttps` (optional): Enable this option to accept DNS-over-HTTPS requests for both wire and json response formats. +- `dnsTlsCertificatePath` (optional): Specify a PKCS #12 certificate (.pfx) file path on the server. The certificate must contain private key. This certificate is used by the DNS-over-TLS and DNS-over-HTTPS optional protocols. +- `dnsTlsCertificatePassword` (optional): Enter the certificate (.pfx) password, if any. +- `tsigKeys` (optional): A pipe `|` separated multi row list of TSIG key name, shared secret, and algorithm. Set this parameter to `false` to remove all existing keys. Supported algorithms are [`hmac-md5.sig-alg.reg.int`, `hmac-sha1`, `hmac-sha256`, `hmac-sha256-128`, `hmac-sha384`, `hmac-sha384-192`, `hmac-sha512`, `hmac-sha512-256`]. +- `defaultRecordTtl` (optional): The default TTL value to use if not specified when adding or updating records in a Zone. +- `dnsAppsEnableAutomaticUpdate` (optional): Set to `true` to allow DNS server to automatically update the DNS Apps from the DNS App Store. The DNS Server will check for updates every 24 hrs when this option is enabled. +- `preferIPv6` (optional): DNS Server will use IPv6 for querying whenever possible with this option enabled. Default value is `false`. +- `udpPayloadSize` (optional): The maximum EDNS UDP payload size that can be used to avoid IP fragmentation. Valid range is 512-4096 bytes. Default value is `1232`. +- `dnssecValidation` (optional): Set this to `true` to enable DNSSEC validation. DNS Server will validate all responses from name servers or forwarders when this option is enabled. +- `resolverRetries` (optional): The number of retries that the recursive resolver must do. +- `resolverTimeout` (optional): The timeout value in milliseconds for the recursive resolver. +- `resolverMaxStackCount` (optional): The max stack count that the recursive resolver must use. +- `forwarderRetries` (optional): The number of retries that the forwarder DNS client must do. +- `forwarderTimeout` (optional): The timeout value in milliseconds for the forwarder DNS client. +- `forwarderConcurrency` (optional): The number of concurrent requests that the forwarder DNS client should do. +- `clientTimeout` (optional): The amount of time the DNS server must wait in milliseconds before responding with a ServerFailure response to a client request when no answer is available. +- `tcpSendTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for an ACK before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports. +- `tcpReceiveTimeout` (optional): The amount of time in milliseconds a TCP socket must wait for data before closing the connection. This option will apply for DNS requests being received by the DNS Server over TCP, TLS, or HTTPS transports. +- `enableLogging` (optional): Enable this option to log error and audit logs into the log file. Default value is `true`. +- `logQueries` (optional): Enable this option to log every query received by this DNS Server and the corresponding response answers into the log file. Default value is `false`. +- `useLocalTime` (optional): Enable this option to use local time instead of UTC for logging. Default value is `false`. +- `logFolder` (optional): The folder path on the server where the log files should be saved. The path can be relative to the DNS server config folder. Default value is `logs`. +- `maxLogFileDays` (optional): Max number of days to keep the log files. Log files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete. +- `maxStatFileDays` (optional): Max number of days to keep the dashboard stats. Stat files older than the specified number of days will be deleted automatically. Recommended value is `365`. Set `0` to disable auto delete. +- `recursion` (optional): Sets the recursion policy for the DNS server. Valid values are [`Deny`, `Allow`, `AllowOnlyForPrivateNetworks`, `UseSpecifiedNetworks`]. +- `recursionDeniedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be denied recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`. +- `recursionAllowedNetworks` (optional): A comma separated list of network addresses in CIDR format that must be allowed recursion. Set this parameter to `false` to remove existing values. These values are only used when `recursion` is set to `UseSpecifiedNetworks`. +- `randomizeName` (optional): Enables QNAME randomization [draft-vixie-dnsext-dns0x20-00](https://tools.ietf.org/html/draft-vixie-dnsext-dns0x20-00) when using UDP as the transport protocol. Default value is `true`. +- `qnameMinimization` (optional): Enables QNAME minimization [draft-ietf-dnsop-rfc7816bis-04](https://tools.ietf.org/html/draft-ietf-dnsop-rfc7816bis-04) when doing recursive resolution. Default value is `true`. +- `nsRevalidation` (optional): Enables [draft-ietf-dnsop-ns-revalidation](https://datatracker.ietf.org/doc/draft-ietf-dnsop-ns-revalidation/) for recursive resolution. Default value is `true`. +- `qpmLimitRequests` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests that is enforces per client subnet. Set value to `0` to disable the feature. +- `qpmLimitErrors` (optional): Sets the Queries Per Minute (QPM) limit on total number of requests which generates an error response that is enforces per client subnet. Set value to `0` to disable the feature. Response with an RCODE of FormatError, ServerFailure, or Refused is considered as an error response. +- `qpmLimitSampleMinutes` (optional): Sets the client query stats sample size in minutes for QPM limit feature. Default value is `5`. +- `qpmLimitIPv4PrefixLength` (optional): Sets the client subnet IPv4 prefix length used to define the subnet. Default value is `24`. +- `qpmLimitIPv6PrefixLength` (optional): Sets the client subnet IPv6 prefix length used to define the subnet. Default value is `56`. +- `serveStale` (optional): Enable the serve stale feature to improve resiliency by using expired or stale records in cache when the DNS server is unable to reach the upstream or authoritative name servers. Default value is `true`. +- `serveStaleTtl` (optional): The TTL value in seconds which should be used for cached records that are expired. When the serve stale TTL too expires for a stale record, it gets removed from the cache. Recommended value is between 1-3 days and maximum supported value is 7 days. Default value is `259200`. +- `temporaryDisableBlockingTill` (read only): An ISO 8601 String with the Date and Time when the Temporary Blocking will end. +- `cacheMinimumRecordTtl` (optional): The minimum TTL value that a record can have in cache. Set a value to make sure that the records with TTL value than it stays in cache for a minimum duration. Default value is `10`. +- `cacheMaximumRecordTtl` (optional): The maximum TTL value that a record can have in cache. Set a lower value to allow the records to expire early. Default value is `86400`. +- `cacheNegativeRecordTtl` (optional): The negative TTL value to use when there is no SOA MINIMUM value available. Default value is `300`. +- `cacheFailureRecordTtl` (optional): The failure TTL value to used for caching failure responses. This allows storing failure record in cache and prevent frequent recursive resolution to name servers that are responding with `ServerFailure`. Default value is `60`. +- `cachePrefetchEligibility` (optional): The minimum initial TTL value of a record needed to be eligible for prefetching. +- `cachePrefetchTrigger` (optional): A record with TTL value less than trigger value will initiate prefetch operation immediately for itself. Set `0` to disable prefetching & auto prefetching. +- `cachePrefetchSampleIntervalInMinutes` (optional): The interval to sample eligible domain names from last hour stats for auto prefetch. +- `cachePrefetchSampleEligibilityHitsPerHour` (optional): Minimum required hits per hour for a domain name to be eligible for auto prefetch. +- `proxyType` (optional): The type of proxy protocol to be used. Valid values are [`None`, `Http`, `Socks5`]. +- `proxyAddress` (optional): The proxy server hostname or IP address. +- `proxyPort` (optional): The proxy server port. +- `proxyUsername` (optional): The proxy server username. +- `proxyPassword` (optional): The proxy server password. +- `proxyBypass` (optional): A comma separated bypass list consisting of IP addresses, network addresses in CIDR format, or host/domain names to never use proxy for. +- `forwarders` (optional): A comma separated list of forwarders to be used by this DNS server. Set this parameter to `false` string to remove existing forwarders so that the DNS server does recursive resolution by itself. +- `forwarderProtocol` (optional): The forwarder DNS transport protocol to be used. Valid values are [`Udp`, `Tcp`, `Tls`, `Https`]. +- `enableBlocking` (optional): Sets the DNS server to block domain names using Blocked Zone and Block List Zone. +- `allowTxtBlockingReport` (optional): Specifies if the DNS Server should respond with TXT records containing a blocked domain report for TXT type requests. +- `blockingType` (optional): Sets how the DNS server should respond to a blocked domain request. Valid values are [`AnyAddress`, `NxDomain`, `CustomAddress`] where `AnyAddress` is default which response with `0.0.0.0` and `::` IP addresses for blocked domains. Using `NxDomain` will respond with `NX Domain` response. `CustomAddress` will return the specified custom blocking addresses. +- `blockListNextUpdatedOn` (read only): An ISO 8601 String with the Date and Time when the blocklist will next be updated. +- `customBlockingAddresses` (optional): Set the custom blocking addresses to be used for blocked domain response. These addresses are returned only when `blockingType` is set to `CustomAddress`. +- `blockListUrls` (optional): A comma separated list of block list URLs that this server must automatically download and use with the block lists zone. DNS Server will use the data returned by the block list URLs to update the block list zone automatically every 24 hours. The expected file format is standard hosts file format or plain text file containing list of domains to block. Set this parameter to `false` to remove existing values. +- `blockListUpdateIntervalHours` (optional): The interval in hours to automatically download and update the block lists. Default value is `24`. + +RESPONSE: +This call returns the newly updated settings in the same format as that of the `getDnsSettings` call. + +### Force Update Block Lists + +This call allows to reset the next update schedule and force download and update of the block lists. + +URL: +`http://localhost:5380/api/settings/forceUpdateBlockLists?token=x` + +OBSOLETE PATH: +`/api/forceUpdateBlockLists` + +PERMISSIONS: +Settings: Modify + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "status": "ok" +} +``` + +### Temporarily Disable Block Lists + +This call temporarily disables the block lists and block list zones. + +URL: +`http://localhost:5380/api/settings/temporaryDisableBlocking?token=x&minutes=5` + +OBSOLETE PATH: +`/api/temporaryDisableBlocking` + +PERMISSIONS: +Settings: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `minutes`: The time in minutes to disable the blocklist for. + +RESPONSE: +``` +{ + "status": "ok", + "response": { + "temporaryDisableBlockingTill": "2021-10-10T01:14:27.1106773Z" + } +} +``` + +### Backup Settings + +This call returns a zip file containing copies of all the items that were requested to be backed up. + +URL: +`http://localhost:5380/api/settings/backup?token=x&blockLists=true&logs=true&scopes=true&stats=true&zones=true&allowedZones=true&blockedZones=true&dnsSettings=true&logSettings=true&authConfig=true` + +OBSOLETE PATH: +`/api/backupSettings` + +PERMISSIONS: +Settings: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `blockLists` (optional): Set to `true` to backup block lists cache files. Default value is `false`. +- `logs` (optional): Set to `true` to backup log files. Default value is `false`. +- `scopes` (optional): Set to `true` to backup DHCP scope files. Default value is `false`. +- `apps` (optional): Set to `true` to backup the installed DNS apps. Default value is `false`. +- `stats` (optional): Set to `true` to backup dashboard stats files. Default value is `false`. +- `zones` (optional): Set to `true` to backup DNS zone files. Default value is `false`. +- `allowedZones` (optional): Set to `true` to backup allowed zones file. Default value is `false`. +- `blockedZones` (optional): Set to `true` to backup blocked zones file. Default value is `false`. +- `dnsSettings` (optional): Set to `true` to backup DNS settings file. Default value is `false`. +- `logSettings` (optional): Set to `true` to backup log settings file. Default value is `false`. +- `authConfig` (optional): Set to `true` to backup the authentication config file. Default value is `false`. + +RESPONSE: +A zip file with content type `application/zip` and content disposition set to `attachment`. + +### Restore Settings + +This call restores selected items from a given backup zip file. + +URL: +`http://localhost:5380/api/settings/restore?token=x&blockLists=true&logs=true&scopes=true&stats=true&zones=true&allowedZones=true&blockedZones=true&dnsSettings=true&logSettings=true&deleteExistingFiles=true&authConfig=true` + +OBSOLETE PATH: +`/api/restoreSettings` + +PERMISSIONS: +Settings: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `blockLists` (optional): Set to `true` to restore block lists cache files. Default value is `false`. +- `logs` (optional): Set to `true` to restore log files. Default value is `false`. +- `scopes` (optional): Set to `true` to restore DHCP scope files. Default value is `false`. +- `apps` (optional): Set to `true` to restore the DNS apps. Default value is `false`. +- `stats` (optional): Set to `true` to restore dashboard stats files. Default value is `false`. +- `zones` (optional): Set to `true` to restore DNS zone files. Default value is `false`. +- `allowedZones` (optional): Set to `true` to restore allowed zones file. Default value is `false`. +- `blockedZones` (optional): Set to `true` to restore blocked zones file. Default value is `false`. +- `dnsSettings` (optional): Set to `true` to restore DNS settings file. Default value is `false`. +- `logSettings` (optional): Set to `true` to restore log settings file. Default value is `false`. +- `authConfig` (optional): Set to `true` to restore the authentication config file. Default value is `false`. +- `deleteExistingFiles` (optional). Set to `true` to delete existing files for selected items. Default value is `false`. + +REQUEST: +This is a `POST` request call where the request must be multi-part form data with the backup zip file data in binary format. + +RESPONSE: +This call returns the newly updated settings in the same format as that of the `getDnsSettings` call. + +## DHCP API Calls + +Allows managing the built-in DHCP server. + +### List DHCP Leases + +Lists all the DHCP leases. + +URL: +`http://localhost:5380/api/dhcp/leases/list?token=x` + +OBSOLETE PATH: +`/api/listDhcpLeases` + +PERMISSIONS: +DhcpServer: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "leases": [ + { + "scope": "Default", + "type": "Reserved", + "hardwareAddress": "00-00-00-00-00-00", + "clientIdentifier": "1-000000000000", + "address": "192.168.1.5", + "hostName": "server1.local", + "leaseObtained": "08/25/2020 17:52:51", + "leaseExpires": "09/26/2020 14:27:12" + }, + { + "scope": "Default", + "type": "Dynamic", + "hardwareAddress": "00-00-00-00-00-00", + "clientIdentifier": "1-000000000000", + "address": "192.168.1.13", + "hostName": null, + "leaseObtained": "06/15/2020 16:41:46", + "leaseExpires": "09/25/2020 12:39:54" + }, + { + "scope": "Default", + "type": "Dynamic", + "hardwareAddress": "00-00-00-00-00-00", + "clientIdentifier": "1-000000000000", + "address": "192.168.1.15", + "hostName": "desktop-ea2miaf.local", + "leaseObtained": "06/18/2020 12:19:03", + "leaseExpires": "09/25/2020 12:17:11" + }, + ] + }, + "status": "ok" +} +``` + +### Remove DHCP Lease + +Removes a dynamic or reserved lease allocation. This API must be used carefully to make sure that there is no IP address conflict caused by removing a lease. + +URL: +`http://localhost:5380/api/dhcp/leases/remove?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` + +OBSOLETE PATH: +`/api/removeDhcpLease` + +PERMISSIONS: +DhcpServer: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. +- `hardwareAddress` (optional): The MAC address of the device bearing the dynamic/reserved lease. Either `hardwareAddress` or `clientIdentifier` must be specified. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Convert To Reserved Lease + +Converts a dynamic lease to reserved lease. + +URL: +`http://localhost:5380/api/dhcp/leases/convertToReserved?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` + +OBSOLETE PATH: +`/api/convertToReservedLease` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. +- `hardwareAddress` (optional): The MAC address of the device bearing the dynamic lease. Either `hardwareAddress` or `clientIdentifier` must be specified. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Convert To Dynamic Lease + +Converts a reserved lease to dynamic lease. + +URL: +`http://localhost:5380/api/dhcp/leases/convertToDynamic?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` + +OBSOLETE PATH: +`/api/convertToDynamicLease` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. +- `hardwareAddress` (optional): The MAC address of the device bearing the reserved lease. Either `hardwareAddress` or `clientIdentifier` must be specified. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### List DHCP Scopes + +Lists all the DHCP scopes available on the server. + +URL: +`http://localhost:5380/api/dhcp/scopes/list?token=x` + +OBSOLETE PATH: +`/api/listDhcpScopes` + +PERMISSIONS: +DhcpServer: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "scopes": [ + { + "name": "Default", + "enabled": false, + "startingAddress": "192.168.1.1", + "endingAddress": "192.168.1.254", + "subnetMask": "255.255.255.0", + "networkAddress": "192.168.1.0", + "broadcastAddress": "192.168.1.255" + } + ] + }, + "status": "ok" +} +``` + +### Get DHCP Scope + +Gets the complete details of the scope configuration. + +URL: +`http://localhost:5380/api/dhcp/scopes/get?token=x&name=Default` + +OBSOLETE PATH: +`/api/getDhcpScope` + +PERMISSIONS: +DhcpServer: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. + +RESPONSE: +``` +{ + "response": { + "name": "Default", + "startingAddress": "192.168.1.1", + "endingAddress": "192.168.1.254", + "subnetMask": "255.255.255.0", + "leaseTimeDays": 7, + "leaseTimeHours": 0, + "leaseTimeMinutes": 0, + "offerDelayTime": 0, + "pingCheckEnabled": false, + "pingCheckTimeout": 1000, + "pingCheckRetries": 2, + "domainName": "local", + "dnsTtl": 900, + "serverAddress": "192.168.1.1", + "serverHostName": "tftp-server-1", + "bootFileName": "boot.bin", + "routerAddress": "192.168.1.1", + "useThisDnsServer": false, + "dnsServers": [ + "192.168.1.5" + ], + "winsServers": [ + "192.168.1.5" + ], + "ntpServers": [ + "192.168.1.5" + ], + "staticRoutes": [ + { + "destination": "172.16.0.0", + "subnetMask": "255.255.255.0", + "router": "192.168.1.2" + } + ], + "vendorInfo": [ + { + "identifier": "substring(vendor-class-identifier,0,9)==\"PXEClient\"", + "information": "06:01:03:0A:04:00:50:58:45:09:14:00:00:11:52:61:73:70:62:65:72:72:79:20:50:69:20:42:6F:6F:74:FF" + } + ], + "exclusions": [ + { + "startingAddress": "192.168.1.1", + "endingAddress": "192.168.1.10" + } + ], + "reservedLeases": [ + { + "hostName": null, + "hardwareAddress": "00-00-00-00-00-00", + "address": "192.168.1.10", + "comments": "comments" + } + ], + "allowOnlyReservedLeases": false + }, + "status": "ok" +} +``` + +### Set DHCP Scope + +Sets the DHCP scope configuration. + +URL: +`http://localhost:5380/api/dhcp/scopes/set?token=x&name=Default&startingAddress=192.168.1.1&endingAddress=192.168.1.254&subnetMask=255.255.255.0&leaseTimeDays=7&leaseTimeHours=0&leaseTimeMinutes=0&offerDelayTime=0&domainName=local&dnsTtl=900&serverAddress=&serverHostName=&bootFileName=&routerAddress=192.168.1.1&useThisDnsServer=false&dnsServers=192.168.1.5&winsServers=192.168.1.5&ntpServers=192.168.1.5&staticRoutes=172.16.0.0|255.255.255.0|192.168.1.2&exclusions=192.168.1.1|192.168.1.10&reservedLeases=hostname|00-00-00-00-00-00|192.168.1.10|comments&allowOnlyReservedLeases=false` + +OBSOLETE PATH: +`/api/setDhcpScope` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `newName` (optional): The new name of the DHCP scope to rename an existing scope. +- `startingAddress` (optional): The starting IP address of the DHCP scope. This parameter is required when creating a new scope. +- `endingAddress` (optional): The ending IP address of the DHCP scope. This parameter is required when creating a new scope. +- `subnetMask` (optional): The subnet mask of the network. This parameter is required when creating a new scope. +- `leaseTimeDays` (optional): The lease time in number of days. +- `leaseTimeHours` (optional): The lease time in number of hours. +- `leaseTimeMinutes` (optional): The lease time in number of minutes. +- `offerDelayTime` (optional): The time duration in milli seconds that the DHCP server delays sending an DHCPOFFER message. +- `pingCheckEnabled` (optional): Set this option to `true` to allow the DHCP server to find out if an IP address is already in use to prevent IP address conflict when some of the devices on the network have manually configured IP addresses. +- `pingCheckTimeout` (optional): The timeout interval to wait for an ping reply. +- `pingCheckRetries` (optional): The maximum number of ping requests to try. +- `domainName` (optional): The domain name to be used by this network. The DHCP server automatically adds forward and reverse DNS entries for each IP address allocations when domain name is configured. +- `dnsTtl` (optional): The TTL value used for forward and reverse DNS records. +- `serverAddress` (optional): The bootstrap TFTP server IP address to be used by the clients. If not specified, the DHCP server's IP address is used. +- `serverHostName` (optional): The optional bootstrap TFTP server host name to be used by the clients to identify the TFTP server. +- `bootFileName` (optional): The boot file name stored on the bootstrap TFTP server to be used by the clients. +- `routerAddress` (optional): The default gateway or router IP address to be used by the clients. +- `useThisDnsServer` (optional): Tells the DHCP server to use this DNS server's IP address to configure the DNS Servers DHCP option for clients. +- `dnsServers` (optional): A comma separated list of DNS server IP addresses to be used by the clients. This parameter is ignored when `useThisDnsServer` is set to `true`. +- `winsServers` (optional): A comma separated list of NBNS/WINS server IP addresses to be used by the clients. +- `ntpServers` (optional): A comma separated list of Network Time Protocol (NTP) server IP addresses to be used by the clients. +- `staticRoutes` (optional): A `|` separated list of static routes in format `{destination network address}|{subnet mask}|{router/gateway address}` to be used by the clients for accessing specified destination networks. +- `vendorInfo` (optional): A `|` separated list of vendor information in format `{vendor class identifier}|{vendor specific information}` where `{vendor specific information}` is a colon separated hex string. +- `exclusions` (optional): A `|` separated list of IP address range in format `{starting address}|{ending address}` that must be excluded or not assigned dynamically to any client by the DHCP server. +- `reservedLeases` (optional): A `|` separated list of reserved IP addresses in format `{host name}|{MAC address}|{reserved IP address}|{comments}` to be assigned to specific clients based on their MAC address. +- `allowOnlyReservedLeases` (optional): Set this parameter to `true` to stop dynamic IP address allocation and allocate only reserved IP addresses. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Add Reserved Lease + +Adds a reserved lease entry to the specified scope. + +URL: +`http://localhost:5380/api/dhcp/scopes/addReservedLease?token=x` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `hardwareAddress`: The MAC address of the client. +- `ipAddress`: The reserved IP address for the client. +- `hostName` (optional): The hostname of the client to override. +- `comments` (optional): Comments for the reserved lease entry. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Remove Reserved Lease + +Removed a reserved lease entry from the specified scope. + +URL: +`http://localhost:5380/api/dhcp/scopes/removeReservedLease?token=x` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. +- `hardwareAddress`: The MAC address of the client. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Enable DHCP Scope + +Enables the DHCP scope allowing the server to allocate leases. + +URL: +`http://localhost:5380/api/dhcp/scopes/enable?token=x&name=Default` + +OBSOLETE PATH: +`/api/enableDhcpScope` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Disable DHCP Scope + +Disables the DHCP scope and stops any further lease allocations. + +URL: +`http://localhost:5380/api/dhcp/scopes/disable?token=x&name=Default` + +OBSOLETE PATH: +`/api/disableDhcpScope` + +PERMISSIONS: +DhcpServer: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### Delete DHCP Scope + +Permanently deletes the DHCP scope from the disk. + +URL: +`http://localhost:5380/api/dhcp/scopes/delete?token=x&name=Default` + +OBSOLETE PATH: +`/api/deleteDhcpScope` + +PERMISSIONS: +DhcpServer: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `name`: The name of the DHCP scope. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +## Administration API Calls + +Allows managing the DNS server administration which includes managing all sessions, users, groups, and permissions. + +### List Sessions + +Returns a list of active user sessions. + +URL: +`http://localhost:5380/api/admin/sessions/list?token=x` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "sessions": [ + { + "username": "admin", + "isCurrentSession": true, + "partialToken": "272f4890427b9ab5", + "type": "Standard", + "tokenName": null, + "lastSeen": "2022-09-17T13:23:44.9972772Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + }, + { + "username": "admin", + "isCurrentSession": false, + "partialToken": "ddfaecb8e9325e77", + "type": "ApiToken", + "tokenName": "MyToken1", + "lastSeen": "2022-09-17T13:22:45.6710766Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + } + ] + }, + "status": "ok" +} +``` + +### Create API Token + +Allows creating a non-expiring API token that can be used with automation scripts to make API calls. The token allows access to API calls with the same privileges as that of the user and thus its advised to create a separate user with limited permissions required for creating the API token. The token cannot be used to change the user's password, or update the user profile details. + +URL: +`http://localhost:5380/api/admin/sessions/createToken?token=x&user=admin&tokenName=MyToken1` + +PERMISSIONS: +Administration: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `user`: The username for the user account for which to generate the API token. +- `tokenName`: The name of the created token to identify its session. + +RESPONSE: +``` +{ + "response": { + "username": "admin", + "tokenName": "MyToken1", + "token": "ddfaecb8e9325e77865ee7e100f89596a65d3eae0e6dddcb33172355b95a64af" + }, + "status": "ok" +} +``` + +### Delete Session + +Deletes a specified user's session. + +URL: +`http://localhost:5380/api/admin/sessions/delete?token=x&partialToken=ddfaecb8e9325e77` + +PERMISSIONS: +Administration: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `partialToken`: The partial token of the session to delete that was returned by the list of sessions. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### List Users + +Returns a list of all users. + +URL: +`http://localhost:5380/api/admin/users/list?token=x` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "users": [ + { + "displayName": "Administrator", + "username": "admin", + "disabled": false, + "previousSessionLoggedOn": "2022-09-17T13:20:32.7933783Z", + "previousSessionRemoteAddress": "127.0.0.1", + "recentSessionLoggedOn": "2022-09-17T13:22:45.671081Z", + "recentSessionRemoteAddress": "127.0.0.1" + }, + { + "displayName": "Shreyas Zare", + "username": "shreyas", + "disabled": false, + "previousSessionLoggedOn": "0001-01-01T00:00:00Z", + "previousSessionRemoteAddress": "0.0.0.0", + "recentSessionLoggedOn": "0001-01-01T00:00:00Z", + "recentSessionRemoteAddress": "0.0.0.0" + } + ] + }, + "status": "ok" +} +``` + +### Create User + +Creates a new user account. + +URL: +`http://localhost:5380/api/admin/users/create?token=x&displayName=User&user=user1&pass=password` + +PERMISSIONS: +Administration: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `user`: A unique username for the user account. +- `pass`: A password for the user account. +- `displayName` (optional): The display name for the user account. + +RESPONSE: +``` +{ + "response": { + "displayName": "User", + "username": "user1", + "disabled": false, + "previousSessionLoggedOn": "0001-01-01T00:00:00", + "previousSessionRemoteAddress": "0.0.0.0", + "recentSessionLoggedOn": "0001-01-01T00:00:00", + "recentSessionRemoteAddress": "0.0.0.0" + }, + "status": "ok" +} +``` + +### Get User Details + +Returns a user account profile details. + +URL: +`http://localhost:5380/api/admin/users/get?token=x&user=admin&includeGroups=true + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `user`: The username for the user account. +- `includeGroups` (optional): Set `true` to include a list of groups in response. + +RESPONSE: +``` +{ + "response": { + "displayName": "Administrator", + "username": "admin", + "disabled": false, + "previousSessionLoggedOn": "2022-09-16T13:22:45.671Z", + "previousSessionRemoteAddress": "127.0.0.1", + "recentSessionLoggedOn": "2022-09-18T09:55:26.9800695Z", + "recentSessionRemoteAddress": "127.0.0.1", + "sessionTimeoutSeconds": 1800, + "memberOfGroups": [ + "Administrators" + ], + "sessions": [ + { + "username": "admin", + "isCurrentSession": false, + "partialToken": "1f8011516cea27af", + "type": "Standard", + "tokenName": null, + "lastSeen": "2022-09-18T09:55:40.6519988Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + }, + { + "username": "admin", + "isCurrentSession": false, + "partialToken": "ddfaecb8e9325e77", + "type": "ApiToken", + "tokenName": "MyToken1", + "lastSeen": "2022-09-17T13:22:45.671Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + } + ], + "groups": [ + "Administrators", + "DHCP Administrators", + "DNS Administrators" + ] + }, + "status": "ok" +} +``` + +### Set User Details + +Allows changing user account profile details. + +URL: +`http://localhost:5380/api/admin/users/set?token=x&user=admin&displayName=Administrator&disabled=false&sessionTimeoutSeconds=1800&memberOfGroups=Administrators` + +PERMISSIONS: +Administration: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `user`: The username for the user account. +- `displayName` (optional): The display name for the user account. +- `newUser` (optional): A new username for renaming the username for the user account. +- `disabled` (optional): Set `true` to disable the user account and delete all its active sessions. +- `sessionTimeoutSeconds` (optional): A session time out value in seconds for the user account. +- `newPass` (optional): A new password to reset the user account password. +- `iterations` (optional): The number of iterations for PBKDF2 SHA256 password hashing. This is only used with the `newPass` option. +- `memberOfGroups` (optional): A list of comma separated group names that the user must be set as a member. + +RESPONSE: +``` +{ + "response": { + "displayName": "Administrator", + "username": "admin", + "disabled": false, + "previousSessionLoggedOn": "2022-09-17T13:22:45.671Z", + "previousSessionRemoteAddress": "127.0.0.1", + "recentSessionLoggedOn": "2022-09-18T09:55:26.9800695Z", + "recentSessionRemoteAddress": "127.0.0.1", + "sessionTimeoutSeconds": 1800, + "memberOfGroups": [ + "Administrators" + ], + "sessions": [ + { + "username": "admin", + "isCurrentSession": false, + "partialToken": "1f8011516cea27af", + "type": "Standard", + "tokenName": null, + "lastSeen": "2022-09-18T09:59:19.9034491Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + }, + { + "username": "admin", + "isCurrentSession": false, + "partialToken": "ddfaecb8e9325e77", + "type": "ApiToken", + "tokenName": "MyToken1", + "lastSeen": "2022-09-17T13:22:45.671Z", + "lastSeenRemoteAddress": "127.0.0.1", + "lastSeenUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0" + } + ] + }, + "status": "ok" +} +``` + +### Delete User + +Deletes a user account. + +URL: +`http://localhost:5380/api/admin/users/delete?token=x&user=user1` + +PERMISSIONS: +Administration: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `user`: The username for the user account to delete. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### List Groups + +Returns a list of all groups. + +URL: +`http://localhost:5380/api/admin/groups/list?token=x` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "groups": [ + { + "name": "Administrators", + "description": "Super administrators" + }, + { + "name": "DHCP Administrators", + "description": "DHCP service administrators" + }, + { + "name": "DNS Administrators", + "description": "DNS service administrators" + } + ] + }, + "status": "ok" +} +``` + +### Create Group + +Creates a new group. + +URL: +`http://localhost:5380/api/admin/groups/create?token=x&group=Group1&description=My%20description` + +PERMISSIONS: +Administration: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `group`: The name of the group to create. +- `description` (optional): The description text for the group. + +RESPONSE: +``` +{ + "response": { + "name": "Group1", + "description": "My description" + }, + "status": "ok" +} +``` + +### Get Group Details + +Returns the details for a group. + +URL: +`http://localhost:5380/api/admin/groups/get?token=x&group=Administrators&includeUsers=true` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `group`: The name of the group. +- `includeUsers` (optional): Set `true` to include a list of users in response. + +RESPONSE: +``` +{ + "response": { + "name": "Administrators", + "description": "Super administrators", + "members": [ + "admin" + ], + "users": [ + "admin", + "shreyas" + ] + }, + "status": "ok" +} +``` + +### Set Group Details + +Allows changing group description or rename a group. + +URL: +`http://localhost:5380/api/admin/groups/set?token=x&group=Administrators&description=Super%20administrators&members=admin` + +PERMISSIONS: +Administration: Modify + +WHERE: +- `token`: The session token generated by the `login` call. +- `group`: The name of the group to update. +- `newGroup` (optional): A new group name to rename the group. +- `description` (optional): A new group description. +- `members` (optional): A comma separated list of usernames to set as the group's members. + +RESPONSE: +``` +{ + "response": { + "name": "Administrators", + "description": "Super administrators", + "members": [ + "admin" + ] + }, + "status": "ok" +} +``` + +### Delete Group + +Allows deleting a group. + +URL: +`http://localhost:5380/api/admin/groups/delete?token=x&group=Group1` + +PERMISSIONS: +Administration: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `group`: The name of the group to delete. + +RESPONSE: +``` +{ + "response": {}, + "status": "ok" +} +``` + +### List Permissions + +URL: +`http://localhost:5380/api/admin/permissions/list?token=x` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. + +RESPONSE: +``` +{ + "response": { + "permissions": [ + { + "section": "Dashboard", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Zones", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DHCP Administrators", + "canView": true, + "canModify": false, + "canDelete": false + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Cache", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Allowed", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Blocked", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Apps", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "DnsClient", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DHCP Administrators", + "canView": true, + "canModify": false, + "canDelete": false + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Settings", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": true, + "canDelete": true + } + ] + }, + { + "section": "DhcpServer", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DHCP Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + { + "section": "Administration", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + } + ] + }, + { + "section": "Logs", + "userPermissions": [], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "DHCP Administrators", + "canView": true, + "canModify": false, + "canDelete": false + }, + { + "name": "DNS Administrators", + "canView": true, + "canModify": false, + "canDelete": false + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + } + ] + }, + "status": "ok" +} +``` + +### Get Permission Details + +Gets details of the permissions for the specified section. + +URL: +`http://localhost:5380/api/admin/permissions/get?token=x§ion=Dashboard&includeUsersAndGroups=true` + +PERMISSIONS: +Administration: View + +WHERE: +- `token`: The session token generated by the `login` call. +- `section`: The name of the section as given in the list of permissions API call. +- `includeUsersAndGroups` (optional): Set to `true` to include a list of users and groups in the response. + +RESPONSE: +``` +{ + "response": { + "section": "Dashboard", + "userPermissions": [ + { + "username": "shreyas", + "canView": true, + "canModify": false, + "canDelete": false + } + ], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ], + "users": [ + "admin", + "shreyas" + ], + "groups": [ + "Administrators", + "DHCP Administrators", + "DNS Administrators", + "Everyone" + ] + }, + "status": "ok" +} +``` + +### Set Permission Details + +Allows changing permissions for the specified section. + +URL: +`http://localhost:5380/api/admin/permissions/set?token=x§ion=Dashboard&userPermissions=shreyas|true|false|false&groupPermissions=Administrators|true|true|true|Everyone|true|false|false` + +PERMISSIONS: +Administration: Delete + +WHERE: +- `token`: The session token generated by the `login` call. +- `section`: The name of the section as given in the list of permissions API call. +- `userPermissions` (optional): A pipe `|` separated table data with each row containing username and boolean values for the view, modify and delete permissions. For example: user1|true|true|true|user2|true|false|false +- `groupPermissions` (optional): A pipe `|` separated table data with each row containing the group name and boolean values for the view, modify and delete permissions. For example: group1|true|true|true|group2|true|true|false + +RESPONSE: +``` +{ + "response": { + "section": "Dashboard", + "userPermissions": [ + { + "username": "shreyas", + "canView": true, + "canModify": false, + "canDelete": false + } + ], + "groupPermissions": [ + { + "name": "Administrators", + "canView": true, + "canModify": true, + "canDelete": true + }, + { + "name": "Everyone", + "canView": true, + "canModify": false, + "canDelete": false + } + ] + }, + "status": "ok" +} +``` + ## Log API Calls ### List Logs @@ -3415,7 +5540,13 @@ RESPONSE: Lists all logs files available on the DNS server. URL: -`http://localhost:5380/api/listLogs?token=x` +`http://localhost:5380/api/logs/list?token=x` + +OBSOLETE PATH: +`/api/listLogs` + +PERMISSIONS: +Logs: View WHERE: - `token`: The session token generated by the `login` call. @@ -3456,11 +5587,17 @@ RESPONSE: Downloads the log file. URL: -`http://localhost:5380/log/{fileName}?token=x&limit=2` +`http://localhost:5380/api/logs/download?token=x&fileName=2020-09-10&limit=2` + +OBSOLETE PATH: +`/log/{fileName}` + +PERMISSIONS: +Logs: View WHERE: -- `{fileName}`: The `fileName` returned by the List Logs API call. - `token`: The session token generated by the `login` call. +- `fileName`: The `fileName` returned by the List Logs API call. - `limit` (optional): The limit of number of mega bytes to download the log file. Default value is `0` when parameter is missing which indicates there is no limit. RESPONSE: @@ -3468,10 +5605,16 @@ Response is a downloadable file with `Content-Type: text/plain` and `Content-Dis ### Delete Log -Permanantly deletes a log file from the disk. +Permanently deletes a log file from the disk. URL: -`http://localhost:5380/api/deleteLog?token=x&log=2020-09-19` +`http://localhost:5380/api/logs/delete?token=x&log=2020-09-19` + +OBSOLETE PATH: +`/api/deleteLog` + +PERMISSIONS: +Logs: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -3487,28 +5630,16 @@ RESPONSE: ### Delete All Logs -Permanantly delete all log files from the disk. +Permanently delete all log files from the disk. URL: -`http://localhost:5380/api/deleteAllLogs?token=x` +`http://localhost:5380/api/logs/deleteAll?token=x` -WHERE: -- `token`: The session token generated by the `login` call. +OBSOLETE PATH: +`/api/deleteAllLogs` -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Delete All Stats - -Permanantly delete all hourly and daily stats files from the disk and clears all stats stored in memory. This call will clear all stats from the Dashboard. - -URL: -`http://localhost:5380/api/deleteAllStats?token=x` +PERMISSIONS: +Logs: Delete WHERE: - `token`: The session token generated by the `login` call. @@ -3526,7 +5657,13 @@ RESPONSE: Queries for logs to a specified DNS app. URL: -`http://localhost:5380/api/queryLogs?token=x&name=AppName&classPath=AppClassPath&=pageNumber=1&entriesPerPage=10&descendingOrder=true&start=yyyy-MM-dd HH:mm:ss&end=yyyy-MM-dd HH:mm:ss&clientIpAddress=&protocol=&responseType=&rcode=&qname=&qtype=&qclass=` +`http://localhost:5380/api/logs/query?token=x&name=AppName&classPath=AppClassPath&=pageNumber=1&entriesPerPage=10&descendingOrder=true&start=yyyy-MM-dd HH:mm:ss&end=yyyy-MM-dd HH:mm:ss&clientIpAddress=&protocol=&responseType=&rcode=&qname=&qtype=&qclass=` + +OBSOLETE PATH: +`/api/queryLogs` + +PERMISSIONS: +Logs: View WHERE: - `token`: The session token generated by the `login` call. @@ -3678,326 +5815,3 @@ RESPONSE: "status": "ok" } ``` - -## DHCP API Calls - -### List DHCP Scopes - -Lists all the DHCP scopes available on the server. - -URL: -`http://localhost:5380/api/listDhcpScopes?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "response": { - "scopes": [ - { - "name": "Default", - "enabled": false, - "startingAddress": "192.168.1.1", - "endingAddress": "192.168.1.254", - "subnetMask": "255.255.255.0", - "networkAddress": "192.168.1.0", - "broadcastAddress": "192.168.1.255" - } - ] - }, - "status": "ok" -} -``` - -### List DHCP Leases - -Lists all the DHCP leases. - -URL: -`http://localhost:5380/api/listDhcpLeases?token=x` - -WHERE: -- `token`: The session token generated by the `login` call. - -RESPONSE: -``` -{ - "response": { - "leases": [ - { - "scope": "Default", - "type": "Reserved", - "hardwareAddress": "00-00-00-00-00-00", - "clientIdentifier": "1-000000000000", - "address": "192.168.1.5", - "hostName": "server1.local", - "leaseObtained": "08/25/2020 17:52:51", - "leaseExpires": "09/26/2020 14:27:12" - }, - { - "scope": "Default", - "type": "Dynamic", - "hardwareAddress": "00-00-00-00-00-00", - "clientIdentifier": "1-000000000000", - "address": "192.168.1.13", - "hostName": null, - "leaseObtained": "06/15/2020 16:41:46", - "leaseExpires": "09/25/2020 12:39:54" - }, - { - "scope": "Default", - "type": "Dynamic", - "hardwareAddress": "00-00-00-00-00-00", - "clientIdentifier": "1-000000000000", - "address": "192.168.1.15", - "hostName": "desktop-ea2miaf.local", - "leaseObtained": "06/18/2020 12:19:03", - "leaseExpires": "09/25/2020 12:17:11" - }, - ] - }, - "status": "ok" -} -``` - -### Get DHCP Scope - -Gets the complete details of the scope configuration. - -URL: -`http://localhost:5380/api/getDhcpScope?token=x&name=Default` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. - -RESPONSE: -``` -{ - "response": { - "name": "Default", - "startingAddress": "192.168.1.1", - "endingAddress": "192.168.1.254", - "subnetMask": "255.255.255.0", - "leaseTimeDays": 7, - "leaseTimeHours": 0, - "leaseTimeMinutes": 0, - "offerDelayTime": 0, - "pingCheckEnabled": false, - "pingCheckTimeout": 1000, - "pingCheckRetries": 2, - "domainName": "local", - "dnsTtl": 900, - "serverAddress": "192.168.1.1", - "serverHostName": "tftp-server-1", - "bootFileName": "boot.bin", - "routerAddress": "192.168.1.1", - "useThisDnsServer": false, - "dnsServers": [ - "192.168.1.5" - ], - "winsServers": [ - "192.168.1.5" - ], - "ntpServers": [ - "192.168.1.5" - ], - "staticRoutes": [ - { - "destination": "172.16.0.0", - "subnetMask": "255.255.255.0", - "router": "192.168.1.2" - } - ], - "vendorInfo": [ - { - "identifier": "substring(vendor-class-identifier,0,9)==\"PXEClient\"", - "information": "06:01:03:0A:04:00:50:58:45:09:14:00:00:11:52:61:73:70:62:65:72:72:79:20:50:69:20:42:6F:6F:74:FF" - } - ], - "exclusions": [ - { - "startingAddress": "192.168.1.1", - "endingAddress": "192.168.1.10" - } - ], - "reservedLeases": [ - { - "hostName": null, - "hardwareAddress": "00-00-00-00-00-00", - "address": "192.168.1.10", - "comments": "comments" - } - ], - "allowOnlyReservedLeases": false - }, - "status": "ok" -} -``` - -### Set DHCP Scope - -Sets the DHCP scope configuration. - -URL: -`http://localhost:5380/api/setDhcpScope?token=x&name=Default&startingAddress=192.168.1.1&endingAddress=192.168.1.254&subnetMask=255.255.255.0&leaseTimeDays=7&leaseTimeHours=0&leaseTimeMinutes=0&offerDelayTime=0&domainName=local&dnsTtl=900&serverAddress=&serverHostName=&bootFileName=&routerAddress=192.168.1.1&useThisDnsServer=false&dnsServers=192.168.1.5&winsServers=192.168.1.5&ntpServers=192.168.1.5&staticRoutes=172.16.0.0|255.255.255.0|192.168.1.2&exclusions=192.168.1.1|192.168.1.10&reservedLeases=hostname|00-00-00-00-00-00|192.168.1.10|comments&allowOnlyReservedLeases=false` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. -- `newName` (optional): The new name of the DHCP scope to rename an existing scope. -- `startingAddress` (optional): The starting IP address of the DHCP scope. This parameter is required when creating a new scope. -- `endingAddress` (optional): The ending IP address of the DHCP scope. This parameter is required when creating a new scope. -- `subnetMask` (optional): The subnet mask of the network. This parameter is required when creating a new scope. -- `leaseTimeDays` (optional): The lease time in number of days. -- `leaseTimeHours` (optional): The lease time in number of hours. -- `leaseTimeMinutes` (optional): The lease time in number of minutes. -- `offerDelayTime` (optional): The time duration in milli seconds that the DHCP server delays sending an DHCPOFFER message. -- `pingCheckEnabled` (optional): Set this option to `true` to allow the DHCP server to find out if an IP address is already in use to prevent IP address conflict when some of the devices on the network have manually configured IP addresses. -- `pingCheckTimeout` (optional): The timeout interval to wait for an ping reply. -- `pingCheckRetries` (optional): The maximum number of ping requests to try. -- `domainName` (optional): The domain name to be used by this network. The DHCP server automatically adds forward and reverse DNS entries for each IP address allocations when domain name is configured. -- `dnsTtl` (optional): The TTL value used for forward and reverse DNS records. -- `serverAddress` (optional): The bootstrap TFTP server IP address to be used by the clients. If not specified, the DHCP server's IP address is used. -- `serverHostName` (optional): The optional bootstrap TFTP server host name to be used by the clients to identify the TFTP server. -- `bootFileName` (optional): The boot file name stored on the bootstrap TFTP server to be used by the clients. -- `routerAddress` (optional): The default gateway or router IP address to be used by the clients. -- `useThisDnsServer` (optional): Tells the DHCP server to use this DNS server's IP address to configure the DNS Servers DHCP option for clients. -- `dnsServers` (optional): A comma separated list of DNS server IP addresses to be used by the clients. This parameter is ignored when `useThisDnsServer` is set to `true`. -- `winsServers` (optional): A comma separated list of NBNS/WINS server IP addresses to be used by the clients. -- `ntpServers` (optional): A comma separated list of Network Time Protocol (NTP) server IP addresses to be used by the clients. -- `staticRoutes` (optional): A `|` separated list of static routes in format `{destination network address}|{subnet mask}|{router/gateway address}` to be used by the clients for accessing specified destination networks. -- `vendorInfo` (optional): A `|` separated list of vendor information in format `{vendor class identifier}|{vendor specific information}` where `{vendor specific information}` is a colon separated hex string. -- `exclusions` (optional): A `|` separated list of IP address range in format `{starting address}|{ending address}` that must be excluded or not assigned dynamically to any client by the DHCP server. -- `reservedLeases` (optional): A `|` separated list of reserved IP addresses in format `{host name}|{MAC address}|{reserved IP address}|{comments}` to be assigned to specific clients based on their MAC address. -- `allowOnlyReservedLeases` (optional): Set this parameter to `true` to stop dynamic IP address allocation and allocate only reserved IP addresses. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Enable DHCP Scope - -Enables the DHCP scope allowing the server to allocate leases. - -URL: -`http://localhost:5380/api/enableDhcpScope?token=x&name=Default` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Disable DHCP Scope - -Disables the DHCP scope and stops any further lease allocations. - -URL: -`http://localhost:5380/api/disableDhcpScope?token=x&name=Default` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Delete DHCP Scope - -Permanently deletes the DHCP scope from the disk. - -URL: -`http://localhost:5380/api/deleteDhcpScope?token=x&name=Default` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Remove DHCP Lease - -Removes a dynamic or reserved lease allocation. This API must be used carefully to make sure that there is no IP address conflict caused by removing a lease. - -URL: -`http://localhost:5380/api/removeDhcpLease?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. -- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. -- `hardwareAddress` (optional): The MAC address of the device bearing the dynamic/reserved lease. Either `hardwareAddress` or `clientIdentifier` must be specified. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Convert To Reserved Lease - -Converts a dynamic lease to reserved lease. - -URL: -`http://localhost:5380/api/convertToReservedLease?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. -- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. -- `hardwareAddress` (optional): The MAC address of the device bearing the dynamic lease. Either `hardwareAddress` or `clientIdentifier` must be specified. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` - -### Convert To Dynamic Lease - -Converts a reserved lease to dynamic lease. - -URL: -`http://localhost:5380/api/convertToDynamicLease?token=x&name=Default&hardwareAddress=00:00:00:00:00:00` - -WHERE: -- `token`: The session token generated by the `login` call. -- `name`: The name of the DHCP scope. -- `clientIdentifier` (optional): The client identifier for the lease. Either `hardwareAddress` or `clientIdentifier` must be specified. -- `hardwareAddress` (optional): The MAC address of the device bearing the reserved lease. Either `hardwareAddress` or `clientIdentifier` must be specified. - -RESPONSE: -``` -{ - "response": {}, - "status": "ok" -} -``` diff --git a/Apps/AdvancedBlockingApp/App.cs b/Apps/AdvancedBlockingApp/App.cs index c721ab2c..00dd625d 100644 --- a/Apps/AdvancedBlockingApp/App.cs +++ b/Apps/AdvancedBlockingApp/App.cs @@ -147,6 +147,7 @@ namespace AdvancedBlocking SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; + handler.UseProxy = _dnsServer.Proxy is not null; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) diff --git a/Apps/FailoverApp/Address.cs b/Apps/FailoverApp/Address.cs index 782143cb..cda6b414 100644 --- a/Apps/FailoverApp/Address.cs +++ b/Apps/FailoverApp/Address.cs @@ -150,7 +150,7 @@ namespace Failover return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; switch (question.Type) diff --git a/Apps/FailoverApp/CNAME.cs b/Apps/FailoverApp/CNAME.cs index 3872da04..e6dcd076 100644 --- a/Apps/FailoverApp/CNAME.cs +++ b/Apps/FailoverApp/CNAME.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -115,7 +115,7 @@ namespace Failover return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; diff --git a/Apps/FailoverApp/HealthCheck.cs b/Apps/FailoverApp/HealthCheck.cs index 3ed7997f..10c200fb 100644 --- a/Apps/FailoverApp/HealthCheck.cs +++ b/Apps/FailoverApp/HealthCheck.cs @@ -127,6 +127,7 @@ namespace Failover httpHandler.ConnectTimeout = TimeSpan.FromMilliseconds(_timeout); httpHandler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(Math.Max(10000, _timeout)); httpHandler.Proxy = proxy; + httpHandler.UseProxy = proxy is not null; httpHandler.AllowAutoRedirect = true; httpHandler.MaxAutomaticRedirections = 10; @@ -141,6 +142,7 @@ namespace Failover httpHandler.ConnectTimeout = TimeSpan.FromMilliseconds(_timeout); httpHandler.PooledConnectionIdleTimeout = TimeSpan.FromMilliseconds(Math.Max(10000, _timeout)); httpHandler.Proxy = proxy; + httpHandler.UseProxy = proxy is not null; httpHandler.AllowAutoRedirect = true; httpHandler.MaxAutomaticRedirections = 10; diff --git a/Apps/FailoverApp/WebHook.cs b/Apps/FailoverApp/WebHook.cs index 798b7164..7aecc7f0 100644 --- a/Apps/FailoverApp/WebHook.cs +++ b/Apps/FailoverApp/WebHook.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -102,6 +102,7 @@ namespace Failover { SocketsHttpHandler httpHandler = new SocketsHttpHandler(); httpHandler.Proxy = proxy; + httpHandler.UseProxy = proxy is not null; httpHandler.AllowAutoRedirect = true; httpHandler.MaxAutomaticRedirections = 10; @@ -114,6 +115,7 @@ namespace Failover { SocketsHttpHandler httpHandler = new SocketsHttpHandler(); httpHandler.Proxy = proxy; + httpHandler.UseProxy = proxy is not null; httpHandler.AllowAutoRedirect = true; httpHandler.MaxAutomaticRedirections = 10; diff --git a/Apps/GeoContinentApp/Address.cs b/Apps/GeoContinentApp/Address.cs index f7770a15..13b5b88b 100644 --- a/Apps/GeoContinentApp/Address.cs +++ b/Apps/GeoContinentApp/Address.cs @@ -72,7 +72,7 @@ namespace GeoContinent return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; switch (question.Type) diff --git a/Apps/GeoContinentApp/CNAME.cs b/Apps/GeoContinentApp/CNAME.cs index 1857bb51..21035742 100644 --- a/Apps/GeoContinentApp/CNAME.cs +++ b/Apps/GeoContinentApp/CNAME.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -71,7 +71,7 @@ namespace GeoContinent return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData); dynamic jsonContinent; diff --git a/Apps/GeoCountryApp/Address.cs b/Apps/GeoCountryApp/Address.cs index 2ae1b112..5cac1ccf 100644 --- a/Apps/GeoCountryApp/Address.cs +++ b/Apps/GeoCountryApp/Address.cs @@ -72,7 +72,7 @@ namespace GeoCountry return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; switch (question.Type) diff --git a/Apps/GeoCountryApp/CNAME.cs b/Apps/GeoCountryApp/CNAME.cs index 548b2535..857c3f59 100644 --- a/Apps/GeoCountryApp/CNAME.cs +++ b/Apps/GeoCountryApp/CNAME.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -71,7 +71,7 @@ namespace GeoCountry return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData); dynamic jsonCountry; diff --git a/Apps/GeoDistanceApp/Address.cs b/Apps/GeoDistanceApp/Address.cs index bbcd674a..84753c1d 100644 --- a/Apps/GeoDistanceApp/Address.cs +++ b/Apps/GeoDistanceApp/Address.cs @@ -89,7 +89,7 @@ namespace GeoDistance return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; switch (question.Type) diff --git a/Apps/GeoDistanceApp/CNAME.cs b/Apps/GeoDistanceApp/CNAME.cs index b693fed0..3e9a4e47 100644 --- a/Apps/GeoDistanceApp/CNAME.cs +++ b/Apps/GeoDistanceApp/CNAME.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -87,7 +87,7 @@ namespace GeoDistance return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { Location location = null; diff --git a/Apps/SplitHorizonApp/SimpleAddress.cs b/Apps/SplitHorizonApp/SimpleAddress.cs index 41c8db9a..7189c6b6 100644 --- a/Apps/SplitHorizonApp/SimpleAddress.cs +++ b/Apps/SplitHorizonApp/SimpleAddress.cs @@ -100,7 +100,7 @@ namespace SplitHorizon } } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsQuestionRecord question = request.Question[0]; switch (question.Type) diff --git a/Apps/SplitHorizonApp/SimpleCNAME.cs b/Apps/SplitHorizonApp/SimpleCNAME.cs index 02868e37..966f2524 100644 --- a/Apps/SplitHorizonApp/SimpleCNAME.cs +++ b/Apps/SplitHorizonApp/SimpleCNAME.cs @@ -48,7 +48,7 @@ namespace SplitHorizon return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { dynamic jsonAppRecordData = JsonConvert.DeserializeObject(appRecordData); dynamic jsonCname = null; diff --git a/Apps/WhatIsMyDnsApp/App.cs b/Apps/WhatIsMyDnsApp/App.cs index d4212345..ba4f9b23 100644 --- a/Apps/WhatIsMyDnsApp/App.cs +++ b/Apps/WhatIsMyDnsApp/App.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -45,7 +45,7 @@ namespace WhatIsMyDns return Task.CompletedTask; } - public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData) + public Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData) { DnsResourceRecord answer; diff --git a/DnsServerApp/Program.cs b/DnsServerApp/Program.cs index 867b6bc2..0f7066a8 100644 --- a/DnsServerApp/Program.cs +++ b/DnsServerApp/Program.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -51,7 +51,7 @@ namespace DnsServerApp break; } - service = new DnsWebService(configFolder, updateCheckUri, new Uri("https://go.technitium.com/?id=40")); + service = new DnsWebService(configFolder, updateCheckUri, new Uri("https://go.technitium.com/?id=44")); service.Start(); Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) diff --git a/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs b/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs index 6283bcae..94cc93d6 100644 --- a/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs +++ b/DnsServerCore.ApplicationCommon/IDnsAppRecordRequestHandler.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -36,10 +36,11 @@ namespace DnsServerCore.ApplicationCommon /// The protocol using which the request was received. /// Tells if the DNS server is configured to allow recursion for the client making this request. /// The name of the application zone that the APP record belongs to. + /// The domain name of the APP record. /// The TTL value set in the APP record. /// The record data in the APP record as required for processing the request. /// The DNS response for the DNS request or null to send no answer response with an SOA authority. - Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, uint appRecordTtl, string appRecordData); + Task ProcessRequestAsync(DnsDatagram request, IPEndPoint remoteEP, DnsTransportProtocol protocol, bool isRecursionAllowed, string zoneName, string appRecordName, uint appRecordTtl, string appRecordData); /// /// A template of the record data format that is required by this app. This template is populated in the UI to allow the user to edit in the expected values. The format could be JSON or any other custom text based format which the app is programmed to parse. This property is optional and can return null if no APP record data is required by the app. diff --git a/DnsServerCore/Auth/AuthManager.cs b/DnsServerCore/Auth/AuthManager.cs new file mode 100644 index 00000000..9caa9120 --- /dev/null +++ b/DnsServerCore/Auth/AuthManager.cs @@ -0,0 +1,837 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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 System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace DnsServerCore.Auth +{ + sealed class AuthManager : IDisposable + { + #region variables + + readonly ConcurrentDictionary _groups = new ConcurrentDictionary(1, 4); + readonly ConcurrentDictionary _users = new ConcurrentDictionary(1, 4); + + readonly ConcurrentDictionary _permissions = new ConcurrentDictionary(1, 11); + + readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(1, 10); + + readonly ConcurrentDictionary _failedLoginAttempts = new ConcurrentDictionary(1, 10); + const int MAX_LOGIN_ATTEMPTS = 5; + + readonly ConcurrentDictionary _blockedAddresses = new ConcurrentDictionary(1, 10); + const int BLOCK_ADDRESS_INTERVAL = 5 * 60 * 1000; + + readonly string _configFolder; + readonly LogManager _log; + + readonly object _lockObj = new object(); + bool _pendingSave; + readonly Timer _saveTimer; + const int SAVE_TIMER_INITIAL_INTERVAL = 10000; + + #endregion + + #region constructor + + public AuthManager(string configFolder, LogManager log) + { + _configFolder = configFolder; + _log = log; + + _saveTimer = new Timer(SaveTimerCallback, null, Timeout.Infinite, Timeout.Infinite); + } + + #endregion + + #region IDisposable + + bool _disposed; + + public void Dispose() + { + if (_disposed) + return; + + if (_saveTimer is not null) + _saveTimer.Dispose(); + + lock (_lockObj) + { + SaveConfigFileInternal(); + } + + _disposed = true; + } + + #endregion + + #region private + + private void SaveTimerCallback(object state) + { + try + { + lock (_lockObj) + { + _pendingSave = false; + SaveConfigFileInternal(); + } + } + catch (Exception ex) + { + _log.Write(ex); + } + } + + private void CreateDefaultConfig() + { + Group adminGroup = CreateGroup(Group.ADMINISTRATORS, "Super administrators"); + Group dnsAdminGroup = CreateGroup(Group.DNS_ADMINISTRATORS, "DNS service administrators"); + Group dhcpAdminGroup = CreateGroup(Group.DHCP_ADMINISTRATORS, "DHCP service administrators"); + Group everyoneGroup = CreateGroup(Group.EVERYONE, "All users"); + + SetPermission(PermissionSection.Dashboard, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Zones, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Cache, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Allowed, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Blocked, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Apps, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.DnsClient, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Settings, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.DhcpServer, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Administration, adminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Logs, adminGroup, PermissionFlag.ViewModifyDelete); + + SetPermission(PermissionSection.Zones, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Cache, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Allowed, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Blocked, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Apps, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.DnsClient, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Settings, dnsAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Logs, dnsAdminGroup, PermissionFlag.View); + + SetPermission(PermissionSection.Zones, dhcpAdminGroup, PermissionFlag.View); + SetPermission(PermissionSection.DnsClient, dhcpAdminGroup, PermissionFlag.View); + SetPermission(PermissionSection.DhcpServer, dhcpAdminGroup, PermissionFlag.ViewModifyDelete); + SetPermission(PermissionSection.Logs, dhcpAdminGroup, PermissionFlag.View); + + SetPermission(PermissionSection.Dashboard, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Zones, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Cache, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Allowed, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Blocked, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Apps, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.DnsClient, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.DhcpServer, everyoneGroup, PermissionFlag.View); + SetPermission(PermissionSection.Logs, everyoneGroup, PermissionFlag.View); + + string adminPassword = Environment.GetEnvironmentVariable("DNS_SERVER_ADMIN_PASSWORD"); + string adminPasswordFile = Environment.GetEnvironmentVariable("DNS_SERVER_ADMIN_PASSWORD_FILE"); + + User adminUser; + + if (!string.IsNullOrEmpty(adminPassword)) + { + adminUser = CreateUser("Administrator", "admin", adminPassword); + } + else if (!string.IsNullOrEmpty(adminPasswordFile)) + { + try + { + using (StreamReader sR = new StreamReader(adminPasswordFile, true)) + { + string password = sR.ReadLine(); + adminUser = CreateUser("Administrator", "admin", password); + } + } + catch (Exception ex) + { + _log.Write(ex); + + adminUser = CreateUser("Administrator", "admin", "admin"); + } + } + else + { + adminUser = CreateUser("Administrator", "admin", "admin"); + } + + adminUser.AddToGroup(adminGroup); + } + + private void LoadConfigFileInternal(UserSession implantSession) + { + string configFile = Path.Combine(_configFolder, "auth.config"); + + try + { + bool passwordResetOption = false; + + if (!File.Exists(configFile)) + { + string passwordResetConfigFile = Path.Combine(_configFolder, "resetadmin.config"); + + if (File.Exists(passwordResetConfigFile)) + { + passwordResetOption = true; + configFile = passwordResetConfigFile; + } + } + + using (FileStream fS = new FileStream(configFile, FileMode.Open, FileAccess.Read)) + { + ReadConfigFrom(new BinaryReader(fS)); + } + + if (implantSession is not null) + { + UserSession newSession; + + using (MemoryStream mS = new MemoryStream()) + { + implantSession.WriteTo(new BinaryWriter(mS)); + + mS.Position = 0; + newSession = new UserSession(new BinaryReader(mS), this); + } + + _sessions.TryAdd(newSession.Token, newSession); + } + + _log.Write("DNS Server auth config file was loaded: " + configFile); + + if (passwordResetOption) + { + User adminUser = GetUser("admin"); + if (adminUser is null) + { + adminUser = CreateUser("Administrator", "admin", "admin"); + } + else + { + adminUser.ChangePassword("admin"); + adminUser.Disabled = false; + } + + adminUser.AddToGroup(GetGroup(Group.ADMINISTRATORS)); + + _log.Write("DNS Server reset password for user: admin"); + SaveConfigFileInternal(); + + try + { + File.Delete(configFile); + } + catch + { } + } + } + catch (FileNotFoundException) + { + _log.Write("DNS Server auth config file was not found: " + configFile); + _log.Write("DNS Server is restoring default auth config file."); + + CreateDefaultConfig(); + + SaveConfigFileInternal(); + } + catch (Exception ex) + { + _log.Write("DNS Server encountered an error while loading auth config file: " + configFile + "\r\n" + ex.ToString()); + _log.Write("Note: You may try deleting the auth config file to fix this issue. However, you will lose auth settings but, rest of the DNS settings and zone data wont be affected."); + throw; + } + } + + private void SaveConfigFileInternal() + { + string configFile = Path.Combine(_configFolder, "auth.config"); + + using (MemoryStream mS = new MemoryStream()) + { + //serialize config + WriteConfigTo(new BinaryWriter(mS)); + + //write config + mS.Position = 0; + + using (FileStream fS = new FileStream(configFile, FileMode.Create, FileAccess.Write)) + { + mS.CopyTo(fS); + } + } + + _log.Write("DNS Server auth config file was saved: " + configFile); + } + + private void ReadConfigFrom(BinaryReader bR) + { + if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "AS") //format + throw new InvalidDataException("DNS Server auth config file format is invalid."); + + int version = bR.ReadByte(); + switch (version) + { + case 1: + { + int count = bR.ReadByte(); + for (int i = 0; i < count; i++) + { + Group group = new Group(bR); + _groups.TryAdd(group.Name.ToLower(), group); + } + } + + { + int count = bR.ReadByte(); + for (int i = 0; i < count; i++) + { + User user = new User(bR, this); + _users.TryAdd(user.Username, user); + } + } + + { + int count = bR.ReadInt32(); + for (int i = 0; i < count; i++) + { + Permission permission = new Permission(bR, this); + _permissions.TryAdd(permission.Section, permission); + } + } + + { + int count = bR.ReadInt32(); + for (int i = 0; i < count; i++) + { + UserSession session = new UserSession(bR, this); + if (!session.HasExpired()) + _sessions.TryAdd(session.Token, session); + } + } + break; + + default: + throw new InvalidDataException("DNS Server auth config version not supported."); + } + } + + private void WriteConfigTo(BinaryWriter bW) + { + bW.Write(Encoding.ASCII.GetBytes("AS")); //format + bW.Write((byte)1); //version + + bW.Write(Convert.ToByte(_groups.Count)); + + foreach (KeyValuePair group in _groups) + group.Value.WriteTo(bW); + + bW.Write(Convert.ToByte(_users.Count)); + + foreach (KeyValuePair user in _users) + user.Value.WriteTo(bW); + + bW.Write(_permissions.Count); + + foreach (KeyValuePair permission in _permissions) + permission.Value.WriteTo(bW); + + List activeSessions = new List(_sessions.Count); + + foreach (KeyValuePair session in _sessions) + { + if (session.Value.HasExpired()) + _sessions.TryRemove(session.Key, out _); + else + activeSessions.Add(session.Value); + } + + bW.Write(activeSessions.Count); + + foreach (UserSession session in activeSessions) + session.WriteTo(bW); + } + + private void FailedLoginAttempt(IPAddress address) + { + _failedLoginAttempts.AddOrUpdate(address, 1, delegate (IPAddress key, int attempts) + { + return attempts + 1; + }); + } + + private bool LoginAttemptsExceedLimit(IPAddress address, int limit) + { + if (!_failedLoginAttempts.TryGetValue(address, out int attempts)) + return false; + + return attempts >= limit; + } + + private void ResetFailedLoginAttempt(IPAddress address) + { + _failedLoginAttempts.TryRemove(address, out _); + } + + private void BlockAddress(IPAddress address, int interval) + { + _blockedAddresses.TryAdd(address, DateTime.UtcNow.AddMilliseconds(interval)); + } + + private bool IsAddressBlocked(IPAddress address) + { + if (!_blockedAddresses.TryGetValue(address, out DateTime expiry)) + return false; + + if (expiry > DateTime.UtcNow) + { + return true; + } + else + { + UnblockAddress(address); + ResetFailedLoginAttempt(address); + + return false; + } + } + + private void UnblockAddress(IPAddress address) + { + _blockedAddresses.TryRemove(address, out _); + } + + #endregion + + #region public + + public User GetUser(string username) + { + if (_users.TryGetValue(username.ToLower(), out User user)) + return user; + + return null; + } + + public User CreateUser(string displayName, string username, string password, int iterations = User.DEFAULT_ITERATIONS) + { + username = username.ToLower(); + + User user = new User(displayName, username, password, iterations); + + if (_users.TryAdd(username, user)) + { + user.AddToGroup(GetGroup(Group.EVERYONE)); + return user; + } + + throw new DnsWebServiceException("User already exists: " + username); + } + + public void ChangeUsername(User user, string newUsername) + { + if (user.Username.Equals(newUsername, StringComparison.OrdinalIgnoreCase)) + return; + + string oldUsername = user.Username; + user.Username = newUsername; + + if (!_users.TryAdd(user.Username, user)) + { + user.Username = oldUsername; //revert + throw new DnsWebServiceException("User already exists: " + newUsername); + } + + _users.TryRemove(oldUsername, out _); + } + + public bool DeleteUser(string username) + { + if (_users.TryRemove(username.ToLower(), out User deletedUser)) + { + //delete all sessions + foreach (UserSession session in GetSessions(deletedUser)) + DeleteSession(session.Token); + + //delete all permissions + foreach (KeyValuePair permission in _permissions) + { + permission.Value.RemovePermission(deletedUser); + permission.Value.RemoveAllSubItemPermissions(deletedUser); + } + + return true; + } + + return false; + } + + public Group GetGroup(string name) + { + if (_groups.TryGetValue(name.ToLower(), out Group group)) + return group; + + return null; + } + + public List GetGroupMembers(Group group) + { + List members = new List(); + + foreach (KeyValuePair user in _users) + { + if (user.Value.IsMemberOfGroup(group)) + members.Add(user.Value); + } + + return members; + } + + public void SyncGroupMembers(Group group, IReadOnlyDictionary users) + { + //remove + foreach (KeyValuePair user in _users) + { + if (!users.ContainsKey(user.Key)) + user.Value.RemoveFromGroup(group); + } + + //set + foreach (KeyValuePair user in users) + user.Value.AddToGroup(group); + } + + public Group CreateGroup(string name, string description) + { + Group group = new Group(name, description); + + if (_groups.TryAdd(name.ToLower(), group)) + return group; + + throw new DnsWebServiceException("Group already exists: " + name); + } + + public void RenameGroup(Group group, string newGroupName) + { + if (group.Name.Equals(newGroupName, StringComparison.OrdinalIgnoreCase)) + { + group.Name = newGroupName; + return; + } + + string oldGroupName = group.Name; + group.Name = newGroupName; + + if (!_groups.TryAdd(group.Name.ToLower(), group)) + { + group.Name = oldGroupName; //revert + throw new DnsWebServiceException("Group already exists: " + newGroupName); + } + + _groups.TryRemove(oldGroupName.ToLower(), out _); + + //update users + foreach (KeyValuePair user in _users) + user.Value.RenameGroup(oldGroupName); + } + + public bool DeleteGroup(string name) + { + name = name.ToLower(); + + switch (name) + { + case "everyone": + case "administrators": + case "dns administrators": + case "dhcp administrators": + throw new InvalidOperationException("Access was denied."); + + default: + if (_groups.TryRemove(name, out Group deletedGroup)) + { + //remove all users from deleted group + foreach (KeyValuePair user in _users) + user.Value.RemoveFromGroup(deletedGroup); + + //delete all permissions + foreach (KeyValuePair permission in _permissions) + { + permission.Value.RemovePermission(deletedGroup); + permission.Value.RemoveAllSubItemPermissions(deletedGroup); + } + + return true; + } + + return false; + } + } + + public UserSession GetSession(string token) + { + if (_sessions.TryGetValue(token, out UserSession session)) + return session; + + return null; + } + + public List GetSessions(User user) + { + List userSessions = new List(); + + foreach (KeyValuePair session in _sessions) + { + if (session.Value.User.Equals(user) && !session.Value.HasExpired()) + userSessions.Add(session.Value); + } + + return userSessions; + } + + public async Task CreateSessionAsync(UserSessionType type, string tokenName, string username, string password, IPAddress remoteAddress, string userAgent) + { + if (IsAddressBlocked(remoteAddress)) + throw new DnsWebServiceException("Max limit of " + MAX_LOGIN_ATTEMPTS + " attempts exceeded. Access blocked for " + (BLOCK_ADDRESS_INTERVAL / 1000) + " seconds."); + + User user = GetUser(username); + + if ((user is null) || !user.PasswordHash.Equals(user.GetPasswordHashFor(password), StringComparison.Ordinal)) + { + if (password != "admin") + { + FailedLoginAttempt(remoteAddress); + + if (LoginAttemptsExceedLimit(remoteAddress, MAX_LOGIN_ATTEMPTS)) + BlockAddress(remoteAddress, BLOCK_ADDRESS_INTERVAL); + + await Task.Delay(1000); + } + + throw new DnsWebServiceException("Invalid username or password for user: " + username); + } + + ResetFailedLoginAttempt(remoteAddress); + + if (user.Disabled) + throw new DnsWebServiceException("User account is disabled. Please contact your administrator."); + + UserSession session = new UserSession(type, tokenName, user, remoteAddress, userAgent); + + if (!_sessions.TryAdd(session.Token, session)) + throw new DnsWebServiceException("Error while creating session. Please try again."); + + user.LoggedInFrom(remoteAddress); + + return session; + } + + public UserSession CreateApiToken(string tokenName, string username, IPAddress remoteAddress, string userAgent) + { + if (IsAddressBlocked(remoteAddress)) + throw new DnsWebServiceException("Max limit of " + MAX_LOGIN_ATTEMPTS + " attempts exceeded. Access blocked for " + (BLOCK_ADDRESS_INTERVAL / 1000) + " seconds."); + + User user = GetUser(username); + if (user is null) + throw new DnsWebServiceException("No such user exists: " + username); + + if (user.Disabled) + throw new DnsWebServiceException("Account is suspended."); + + UserSession session = new UserSession(UserSessionType.ApiToken, tokenName, user, remoteAddress, userAgent); + + if (!_sessions.TryAdd(session.Token, session)) + throw new DnsWebServiceException("Error while creating session. Please try again."); + + user.LoggedInFrom(remoteAddress); + + return session; + } + + public UserSession DeleteSession(string token) + { + if (_sessions.TryRemove(token, out UserSession session)) + return session; + + return null; + } + + public Permission GetPermission(PermissionSection section) + { + if (_permissions.TryGetValue(section, out Permission permission)) + return permission; + + return null; + } + + public Permission GetPermission(PermissionSection section, string subItemName) + { + if (_permissions.TryGetValue(section, out Permission permission)) + return permission.GetSubItemPermission(subItemName); + + return null; + } + + public void SetPermission(PermissionSection section, User user, PermissionFlag flags) + { + Permission permission = _permissions.GetOrAdd(section, delegate (PermissionSection key) + { + return new Permission(key); + }); + + permission.SetPermission(user, flags); + } + + public void SetPermission(PermissionSection section, string subItemName, User user, PermissionFlag flags) + { + Permission permission = _permissions.GetOrAdd(section, delegate (PermissionSection key) + { + return new Permission(key); + }); + + permission.SetSubItemPermission(subItemName, user, flags); + } + + public void SetPermission(PermissionSection section, Group group, PermissionFlag flags) + { + Permission permission = _permissions.GetOrAdd(section, delegate (PermissionSection key) + { + return new Permission(key); + }); + + permission.SetPermission(group, flags); + } + + public void SetPermission(PermissionSection section, string subItemName, Group group, PermissionFlag flags) + { + Permission permission = _permissions.GetOrAdd(section, delegate (PermissionSection key) + { + return new Permission(key); + }); + + permission.SetSubItemPermission(subItemName, group, flags); + } + + public bool RemovePermission(PermissionSection section, User user) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.RemovePermission(user); + } + + public bool RemovePermission(PermissionSection section, string subItemName, User user) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.RemoveSubItemPermission(subItemName, user); + } + + public bool RemovePermission(PermissionSection section, Group group) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.RemovePermission(group); + } + + public bool RemovePermission(PermissionSection section, string subItemName, Group group) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.RemoveSubItemPermission(subItemName, group); + } + + public bool RemoveAllPermissions(PermissionSection section, string subItemName) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.RemoveAllSubItemPermissions(subItemName); + } + + public bool IsPermitted(PermissionSection section, User user, PermissionFlag flag) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.IsPermitted(user, flag); + } + + public bool IsPermitted(PermissionSection section, string subItemName, User user, PermissionFlag flag) + { + return _permissions.TryGetValue(section, out Permission permission) && permission.IsSubItemPermitted(subItemName, user, flag); + } + + public void LoadOldConfig(string password, bool isPasswordHash) + { + User user = GetUser("admin"); + if (user is null) + user = CreateUser("Administrator", "admin", "admin"); + + user.AddToGroup(GetGroup(Group.ADMINISTRATORS)); + + if (isPasswordHash) + user.LoadOldSchemeCredentials(password); + else + user.ChangePassword(password); + + lock (_lockObj) + { + SaveConfigFileInternal(); + } + } + + public void LoadConfigFile(UserSession implantSession = null) + { + lock (_lockObj) + { + _groups.Clear(); + _users.Clear(); + _permissions.Clear(); + _sessions.Clear(); + + LoadConfigFileInternal(implantSession); + } + } + + public void SaveConfigFile() + { + lock (_lockObj) + { + if (_pendingSave) + return; + + _pendingSave = true; + _saveTimer.Change(SAVE_TIMER_INITIAL_INTERVAL, Timeout.Infinite); + } + } + + #endregion + + #region properties + + public ICollection Groups + { get { return _groups.Values; } } + + public ICollection Users + { get { return _users.Values; } } + + public ICollection Permissions + { get { return _permissions.Values; } } + + public ICollection Sessions + { get { return _sessions.Values; } } + + #endregion + } +} diff --git a/DnsServerCore/Auth/Group.cs b/DnsServerCore/Auth/Group.cs new file mode 100644 index 00000000..6a550290 --- /dev/null +++ b/DnsServerCore/Auth/Group.cs @@ -0,0 +1,140 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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 System; +using System.IO; +using TechnitiumLibrary.IO; + +namespace DnsServerCore.Auth +{ + class Group : IComparable + { + #region variables + + public const string ADMINISTRATORS = "Administrators"; + public const string EVERYONE = "Everyone"; + public const string DNS_ADMINISTRATORS = "DNS Administrators"; + public const string DHCP_ADMINISTRATORS = "DHCP Administrators"; + + string _name; + string _description; + + #endregion + + #region constructor + + public Group(string name, string description) + { + Name = name; + Description = description; + } + + public Group(BinaryReader bR) + { + switch (bR.ReadByte()) + { + case 1: + _name = bR.ReadShortString(); + _description = bR.ReadShortString(); + break; + + default: + throw new InvalidDataException("Invalid data or version not supported."); + } + } + + #endregion + + #region public + + public void WriteTo(BinaryWriter bW) + { + bW.Write((byte)1); + bW.WriteShortString(_name); + bW.WriteShortString(_description); + } + + public override bool Equals(object obj) + { + if (obj is not Group other) + return false; + + return _name.Equals(other._name, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + return _name; + } + + public int CompareTo(Group other) + { + return _name.CompareTo(other._name); + } + + #endregion + + #region properties + + public string Name + { + get { return _name; } + set + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Group name cannot be null or empty.", nameof(Name)); + + if (value.Length > 255) + throw new ArgumentException("Group name length cannot exceed 255 characters.", nameof(Name)); + + switch (_name?.ToLower()) + { + case "everyone": + case "administrators": + case "dns administrators": + case "dhcp administrators": + throw new InvalidOperationException("Access was denied."); + + default: + _name = value; + break; + } + } + } + + public string Description + { + get { return _description; } + set + { + if (value.Length > 255) + throw new ArgumentException("Group description length cannot exceed 255 characters.", nameof(Description)); + + _description = value; + } + } + + #endregion + } +} diff --git a/DnsServerCore/Auth/Permission.cs b/DnsServerCore/Auth/Permission.cs new file mode 100644 index 00000000..05ac906b --- /dev/null +++ b/DnsServerCore/Auth/Permission.cs @@ -0,0 +1,339 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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 System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using TechnitiumLibrary.IO; + +namespace DnsServerCore.Auth +{ + enum PermissionSection : byte + { + Unknown = 0, + Dashboard = 1, + Zones = 2, + Cache = 3, + Allowed = 4, + Blocked = 5, + Apps = 6, + DnsClient = 7, + Settings = 8, + DhcpServer = 9, + Administration = 10, + Logs = 11 + } + + [Flags] + enum PermissionFlag : byte + { + None = 0, + View = 1, + Modify = 2, + Delete = 4, + ViewModify = 3, + ViewModifyDelete = 7 + } + + class Permission : IComparable + { + #region variables + + readonly PermissionSection _section; + readonly string _subItemName; + + readonly ConcurrentDictionary _userPermissions; + readonly ConcurrentDictionary _groupPermissions; + + readonly ConcurrentDictionary _subItemPermissions; + + #endregion + + #region constructor + + public Permission(PermissionSection section, string subItemName = null) + { + _section = section; + _subItemName = subItemName; + + _userPermissions = new ConcurrentDictionary(1, 1); + _groupPermissions = new ConcurrentDictionary(1, 1); + + _subItemPermissions = new ConcurrentDictionary(1, 1); + } + + public Permission(BinaryReader bR, AuthManager authManager) + { + switch (bR.ReadByte()) + { + case 1: + _section = (PermissionSection)bR.ReadByte(); + + { + int count = bR.ReadByte(); + _userPermissions = new ConcurrentDictionary(1, count); + + for (int i = 0; i < count; i++) + { + User user = authManager.GetUser(bR.ReadShortString()); + PermissionFlag flag = (PermissionFlag)bR.ReadByte(); + + if (user is not null) + _userPermissions.TryAdd(user, flag); + } + } + + { + int count = bR.ReadByte(); + _groupPermissions = new ConcurrentDictionary(1, count); + + for (int i = 0; i < count; i++) + { + Group group = authManager.GetGroup(bR.ReadShortString()); + PermissionFlag flag = (PermissionFlag)bR.ReadByte(); + + if (group is not null) + _groupPermissions.TryAdd(group, flag); + } + } + + { + int count = bR.ReadByte(); + _subItemPermissions = new ConcurrentDictionary(1, count); + + for (int i = 0; i < count; i++) + { + string subItemName = bR.ReadShortString(); + Permission subItemPermission = new Permission(bR, authManager); + + _subItemPermissions.TryAdd(subItemName.ToLower(), subItemPermission); + } + } + + break; + + default: + throw new InvalidDataException("Invalid data or version not supported."); + } + } + + #endregion + + #region public + + public void SetPermission(User user, PermissionFlag flags) + { + _userPermissions[user] = flags; + } + + public void SyncPermissions(IReadOnlyDictionary userPermissions) + { + //remove non-existent permissions + foreach (KeyValuePair userPermission in _userPermissions) + { + if (!userPermissions.ContainsKey(userPermission.Key)) + _userPermissions.TryRemove(userPermission.Key, out _); + } + + //set new permissions + foreach (KeyValuePair userPermission in userPermissions) + _userPermissions[userPermission.Key] = userPermission.Value; + } + + public void SetSubItemPermission(string subItemName, User user, PermissionFlag flags) + { + Permission subItemPermission = _subItemPermissions.GetOrAdd(subItemName.ToLower(), delegate (string key) + { + return new Permission(_section, key); + }); + + subItemPermission.SetPermission(user, flags); + } + + public void SetPermission(Group group, PermissionFlag flags) + { + _groupPermissions[group] = flags; + } + + public void SyncPermissions(IReadOnlyDictionary groupPermissions) + { + //remove non-existent permissions + foreach (KeyValuePair groupPermission in _groupPermissions) + { + if (!groupPermissions.ContainsKey(groupPermission.Key)) + _groupPermissions.TryRemove(groupPermission.Key, out _); + } + + //set new permissions + foreach (KeyValuePair groupPermission in groupPermissions) + _groupPermissions[groupPermission.Key] = groupPermission.Value; + } + + public void SetSubItemPermission(string subItemName, Group group, PermissionFlag flags) + { + Permission subItemPermission = _subItemPermissions.GetOrAdd(subItemName.ToLower(), delegate (string key) + { + return new Permission(_section, key); + }); + + subItemPermission.SetPermission(group, flags); + } + + public bool RemovePermission(User user) + { + return _userPermissions.TryRemove(user, out _); + } + + public bool RemoveSubItemPermission(string subItemName, User user) + { + return _subItemPermissions.TryGetValue(subItemName.ToLower(), out Permission subItemPermission) && subItemPermission.RemovePermission(user); + } + + public bool RemovePermission(Group group) + { + return _groupPermissions.TryRemove(group, out _); + } + + public bool RemoveSubItemPermission(string subItemName, Group group) + { + return _subItemPermissions.TryGetValue(subItemName.ToLower(), out Permission subItemPermission) && subItemPermission.RemovePermission(group); + } + + public bool RemoveAllSubItemPermissions(User user) + { + bool removed = false; + + foreach (KeyValuePair subItemPermission in _subItemPermissions) + { + if (subItemPermission.Value.RemovePermission(user)) + removed = true; + } + + return removed; + } + + public bool RemoveAllSubItemPermissions(Group group) + { + bool removed = false; + + foreach (KeyValuePair subItemPermission in _subItemPermissions) + { + if (subItemPermission.Value.RemovePermission(group)) + removed = true; + } + + return removed; + } + + public bool RemoveAllSubItemPermissions(string subItemName) + { + return _subItemPermissions.TryRemove(subItemName, out _); + } + + public Permission GetSubItemPermission(string subItemName) + { + if (_subItemPermissions.TryGetValue(subItemName.ToLower(), out Permission subItemPermission)) + return subItemPermission; + + return null; + } + + public bool IsPermitted(User user, PermissionFlag flag) + { + if (_userPermissions.TryGetValue(user, out PermissionFlag userPermissions) && userPermissions.HasFlag(flag)) + return true; + + foreach (Group group in user.MemberOfGroups) + { + if (_groupPermissions.TryGetValue(group, out PermissionFlag groupPermissions) && groupPermissions.HasFlag(flag)) + return true; + } + + return false; + } + + public bool IsSubItemPermitted(string subItemName, User user, PermissionFlag flag) + { + return _subItemPermissions.TryGetValue(subItemName.ToLower(), out Permission subItemPermission) && subItemPermission.IsPermitted(user, flag); + } + + public void WriteTo(BinaryWriter bW) + { + bW.Write((byte)1); + bW.Write((byte)_section); + + { + bW.Write(Convert.ToByte(_userPermissions.Count)); + + foreach (KeyValuePair userPermission in _userPermissions) + { + bW.WriteShortString(userPermission.Key.Username); + bW.Write((byte)userPermission.Value); + } + } + + { + bW.Write(Convert.ToByte(_groupPermissions.Count)); + + foreach (KeyValuePair groupPermission in _groupPermissions) + { + bW.WriteShortString(groupPermission.Key.Name); + bW.Write((byte)groupPermission.Value); + } + } + + { + bW.Write(Convert.ToByte(_subItemPermissions.Count)); + + foreach (KeyValuePair subItemPermission in _subItemPermissions) + { + bW.WriteShortString(subItemPermission.Key); + subItemPermission.Value.WriteTo(bW); + } + } + } + + public int CompareTo(Permission other) + { + return _section.CompareTo(other._section); + } + + #endregion + + #region properties + + public PermissionSection Section + { get { return _section; } } + + public string SubItemName + { get { return _subItemName; } } + + public IReadOnlyDictionary UserPermissions + { get { return _userPermissions; } } + + public IReadOnlyDictionary GroupPermissions + { get { return _groupPermissions; } } + + public IReadOnlyDictionary SubItemPermissions + { get { return _subItemPermissions; } } + + #endregion + } +} diff --git a/DnsServerCore/Auth/User.cs b/DnsServerCore/Auth/User.cs new file mode 100644 index 00000000..60717ad5 --- /dev/null +++ b/DnsServerCore/Auth/User.cs @@ -0,0 +1,357 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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 System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using TechnitiumLibrary.IO; +using TechnitiumLibrary.Net; + +namespace DnsServerCore.Auth +{ + enum UserPasswordHashType : byte + { + Unknown = 0, + OldScheme = 1, + PBKDF2_SHA256 = 2 + } + + class User : IComparable + { + #region variables + + static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); + + public const int DEFAULT_ITERATIONS = 1000000; + + string _displayName; + string _username; + UserPasswordHashType _passwordHashType; + int _iterations; + byte[] _salt; + string _passwordHash; + bool _disabled; + int _sessionTimeoutSeconds = 30 * 60; //default 30 mins + + DateTime _previousSessionLoggedOn; + IPAddress _previousSessionRemoteAddress; + DateTime _recentSessionLoggedOn; + IPAddress _recentSessionRemoteAddress; + + readonly ConcurrentDictionary _memberOfGroups; + + #endregion + + #region constructor + + public User(string displayName, string username, string password, int iterations = DEFAULT_ITERATIONS) + { + DisplayName = displayName; + Username = username; + + ChangePassword(password, iterations); + + _previousSessionRemoteAddress = IPAddress.Any; + _recentSessionRemoteAddress = IPAddress.Any; + + _memberOfGroups = new ConcurrentDictionary(1, 2); + } + + public User(BinaryReader bR, AuthManager authManager) + { + switch (bR.ReadByte()) + { + case 1: + _displayName = bR.ReadShortString(); + _username = bR.ReadShortString(); + _passwordHashType = (UserPasswordHashType)bR.ReadByte(); + _iterations = bR.ReadInt32(); + _salt = bR.ReadBuffer(); + _passwordHash = bR.ReadShortString(); + _disabled = bR.ReadBoolean(); + _sessionTimeoutSeconds = bR.ReadInt32(); + + _previousSessionLoggedOn = bR.ReadDateTime(); + _previousSessionRemoteAddress = IPAddressExtension.ReadFrom(bR); + _recentSessionLoggedOn = bR.ReadDateTime(); + _recentSessionRemoteAddress = IPAddressExtension.ReadFrom(bR); + + { + int count = bR.ReadByte(); + _memberOfGroups = new ConcurrentDictionary(1, count); + + for (int i = 0; i < count; i++) + { + Group group = authManager.GetGroup(bR.ReadShortString()); + if (group is not null) + _memberOfGroups.TryAdd(group.Name.ToLower(), group); + } + } + break; + + default: + throw new InvalidDataException("Invalid data or version not supported."); + } + } + + #endregion + + #region internal + + internal void RenameGroup(string oldName) + { + if (_memberOfGroups.TryRemove(oldName.ToLower(), out Group renamedGroup)) + _memberOfGroups.TryAdd(renamedGroup.Name.ToLower(), renamedGroup); + } + + #endregion + + #region public + + public string GetPasswordHashFor(string password) + { + switch (_passwordHashType) + { + case UserPasswordHashType.OldScheme: + using (HMAC hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password))) + { + return Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(_username))).ToLower(); + } + + case UserPasswordHashType.PBKDF2_SHA256: + return Convert.ToHexString(Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), _salt, _iterations, HashAlgorithmName.SHA256, 32)).ToLower(); + + default: + throw new NotSupportedException(); + } + } + + public void ChangePassword(string newPassword, int iterations = DEFAULT_ITERATIONS) + { + _passwordHashType = UserPasswordHashType.PBKDF2_SHA256; + _iterations = iterations; + + _salt = new byte[32]; + _rng.GetBytes(_salt); + + _passwordHash = GetPasswordHashFor(newPassword); + } + + public void LoadOldSchemeCredentials(string passwordHash) + { + _passwordHashType = UserPasswordHashType.OldScheme; + _passwordHash = passwordHash; + } + + public void LoggedInFrom(IPAddress remoteAddress) + { + _previousSessionLoggedOn = _recentSessionLoggedOn; + _previousSessionRemoteAddress = _recentSessionRemoteAddress; + + _recentSessionLoggedOn = DateTime.UtcNow; + _recentSessionRemoteAddress = remoteAddress; + } + + public void AddToGroup(Group group) + { + if (_memberOfGroups.Count == 255) + throw new InvalidOperationException("Cannot add user to group: user can be member of max 255 groups."); + + _memberOfGroups.TryAdd(group.Name.ToLower(), group); + } + + public bool RemoveFromGroup(Group group) + { + if (group.Name.Equals("everyone", StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("Access was denied."); + + return _memberOfGroups.TryRemove(group.Name.ToLower(), out _); + } + + public void SyncGroups(IReadOnlyDictionary groups) + { + //remove non-existent groups + foreach (KeyValuePair group in _memberOfGroups) + { + if (!groups.ContainsKey(group.Key)) + _memberOfGroups.TryRemove(group.Key, out _); + } + + //set new groups + foreach (KeyValuePair group in groups) + _memberOfGroups[group.Key] = group.Value; + } + + public bool IsMemberOfGroup(Group group) + { + return _memberOfGroups.ContainsKey(group.Name.ToLower()); + } + + public void WriteTo(BinaryWriter bW) + { + bW.Write((byte)1); + bW.WriteShortString(_displayName); + bW.WriteShortString(_username); + bW.Write((byte)_passwordHashType); + bW.Write(_iterations); + bW.WriteBuffer(_salt); + bW.WriteShortString(_passwordHash); + bW.Write(_disabled); + bW.Write(_sessionTimeoutSeconds); + + bW.Write(_previousSessionLoggedOn); + IPAddressExtension.WriteTo(_previousSessionRemoteAddress, bW); + bW.Write(_recentSessionLoggedOn); + IPAddressExtension.WriteTo(_recentSessionRemoteAddress, bW); + + bW.Write(Convert.ToByte(_memberOfGroups.Count)); + + foreach (KeyValuePair group in _memberOfGroups) + bW.WriteShortString(group.Value.Name.ToLower()); + } + + public override bool Equals(object obj) + { + if (obj is not User other) + return false; + + return _username.Equals(other._username, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + return _username; + } + + public int CompareTo(User other) + { + return _username.CompareTo(other._username); + } + + #endregion + + #region properties + + public string DisplayName + { + get { return _displayName; } + set + { + if (value.Length > 255) + throw new ArgumentException("Display name length cannot exceed 255 characters.", nameof(DisplayName)); + + _displayName = value; + + if (string.IsNullOrWhiteSpace(_displayName)) + _displayName = _username; + } + } + + public string Username + { + get { return _username; } + set + { + if (_passwordHashType == UserPasswordHashType.OldScheme) + throw new InvalidOperationException("Cannot change username when using old password hash scheme. Change password once and try again."); + + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Username cannot be null or empty.", nameof(Username)); + + if (value.Length > 255) + throw new ArgumentException("Username length cannot exceed 255 characters.", nameof(Username)); + + foreach (char c in value) + { + if ((c >= 97) && (c <= 122)) //[a-z] + continue; + + if ((c >= 65) && (c <= 90)) //[A-Z] + continue; + + if ((c >= 48) && (c <= 57)) //[0-9] + continue; + + if (c == '-') + continue; + + if (c == '_') + continue; + + if (c == '.') + continue; + + throw new ArgumentException("Username can contain only alpha numeric, '-', '_', or '.' characters.", nameof(Username)); + } + + _username = value.ToLower(); + } + } + + public UserPasswordHashType PasswordHashType + { get { return _passwordHashType; } } + + public string PasswordHash + { get { return _passwordHash; } } + + public bool Disabled + { + get { return _disabled; } + set { _disabled = value; } + } + + public int SessionTimeoutSeconds + { + get { return _sessionTimeoutSeconds; } + set + { + if ((value < 0) || (value > 604800)) + throw new ArgumentOutOfRangeException(nameof(SessionTimeoutSeconds), "Session timeout value must be between 0-604800 seconds."); + + _sessionTimeoutSeconds = value; + } + } + + public DateTime PreviousSessionLoggedOn + { get { return _previousSessionLoggedOn; } } + + public IPAddress PreviousSessionRemoteAddress + { get { return _previousSessionRemoteAddress; } } + + public DateTime RecentSessionLoggedOn + { get { return _recentSessionLoggedOn; } } + + public IPAddress RecentSessionRemoteAddress + { get { return _recentSessionRemoteAddress; } } + + public ICollection MemberOfGroups + { get { return _memberOfGroups.Values; } } + + #endregion + } +} diff --git a/DnsServerCore/Auth/UserSession.cs b/DnsServerCore/Auth/UserSession.cs new file mode 100644 index 00000000..14d5d1aa --- /dev/null +++ b/DnsServerCore/Auth/UserSession.cs @@ -0,0 +1,179 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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 System; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using TechnitiumLibrary.IO; +using TechnitiumLibrary.Net; + +namespace DnsServerCore.Auth +{ + enum UserSessionType : byte + { + Unknown = 0, + Standard = 1, + ApiToken = 2 + } + + class UserSession : IComparable + { + #region variables + + static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); + + readonly string _token; + readonly UserSessionType _type; + readonly string _tokenName; + readonly User _user; + DateTime _lastSeen; + IPAddress _lastSeenRemoteAddress; + string _lastSeenUserAgent; + + #endregion + + #region constructor + + public UserSession(UserSessionType type, string tokenName, User user, IPAddress remoteAddress, string lastSeenUserAgent) + { + if ((tokenName is not null) && (tokenName.Length > 255)) + throw new ArgumentOutOfRangeException(nameof(tokenName), "Token name length cannot exceed 255 characters."); + + byte[] tokenBytes = new byte[32]; + _rng.GetBytes(tokenBytes); + _token = Convert.ToHexString(tokenBytes).ToLower(); + + _type = type; + _tokenName = tokenName; + _user = user; + _lastSeen = DateTime.UtcNow; + _lastSeenRemoteAddress = remoteAddress; + _lastSeenUserAgent = lastSeenUserAgent; + + if ((_lastSeenUserAgent is not null) && (_lastSeenUserAgent.Length > 255)) + _lastSeenUserAgent = _lastSeenUserAgent.Substring(0, 255); + } + + public UserSession(BinaryReader bR, AuthManager authManager) + { + switch (bR.ReadByte()) + { + case 1: + _token = bR.ReadShortString(); + _type = (UserSessionType)bR.ReadByte(); + + _tokenName = bR.ReadShortString(); + if (_tokenName.Length == 0) + _tokenName = null; + + _user = authManager.GetUser(bR.ReadShortString()); + _lastSeen = bR.ReadDateTime(); + _lastSeenRemoteAddress = IPAddressExtension.ReadFrom(bR); + + _lastSeenUserAgent = bR.ReadShortString(); + if (_lastSeenUserAgent.Length == 0) + _lastSeenUserAgent = null; + + break; + + default: + throw new InvalidDataException("Invalid data or version not supported."); + } + } + + #endregion + + #region public + + public void UpdateLastSeen(IPAddress remoteAddress, string lastSeenUserAgent) + { + _lastSeen = DateTime.UtcNow; + _lastSeenRemoteAddress = remoteAddress; + _lastSeenUserAgent = lastSeenUserAgent; + + if ((_lastSeenUserAgent is not null) && (_lastSeenUserAgent.Length > 255)) + _lastSeenUserAgent = _lastSeenUserAgent.Substring(0, 255); + } + + public bool HasExpired() + { + if (_type == UserSessionType.ApiToken) + return false; + + if (_user.SessionTimeoutSeconds == 0) + return false; + + return _lastSeen.AddSeconds(_user.SessionTimeoutSeconds) < DateTime.UtcNow; + } + + public void WriteTo(BinaryWriter bW) + { + bW.Write((byte)1); + bW.WriteShortString(_token); + bW.Write((byte)_type); + + if (_tokenName is null) + bW.Write((byte)0); + else + bW.WriteShortString(_tokenName); + + bW.WriteShortString(_user.Username); + bW.Write(_lastSeen); + _lastSeenRemoteAddress.WriteTo(bW); + + if (_lastSeenUserAgent is null) + bW.Write((byte)0); + else + bW.WriteShortString(_lastSeenUserAgent); + } + + public int CompareTo(UserSession other) + { + return other._lastSeen.CompareTo(_lastSeen); + } + + #endregion + + #region properties + + public string Token + { get { return _token; } } + + public UserSessionType Type + { get { return _type; } } + + public string TokenName + { get { return _tokenName; } } + + public User User + { get { return _user; } } + + public DateTime LastSeen + { get { return _lastSeen; } } + + public IPAddress LastSeenRemoteAddress + { get { return _lastSeenRemoteAddress; } } + + public string LastSeenUserAgent + { get { return _lastSeenUserAgent; } } + + #endregion + } +} diff --git a/DnsServerCore/Dhcp/DhcpServer.cs b/DnsServerCore/Dhcp/DhcpServer.cs index f0bca697..6a704ed4 100644 --- a/DnsServerCore/Dhcp/DhcpServer.cs +++ b/DnsServerCore/Dhcp/DhcpServer.cs @@ -17,6 +17,7 @@ along with this program. If not, see . */ +using DnsServerCore.Auth; using DnsServerCore.Dhcp.Options; using DnsServerCore.Dns.ZoneManagers; using DnsServerCore.Dns.Zones; @@ -69,6 +70,7 @@ namespace DnsServerCore.Dhcp readonly ConcurrentDictionary _scopes = new ConcurrentDictionary(); AuthZoneManager _authZoneManager; + AuthManager _authManager; ConcurrentDictionary _modifiedDnsAuthZones = new ConcurrentDictionary(); readonly Timer _saveModifiedDnsAuthZonesTimer; @@ -760,16 +762,42 @@ namespace DnsServerCore.Dhcp { //zone does not exists; create new primary zone zoneInfo = _authZoneManager.CreatePrimaryZone(scope.DomainName, _authZoneManager.ServerDomain, false); - log?.Write("DHCP Server create DNS primary zone '" + scope.DomainName + "'."); + if (zoneInfo is null) + { + log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'."); + return; + } + + //set permissions + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); + + log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'."); + _authZoneManager.SaveZoneFile(zoneInfo.Name); } - else if (zoneInfo.Type != AuthZoneType.Primary) + else if ((zoneInfo.Type != AuthZoneType.Primary) && (zoneInfo.Type != AuthZoneType.Forwarder)) { if (zoneInfo.Name.Equals(scope.DomainName, StringComparison.OrdinalIgnoreCase)) - throw new DhcpServerException("Cannot update DNS zone '" + zoneInfo.Name + "': not a primary zone."); + throw new DhcpServerException("Cannot update DNS zone '" + zoneInfo.Name + "': not a primary or a forwarder zone."); //create new primary zone zoneInfo = _authZoneManager.CreatePrimaryZone(scope.DomainName, _authZoneManager.ServerDomain, false); - log?.Write("DHCP Server create DNS primary zone '" + scope.DomainName + "'."); + if (zoneInfo is null) + { + log?.Write("DHCP Server failed to create DNS primary zone '" + scope.DomainName + "'."); + return; + } + + //set permissions + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); + + log?.Write("DHCP Server create DNS primary zone '" + zoneInfo.Name + "'."); + _authZoneManager.SaveZoneFile(zoneInfo.Name); } zoneName = zoneInfo.Name; @@ -803,18 +831,44 @@ namespace DnsServerCore.Dhcp //reverse zone does not exists; create new reverse primary zone reverseZoneInfo = _authZoneManager.CreatePrimaryZone(reverseZone, _authZoneManager.ServerDomain, false); - log?.Write("DHCP Server create DNS primary zone '" + reverseZone + "'."); + if (reverseZoneInfo is null) + { + log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'."); + return; + } + + //set permissions + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); + + log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'."); + _authZoneManager.SaveZoneFile(reverseZoneInfo.Name); } - else if (reverseZoneInfo.Type != AuthZoneType.Primary) + else if ((reverseZoneInfo.Type != AuthZoneType.Primary) && (reverseZoneInfo.Type != AuthZoneType.Forwarder)) { string reverseZone = Zone.GetReverseZone(address, scope.SubnetMask); if (reverseZoneInfo.Name.Equals(reverseZone, StringComparison.OrdinalIgnoreCase)) - throw new DhcpServerException("Cannot update reverse DNS zone '" + reverseZoneInfo.Name + "': not a primary zone."); + throw new DhcpServerException("Cannot update reverse DNS zone '" + reverseZoneInfo.Name + "': not a primary or a forwarder zone."); //create new reverse primary zone reverseZoneInfo = _authZoneManager.CreatePrimaryZone(reverseZone, _authZoneManager.ServerDomain, false); - log?.Write("DHCP Server create DNS primary zone '" + reverseZone + "'."); + if (reverseZoneInfo is null) + { + log?.Write("DHCP Server failed to create DNS primary zone '" + reverseZone + "'."); + return; + } + + //set permissions + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _authManager.GetGroup(Group.DHCP_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); + + log?.Write("DHCP Server create DNS primary zone '" + reverseZoneInfo.Name + "'."); + _authZoneManager.SaveZoneFile(reverseZoneInfo.Name); } reverseZoneName = reverseZoneInfo.Name; @@ -825,7 +879,7 @@ namespace DnsServerCore.Dhcp { //remove from forward zone AuthZoneInfo zoneInfo = _authZoneManager.FindAuthZoneInfo(domain); - if ((zoneInfo is not null) && (zoneInfo.Type == AuthZoneType.Primary)) + if ((zoneInfo is not null) && ((zoneInfo.Type == AuthZoneType.Primary) || (zoneInfo.Type == AuthZoneType.Forwarder))) { //primary zone exists zoneName = zoneInfo.Name; @@ -835,7 +889,7 @@ namespace DnsServerCore.Dhcp //remove from reverse zone AuthZoneInfo reverseZoneInfo = _authZoneManager.FindAuthZoneInfo(reverseDomain); - if ((reverseZoneInfo != null) && (reverseZoneInfo.Type == AuthZoneType.Primary)) + if ((reverseZoneInfo != null) && ((reverseZoneInfo.Type == AuthZoneType.Primary) || (reverseZoneInfo.Type == AuthZoneType.Forwarder))) { //primary reverse zone exists reverseZoneName = reverseZoneInfo.Name; @@ -1397,6 +1451,12 @@ namespace DnsServerCore.Dhcp set { _authZoneManager = value; } } + internal AuthManager AuthManager + { + get { return _authManager; } + set { _authManager = value; } + } + public LogManager LogManager { get { return _log; } diff --git a/DnsServerCore/Dhcp/Lease.cs b/DnsServerCore/Dhcp/Lease.cs index b20b1ad6..0635844e 100644 --- a/DnsServerCore/Dhcp/Lease.cs +++ b/DnsServerCore/Dhcp/Lease.cs @@ -88,7 +88,7 @@ namespace DnsServerCore.Dhcp _hostName = null; _hardwareAddress = bR.ReadBuffer(); - _address = IPAddressExtension.Parse(bR); + _address = IPAddressExtension.ReadFrom(bR); if (version >= 2) { diff --git a/DnsServerCore/Dhcp/Scope.cs b/DnsServerCore/Dhcp/Scope.cs index bdadca51..2fcfe4dd 100644 --- a/DnsServerCore/Dhcp/Scope.cs +++ b/DnsServerCore/Dhcp/Scope.cs @@ -120,7 +120,7 @@ namespace DnsServerCore.Dhcp _name = bR.ReadShortString(); _enabled = bR.ReadBoolean(); - ChangeNetwork(IPAddressExtension.Parse(bR), IPAddressExtension.Parse(bR), IPAddressExtension.Parse(bR)); + ChangeNetwork(IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR)); _leaseTimeDays = bR.ReadUInt16(); _leaseTimeHours = bR.ReadByte(); @@ -143,7 +143,7 @@ namespace DnsServerCore.Dhcp if (version >= 2) { - _serverAddress = IPAddressExtension.Parse(bR); + _serverAddress = IPAddressExtension.ReadFrom(bR); if (_serverAddress.Equals(IPAddress.Any)) _serverAddress = null; } @@ -159,7 +159,7 @@ namespace DnsServerCore.Dhcp _bootFileName = null; } - _routerAddress = IPAddressExtension.Parse(bR); + _routerAddress = IPAddressExtension.ReadFrom(bR); if (_routerAddress.Equals(IPAddress.Any)) _routerAddress = null; @@ -177,7 +177,7 @@ namespace DnsServerCore.Dhcp IPAddress[] dnsServers = new IPAddress[count]; for (int i = 0; i < count; i++) - dnsServers[i] = IPAddressExtension.Parse(bR); + dnsServers[i] = IPAddressExtension.ReadFrom(bR); _dnsServers = dnsServers; } @@ -191,7 +191,7 @@ namespace DnsServerCore.Dhcp IPAddress[] winsServers = new IPAddress[count]; for (int i = 0; i < count; i++) - winsServers[i] = IPAddressExtension.Parse(bR); + winsServers[i] = IPAddressExtension.ReadFrom(bR); _winsServers = winsServers; } @@ -204,7 +204,7 @@ namespace DnsServerCore.Dhcp IPAddress[] ntpServers = new IPAddress[count]; for (int i = 0; i < count; i++) - ntpServers[i] = IPAddressExtension.Parse(bR); + ntpServers[i] = IPAddressExtension.ReadFrom(bR); _ntpServers = ntpServers; } @@ -249,7 +249,7 @@ namespace DnsServerCore.Dhcp Exclusion[] exclusions = new Exclusion[count]; for (int i = 0; i < count; i++) - exclusions[i] = new Exclusion(IPAddressExtension.Parse(bR), IPAddressExtension.Parse(bR)); + exclusions[i] = new Exclusion(IPAddressExtension.ReadFrom(bR), IPAddressExtension.ReadFrom(bR)); _exclusions = exclusions; } @@ -455,7 +455,7 @@ namespace DnsServerCore.Dhcp clientDomainName = request.HostName.HostName + "." + _domainName; } - else if (request.ClientFullyQualifiedDomainName.DomainName.Contains(".")) + else if (request.ClientFullyQualifiedDomainName.DomainName.Contains('.')) { //client domain is fqdn if (request.ClientFullyQualifiedDomainName.DomainName.EndsWith("." + _domainName, StringComparison.OrdinalIgnoreCase)) @@ -1161,6 +1161,19 @@ namespace DnsServerCore.Dhcp } } + public bool AddReservedLease(Lease reservedLease) + { + return _reservedLeases.TryAdd(reservedLease.ClientIdentifier, reservedLease); + } + + public bool RemoveReservedLease(string hardwareAddress) + { + byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); + ClientIdentifierOption reservedLeaseClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, hardwareAddressBytes); + + return _reservedLeases.TryRemove(reservedLeaseClientIdentifier, out _); + } + public Lease RemoveLease(string hardwareAddress) { byte[] hardwareAddressBytes = Lease.ParseHardwareAddress(hardwareAddress); @@ -1182,12 +1195,12 @@ namespace DnsServerCore.Dhcp if (removedLease.Type == LeaseType.Reserved) { //remove reserved lease - ClientIdentifierOption reservedLeasesClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, removedLease.HardwareAddress); - if (_reservedLeases.TryGetValue(reservedLeasesClientIdentifier, out Lease existingReservedLease)) + ClientIdentifierOption reservedLeaseClientIdentifier = new ClientIdentifierOption((byte)DhcpMessageHardwareAddressType.Ethernet, removedLease.HardwareAddress); + if (_reservedLeases.TryGetValue(reservedLeaseClientIdentifier, out Lease existingReservedLease)) { //remove reserved lease only if the IP addresses match if (existingReservedLease.Address.Equals(removedLease.Address)) - _reservedLeases.TryRemove(reservedLeasesClientIdentifier, out _); + _reservedLeases.TryRemove(reservedLeaseClientIdentifier, out _); } } diff --git a/DnsServerCore/Dns/DnsServer.cs b/DnsServerCore/Dns/DnsServer.cs index 20d21c36..cc9e87ee 100644 --- a/DnsServerCore/Dns/DnsServer.cs +++ b/DnsServerCore/Dns/DnsServer.cs @@ -1464,7 +1464,7 @@ namespace DnsServerCore.Dns { AuthZoneInfo zoneInfo = _authZoneManager.FindAuthZoneInfo(appResourceRecord.Name); - DnsDatagram appResponse = await appRecordRequestHandler.ProcessRequestAsync(request, remoteEP, protocol, isRecursionAllowed, zoneInfo.Name, appResourceRecord.TtlValue, appRecord.Data); + DnsDatagram appResponse = await appRecordRequestHandler.ProcessRequestAsync(request, remoteEP, protocol, isRecursionAllowed, zoneInfo.Name, appResourceRecord.Name, appResourceRecord.TtlValue, appRecord.Data); if (appResponse is null) { //return no error response with SOA @@ -1540,7 +1540,7 @@ namespace DnsServerCore.Dns int queryCount = 0; do { - DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord((lastRR.RDATA as DnsCNAMERecordData).Domain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, _dnssecValidation ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None); + DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord((lastRR.RDATA as DnsCNAMERecordData).Domain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None); //query authoritative zone first newResponse = _authZoneManager.Query(newRequest, isRecursionAllowed); diff --git a/DnsServerCore/Dns/StatsManager.cs b/DnsServerCore/Dns/StatsManager.cs index 6a0b11a6..fa3a6905 100644 --- a/DnsServerCore/Dns/StatsManager.cs +++ b/DnsServerCore/Dns/StatsManager.cs @@ -1368,7 +1368,7 @@ namespace DnsServerCore.Dns _clientIpAddresses = new ConcurrentDictionary(1, count); for (int i = 0; i < count; i++) - _clientIpAddresses.TryAdd(IPAddressExtension.Parse(bR), new Counter(bR.ReadInt32())); + _clientIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt32())); if (version < 6) _totalClients = count; @@ -1393,7 +1393,7 @@ namespace DnsServerCore.Dns _errorIpAddresses = new ConcurrentDictionary(1, count); for (int i = 0; i < count; i++) - _errorIpAddresses.TryAdd(IPAddressExtension.Parse(bR), new Counter(bR.ReadInt32())); + _errorIpAddresses.TryAdd(IPAddressExtension.ReadFrom(bR), new Counter(bR.ReadInt32())); } else { diff --git a/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs b/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs index 23cae5d2..699a457a 100644 --- a/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs +++ b/DnsServerCore/Dns/ZoneManagers/AuthZoneManager.cs @@ -1697,9 +1697,6 @@ namespace DnsServerCore.Dns.ZoneManagers if (apexZone is StubZone) return GetReferralResponse(request, false, apexZone, apexZone, isRecursionAllowed); - if (apexZone is ForwarderZone) - return GetForwarderResponse(request, null, closest, apexZone, isRecursionAllowed); - DnsResponseCode rCode = DnsResponseCode.NoError; IReadOnlyList answer = null; IReadOnlyList authority = null; @@ -1733,6 +1730,9 @@ namespace DnsServerCore.Dns.ZoneManagers authority = apexZone.QueryRecords(DnsResourceRecordType.APP, false); if (authority.Count == 0) { + if (apexZone is ForwarderZone) + return GetForwarderResponse(request, null, closest, apexZone, isRecursionAllowed); //no DNAME or APP record available so process FWD response + if (!hasSubDomains) rCode = DnsResponseCode.NxDomain; @@ -1767,12 +1767,20 @@ namespace DnsServerCore.Dns.ZoneManagers else { //zone found - if ((question.Type == DnsResourceRecordType.DS) && (zone is ApexZone)) + if (question.Type == DnsResourceRecordType.DS) { - if (delegation is null || !delegation.IsActive || (delegation.Name.Length > apexZone.Name.Length)) - return null; //no authoritative parent side delegation zone available to answer for DS + if (zone is ApexZone) + { + if (delegation is null || !delegation.IsActive || (delegation.Name.Length > apexZone.Name.Length)) + return null; //no authoritative parent side delegation zone available to answer for DS - zone = delegation; //switch zone to parent side sub domain delegation zone for DS record + zone = delegation; //switch zone to parent side sub domain delegation zone for DS record + } + } + else if (zone.Equals(delegation)) + { + //zone is delegation + return GetReferralResponse(request, dnssecOk, delegation, apexZone, isRecursionAllowed); } IReadOnlyList authority = null; @@ -1806,9 +1814,6 @@ namespace DnsServerCore.Dns.ZoneManagers if (apexZone is StubZone) return GetReferralResponse(request, false, apexZone, apexZone, isRecursionAllowed); - - if (apexZone is ForwarderZone) - return GetForwarderResponse(request, zone, closest, apexZone, isRecursionAllowed); } authority = zone.QueryRecords(DnsResourceRecordType.APP, false); @@ -1822,6 +1827,9 @@ namespace DnsServerCore.Dns.ZoneManagers authority = apexZone.QueryRecords(DnsResourceRecordType.APP, false); if (authority.Count == 0) { + if (apexZone is ForwarderZone) + return GetForwarderResponse(request, zone, closest, apexZone, isRecursionAllowed); //no APP record available so process FWD response + authority = apexZone.QueryRecords(DnsResourceRecordType.SOA, dnssecOk); if (dnssecOk) diff --git a/DnsServerCore/Dns/ZoneManagers/BlockListZoneManager.cs b/DnsServerCore/Dns/ZoneManagers/BlockListZoneManager.cs index c582345a..a33a1421 100644 --- a/DnsServerCore/Dns/ZoneManagers/BlockListZoneManager.cs +++ b/DnsServerCore/Dns/ZoneManagers/BlockListZoneManager.cs @@ -321,6 +321,7 @@ namespace DnsServerCore.Dns.ZoneManagers SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; + handler.UseProxy = _dnsServer.Proxy is not null; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) diff --git a/DnsServerCore/Dns/Zones/AuthZoneInfo.cs b/DnsServerCore/Dns/Zones/AuthZoneInfo.cs index 40cf5864..009400ba 100644 --- a/DnsServerCore/Dns/Zones/AuthZoneInfo.cs +++ b/DnsServerCore/Dns/Zones/AuthZoneInfo.cs @@ -110,7 +110,7 @@ namespace DnsServerCore.Dns.Zones IPAddress[] nameServers = new IPAddress[count]; for (int i = 0; i < count; i++) - nameServers[i] = IPAddressExtension.Parse(bR); + nameServers[i] = IPAddressExtension.ReadFrom(bR); _zoneTransferNameServers = nameServers; } @@ -125,7 +125,7 @@ namespace DnsServerCore.Dns.Zones IPAddress[] nameServers = new IPAddress[count]; for (int i = 0; i < count; i++) - nameServers[i] = IPAddressExtension.Parse(bR); + nameServers[i] = IPAddressExtension.ReadFrom(bR); _notifyNameServers = nameServers; } diff --git a/DnsServerCore/Dns/Zones/ForwarderSubDomainZone.cs b/DnsServerCore/Dns/Zones/ForwarderSubDomainZone.cs index 64a64d14..badb179b 100644 --- a/DnsServerCore/Dns/Zones/ForwarderSubDomainZone.cs +++ b/DnsServerCore/Dns/Zones/ForwarderSubDomainZone.cs @@ -40,7 +40,6 @@ namespace DnsServerCore.Dns.Zones { case DnsResourceRecordType.SOA: case DnsResourceRecordType.DS: - case DnsResourceRecordType.APP: throw new DnsServerException("The record type is not supported by forwarder zones."); default: @@ -54,7 +53,6 @@ namespace DnsServerCore.Dns.Zones switch (record.Type) { case DnsResourceRecordType.DS: - case DnsResourceRecordType.APP: throw new DnsServerException("The record type is not supported by forwarder zones."); default: diff --git a/DnsServerCore/Dns/Zones/ForwarderZone.cs b/DnsServerCore/Dns/Zones/ForwarderZone.cs index 756d3719..97bbfb02 100644 --- a/DnsServerCore/Dns/Zones/ForwarderZone.cs +++ b/DnsServerCore/Dns/Zones/ForwarderZone.cs @@ -61,7 +61,6 @@ namespace DnsServerCore.Dns.Zones case DnsResourceRecordType.SOA: case DnsResourceRecordType.DS: - case DnsResourceRecordType.APP: throw new DnsServerException("The record type is not supported by forwarder zones."); default: @@ -75,7 +74,6 @@ namespace DnsServerCore.Dns.Zones switch (record.Type) { case DnsResourceRecordType.DS: - case DnsResourceRecordType.APP: throw new DnsServerException("The record type is not supported by forwarder zones."); default: diff --git a/DnsServerCore/DnsWebService.cs b/DnsServerCore/DnsWebService.cs index 8f760e51..41e1164c 100644 --- a/DnsServerCore/DnsWebService.cs +++ b/DnsServerCore/DnsWebService.cs @@ -17,6 +17,7 @@ along with this program. If not, see . */ +using DnsServerCore.Auth; using DnsServerCore.Dhcp; using DnsServerCore.Dns; using DnsServerCore.Dns.ResourceRecords; @@ -25,7 +26,6 @@ using DnsServerCore.Dns.Zones; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -67,6 +67,10 @@ namespace DnsServerCore readonly static RandomNumberGenerator _rng = RandomNumberGenerator.Create(); + readonly LogManager _log; + readonly AuthManager _authManager; + + readonly WebServiceAuthApi _authApi; readonly WebServiceDashboardApi _dashboardApi; readonly WebServiceZonesApi _zonesApi; readonly WebServiceOtherZonesApi _otherZonesApi; @@ -79,8 +83,6 @@ namespace DnsServerCore readonly string _configFolder; readonly Uri _updateCheckUri; - readonly LogManager _log; - DnsServer _dnsServer; DhcpServer _dhcpServer; @@ -109,13 +111,6 @@ namespace DnsServerCore const int TLS_CERTIFICATE_UPDATE_TIMER_INITIAL_INTERVAL = 60000; const int TLS_CERTIFICATE_UPDATE_TIMER_INTERVAL = 60000; - const int MAX_LOGIN_ATTEMPTS = 5; - const int BLOCK_ADDRESS_INTERVAL = 5 * 60 * 1000; - readonly ConcurrentDictionary _failedLoginAttempts = new ConcurrentDictionary(); - readonly ConcurrentDictionary _blockedAddresses = new ConcurrentDictionary(); - readonly ConcurrentDictionary _credentials = new ConcurrentDictionary(); - readonly ConcurrentDictionary _sessions = new ConcurrentDictionary(); - volatile ServiceState _state = ServiceState.Stopped; Timer _blockListUpdateTimer; @@ -135,20 +130,12 @@ namespace DnsServerCore public DnsWebService(string configFolder = null, Uri updateCheckUri = null, Uri appStoreUri = null) { - _dashboardApi = new WebServiceDashboardApi(this); - _zonesApi = new WebServiceZonesApi(this); - _otherZonesApi = new WebServiceOtherZonesApi(this); - _appsApi = new WebServiceAppsApi(this, appStoreUri); - _dhcpApi = new WebServiceDhcpApi(this); - _logsApi = new WebServiceLogsApi(this); - Assembly assembly = Assembly.GetExecutingAssembly(); - AssemblyName assemblyName = assembly.GetName(); - _currentVersion = assemblyName.Version; + _currentVersion = assembly.GetName().Version; _appFolder = Path.GetDirectoryName(assembly.Location); - if (configFolder == null) + if (configFolder is null) _configFolder = Path.Combine(_appFolder, "config"); else _configFolder = configFolder; @@ -159,6 +146,15 @@ namespace DnsServerCore _updateCheckUri = updateCheckUri; _log = new LogManager(_configFolder); + _authManager = new AuthManager(_configFolder, _log); + + _authApi = new WebServiceAuthApi(this); + _dashboardApi = new WebServiceDashboardApi(this); + _zonesApi = new WebServiceZonesApi(this); + _otherZonesApi = new WebServiceOtherZonesApi(this); + _appsApi = new WebServiceAppsApi(this, appStoreUri); + _dhcpApi = new WebServiceDhcpApi(this); + _logsApi = new WebServiceLogsApi(this); string blockListsFolder = Path.Combine(_configFolder, "blocklists"); @@ -179,16 +175,22 @@ namespace DnsServerCore Stop(); - if (_webService != null) + if (_appsApi is not null) + _appsApi.Dispose(); + + if (_webService is not null) _webService.Close(); - if (_dnsServer != null) + if (_dnsServer is not null) _dnsServer.Dispose(); - if (_dhcpServer != null) + if (_dhcpServer is not null) _dhcpServer.Dispose(); - if (_log != null) + if (_authManager is not null) + _authManager.Dispose(); + + if (_log is not null) _log.Dispose(); _disposed = true; @@ -374,16 +376,26 @@ namespace DnsServerCore switch (path) { + case "/api/user/login": case "/api/login": - await LoginAsync(request, jsonWriter); + await _authApi.LoginAsync(request, jsonWriter, UserSessionType.Standard); break; + case "/api/user/createToken": + await _authApi.LoginAsync(request, jsonWriter, UserSessionType.ApiToken); + break; + + case "/api/user/logout": case "/api/logout": - Logout(request); + _authApi.Logout(request); + break; + + case "/api/user/session/get": + _authApi.GetCurrentSessionDetails(request, jsonWriter); break; default: - if (!IsSessionValid(request)) + if (!TryGetSession(request, out UserSession session)) throw new InvalidTokenWebServiceException("Invalid token or session expired."); jsonWriter.WritePropertyName("response"); @@ -393,315 +405,756 @@ namespace DnsServerCore { switch (path) { - case "/api/changePassword": - ChangePassword(request); + case "/api/user/session/delete": + _authApi.DeleteSession(request, false); break; + case "/api/user/changePassword": + case "/api/changePassword": + _authApi.ChangePassword(request); + break; + + case "/api/user/profile/get": + _authApi.GetProfile(request, jsonWriter); + break; + + case "/api/user/profile/set": + _authApi.SetProfile(request, jsonWriter); + break; + + case "/api/user/checkForUpdate": case "/api/checkForUpdate": await CheckForUpdateAsync(request, jsonWriter); break; - case "/api/getDnsSettings": - GetDnsSettings(jsonWriter); - break; - - case "/api/setDnsSettings": - SetDnsSettings(request, jsonWriter); - break; - - case "/api/forceUpdateBlockLists": - ForceUpdateBlockLists(request); - break; - - case "/api/temporaryDisableBlocking": - TemporaryDisableBlocking(request, jsonWriter); - break; - - case "/api/backupSettings": - await BackupSettingsAsync(request, response); - return; - - case "/api/restoreSettings": - await RestoreSettingsAsync(request, jsonWriter); - break; - + case "/api/dashboard/stats/get": case "/api/getStats": + if (!_authManager.IsPermitted(PermissionSection.Dashboard, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + await _dashboardApi.GetStats(request, jsonWriter); break; + case "/api/dashboard/stats/getTop": case "/api/getTopStats": + if (!_authManager.IsPermitted(PermissionSection.Dashboard, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + await _dashboardApi.GetTopStats(request, jsonWriter); break; - case "/api/flushDnsCache": - _otherZonesApi.FlushCache(request); - break; - - case "/api/listCachedZones": - _otherZonesApi.ListCachedZones(request, jsonWriter); - break; - - case "/api/deleteCachedZone": - _otherZonesApi.DeleteCachedZone(request); - break; - - case "/api/listAllowedZones": - _otherZonesApi.ListAllowedZones(request, jsonWriter); - break; - - case "/api/importAllowedZones": - await _otherZonesApi.ImportAllowedZonesAsync(request); - break; - - case "/api/exportAllowedZones": - _otherZonesApi.ExportAllowedZones(response); - return; - - case "/api/deleteAllowedZone": - _otherZonesApi.DeleteAllowedZone(request); - break; - - case "/api/flushAllowedZone": - _otherZonesApi.FlushAllowedZone(request); - break; - - case "/api/allowZone": - _otherZonesApi.AllowZone(request); - break; - - case "/api/listBlockedZones": - _otherZonesApi.ListBlockedZones(request, jsonWriter); - break; - - case "/api/importBlockedZones": - await _otherZonesApi.ImportBlockedZonesAsync(request); - break; - - case "/api/exportBlockedZones": - _otherZonesApi.ExportBlockedZones(response); - return; - - case "/api/deleteBlockedZone": - _otherZonesApi.DeleteBlockedZone(request); - break; - - case "/api/flushBlockedZone": - _otherZonesApi.FlushBlockedZone(request); - break; - - case "/api/blockZone": - _otherZonesApi.BlockZone(request); + case "/api/dashboard/stats/deleteAll": + case "/api/deleteAllStats": + if (!_authManager.IsPermitted(PermissionSection.Dashboard, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _logsApi.DeleteAllStats(request); break; + case "/api/zones/list": case "/api/zone/list": case "/api/listZones": - _zonesApi.ListZones(jsonWriter); + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.ListZones(request, jsonWriter); break; + case "/api/zones/create": case "/api/zone/create": case "/api/createZone": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + await _zonesApi.CreateZoneAsync(request, jsonWriter); break; - case "/api/zone/dnssec/sign": - _zonesApi.SignPrimaryZone(request); - break; - - case "/api/zone/dnssec/unsign": - _zonesApi.UnsignPrimaryZone(request); - break; - - case "/api/zone/dnssec/getProperties": - _zonesApi.GetPrimaryZoneDnssecProperties(request, jsonWriter); - break; - - case "/api/zone/dnssec/convertToNSEC": - _zonesApi.ConvertPrimaryZoneToNSEC(request); - break; - - case "/api/zone/dnssec/convertToNSEC3": - _zonesApi.ConvertPrimaryZoneToNSEC3(request); - break; - - case "/api/zone/dnssec/updateNSEC3Params": - _zonesApi.UpdatePrimaryZoneNSEC3Parameters(request); - break; - - case "/api/zone/dnssec/updateDnsKeyTtl": - _zonesApi.UpdatePrimaryZoneDnssecDnsKeyTtl(request); - break; - - case "/api/zone/dnssec/generatePrivateKey": - _zonesApi.GenerateAndAddPrimaryZoneDnssecPrivateKey(request); - break; - - case "/api/zone/dnssec/updatePrivateKey": - _zonesApi.UpdatePrimaryZoneDnssecPrivateKey(request); - break; - - case "/api/zone/dnssec/deletePrivateKey": - _zonesApi.DeletePrimaryZoneDnssecPrivateKey(request); - break; - - case "/api/zone/dnssec/publishAllPrivateKeys": - _zonesApi.PublishAllGeneratedPrimaryZoneDnssecPrivateKeys(request); - break; - - case "/api/zone/dnssec/rolloverDnsKey": - _zonesApi.RolloverPrimaryZoneDnsKey(request); - break; - - case "/api/zone/dnssec/retireDnsKey": - _zonesApi.RetirePrimaryZoneDnsKey(request); - break; - - case "/api/zone/delete": - case "/api/deleteZone": - _zonesApi.DeleteZone(request); - break; - + case "/api/zones/enable": case "/api/zone/enable": case "/api/enableZone": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + _zonesApi.EnableZone(request); break; + case "/api/zones/disable": case "/api/zone/disable": case "/api/disableZone": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + _zonesApi.DisableZone(request); break; - case "/api/zone/options/get": - _zonesApi.GetZoneOptions(request, jsonWriter); - break; - - case "/api/zone/options/set": - _zonesApi.SetZoneOptions(request); + case "/api/zones/delete": + case "/api/zone/delete": + case "/api/deleteZone": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.DeleteZone(request); break; + case "/api/zones/resync": case "/api/zone/resync": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + _zonesApi.ResyncZone(request); break; + case "/api/zones/options/get": + case "/api/zone/options/get": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.GetZoneOptions(request, jsonWriter); + break; + + case "/api/zones/options/set": + case "/api/zone/options/set": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.SetZoneOptions(request); + break; + + case "/api/zones/permissions/get": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.GetPermissionDetails(request, jsonWriter, PermissionSection.Zones); + break; + + case "/api/zones/permissions/set": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.SetPermissionsDetails(request, jsonWriter, PermissionSection.Zones); + break; + + case "/api/zones/dnssec/sign": + case "/api/zone/dnssec/sign": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.SignPrimaryZone(request); + break; + + case "/api/zones/dnssec/unsign": + case "/api/zone/dnssec/unsign": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.UnsignPrimaryZone(request); + break; + + case "/api/zones/dnssec/properties/get": + case "/api/zone/dnssec/getProperties": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.GetPrimaryZoneDnssecProperties(request, jsonWriter); + break; + + case "/api/zones/dnssec/properties/convertToNSEC": + case "/api/zone/dnssec/convertToNSEC": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.ConvertPrimaryZoneToNSEC(request); + break; + + case "/api/zones/dnssec/properties/convertToNSEC3": + case "/api/zone/dnssec/convertToNSEC3": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.ConvertPrimaryZoneToNSEC3(request); + break; + + case "/api/zones/dnssec/properties/updateNSEC3Params": + case "/api/zone/dnssec/updateNSEC3Params": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.UpdatePrimaryZoneNSEC3Parameters(request); + break; + + case "/api/zones/dnssec/properties/updateDnsKeyTtl": + case "/api/zone/dnssec/updateDnsKeyTtl": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.UpdatePrimaryZoneDnssecDnsKeyTtl(request); + break; + + case "/api/zones/dnssec/properties/generatePrivateKey": + case "/api/zone/dnssec/generatePrivateKey": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.GenerateAndAddPrimaryZoneDnssecPrivateKey(request); + break; + + case "/api/zones/dnssec/properties/updatePrivateKey": + case "/api/zone/dnssec/updatePrivateKey": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.UpdatePrimaryZoneDnssecPrivateKey(request); + break; + + case "/api/zones/dnssec/properties/deletePrivateKey": + case "/api/zone/dnssec/deletePrivateKey": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.DeletePrimaryZoneDnssecPrivateKey(request); + break; + + case "/api/zones/dnssec/properties/publishAllPrivateKeys": + case "/api/zone/dnssec/publishAllPrivateKeys": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.PublishAllGeneratedPrimaryZoneDnssecPrivateKeys(request); + break; + + case "/api/zones/dnssec/properties/rolloverDnsKey": + case "/api/zone/dnssec/rolloverDnsKey": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.RolloverPrimaryZoneDnsKey(request); + break; + + case "/api/zones/dnssec/properties/retireDnsKey": + case "/api/zone/dnssec/retireDnsKey": + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _zonesApi.RetirePrimaryZoneDnsKey(request); + break; + + case "/api/zones/records/add": case "/api/zone/addRecord": case "/api/addRecord": _zonesApi.AddRecord(request, jsonWriter); break; + case "/api/zones/records/get": case "/api/zone/getRecords": case "/api/getRecords": _zonesApi.GetRecords(request, jsonWriter); break; - case "/api/zone/deleteRecord": - case "/api/deleteRecord": - _zonesApi.DeleteRecord(request); - break; - + case "/api/zones/records/update": case "/api/zone/updateRecord": case "/api/updateRecord": _zonesApi.UpdateRecord(request, jsonWriter); break; + case "/api/zones/records/delete": + case "/api/zone/deleteRecord": + case "/api/deleteRecord": + _zonesApi.DeleteRecord(request); + break; + + case "/api/cache/list": + case "/api/listCachedZones": + if (!_authManager.IsPermitted(PermissionSection.Cache, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.ListCachedZones(request, jsonWriter); + break; + + case "/api/cache/delete": + case "/api/deleteCachedZone": + if (!_authManager.IsPermitted(PermissionSection.Cache, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.DeleteCachedZone(request); + break; + + case "/api/cache/flush": + case "/api/flushDnsCache": + if (!_authManager.IsPermitted(PermissionSection.Cache, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.FlushCache(request); + break; + + case "/api/allowed/list": + case "/api/listAllowedZones": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.ListAllowedZones(request, jsonWriter); + break; + + case "/api/allowed/add": + case "/api/allowZone": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.AllowZone(request); + break; + + case "/api/allowed/delete": + case "/api/deleteAllowedZone": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.DeleteAllowedZone(request); + break; + + case "/api/allowed/flush": + case "/api/flushAllowedZone": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.FlushAllowedZone(request); + break; + + case "/api/allowed/import": + case "/api/importAllowedZones": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + await _otherZonesApi.ImportAllowedZonesAsync(request); + break; + + case "/api/allowed/export": + case "/api/exportAllowedZones": + if (!_authManager.IsPermitted(PermissionSection.Allowed, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.ExportAllowedZones(response); + return; + + case "/api/blocked/list": + case "/api/listBlockedZones": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.ListBlockedZones(request, jsonWriter); + break; + + case "/api/blocked/add": + case "/api/blockZone": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.BlockZone(request); + break; + + case "/api/blocked/delete": + case "/api/deleteBlockedZone": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.DeleteBlockedZone(request); + break; + + case "/api/blocked/flush": + case "/api/flushBlockedZone": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.FlushBlockedZone(request); + break; + + case "/api/blocked/import": + case "/api/importBlockedZones": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + await _otherZonesApi.ImportBlockedZonesAsync(request); + break; + + case "/api/blocked/export": + case "/api/exportBlockedZones": + if (!_authManager.IsPermitted(PermissionSection.Blocked, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _otherZonesApi.ExportBlockedZones(response); + return; + case "/api/apps/list": - await _appsApi.ListInstalledAppsAsync(jsonWriter); + if (_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View) || + _authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.View) || + _authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) + { + await _appsApi.ListInstalledAppsAsync(jsonWriter); + } + else + { + throw new DnsWebServiceException("Access was denied."); + } + break; case "/api/apps/listStoreApps": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.ListStoreApps(jsonWriter); break; case "/api/apps/downloadAndInstall": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.DownloadAndInstallAppAsync(request); break; case "/api/apps/downloadAndUpdate": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.DownloadAndUpdateAppAsync(request); break; case "/api/apps/install": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.InstallAppAsync(request); break; case "/api/apps/update": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.UpdateAppAsync(request); break; case "/api/apps/uninstall": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + _appsApi.UninstallApp(request); break; + case "/api/apps/config/get": case "/api/apps/getConfig": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.GetAppConfigAsync(request, jsonWriter); break; + case "/api/apps/config/set": case "/api/apps/setConfig": + if (!_authManager.IsPermitted(PermissionSection.Apps, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + await _appsApi.SetAppConfigAsync(request); break; + case "/api/dnsClient/resolve": case "/api/resolveQuery": + if (!_authManager.IsPermitted(PermissionSection.DnsClient, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + await ResolveQueryAsync(request, jsonWriter); break; - case "/api/listLogs": - _logsApi.ListLogs(jsonWriter); + case "/api/settings/get": + case "/api/getDnsSettings": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + GetDnsSettings(jsonWriter); break; - case "/api/deleteLog": - _logsApi.DeleteLog(request); + case "/api/settings/set": + case "/api/setDnsSettings": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + SetDnsSettings(request, jsonWriter); break; - case "/api/deleteAllLogs": - _logsApi.DeleteAllLogs(request); + case "/api/settings/forceUpdateBlockLists": + case "/api/forceUpdateBlockLists": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + ForceUpdateBlockLists(request); break; - case "/api/deleteAllStats": - _logsApi.DeleteAllStats(request); + case "/api/settings/temporaryDisableBlocking": + case "/api/temporaryDisableBlocking": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + TemporaryDisableBlocking(request, jsonWriter); break; - case "/api/queryLogs": - await _logsApi.QueryLogsAsync(request, jsonWriter); - break; - - case "/api/listDhcpScopes": - _dhcpApi.ListDhcpScopes(jsonWriter); + case "/api/settings/backup": + case "/api/backupSettings": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + await BackupSettingsAsync(request, response); + return; + + case "/api/settings/restore": + case "/api/restoreSettings": + if (!_authManager.IsPermitted(PermissionSection.Settings, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + await RestoreSettingsAsync(request, jsonWriter); break; + case "/api/dhcp/leases/list": case "/api/listDhcpLeases": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + _dhcpApi.ListDhcpLeases(jsonWriter); break; - case "/api/getDhcpScope": - _dhcpApi.GetDhcpScope(request, jsonWriter); - break; - - case "/api/setDhcpScope": - await _dhcpApi.SetDhcpScopeAsync(request); - break; - - case "/api/enableDhcpScope": - await _dhcpApi.EnableDhcpScopeAsync(request); - break; - - case "/api/disableDhcpScope": - _dhcpApi.DisableDhcpScope(request); - break; - - case "/api/deleteDhcpScope": - _dhcpApi.DeleteDhcpScope(request); - break; - + case "/api/dhcp/leases/remove": case "/api/removeDhcpLease": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + _dhcpApi.RemoveDhcpLease(request); break; + case "/api/dhcp/leases/convertToReserved": case "/api/convertToReservedLease": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + _dhcpApi.ConvertToReservedLease(request); break; + case "/api/dhcp/leases/convertToDynamic": case "/api/convertToDynamicLease": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + _dhcpApi.ConvertToDynamicLease(request); break; + case "/api/dhcp/scopes/list": + case "/api/listDhcpScopes": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.ListDhcpScopes(jsonWriter); + break; + + case "/api/dhcp/scopes/get": + case "/api/getDhcpScope": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.GetDhcpScope(request, jsonWriter); + break; + + case "/api/dhcp/scopes/set": + case "/api/setDhcpScope": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + await _dhcpApi.SetDhcpScopeAsync(request); + break; + + case "/api/dhcp/scopes/addReservedLease": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.AddReservedLease(request); + break; + + case "/api/dhcp/scopes/removeReservedLease": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.RemoveReservedLease(request); + break; + + case "/api/dhcp/scopes/enable": + case "/api/enableDhcpScope": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + await _dhcpApi.EnableDhcpScopeAsync(request); + break; + + case "/api/dhcp/scopes/disable": + case "/api/disableDhcpScope": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.DisableDhcpScope(request); + break; + + case "/api/dhcp/scopes/delete": + case "/api/deleteDhcpScope": + if (!_authManager.IsPermitted(PermissionSection.DhcpServer, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _dhcpApi.DeleteDhcpScope(request); + break; + + case "/api/admin/sessions/list": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.ListSessions(request, jsonWriter); + break; + + case "/api/admin/sessions/createToken": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.CreateApiToken(request, jsonWriter); + break; + + case "/api/admin/sessions/delete": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.DeleteSession(request, true); + break; + + case "/api/admin/users/list": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.ListUsers(jsonWriter); + break; + + case "/api/admin/users/create": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.CreateUser(request, jsonWriter); + break; + + case "/api/admin/users/get": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.GetUserDetails(request, jsonWriter); + break; + + case "/api/admin/users/set": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.SetUserDetails(request, jsonWriter); + break; + + case "/api/admin/users/delete": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.DeleteUser(request); + break; + + case "/api/admin/groups/list": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.ListGroups(jsonWriter); + break; + + case "/api/admin/groups/create": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.CreateGroup(request, jsonWriter); + break; + + case "/api/admin/groups/get": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.GetGroupDetails(request, jsonWriter); + break; + + case "/api/admin/groups/set": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.SetGroupDetails(request, jsonWriter); + break; + + case "/api/admin/groups/delete": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.DeleteGroup(request); + break; + + case "/api/admin/permissions/list": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.ListPermissions(jsonWriter); + break; + + case "/api/admin/permissions/get": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.GetPermissionDetails(request, jsonWriter, PermissionSection.Unknown); + break; + + case "/api/admin/permissions/set": + if (!_authManager.IsPermitted(PermissionSection.Administration, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _authApi.SetPermissionsDetails(request, jsonWriter, PermissionSection.Unknown); + break; + + case "/api/logs/list": + case "/api/listLogs": + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + _logsApi.ListLogs(jsonWriter); + break; + + case "/api/logs/download": + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + await _logsApi.DownloadLogAsync(request, response); + return; + + case "/api/logs/delete": + case "/api/deleteLog": + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _logsApi.DeleteLog(request); + break; + + case "/api/logs/deleteAll": + case "/api/deleteAllLogs": + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + + _logsApi.DeleteAllLogs(request); + break; + + case "/api/logs/query": + case "/api/queryLogs": + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + + await _logsApi.QueryLogsAsync(request, jsonWriter); + break; + default: await SendErrorAsync(response, 404); return; @@ -737,7 +1190,16 @@ namespace DnsServerCore } catch (Exception ex) { - _log.Write(GetRequestRemoteEndPoint(request), ex); + UserSession session = null; + + string strToken = request.QueryString["token"]; + if (!string.IsNullOrEmpty(strToken)) + session = _authManager.GetSession(strToken); + + if (session is null) + _log.Write(GetRequestRemoteEndPoint(request), ex); + else + _log.Write(GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + ex.ToString()); mS.SetLength(0); JsonTextWriter jsonWriter = new JsonTextWriter(new StreamWriter(mS)); @@ -775,12 +1237,15 @@ namespace DnsServerCore } else if (path.StartsWith("/log/")) { - if (!IsSessionValid(request)) + if (!TryGetSession(request, out UserSession session)) { await SendErrorAsync(response, 403, "Invalid token or session expired."); return; } + if (!_authManager.IsPermitted(PermissionSection.Logs, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + string[] pathParts = path.Split('/'); string logFileName = pathParts[2]; @@ -820,7 +1285,16 @@ namespace DnsServerCore if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped)) return; //web service stopping - _log.Write(GetRequestRemoteEndPoint(request), ex); + UserSession session = null; + + string strToken = request.QueryString["token"]; + if (!string.IsNullOrEmpty(strToken)) + session = _authManager.GetSession(strToken); + + if (session is null) + _log.Write(GetRequestRemoteEndPoint(request), ex); + else + _log.Write(GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + ex.ToString()); await SendError(response, ex); } @@ -923,198 +1397,34 @@ namespace DnsServerCore } } - #endregion - - #region user session - - private string CreateSession(string username) - { - string token = BinaryNumber.GenerateRandomNumber256().ToString(); - - if (!_sessions.TryAdd(token, new UserSession(username))) - throw new DnsWebServiceException("Error while creating session. Please try again."); - - return token; - } - - private UserSession GetSession(string token) - { - if (_sessions.TryGetValue(token, out UserSession session)) - return session; - - return null; - } - internal UserSession GetSession(HttpListenerRequest request) { string strToken = request.QueryString["token"]; if (string.IsNullOrEmpty(strToken)) throw new DnsWebServiceException("Parameter 'token' missing."); - return GetSession(strToken); + return _authManager.GetSession(strToken); } - private UserSession DeleteSession(string token) + internal bool TryGetSession(HttpListenerRequest request, out UserSession session) { - if (_sessions.TryRemove(token, out UserSession session)) - return session; - - return null; - } - - private UserSession DeleteSession(HttpListenerRequest request) - { - string strToken = request.QueryString["token"]; - if (string.IsNullOrEmpty(strToken)) - throw new DnsWebServiceException("Parameter 'token' missing."); - - return DeleteSession(strToken); - } - - private void FailedLoginAttempt(IPAddress address) - { - _failedLoginAttempts.AddOrUpdate(address, 1, delegate (IPAddress key, int attempts) - { - return attempts + 1; - }); - } - - private bool LoginAttemptsExceedLimit(IPAddress address, int limit) - { - if (!_failedLoginAttempts.TryGetValue(address, out int attempts)) - return false; - - return attempts >= limit; - } - - private void ResetFailedLoginAttempt(IPAddress address) - { - _failedLoginAttempts.TryRemove(address, out _); - } - - private void BlockAddress(IPAddress address, int interval) - { - _blockedAddresses.TryAdd(address, DateTime.UtcNow.AddMilliseconds(interval)); - } - - private bool IsAddressBlocked(IPAddress address) - { - if (!_blockedAddresses.TryGetValue(address, out DateTime expiry)) - return false; - - if (expiry > DateTime.UtcNow) - { - return true; - } - else - { - UnblockAddress(address); - ResetFailedLoginAttempt(address); - - return false; - } - } - - private void UnblockAddress(IPAddress address) - { - _blockedAddresses.TryRemove(address, out _); - } - - #endregion - - #region auth api - - private async Task LoginAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) - { - string strUsername = request.QueryString["user"]; - if (string.IsNullOrEmpty(strUsername)) - throw new DnsWebServiceException("Parameter 'user' missing."); - - string strPassword = request.QueryString["pass"]; - if (string.IsNullOrEmpty(strPassword)) - throw new DnsWebServiceException("Parameter 'pass' missing."); - - IPEndPoint remoteEP = GetRequestRemoteEndPoint(request); - - if (IsAddressBlocked(remoteEP.Address)) - throw new DnsWebServiceException("Max limit of " + MAX_LOGIN_ATTEMPTS + " attempts exceeded. Access blocked for " + (BLOCK_ADDRESS_INTERVAL / 1000) + " seconds."); - - strUsername = strUsername.Trim().ToLower(); - string strPasswordHash = GetPasswordHash(strUsername, strPassword); - - if (!_credentials.TryGetValue(strUsername, out string passwordHash) || (passwordHash != strPasswordHash)) - { - if (strPassword != "admin") //exception for default password - { - FailedLoginAttempt(remoteEP.Address); - - if (LoginAttemptsExceedLimit(remoteEP.Address, MAX_LOGIN_ATTEMPTS)) - BlockAddress(remoteEP.Address, BLOCK_ADDRESS_INTERVAL); - - await Task.Delay(1000); - } - - throw new DnsWebServiceException("Invalid username or password for user: " + strUsername); - } - - ResetFailedLoginAttempt(remoteEP.Address); - - _log.Write(remoteEP, "[" + strUsername + "] User logged in."); - - string token = CreateSession(strUsername); - - jsonWriter.WritePropertyName("token"); - jsonWriter.WriteValue(token); - } - - private bool IsSessionValid(HttpListenerRequest request) - { - UserSession session = GetSession(request); - if (session == null) + session = GetSession(request); + if ((session is null) || session.User.Disabled) return false; if (session.HasExpired()) { - DeleteSession(request); + _authManager.DeleteSession(session.Token); + _authManager.SaveConfigFile(); return false; } - session.UpdateLastSeen(); + IPEndPoint remoteEP = GetRequestRemoteEndPoint(request); + + session.UpdateLastSeen(remoteEP.Address, request.UserAgent); return true; } - private void ChangePassword(HttpListenerRequest request) - { - string strToken = request.QueryString["token"]; - if (string.IsNullOrEmpty(strToken)) - throw new DnsWebServiceException("Parameter 'token' missing."); - - string strPassword = request.QueryString["pass"]; - if (string.IsNullOrEmpty(strPassword)) - throw new DnsWebServiceException("Parameter 'pass' missing."); - - UserSession session = GetSession(strToken); - if (session == null) - throw new DnsWebServiceException("User session does not exists."); - - SetCredentials(session.Username, strPassword); - SaveConfigFile(); - - _log.Write(GetRequestRemoteEndPoint(request), "[" + session.Username + "] Password was changed for user."); - } - - private void Logout(HttpListenerRequest request) - { - string strToken = request.QueryString["token"]; - if (string.IsNullOrEmpty(strToken)) - throw new DnsWebServiceException("Parameter 'token' missing."); - - UserSession session = DeleteSession(strToken); - - if (session != null) - _log.Write(GetRequestRemoteEndPoint(request), "[" + session.Username + "] User logged out."); - } - #endregion #region update api @@ -1132,6 +1442,7 @@ namespace DnsServerCore { SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsServer.Proxy; + handler.UseProxy = _dnsServer.Proxy is not null; using (HttpClient http = new HttpClient(handler)) { @@ -1218,6 +1529,11 @@ namespace DnsServerCore return strVersion; } + internal string GetServerVersion() + { + return GetCleanVersion(_currentVersion); + } + #endregion #region settings api @@ -1225,7 +1541,7 @@ namespace DnsServerCore private void GetDnsSettings(JsonTextWriter jsonWriter) { jsonWriter.WritePropertyName("version"); - jsonWriter.WriteValue(GetCleanVersion(_currentVersion)); + jsonWriter.WriteValue(GetServerVersion()); jsonWriter.WritePropertyName("dnsServerDomain"); jsonWriter.WriteValue(_dnsServer.ServerDomain); @@ -1316,6 +1632,9 @@ namespace DnsServerCore jsonWriter.WritePropertyName("defaultRecordTtl"); jsonWriter.WriteValue(_zonesApi.DefaultRecordTtl); + jsonWriter.WritePropertyName("dnsAppsEnableAutomaticUpdate"); + jsonWriter.WriteValue(_appsApi.EnableAutomaticUpdate); + jsonWriter.WritePropertyName("preferIPv6"); jsonWriter.WriteValue(_dnsServer.PreferIPv6); @@ -1825,6 +2144,10 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(strDefaultRecordTtl)) _zonesApi.DefaultRecordTtl = uint.Parse(strDefaultRecordTtl); + string strDnsAppsEnableAutomaticUpdate = request.QueryString["dnsAppsEnableAutomaticUpdate"]; + if (!string.IsNullOrEmpty(strDnsAppsEnableAutomaticUpdate)) + _appsApi.EnableAutomaticUpdate = bool.Parse(strDnsAppsEnableAutomaticUpdate); + string strPreferIPv6 = request.QueryString["preferIPv6"]; if (!string.IsNullOrEmpty(strPreferIPv6)) _dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6); @@ -2141,21 +2464,18 @@ namespace DnsServerCore } } + bool blockListUrlsUpdated = false; string strBlockListUrls = request.QueryString["blockListUrls"]; if (!string.IsNullOrEmpty(strBlockListUrls)) { if (strBlockListUrls == "false") { - StopBlockListUpdateTimer(); - _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); _dnsServer.BlockListZoneManager.Flush(); } else { - bool updated = false; - string[] strBlockListUrlList = strBlockListUrls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (oldWebServiceHttpPort != _webServiceHttpPort) @@ -2165,17 +2485,17 @@ namespace DnsServerCore if (strBlockListUrlList[i].Contains("http://localhost:" + oldWebServiceHttpPort + "/blocklist.txt")) { strBlockListUrlList[i] = "http://localhost:" + _webServiceHttpPort + "/blocklist.txt"; - updated = true; + blockListUrlsUpdated = true; break; } } } - if (!updated) + if (!blockListUrlsUpdated) { if (strBlockListUrlList.Length != (_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)) { - updated = true; + blockListUrlsUpdated = true; } else { @@ -2187,7 +2507,7 @@ namespace DnsServerCore if (!_dnsServer.BlockListZoneManager.AllowListUrls.Contains(new Uri(strAllowListUrl))) { - updated = true; + blockListUrlsUpdated = true; break; } } @@ -2195,7 +2515,7 @@ namespace DnsServerCore { if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(new Uri(strBlockListUrl))) { - updated = true; + blockListUrlsUpdated = true; break; } } @@ -2203,7 +2523,7 @@ namespace DnsServerCore } } - if (updated) + if (blockListUrlsUpdated) { _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); @@ -2225,8 +2545,6 @@ namespace DnsServerCore _dnsServer.BlockListZoneManager.BlockListUrls.Add(blockListUrl); } } - - ForceUpdateBlockLists(); } } } @@ -2236,12 +2554,24 @@ namespace DnsServerCore { int blockListUpdateIntervalHours = int.Parse(strBlockListUpdateIntervalHours); - if ((blockListUpdateIntervalHours < 1) || (blockListUpdateIntervalHours > 168)) - throw new DnsWebServiceException("Parameter `blockListUpdateIntervalHours` must be between 1 hour and 168 hours (7 days)."); + if ((blockListUpdateIntervalHours < 0) || (blockListUpdateIntervalHours > 168)) + throw new DnsWebServiceException("Parameter `blockListUpdateIntervalHours` must be between 1 hour and 168 hours (7 days) or 0 to disable automatic update."); _blockListUpdateIntervalHours = blockListUpdateIntervalHours; } + if ((_blockListUpdateIntervalHours > 0) && ((_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count) > 0)) + { + if (blockListUrlsUpdated || (_blockListUpdateTimer is null)) + ForceUpdateBlockLists(); + + StartBlockListUpdateTimer(); + } + else + { + StopBlockListUpdateTimer(); + } + if ((_webServiceTlsCertificatePath == null) && (_dnsTlsCertificatePath == null)) StopTlsCertificateUpdateTimer(); @@ -2257,7 +2587,7 @@ namespace DnsServerCore SaveConfigFile(); _log.Save(); - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Settings were updated {dnsServerDomain: " + _dnsServer.ServerDomain + "; dnsServerLocalEndPoints: " + strDnsServerLocalEndPoints + "; webServiceLocalAddresses: " + strWebServiceLocalAddresses + "; webServiceHttpPort: " + _webServiceHttpPort + "; webServiceEnableTls: " + strWebServiceEnableTls + "; webServiceHttpToTlsRedirect: " + strWebServiceHttpToTlsRedirect + "; webServiceTlsPort: " + strWebServiceTlsPort + "; webServiceUseSelfSignedTlsCertificate: " + _webServiceUseSelfSignedTlsCertificate + "; webServiceTlsCertificatePath: " + strWebServiceTlsCertificatePath + "; enableDnsOverHttp: " + _dnsServer.EnableDnsOverHttp + "; enableDnsOverTls: " + _dnsServer.EnableDnsOverTls + "; enableDnsOverHttps: " + _dnsServer.EnableDnsOverHttps + "; dnsTlsCertificatePath: " + _dnsTlsCertificatePath + "; defaultRecordTtl: " + _zonesApi.DefaultRecordTtl + "; preferIPv6: " + _dnsServer.PreferIPv6 + "; enableLogging: " + strEnableLogging + "; logQueries: " + (_dnsServer.QueryLogManager != null) + "; useLocalTime: " + strUseLocalTime + "; logFolder: " + strLogFolder + "; maxLogFileDays: " + strMaxLogFileDays + "; recursion: " + _dnsServer.Recursion.ToString() + "; randomizeName: " + strRandomizeName + "; qnameMinimization: " + strQnameMinimization + "; serveStale: " + strServeStale + "; serveStaleTtl: " + strServeStaleTtl + "; cachePrefetchEligibility: " + strCachePrefetchEligibility + "; cachePrefetchTrigger: " + strCachePrefetchTrigger + "; cachePrefetchSampleIntervalInMinutes: " + strCachePrefetchSampleIntervalInMinutes + "; cachePrefetchSampleEligibilityHitsPerHour: " + strCachePrefetchSampleEligibilityHitsPerHour + "; proxyType: " + strProxyType + "; forwarders: " + strForwarders + "; forwarderProtocol: " + strForwarderProtocol + "; enableBlocking: " + _dnsServer.EnableBlocking + "; allowTxtBlockingReport: " + _dnsServer.AllowTxtBlockingReport + "; blockingType: " + _dnsServer.BlockingType.ToString() + "; blockListUrl: " + strBlockListUrls + "; blockListUpdateIntervalHours: " + strBlockListUpdateIntervalHours + ";}"); + _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).User.Username + "] DNS Settings were updated {dnsServerDomain: " + _dnsServer.ServerDomain + "; dnsServerLocalEndPoints: " + strDnsServerLocalEndPoints + "; webServiceLocalAddresses: " + strWebServiceLocalAddresses + "; webServiceHttpPort: " + _webServiceHttpPort + "; webServiceEnableTls: " + strWebServiceEnableTls + "; webServiceHttpToTlsRedirect: " + strWebServiceHttpToTlsRedirect + "; webServiceTlsPort: " + strWebServiceTlsPort + "; webServiceUseSelfSignedTlsCertificate: " + _webServiceUseSelfSignedTlsCertificate + "; webServiceTlsCertificatePath: " + strWebServiceTlsCertificatePath + "; enableDnsOverHttp: " + _dnsServer.EnableDnsOverHttp + "; enableDnsOverTls: " + _dnsServer.EnableDnsOverTls + "; enableDnsOverHttps: " + _dnsServer.EnableDnsOverHttps + "; dnsTlsCertificatePath: " + _dnsTlsCertificatePath + "; defaultRecordTtl: " + _zonesApi.DefaultRecordTtl + "; preferIPv6: " + _dnsServer.PreferIPv6 + "; enableLogging: " + strEnableLogging + "; logQueries: " + (_dnsServer.QueryLogManager != null) + "; useLocalTime: " + strUseLocalTime + "; logFolder: " + strLogFolder + "; maxLogFileDays: " + strMaxLogFileDays + "; recursion: " + _dnsServer.Recursion.ToString() + "; randomizeName: " + strRandomizeName + "; qnameMinimization: " + strQnameMinimization + "; serveStale: " + strServeStale + "; serveStaleTtl: " + strServeStaleTtl + "; cachePrefetchEligibility: " + strCachePrefetchEligibility + "; cachePrefetchTrigger: " + strCachePrefetchTrigger + "; cachePrefetchSampleIntervalInMinutes: " + strCachePrefetchSampleIntervalInMinutes + "; cachePrefetchSampleEligibilityHitsPerHour: " + strCachePrefetchSampleEligibilityHitsPerHour + "; proxyType: " + strProxyType + "; forwarders: " + strForwarders + "; forwarderProtocol: " + strForwarderProtocol + "; enableBlocking: " + _dnsServer.EnableBlocking + "; allowTxtBlockingReport: " + _dnsServer.AllowTxtBlockingReport + "; blockingType: " + _dnsServer.BlockingType.ToString() + "; blockListUrl: " + strBlockListUrls + "; blockListUpdateIntervalHours: " + strBlockListUpdateIntervalHours + ";}"); GetDnsSettings(jsonWriter); @@ -2358,6 +2688,7 @@ namespace DnsServerCore bool allowedZones = false; bool blockedZones = false; bool dnsSettings = false; + bool authConfig = false; bool logSettings = false; string strBlockLists = request.QueryString["blockLists"]; @@ -2396,6 +2727,10 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(strDnsSettings)) dnsSettings = bool.Parse(strDnsSettings); + string strAuthConfig = request.QueryString["authConfig"]; + if (!string.IsNullOrEmpty(strAuthConfig)) + authConfig = bool.Parse(strAuthConfig); + string strLogSettings = request.QueryString["logSettings"]; if (!string.IsNullOrEmpty(strLogSettings)) logSettings = bool.Parse(strLogSettings); @@ -2427,15 +2762,7 @@ namespace DnsServerCore if (logFile.Equals(_log.CurrentLogFile, StringComparison.OrdinalIgnoreCase)) { - using (FileStream fS = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - ZipArchiveEntry entry = backupZip.CreateEntry(entryName); - - using (Stream s = entry.Open()) - { - await fS.CopyToAsync(s); - } - } + await CreateBackupEntryFromFileAsync(backupZip, logFile, entryName); } else { @@ -2466,7 +2793,7 @@ namespace DnsServerCore entryName = entryName.TrimStart('/'); - backupZip.CreateEntryFromFile(appFile, entryName); + await CreateBackupEntryFromFileAsync(backupZip, appFile, entryName); } } @@ -2521,6 +2848,14 @@ namespace DnsServerCore backupZip.CreateEntryFromFile(dnsSettingsFile, "dns.config"); } + if (authConfig) + { + string authSettingsFile = Path.Combine(_configFolder, "auth.config"); + + if (File.Exists(authSettingsFile)) + backupZip.CreateEntryFromFile(authSettingsFile, "auth.config"); + } + if (logSettings) { string logSettingsFile = Path.Combine(_configFolder, "log.config"); @@ -2555,7 +2890,29 @@ namespace DnsServerCore } } - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Settings backup zip file was exported."); + _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).User.Username + "] Settings backup zip file was exported."); + } + + private static async Task CreateBackupEntryFromFileAsync(ZipArchive backupZip, string sourceFileName, string entryName) + { + using (FileStream fS = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + ZipArchiveEntry entry = backupZip.CreateEntry(entryName); + + DateTime lastWrite = File.GetLastWriteTime(sourceFileName); + + // If file to be archived has an invalid last modified time, use the first datetime representable in the Zip timestamp format + // (midnight on January 1, 1980): + if (lastWrite.Year < 1980 || lastWrite.Year > 2107) + lastWrite = new DateTime(1980, 1, 1, 0, 0, 0); + + entry.LastWriteTime = lastWrite; + + using (Stream sE = entry.Open()) + { + await fS.CopyToAsync(sE); + } + } } private async Task RestoreSettingsAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) @@ -2569,6 +2926,7 @@ namespace DnsServerCore bool allowedZones = false; bool blockedZones = false; bool dnsSettings = false; + bool authConfig = false; bool logSettings = false; bool deleteExistingFiles = false; @@ -2609,6 +2967,10 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(strDnsSettings)) dnsSettings = bool.Parse(strDnsSettings); + string strAuthConfig = request.QueryString["authConfig"]; + if (!string.IsNullOrEmpty(strAuthConfig)) + authConfig = bool.Parse(strAuthConfig); + string strLogSettings = request.QueryString["logSettings"]; if (!string.IsNullOrEmpty(strLogSettings)) logSettings = bool.Parse(strLogSettings); @@ -2651,6 +3013,8 @@ namespace DnsServerCore fS.Position = 0; using (ZipArchive backupZip = new ZipArchive(fS, ZipArchiveMode.Read, false, Encoding.UTF8)) { + UserSession session = GetSession(request); + if (logSettings || logs) { //stop logging @@ -2699,6 +3063,27 @@ namespace DnsServerCore } } + if (authConfig) + { + ZipArchiveEntry entry = backupZip.GetEntry("auth.config"); + if (entry != null) + entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); + + //reload auth config + _authManager.LoadConfigFile(session); + } + + if (dnsSettings) + { + ZipArchiveEntry entry = backupZip.GetEntry("dns.config"); + if (entry != null) + entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); + + //reload settings and block list zone + LoadConfigFile(); + _dnsServer.BlockListZoneManager.LoadBlockLists(); + } + if (blockLists) { if (deleteExistingFiles) @@ -2719,37 +3104,6 @@ namespace DnsServerCore } } - if (scopes) - { - //stop dhcp server - _dhcpServer.Stop(); - - try - { - if (deleteExistingFiles) - { - //delete existing scope files - string[] scopeFiles = Directory.GetFiles(Path.Combine(_configFolder, "scopes"), "*.scope", SearchOption.TopDirectoryOnly); - foreach (string scopeFile in scopeFiles) - { - File.Delete(scopeFile); - } - } - - //extract scope files from backup - foreach (ZipArchiveEntry entry in backupZip.Entries) - { - if (entry.FullName.StartsWith("scopes/")) - entry.ExtractToFile(Path.Combine(_configFolder, "scopes", entry.Name), true); - } - } - finally - { - //start dhcp server - _dhcpServer.Start(); - } - } - if (apps) { //unload apps @@ -2788,35 +3142,6 @@ namespace DnsServerCore _dnsServer.DnsApplicationManager.LoadAllApplications(); } - if (stats) - { - if (deleteExistingFiles) - { - //delete existing stats files - string[] hourlyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.stat", SearchOption.TopDirectoryOnly); - foreach (string hourlyStatsFile in hourlyStatsFiles) - { - File.Delete(hourlyStatsFile); - } - - string[] dailyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.dstat", SearchOption.TopDirectoryOnly); - foreach (string dailyStatsFile in dailyStatsFiles) - { - File.Delete(dailyStatsFile); - } - } - - //extract stats files from backup - foreach (ZipArchiveEntry entry in backupZip.Entries) - { - if (entry.FullName.StartsWith("stats/")) - entry.ExtractToFile(Path.Combine(_configFolder, "stats", entry.Name), true); - } - - //reload stats - _dnsServer.StatsManager.ReloadStats(); - } - if (zones) { if (deleteExistingFiles) @@ -2838,6 +3163,7 @@ namespace DnsServerCore //reload zones _dnsServer.AuthZoneManager.LoadAllZoneFiles(); + InspectAndFixZonePermissions(); } if (allowedZones) @@ -2876,18 +3202,67 @@ namespace DnsServerCore _dnsServer.BlockedZoneManager.LoadBlockedZoneFile(); } - if (dnsSettings) + if (scopes) { - ZipArchiveEntry entry = backupZip.GetEntry("dns.config"); - if (entry != null) - entry.ExtractToFile(Path.Combine(_configFolder, entry.Name), true); + //stop dhcp server + _dhcpServer.Stop(); - //reload settings and block list zone - LoadConfigFile(); - _dnsServer.BlockListZoneManager.LoadBlockLists(); + try + { + if (deleteExistingFiles) + { + //delete existing scope files + string[] scopeFiles = Directory.GetFiles(Path.Combine(_configFolder, "scopes"), "*.scope", SearchOption.TopDirectoryOnly); + foreach (string scopeFile in scopeFiles) + { + File.Delete(scopeFile); + } + } + + //extract scope files from backup + foreach (ZipArchiveEntry entry in backupZip.Entries) + { + if (entry.FullName.StartsWith("scopes/")) + entry.ExtractToFile(Path.Combine(_configFolder, "scopes", entry.Name), true); + } + } + finally + { + //start dhcp server + _dhcpServer.Start(); + } } - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Settings backup zip file was restored."); + if (stats) + { + if (deleteExistingFiles) + { + //delete existing stats files + string[] hourlyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.stat", SearchOption.TopDirectoryOnly); + foreach (string hourlyStatsFile in hourlyStatsFiles) + { + File.Delete(hourlyStatsFile); + } + + string[] dailyStatsFiles = Directory.GetFiles(Path.Combine(_configFolder, "stats"), "*.dstat", SearchOption.TopDirectoryOnly); + foreach (string dailyStatsFile in dailyStatsFiles) + { + File.Delete(dailyStatsFile); + } + } + + //extract stats files from backup + foreach (ZipArchiveEntry entry in backupZip.Entries) + { + if (entry.FullName.StartsWith("stats/")) + entry.ExtractToFile(Path.Combine(_configFolder, "stats", entry.Name), true); + } + + //reload stats + _dnsServer.StatsManager.ReloadStats(); + } + + _log.Write(GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Settings backup zip file was restored."); } } } @@ -2911,8 +3286,8 @@ namespace DnsServerCore private void ForceUpdateBlockLists(HttpListenerRequest request) { - if (ForceUpdateBlockLists()) - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Block list update was triggered."); + ForceUpdateBlockLists(); + _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).User.Username + "] Block list update was triggered."); } private void TemporaryDisableBlocking(HttpListenerRequest request, JsonTextWriter jsonWriter) @@ -2932,7 +3307,7 @@ namespace DnsServerCore try { _dnsServer.EnableBlocking = true; - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocking was enabled after " + minutes + " minute(s) being temporarily disabled."); + _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).User.Username + "] Blocking was enabled after " + minutes + " minute(s) being temporarily disabled."); } catch (Exception ex) { @@ -2947,7 +3322,7 @@ namespace DnsServerCore _dnsServer.EnableBlocking = false; _temporaryDisableBlockingTill = DateTime.UtcNow.AddMinutes(minutes); - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] Blocking was temporarily disabled for " + minutes + " minute(s)."); + _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).User.Username + "] Blocking was temporarily disabled for " + minutes + " minute(s)."); } else { @@ -3123,15 +3498,29 @@ namespace DnsServerCore if (importResponse) { + UserSession session = GetSession(request); + AuthZoneInfo zoneInfo = _dnsServer.AuthZoneManager.FindAuthZoneInfo(domain); if ((zoneInfo is null) || ((zoneInfo.Type == AuthZoneType.Secondary) && !zoneInfo.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))) { + if (!_authManager.IsPermitted(PermissionSection.Zones, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + zoneInfo = _dnsServer.AuthZoneManager.CreatePrimaryZone(domain, _dnsServer.ServerDomain, false); if (zoneInfo is null) throw new DnsServerException("Cannot import records: failed to create primary zone."); + + //set permissions + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); } else { + if (!_authManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + switch (zoneInfo.Type) { case AuthZoneType.Primary: @@ -3183,7 +3572,7 @@ namespace DnsServerCore _dnsServer.AuthZoneManager.ImportRecords(zoneInfo.Name, importRecords); } - _log.Write(GetRequestRemoteEndPoint(request), "[" + GetSession(request).Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; zone: " + zoneInfo.Name + "; type: " + type + ";}"); + _log.Write(GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] DNS Client imported record(s) for authoritative zone {server: " + server + "; zone: " + zoneInfo.Name + "; type: " + type + ";}"); _dnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } @@ -3200,53 +3589,25 @@ namespace DnsServerCore #endregion - #region auth - - private void SetCredentials(string username, string password) - { - username = username.ToLower(); - string passwordHash = GetPasswordHash(username, password); - - _credentials[username] = passwordHash; - } - - private void LoadCredentials(string username, string passwordHash) - { - username = username.ToLower(); - - _credentials[username] = passwordHash; - } - - private static string GetPasswordHash(string username, string password) - { - using (HMAC hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password))) - { - return Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(username))).ToLower(); - } - } - - #endregion - #region block list - private bool ForceUpdateBlockLists() + private void ForceUpdateBlockLists() { - if ((_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count) > 0) + Task.Run(async delegate () { - _blockListLastUpdatedOn = new DateTime(); - - StopBlockListUpdateTimer(); - StartBlockListUpdateTimer(); - - return true; - } - - return false; + if (await _dnsServer.BlockListZoneManager.UpdateBlockListsAsync()) + { + //block lists were updated + //save last updated on time + _blockListLastUpdatedOn = DateTime.UtcNow; + SaveConfigFile(); + } + }); } private void StartBlockListUpdateTimer() { - if (_blockListUpdateTimer == null) + if (_blockListUpdateTimer is null) { _blockListUpdateTimer = new Timer(async delegate (object state) { @@ -3274,7 +3635,7 @@ namespace DnsServerCore private void StopBlockListUpdateTimer() { - if (_blockListUpdateTimer != null) + if (_blockListUpdateTimer is not null) { _blockListUpdateTimer.Dispose(); _blockListUpdateTimer = null; @@ -3380,613 +3741,16 @@ namespace DnsServerCore try { - bool passwordResetOption = false; - - if (!File.Exists(configFile)) - { - string passwordResetConfigFile = Path.Combine(_configFolder, "reset.config"); - - if (File.Exists(passwordResetConfigFile)) - { - passwordResetOption = true; - configFile = passwordResetConfigFile; - } - } - - byte version; + int version; using (FileStream fS = new FileStream(configFile, FileMode.Open, FileAccess.Read)) { - BinaryReader bR = new BinaryReader(fS); - - if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DS") //format - throw new InvalidDataException("DnsServer config file format is invalid."); - - version = bR.ReadByte(); - switch (version) - { - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - case 9: - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - _dnsServer.ServerDomain = bR.ReadShortString(); - _webServiceHttpPort = bR.ReadInt32(); - - if (version >= 13) - { - { - int count = bR.ReadByte(); - if (count > 0) - { - IPAddress[] localAddresses = new IPAddress[count]; - - for (int i = 0; i < count; i++) - localAddresses[i] = IPAddressExtension.Parse(bR); - - _webServiceLocalAddresses = localAddresses; - } - } - - _webServiceTlsPort = bR.ReadInt32(); - _webServiceEnableTls = bR.ReadBoolean(); - _webServiceHttpToTlsRedirect = bR.ReadBoolean(); - _webServiceTlsCertificatePath = bR.ReadShortString(); - _webServiceTlsCertificatePassword = bR.ReadShortString(); - - if (_webServiceTlsCertificatePath.Length == 0) - _webServiceTlsCertificatePath = null; - - if (_webServiceTlsCertificatePath != null) - { - try - { - LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); - } - catch (Exception ex) - { - _log.Write("DNS Server encountered an error while loading Web Service TLS certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); - } - - StartTlsCertificateUpdateTimer(); - } - } - else - { - _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; - - _webServiceTlsPort = 53443; - _webServiceEnableTls = false; - _webServiceHttpToTlsRedirect = false; - _webServiceTlsCertificatePath = string.Empty; - _webServiceTlsCertificatePassword = string.Empty; - } - - _dnsServer.PreferIPv6 = bR.ReadBoolean(); - - if (bR.ReadBoolean()) //logQueries - _dnsServer.QueryLogManager = _log; - - if (version >= 14) - _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); - else - _dnsServer.StatsManager.MaxStatFileDays = 0; - - if (version >= 17) - { - _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); - - { - int count = bR.ReadByte(); - if (count > 0) - { - NetworkAddress[] networks = new NetworkAddress[count]; - - for (int i = 0; i < count; i++) - networks[i] = NetworkAddress.Parse(bR); - - _dnsServer.RecursionDeniedNetworks = networks; - } - } - - - { - int count = bR.ReadByte(); - if (count > 0) - { - NetworkAddress[] networks = new NetworkAddress[count]; - - for (int i = 0; i < count; i++) - networks[i] = NetworkAddress.Parse(bR); - - _dnsServer.RecursionAllowedNetworks = networks; - } - } - } - else - { - bool allowRecursion = bR.ReadBoolean(); - bool allowRecursionOnlyForPrivateNetworks; - - if (version >= 4) - allowRecursionOnlyForPrivateNetworks = bR.ReadBoolean(); - else - allowRecursionOnlyForPrivateNetworks = true; //default true for security reasons - - if (allowRecursion) - { - if (allowRecursionOnlyForPrivateNetworks) - _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; - else - _dnsServer.Recursion = DnsServerRecursion.Allow; - } - else - { - _dnsServer.Recursion = DnsServerRecursion.Deny; - } - } - - if (version >= 12) - _dnsServer.RandomizeName = bR.ReadBoolean(); - else - _dnsServer.RandomizeName = true; //default true to enable security feature - - if (version >= 15) - _dnsServer.QnameMinimization = bR.ReadBoolean(); - else - _dnsServer.QnameMinimization = true; //default true to enable privacy feature - - if (version >= 20) - { - _dnsServer.QpmLimitRequests = bR.ReadInt32(); - _dnsServer.QpmLimitErrors = bR.ReadInt32(); - _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); - _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); - _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); - } - else if (version >= 17) - { - _dnsServer.QpmLimitRequests = bR.ReadInt32(); - _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); - _ = bR.ReadInt32(); //read obsolete value _dnsServer.QpmLimitSamplingIntervalInMinutes - } - else - { - _dnsServer.QpmLimitRequests = 0; - _dnsServer.QpmLimitErrors = 0; - _dnsServer.QpmLimitSampleMinutes = 1; - _dnsServer.QpmLimitIPv4PrefixLength = 24; - _dnsServer.QpmLimitIPv6PrefixLength = 56; - } - - if (version >= 13) - { - _dnsServer.ServeStale = bR.ReadBoolean(); - _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); - } - else - { - _dnsServer.ServeStale = true; - _dnsServer.CacheZoneManager.ServeStaleTtl = CacheZoneManager.SERVE_STALE_TTL; - } - - if (version >= 9) - { - _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); - _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); - _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); - _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); - } - else - { - _dnsServer.CachePrefetchEligibility = 2; - _dnsServer.CachePrefetchTrigger = 9; - _dnsServer.CachePrefetchSampleIntervalInMinutes = 5; - _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = 30; - } - - NetProxyType proxyType = (NetProxyType)bR.ReadByte(); - if (proxyType != NetProxyType.None) - { - string address = bR.ReadShortString(); - int port = bR.ReadInt32(); - NetworkCredential credential = null; - - if (bR.ReadBoolean()) //credential set - credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); - - _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); - - if (version >= 10) - { - int count = bR.ReadByte(); - List bypassList = new List(count); - - for (int i = 0; i < count; i++) - bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); - - _dnsServer.Proxy.BypassList = bypassList; - } - else - { - _dnsServer.Proxy.BypassList = null; - } - } - else - { - _dnsServer.Proxy = null; - } - - { - int count = bR.ReadByte(); - if (count > 0) - { - NameServerAddress[] forwarders = new NameServerAddress[count]; - - for (int i = 0; i < count; i++) - forwarders[i] = new NameServerAddress(bR); - - _dnsServer.Forwarders = forwarders; - } - } - - if (version <= 10) - { - DnsTransportProtocol forwarderProtocol = (DnsTransportProtocol)bR.ReadByte(); - - if (_dnsServer.Forwarders != null) - { - List forwarders = new List(); - - foreach (NameServerAddress forwarder in _dnsServer.Forwarders) - { - if (forwarder.Protocol == forwarderProtocol) - forwarders.Add(forwarder); - else - forwarders.Add(forwarder.ChangeProtocol(forwarderProtocol)); - } - - _dnsServer.Forwarders = forwarders; - } - } - - { - int count = bR.ReadByte(); - if (count > 0) - { - if (version > 2) - { - for (int i = 0; i < count; i++) - LoadCredentials(bR.ReadShortString(), bR.ReadShortString()); - } - else - { - for (int i = 0; i < count; i++) - SetCredentials(bR.ReadShortString(), bR.ReadShortString()); - } - } - } - - if (version <= 6) - { - int count = bR.ReadInt32(); - _configDisabledZones = new List(count); - - for (int i = 0; i < count; i++) - { - string domain = bR.ReadShortString(); - _configDisabledZones.Add(domain); - } - } - - if (version >= 18) - _dnsServer.EnableBlocking = bR.ReadBoolean(); - else - _dnsServer.EnableBlocking = true; - - if (version >= 18) - _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); - else if (version >= 16) - _dnsServer.BlockingType = bR.ReadBoolean() ? DnsServerBlockingType.NxDomain : DnsServerBlockingType.AnyAddress; - else - _dnsServer.BlockingType = DnsServerBlockingType.AnyAddress; - - if (version >= 18) - { - //read custom blocking addresses - int count = bR.ReadByte(); - if (count > 0) - { - List dnsARecords = new List(); - List dnsAAAARecords = new List(); - - for (int i = 0; i < count; i++) - { - IPAddress customAddress = IPAddressExtension.Parse(bR); - - switch (customAddress.AddressFamily) - { - case AddressFamily.InterNetwork: - dnsARecords.Add(new DnsARecordData(customAddress)); - break; - - case AddressFamily.InterNetworkV6: - dnsAAAARecords.Add(new DnsAAAARecordData(customAddress)); - break; - } - } - - _dnsServer.CustomBlockingARecords = dnsARecords; - _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; - } - } - else - { - _dnsServer.CustomBlockingARecords = null; - _dnsServer.CustomBlockingAAAARecords = null; - } - - if (version > 4) - { - //read block list urls - int count = bR.ReadByte(); - - for (int i = 0; i < count; i++) - { - string listUrl = bR.ReadShortString(); - - if (listUrl.StartsWith("!")) - _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); - else - _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); - } - - _blockListLastUpdatedOn = bR.ReadDateTime(); - - if (version >= 13) - _blockListUpdateIntervalHours = bR.ReadInt32(); - } - else - { - _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); - _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); - _blockListLastUpdatedOn = DateTime.MinValue; - _blockListUpdateIntervalHours = 24; - } - - if (version >= 11) - { - int count = bR.ReadByte(); - if (count > 0) - { - IPEndPoint[] localEndPoints = new IPEndPoint[count]; - - for (int i = 0; i < count; i++) - localEndPoints[i] = (IPEndPoint)EndPointExtension.Parse(bR); - - _dnsServer.LocalEndPoints = localEndPoints; - } - } - else if (version >= 6) - { - int count = bR.ReadByte(); - if (count > 0) - { - IPEndPoint[] localEndPoints = new IPEndPoint[count]; - - for (int i = 0; i < count; i++) - localEndPoints[i] = new IPEndPoint(IPAddressExtension.Parse(bR), 53); - - _dnsServer.LocalEndPoints = localEndPoints; - } - } - else - { - _dnsServer.LocalEndPoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Any, 53), new IPEndPoint(IPAddress.IPv6Any, 53) }; - } - - if (version >= 8) - { - _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); - _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); - _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); - _dnsTlsCertificatePath = bR.ReadShortString(); - _dnsTlsCertificatePassword = bR.ReadShortString(); - - if (_dnsTlsCertificatePath.Length == 0) - _dnsTlsCertificatePath = null; - - if (_dnsTlsCertificatePath != null) - { - try - { - LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); - } - catch (Exception ex) - { - _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); - } - - StartTlsCertificateUpdateTimer(); - } - } - else - { - _dnsServer.EnableDnsOverHttp = false; - _dnsServer.EnableDnsOverTls = false; - _dnsServer.EnableDnsOverHttps = false; - _dnsTlsCertificatePath = string.Empty; - _dnsTlsCertificatePassword = string.Empty; - } - - if (version >= 19) - { - _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); - _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); - _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); - _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); - } - else - { - _dnsServer.CacheZoneManager.MinimumRecordTtl = CacheZoneManager.MINIMUM_RECORD_TTL; - _dnsServer.CacheZoneManager.MaximumRecordTtl = CacheZoneManager.MAXIMUM_RECORD_TTL; - _dnsServer.CacheZoneManager.NegativeRecordTtl = CacheZoneManager.NEGATIVE_RECORD_TTL; - _dnsServer.CacheZoneManager.FailureRecordTtl = CacheZoneManager.FAILURE_RECORD_TTL; - } - - if (version >= 21) - { - int count = bR.ReadByte(); - Dictionary tsigKeys = new Dictionary(count); - - for (int i = 0; i < count; i++) - { - string keyName = bR.ReadShortString(); - string sharedSecret = bR.ReadShortString(); - TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); - - tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); - } - - _dnsServer.TsigKeys = tsigKeys; - } - else if (version >= 20) - { - int count = bR.ReadByte(); - Dictionary tsigKeys = new Dictionary(count); - - for (int i = 0; i < count; i++) - { - string keyName = bR.ReadShortString(); - string sharedSecret = bR.ReadShortString(); - - tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, TsigAlgorithm.HMAC_SHA256)); - } - - _dnsServer.TsigKeys = tsigKeys; - } - else - { - _dnsServer.TsigKeys = null; - } - - if (version >= 22) - _dnsServer.NsRevalidation = bR.ReadBoolean(); - else - _dnsServer.NsRevalidation = true; //default true for security reasons - - if (version >= 23) - { - _dnsServer.AllowTxtBlockingReport = bR.ReadBoolean(); - _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); - } - else - { - _dnsServer.AllowTxtBlockingReport = true; - _zonesApi.DefaultRecordTtl = 3600; - } - - if (version >= 24) - { - _webServiceUseSelfSignedTlsCertificate = bR.ReadBoolean(); - - SelfSignedCertCheck(false, false); - } - else - { - _webServiceUseSelfSignedTlsCertificate = false; - } - - if (version >= 25) - _dnsServer.UdpPayloadSize = bR.ReadUInt16(); - else - _dnsServer.UdpPayloadSize = DnsDatagram.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; - - if (version >= 26) - { - _dnsServer.DnssecValidation = bR.ReadBoolean(); - - _dnsServer.ResolverRetries = bR.ReadInt32(); - _dnsServer.ResolverTimeout = bR.ReadInt32(); - _dnsServer.ResolverMaxStackCount = bR.ReadInt32(); - - _dnsServer.ForwarderRetries = bR.ReadInt32(); - _dnsServer.ForwarderTimeout = bR.ReadInt32(); - _dnsServer.ForwarderConcurrency = bR.ReadInt32(); - - _dnsServer.ClientTimeout = bR.ReadInt32(); - _dnsServer.TcpSendTimeout = bR.ReadInt32(); - _dnsServer.TcpReceiveTimeout = bR.ReadInt32(); - } - else - { - _dnsServer.DnssecValidation = true; - CreateForwarderZoneToDisableDnssecForNTP(); - - _dnsServer.ResolverRetries = 2; - _dnsServer.ResolverTimeout = 2000; - _dnsServer.ResolverMaxStackCount = 16; - - _dnsServer.ForwarderRetries = 3; - _dnsServer.ForwarderTimeout = 2000; - _dnsServer.ForwarderConcurrency = 2; - - _dnsServer.ClientTimeout = 4000; - _dnsServer.TcpSendTimeout = 10000; - _dnsServer.TcpReceiveTimeout = 10000; - } - - if (version >= 27) - _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt32(); - else - _dnsServer.CacheZoneManager.MaximumEntries = 10000; - - break; - - default: - throw new InvalidDataException("DNS Server config version not supported."); - } + version = ReadConfigFrom(new BinaryReader(fS)); } _log.Write("DNS Server config file was loaded: " + configFile); - if (passwordResetOption) - { - SetCredentials("admin", "admin"); - - _log.Write("DNS Server reset password for user: admin"); - SaveConfigFile(); - - try - { - File.Delete(configFile); - } - catch - { } - } - - if (version <= 6) + if (version <= 27) SaveConfigFile(); //save as new config version to avoid loading old version next time } catch (FileNotFoundException) @@ -3994,47 +3758,26 @@ namespace DnsServerCore _log.Write("DNS Server config file was not found: " + configFile); _log.Write("DNS Server is restoring default config file."); + //general string serverDomain = Environment.GetEnvironmentVariable("DNS_SERVER_DOMAIN"); if (!string.IsNullOrEmpty(serverDomain)) _dnsServer.ServerDomain = serverDomain; - string adminPassword = Environment.GetEnvironmentVariable("DNS_SERVER_ADMIN_PASSWORD"); - string adminPasswordFile = Environment.GetEnvironmentVariable("DNS_SERVER_ADMIN_PASSWORD_FILE"); - - if (!string.IsNullOrEmpty(adminPassword)) - { - SetCredentials("admin", adminPassword); - } - else if (!string.IsNullOrEmpty(adminPasswordFile)) - { - try - { - using (StreamReader sR = new StreamReader(adminPasswordFile, true)) - { - string password = sR.ReadLine(); - SetCredentials("admin", password); - } - } - catch (Exception ex) - { - _log.Write(ex); - - SetCredentials("admin", "admin"); - } - } - else - { - SetCredentials("admin", "admin"); - } + _appsApi.EnableAutomaticUpdate = true; string strPreferIPv6 = Environment.GetEnvironmentVariable("DNS_SERVER_PREFER_IPV6"); if (!string.IsNullOrEmpty(strPreferIPv6)) _dnsServer.PreferIPv6 = bool.Parse(strPreferIPv6); + _dnsServer.DnssecValidation = true; + CreateForwarderZoneToDisableDnssecForNTP(); + + //optional protocols string strDnsOverHttp = Environment.GetEnvironmentVariable("DNS_SERVER_OPTIONAL_PROTOCOL_DNS_OVER_HTTP"); if (!string.IsNullOrEmpty(strDnsOverHttp)) _dnsServer.EnableDnsOverHttp = bool.Parse(strDnsOverHttp); + //recursion string strRecursion = Environment.GetEnvironmentVariable("DNS_SERVER_RECURSION"); if (!string.IsNullOrEmpty(strRecursion)) _dnsServer.Recursion = Enum.Parse(strRecursion, true); @@ -4065,6 +3808,14 @@ namespace DnsServerCore _dnsServer.RecursionAllowedNetworks = networks; } + _dnsServer.RandomizeName = true; //default true to enable security feature + _dnsServer.QnameMinimization = true; //default true to enable privacy feature + _dnsServer.NsRevalidation = true; //default true for security reasons + + //cache + _dnsServer.CacheZoneManager.MaximumEntries = 10000; + + //blocking string strEnableBlocking = Environment.GetEnvironmentVariable("DNS_SERVER_ENABLE_BLOCKING"); if (!string.IsNullOrEmpty(strEnableBlocking)) _dnsServer.EnableBlocking = bool.Parse(strEnableBlocking); @@ -4073,6 +3824,31 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(strAllowTxtBlockingReport)) _dnsServer.AllowTxtBlockingReport = bool.Parse(strAllowTxtBlockingReport); + string strBlockListUrls = Environment.GetEnvironmentVariable("DNS_SERVER_BLOCK_LIST_URLS"); + if (!string.IsNullOrEmpty(strBlockListUrls)) + { + string[] strBlockListUrlList = strBlockListUrls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string strBlockListUrl in strBlockListUrlList) + { + if (strBlockListUrl.StartsWith("!")) + { + Uri allowListUrl = new Uri(strBlockListUrl.Substring(1)); + + if (!_dnsServer.BlockListZoneManager.AllowListUrls.Contains(allowListUrl)) + _dnsServer.BlockListZoneManager.AllowListUrls.Add(allowListUrl); + } + else + { + Uri blockListUrl = new Uri(strBlockListUrl); + + if (!_dnsServer.BlockListZoneManager.BlockListUrls.Contains(blockListUrl)) + _dnsServer.BlockListZoneManager.BlockListUrls.Add(blockListUrl); + } + } + } + + //proxy & forwarders string strForwarders = Environment.GetEnvironmentVariable("DNS_SERVER_FORWARDERS"); if (!string.IsNullOrEmpty(strForwarders)) { @@ -4093,19 +3869,11 @@ namespace DnsServerCore _dnsServer.Forwarders = forwarders; } + //logging string strUseLocalTime = Environment.GetEnvironmentVariable("DNS_SERVER_LOG_USING_LOCAL_TIME"); if (!string.IsNullOrEmpty(strUseLocalTime)) _log.UseLocalTime = bool.Parse(strUseLocalTime); - _dnsServer.RandomizeName = true; //default true to enable security feature - _dnsServer.QnameMinimization = true; //default true to enable privacy feature - _dnsServer.NsRevalidation = true; //default true for security reasons - - _dnsServer.DnssecValidation = true; - CreateForwarderZoneToDisableDnssecForNTP(); - - _dnsServer.CacheZoneManager.MaximumEntries = 10000; - SaveConfigFile(); } catch (Exception ex) @@ -4125,6 +3893,11 @@ namespace DnsServerCore string fwdRecordComments = "This forwarder zone was automatically created to disable DNSSEC validation for ntp.org to allow systems with no real-time clock (e.g. Raspberry Pi) to sync time via NTP when booting."; if (_dnsServer.AuthZoneManager.CreateForwarderZone(ntpDomain, DnsTransportProtocol.Udp, "this-server", false, NetProxyType.None, null, 0, null, null, fwdRecordComments) is not null) { + //set permissions + _authManager.SetPermission(PermissionSection.Zones, ntpDomain, _authManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, ntpDomain, _authManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _authManager.SaveConfigFile(); + Directory.CreateDirectory(Path.Combine(_dnsServer.ConfigFolder, "zones")); _dnsServer.AuthZoneManager.SaveZoneFile(ntpDomain); } @@ -4138,13 +3911,911 @@ namespace DnsServerCore using (MemoryStream mS = new MemoryStream()) { //serialize config - BinaryWriter bW = new BinaryWriter(mS); + WriteConfigTo(new BinaryWriter(mS)); - bW.Write(Encoding.ASCII.GetBytes("DS")); //format - bW.Write((byte)27); //version + //write config + mS.Position = 0; - bW.WriteShortString(_dnsServer.ServerDomain); + using (FileStream fS = new FileStream(configFile, FileMode.Create, FileAccess.Write)) + { + mS.CopyTo(fS); + } + } + + _log.Write("DNS Server config file was saved: " + configFile); + } + + private void InspectAndFixZonePermissions() + { + Permission permission = _authManager.GetPermission(PermissionSection.Zones); + IReadOnlyDictionary subItemPermissions = permission.SubItemPermissions; + + //remove ghost permissions + foreach (KeyValuePair subItemPermission in subItemPermissions) + { + string zoneName = subItemPermission.Key; + + if (_dnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName) is null) + permission.RemoveAllSubItemPermissions(zoneName); //no such zone exists; remove permissions + } + + //add missing admin permissions + List zones = _dnsServer.AuthZoneManager.ListZones(); + Group admins = _authManager.GetGroup(Group.ADMINISTRATORS); + Group dnsAdmins = _authManager.GetGroup(Group.DNS_ADMINISTRATORS); + + foreach (AuthZoneInfo zone in zones) + { + if (zone.Internal) + { + _authManager.SetPermission(PermissionSection.Zones, zone.Name, admins, PermissionFlag.View); + _authManager.SetPermission(PermissionSection.Zones, zone.Name, dnsAdmins, PermissionFlag.View); + } + else + { + _authManager.SetPermission(PermissionSection.Zones, zone.Name, admins, PermissionFlag.ViewModifyDelete); + _authManager.SetPermission(PermissionSection.Zones, zone.Name, dnsAdmins, PermissionFlag.ViewModifyDelete); + } + } + + _authManager.SaveConfigFile(); + } + + private int ReadConfigFrom(BinaryReader bR) + { + if (Encoding.ASCII.GetString(bR.ReadBytes(2)) != "DS") //format + throw new InvalidDataException("DNS Server config file format is invalid."); + + int version = bR.ReadByte(); + + if (version == 28) + { + ReadConfigFrom(bR, version); + } + else if ((version >= 2) && (version <= 27)) + { + ReadOldConfigFrom(bR, version); + + //new default settings + _appsApi.EnableAutomaticUpdate = true; + } + else + { + throw new InvalidDataException("DNS Server config version not supported."); + } + + return version; + } + + private void ReadConfigFrom(BinaryReader bR, int version) + { + //web service + { + _webServiceHttpPort = bR.ReadInt32(); + _webServiceTlsPort = bR.ReadInt32(); + + { + int count = bR.ReadByte(); + if (count > 0) + { + IPAddress[] localAddresses = new IPAddress[count]; + + for (int i = 0; i < count; i++) + localAddresses[i] = IPAddressExtension.ReadFrom(bR); + + _webServiceLocalAddresses = localAddresses; + } + } + + _webServiceEnableTls = bR.ReadBoolean(); + _webServiceHttpToTlsRedirect = bR.ReadBoolean(); + _webServiceUseSelfSignedTlsCertificate = bR.ReadBoolean(); + + _webServiceTlsCertificatePath = bR.ReadShortString(); + _webServiceTlsCertificatePassword = bR.ReadShortString(); + } + + //dns + { + //general + _dnsServer.ServerDomain = bR.ReadShortString(); + + { + int count = bR.ReadByte(); + if (count > 0) + { + IPEndPoint[] localEndPoints = new IPEndPoint[count]; + + for (int i = 0; i < count; i++) + localEndPoints[i] = (IPEndPoint)EndPointExtension.ReadFrom(bR); + + _dnsServer.LocalEndPoints = localEndPoints; + } + } + + _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); + _appsApi.EnableAutomaticUpdate = bR.ReadBoolean(); + + _dnsServer.PreferIPv6 = bR.ReadBoolean(); + _dnsServer.UdpPayloadSize = bR.ReadUInt16(); + _dnsServer.DnssecValidation = bR.ReadBoolean(); + + _dnsServer.QpmLimitRequests = bR.ReadInt32(); + _dnsServer.QpmLimitErrors = bR.ReadInt32(); + _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); + _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); + _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); + + _dnsServer.ClientTimeout = bR.ReadInt32(); + _dnsServer.TcpSendTimeout = bR.ReadInt32(); + _dnsServer.TcpReceiveTimeout = bR.ReadInt32(); + + //optional protocols + _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); + _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); + _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); + + _dnsTlsCertificatePath = bR.ReadShortString(); + _dnsTlsCertificatePassword = bR.ReadShortString(); + + if (_dnsTlsCertificatePath.Length == 0) + _dnsTlsCertificatePath = null; + + if (_dnsTlsCertificatePath != null) + { + try + { + LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); + } + catch (Exception ex) + { + _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); + } + + StartTlsCertificateUpdateTimer(); + } + else + { + StopTlsCertificateUpdateTimer(); + } + + //tsig + { + int count = bR.ReadByte(); + Dictionary tsigKeys = new Dictionary(count); + + for (int i = 0; i < count; i++) + { + string keyName = bR.ReadShortString(); + string sharedSecret = bR.ReadShortString(); + TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); + + tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); + } + + _dnsServer.TsigKeys = tsigKeys; + } + + //recursion + _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); + + { + int count = bR.ReadByte(); + if (count > 0) + { + NetworkAddress[] networks = new NetworkAddress[count]; + + for (int i = 0; i < count; i++) + networks[i] = NetworkAddress.ReadFrom(bR); + + _dnsServer.RecursionDeniedNetworks = networks; + } + } + + + { + int count = bR.ReadByte(); + if (count > 0) + { + NetworkAddress[] networks = new NetworkAddress[count]; + + for (int i = 0; i < count; i++) + networks[i] = NetworkAddress.ReadFrom(bR); + + _dnsServer.RecursionAllowedNetworks = networks; + } + } + + _dnsServer.RandomizeName = bR.ReadBoolean(); + _dnsServer.QnameMinimization = bR.ReadBoolean(); + _dnsServer.NsRevalidation = bR.ReadBoolean(); + + _dnsServer.ResolverRetries = bR.ReadInt32(); + _dnsServer.ResolverTimeout = bR.ReadInt32(); + _dnsServer.ResolverMaxStackCount = bR.ReadInt32(); + + //cache + _dnsServer.ServeStale = bR.ReadBoolean(); + _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); + + _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt64(); + _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); + + _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); + _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); + _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); + _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); + + //blocking + _dnsServer.EnableBlocking = bR.ReadBoolean(); + _dnsServer.AllowTxtBlockingReport = bR.ReadBoolean(); + + _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); + + { + //read custom blocking addresses + int count = bR.ReadByte(); + if (count > 0) + { + List dnsARecords = new List(); + List dnsAAAARecords = new List(); + + for (int i = 0; i < count; i++) + { + IPAddress customAddress = IPAddressExtension.ReadFrom(bR); + + switch (customAddress.AddressFamily) + { + case AddressFamily.InterNetwork: + dnsARecords.Add(new DnsARecordData(customAddress)); + break; + + case AddressFamily.InterNetworkV6: + dnsAAAARecords.Add(new DnsAAAARecordData(customAddress)); + break; + } + } + + _dnsServer.CustomBlockingARecords = dnsARecords; + _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; + } + } + + { + //read block list urls + int count = bR.ReadByte(); + + for (int i = 0; i < count; i++) + { + string listUrl = bR.ReadShortString(); + + if (listUrl.StartsWith("!")) + _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); + else + _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); + } + + _blockListUpdateIntervalHours = bR.ReadInt32(); + _blockListLastUpdatedOn = bR.ReadDateTime(); + } + + //proxy & forwarders + NetProxyType proxyType = (NetProxyType)bR.ReadByte(); + if (proxyType != NetProxyType.None) + { + string address = bR.ReadShortString(); + int port = bR.ReadInt32(); + NetworkCredential credential = null; + + if (bR.ReadBoolean()) //credential set + credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); + + _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); + + int count = bR.ReadByte(); + List bypassList = new List(count); + + for (int i = 0; i < count; i++) + bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); + + _dnsServer.Proxy.BypassList = bypassList; + } + else + { + _dnsServer.Proxy = null; + } + + { + int count = bR.ReadByte(); + if (count > 0) + { + NameServerAddress[] forwarders = new NameServerAddress[count]; + + for (int i = 0; i < count; i++) + forwarders[i] = new NameServerAddress(bR); + + _dnsServer.Forwarders = forwarders; + } + } + + _dnsServer.ForwarderRetries = bR.ReadInt32(); + _dnsServer.ForwarderTimeout = bR.ReadInt32(); + _dnsServer.ForwarderConcurrency = bR.ReadInt32(); + + //logging + if (bR.ReadBoolean()) //log all queries + _dnsServer.QueryLogManager = _log; + else + _dnsServer.QueryLogManager = null; + + _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); + } + } + + private void ReadOldConfigFrom(BinaryReader bR, int version) + { + _dnsServer.ServerDomain = bR.ReadShortString(); + _webServiceHttpPort = bR.ReadInt32(); + + if (version >= 13) + { + { + int count = bR.ReadByte(); + if (count > 0) + { + IPAddress[] localAddresses = new IPAddress[count]; + + for (int i = 0; i < count; i++) + localAddresses[i] = IPAddressExtension.ReadFrom(bR); + + _webServiceLocalAddresses = localAddresses; + } + } + + _webServiceTlsPort = bR.ReadInt32(); + _webServiceEnableTls = bR.ReadBoolean(); + _webServiceHttpToTlsRedirect = bR.ReadBoolean(); + _webServiceTlsCertificatePath = bR.ReadShortString(); + _webServiceTlsCertificatePassword = bR.ReadShortString(); + + if (_webServiceTlsCertificatePath.Length == 0) + _webServiceTlsCertificatePath = null; + + if (_webServiceTlsCertificatePath != null) + { + try + { + LoadWebServiceTlsCertificate(_webServiceTlsCertificatePath, _webServiceTlsCertificatePassword); + } + catch (Exception ex) + { + _log.Write("DNS Server encountered an error while loading Web Service TLS certificate: " + _webServiceTlsCertificatePath + "\r\n" + ex.ToString()); + } + + StartTlsCertificateUpdateTimer(); + } + } + else + { + _webServiceLocalAddresses = new IPAddress[] { IPAddress.Any, IPAddress.IPv6Any }; + + _webServiceTlsPort = 53443; + _webServiceEnableTls = false; + _webServiceHttpToTlsRedirect = false; + _webServiceTlsCertificatePath = string.Empty; + _webServiceTlsCertificatePassword = string.Empty; + } + + _dnsServer.PreferIPv6 = bR.ReadBoolean(); + + if (bR.ReadBoolean()) //logQueries + _dnsServer.QueryLogManager = _log; + + if (version >= 14) + _dnsServer.StatsManager.MaxStatFileDays = bR.ReadInt32(); + else + _dnsServer.StatsManager.MaxStatFileDays = 0; + + if (version >= 17) + { + _dnsServer.Recursion = (DnsServerRecursion)bR.ReadByte(); + + { + int count = bR.ReadByte(); + if (count > 0) + { + NetworkAddress[] networks = new NetworkAddress[count]; + + for (int i = 0; i < count; i++) + networks[i] = NetworkAddress.ReadFrom(bR); + + _dnsServer.RecursionDeniedNetworks = networks; + } + } + + + { + int count = bR.ReadByte(); + if (count > 0) + { + NetworkAddress[] networks = new NetworkAddress[count]; + + for (int i = 0; i < count; i++) + networks[i] = NetworkAddress.ReadFrom(bR); + + _dnsServer.RecursionAllowedNetworks = networks; + } + } + } + else + { + bool allowRecursion = bR.ReadBoolean(); + bool allowRecursionOnlyForPrivateNetworks; + + if (version >= 4) + allowRecursionOnlyForPrivateNetworks = bR.ReadBoolean(); + else + allowRecursionOnlyForPrivateNetworks = true; //default true for security reasons + + if (allowRecursion) + { + if (allowRecursionOnlyForPrivateNetworks) + _dnsServer.Recursion = DnsServerRecursion.AllowOnlyForPrivateNetworks; + else + _dnsServer.Recursion = DnsServerRecursion.Allow; + } + else + { + _dnsServer.Recursion = DnsServerRecursion.Deny; + } + } + + if (version >= 12) + _dnsServer.RandomizeName = bR.ReadBoolean(); + else + _dnsServer.RandomizeName = true; //default true to enable security feature + + if (version >= 15) + _dnsServer.QnameMinimization = bR.ReadBoolean(); + else + _dnsServer.QnameMinimization = true; //default true to enable privacy feature + + if (version >= 20) + { + _dnsServer.QpmLimitRequests = bR.ReadInt32(); + _dnsServer.QpmLimitErrors = bR.ReadInt32(); + _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); + _dnsServer.QpmLimitIPv4PrefixLength = bR.ReadInt32(); + _dnsServer.QpmLimitIPv6PrefixLength = bR.ReadInt32(); + } + else if (version >= 17) + { + _dnsServer.QpmLimitRequests = bR.ReadInt32(); + _dnsServer.QpmLimitSampleMinutes = bR.ReadInt32(); + _ = bR.ReadInt32(); //read obsolete value _dnsServer.QpmLimitSamplingIntervalInMinutes + } + else + { + _dnsServer.QpmLimitRequests = 0; + _dnsServer.QpmLimitErrors = 0; + _dnsServer.QpmLimitSampleMinutes = 1; + _dnsServer.QpmLimitIPv4PrefixLength = 24; + _dnsServer.QpmLimitIPv6PrefixLength = 56; + } + + if (version >= 13) + { + _dnsServer.ServeStale = bR.ReadBoolean(); + _dnsServer.CacheZoneManager.ServeStaleTtl = bR.ReadUInt32(); + } + else + { + _dnsServer.ServeStale = true; + _dnsServer.CacheZoneManager.ServeStaleTtl = CacheZoneManager.SERVE_STALE_TTL; + } + + if (version >= 9) + { + _dnsServer.CachePrefetchEligibility = bR.ReadInt32(); + _dnsServer.CachePrefetchTrigger = bR.ReadInt32(); + _dnsServer.CachePrefetchSampleIntervalInMinutes = bR.ReadInt32(); + _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = bR.ReadInt32(); + } + else + { + _dnsServer.CachePrefetchEligibility = 2; + _dnsServer.CachePrefetchTrigger = 9; + _dnsServer.CachePrefetchSampleIntervalInMinutes = 5; + _dnsServer.CachePrefetchSampleEligibilityHitsPerHour = 30; + } + + NetProxyType proxyType = (NetProxyType)bR.ReadByte(); + if (proxyType != NetProxyType.None) + { + string address = bR.ReadShortString(); + int port = bR.ReadInt32(); + NetworkCredential credential = null; + + if (bR.ReadBoolean()) //credential set + credential = new NetworkCredential(bR.ReadShortString(), bR.ReadShortString()); + + _dnsServer.Proxy = NetProxy.CreateProxy(proxyType, address, port, credential); + + if (version >= 10) + { + int count = bR.ReadByte(); + List bypassList = new List(count); + + for (int i = 0; i < count; i++) + bypassList.Add(new NetProxyBypassItem(bR.ReadShortString())); + + _dnsServer.Proxy.BypassList = bypassList; + } + else + { + _dnsServer.Proxy.BypassList = null; + } + } + else + { + _dnsServer.Proxy = null; + } + + { + int count = bR.ReadByte(); + if (count > 0) + { + NameServerAddress[] forwarders = new NameServerAddress[count]; + + for (int i = 0; i < count; i++) + forwarders[i] = new NameServerAddress(bR); + + _dnsServer.Forwarders = forwarders; + } + } + + if (version <= 10) + { + DnsTransportProtocol forwarderProtocol = (DnsTransportProtocol)bR.ReadByte(); + + if (_dnsServer.Forwarders != null) + { + List forwarders = new List(); + + foreach (NameServerAddress forwarder in _dnsServer.Forwarders) + { + if (forwarder.Protocol == forwarderProtocol) + forwarders.Add(forwarder); + else + forwarders.Add(forwarder.ChangeProtocol(forwarderProtocol)); + } + + _dnsServer.Forwarders = forwarders; + } + } + + { + int count = bR.ReadByte(); + if (count > 0) + { + if (version > 2) + { + for (int i = 0; i < count; i++) + { + string username = bR.ReadShortString(); + string passwordHash = bR.ReadShortString(); + + if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) + { + _authManager.LoadOldConfig(passwordHash, true); + break; + } + } + } + else + { + for (int i = 0; i < count; i++) + { + string username = bR.ReadShortString(); + string password = bR.ReadShortString(); + + if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) + { + _authManager.LoadOldConfig(password, false); + break; + } + } + } + } + } + + if (version <= 6) + { + int count = bR.ReadInt32(); + _configDisabledZones = new List(count); + + for (int i = 0; i < count; i++) + { + string domain = bR.ReadShortString(); + _configDisabledZones.Add(domain); + } + } + + if (version >= 18) + _dnsServer.EnableBlocking = bR.ReadBoolean(); + else + _dnsServer.EnableBlocking = true; + + if (version >= 18) + _dnsServer.BlockingType = (DnsServerBlockingType)bR.ReadByte(); + else if (version >= 16) + _dnsServer.BlockingType = bR.ReadBoolean() ? DnsServerBlockingType.NxDomain : DnsServerBlockingType.AnyAddress; + else + _dnsServer.BlockingType = DnsServerBlockingType.AnyAddress; + + if (version >= 18) + { + //read custom blocking addresses + int count = bR.ReadByte(); + if (count > 0) + { + List dnsARecords = new List(); + List dnsAAAARecords = new List(); + + for (int i = 0; i < count; i++) + { + IPAddress customAddress = IPAddressExtension.ReadFrom(bR); + + switch (customAddress.AddressFamily) + { + case AddressFamily.InterNetwork: + dnsARecords.Add(new DnsARecordData(customAddress)); + break; + + case AddressFamily.InterNetworkV6: + dnsAAAARecords.Add(new DnsAAAARecordData(customAddress)); + break; + } + } + + _dnsServer.CustomBlockingARecords = dnsARecords; + _dnsServer.CustomBlockingAAAARecords = dnsAAAARecords; + } + } + else + { + _dnsServer.CustomBlockingARecords = null; + _dnsServer.CustomBlockingAAAARecords = null; + } + + if (version > 4) + { + //read block list urls + int count = bR.ReadByte(); + + for (int i = 0; i < count; i++) + { + string listUrl = bR.ReadShortString(); + + if (listUrl.StartsWith("!")) + _dnsServer.BlockListZoneManager.AllowListUrls.Add(new Uri(listUrl.Substring(1))); + else + _dnsServer.BlockListZoneManager.BlockListUrls.Add(new Uri(listUrl)); + } + + _blockListLastUpdatedOn = bR.ReadDateTime(); + + if (version >= 13) + _blockListUpdateIntervalHours = bR.ReadInt32(); + } + else + { + _dnsServer.BlockListZoneManager.AllowListUrls.Clear(); + _dnsServer.BlockListZoneManager.BlockListUrls.Clear(); + _blockListLastUpdatedOn = DateTime.MinValue; + _blockListUpdateIntervalHours = 24; + } + + if (version >= 11) + { + int count = bR.ReadByte(); + if (count > 0) + { + IPEndPoint[] localEndPoints = new IPEndPoint[count]; + + for (int i = 0; i < count; i++) + localEndPoints[i] = (IPEndPoint)EndPointExtension.ReadFrom(bR); + + _dnsServer.LocalEndPoints = localEndPoints; + } + } + else if (version >= 6) + { + int count = bR.ReadByte(); + if (count > 0) + { + IPEndPoint[] localEndPoints = new IPEndPoint[count]; + + for (int i = 0; i < count; i++) + localEndPoints[i] = new IPEndPoint(IPAddressExtension.ReadFrom(bR), 53); + + _dnsServer.LocalEndPoints = localEndPoints; + } + } + else + { + _dnsServer.LocalEndPoints = new IPEndPoint[] { new IPEndPoint(IPAddress.Any, 53), new IPEndPoint(IPAddress.IPv6Any, 53) }; + } + + if (version >= 8) + { + _dnsServer.EnableDnsOverHttp = bR.ReadBoolean(); + _dnsServer.EnableDnsOverTls = bR.ReadBoolean(); + _dnsServer.EnableDnsOverHttps = bR.ReadBoolean(); + _dnsTlsCertificatePath = bR.ReadShortString(); + _dnsTlsCertificatePassword = bR.ReadShortString(); + + if (_dnsTlsCertificatePath.Length == 0) + _dnsTlsCertificatePath = null; + + if (_dnsTlsCertificatePath != null) + { + try + { + LoadDnsTlsCertificate(_dnsTlsCertificatePath, _dnsTlsCertificatePassword); + } + catch (Exception ex) + { + _log.Write("DNS Server encountered an error while loading DNS Server TLS certificate: " + _dnsTlsCertificatePath + "\r\n" + ex.ToString()); + } + + StartTlsCertificateUpdateTimer(); + } + } + else + { + _dnsServer.EnableDnsOverHttp = false; + _dnsServer.EnableDnsOverTls = false; + _dnsServer.EnableDnsOverHttps = false; + _dnsTlsCertificatePath = string.Empty; + _dnsTlsCertificatePassword = string.Empty; + } + + if (version >= 19) + { + _dnsServer.CacheZoneManager.MinimumRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.MaximumRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.NegativeRecordTtl = bR.ReadUInt32(); + _dnsServer.CacheZoneManager.FailureRecordTtl = bR.ReadUInt32(); + } + else + { + _dnsServer.CacheZoneManager.MinimumRecordTtl = CacheZoneManager.MINIMUM_RECORD_TTL; + _dnsServer.CacheZoneManager.MaximumRecordTtl = CacheZoneManager.MAXIMUM_RECORD_TTL; + _dnsServer.CacheZoneManager.NegativeRecordTtl = CacheZoneManager.NEGATIVE_RECORD_TTL; + _dnsServer.CacheZoneManager.FailureRecordTtl = CacheZoneManager.FAILURE_RECORD_TTL; + } + + if (version >= 21) + { + int count = bR.ReadByte(); + Dictionary tsigKeys = new Dictionary(count); + + for (int i = 0; i < count; i++) + { + string keyName = bR.ReadShortString(); + string sharedSecret = bR.ReadShortString(); + TsigAlgorithm algorithm = (TsigAlgorithm)bR.ReadByte(); + + tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, algorithm)); + } + + _dnsServer.TsigKeys = tsigKeys; + } + else if (version >= 20) + { + int count = bR.ReadByte(); + Dictionary tsigKeys = new Dictionary(count); + + for (int i = 0; i < count; i++) + { + string keyName = bR.ReadShortString(); + string sharedSecret = bR.ReadShortString(); + + tsigKeys.Add(keyName, new TsigKey(keyName, sharedSecret, TsigAlgorithm.HMAC_SHA256)); + } + + _dnsServer.TsigKeys = tsigKeys; + } + else + { + _dnsServer.TsigKeys = null; + } + + if (version >= 22) + _dnsServer.NsRevalidation = bR.ReadBoolean(); + else + _dnsServer.NsRevalidation = true; //default true for security reasons + + if (version >= 23) + { + _dnsServer.AllowTxtBlockingReport = bR.ReadBoolean(); + _zonesApi.DefaultRecordTtl = bR.ReadUInt32(); + } + else + { + _dnsServer.AllowTxtBlockingReport = true; + _zonesApi.DefaultRecordTtl = 3600; + } + + if (version >= 24) + { + _webServiceUseSelfSignedTlsCertificate = bR.ReadBoolean(); + + SelfSignedCertCheck(false, false); + } + else + { + _webServiceUseSelfSignedTlsCertificate = false; + } + + if (version >= 25) + _dnsServer.UdpPayloadSize = bR.ReadUInt16(); + else + _dnsServer.UdpPayloadSize = DnsDatagram.EDNS_DEFAULT_UDP_PAYLOAD_SIZE; + + if (version >= 26) + { + _dnsServer.DnssecValidation = bR.ReadBoolean(); + + _dnsServer.ResolverRetries = bR.ReadInt32(); + _dnsServer.ResolverTimeout = bR.ReadInt32(); + _dnsServer.ResolverMaxStackCount = bR.ReadInt32(); + + _dnsServer.ForwarderRetries = bR.ReadInt32(); + _dnsServer.ForwarderTimeout = bR.ReadInt32(); + _dnsServer.ForwarderConcurrency = bR.ReadInt32(); + + _dnsServer.ClientTimeout = bR.ReadInt32(); + _dnsServer.TcpSendTimeout = bR.ReadInt32(); + _dnsServer.TcpReceiveTimeout = bR.ReadInt32(); + } + else + { + _dnsServer.DnssecValidation = true; + CreateForwarderZoneToDisableDnssecForNTP(); + + _dnsServer.ResolverRetries = 2; + _dnsServer.ResolverTimeout = 2000; + _dnsServer.ResolverMaxStackCount = 16; + + _dnsServer.ForwarderRetries = 3; + _dnsServer.ForwarderTimeout = 2000; + _dnsServer.ForwarderConcurrency = 2; + + _dnsServer.ClientTimeout = 4000; + _dnsServer.TcpSendTimeout = 10000; + _dnsServer.TcpReceiveTimeout = 10000; + } + + if (version >= 27) + _dnsServer.CacheZoneManager.MaximumEntries = bR.ReadInt32(); + else + _dnsServer.CacheZoneManager.MaximumEntries = 10000; + } + + private void WriteConfigTo(BinaryWriter bW) + { + bW.Write(Encoding.ASCII.GetBytes("DS")); //format + bW.Write((byte)28); //version + + //web service + { bW.Write(_webServiceHttpPort); + bW.Write(_webServiceTlsPort); { bW.Write(Convert.ToByte(_webServiceLocalAddresses.Count)); @@ -4153,25 +4824,83 @@ namespace DnsServerCore localAddress.WriteTo(bW); } - bW.Write(_webServiceTlsPort); bW.Write(_webServiceEnableTls); bW.Write(_webServiceHttpToTlsRedirect); + bW.Write(_webServiceUseSelfSignedTlsCertificate); - if (_webServiceTlsCertificatePath == null) + if (_webServiceTlsCertificatePath is null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePath); - if (_webServiceTlsCertificatePassword == null) + if (_webServiceTlsCertificatePassword is null) bW.WriteShortString(string.Empty); else bW.WriteShortString(_webServiceTlsCertificatePassword); + } + + //dns + { + //general + bW.WriteShortString(_dnsServer.ServerDomain); + + { + bW.Write(Convert.ToByte(_dnsServer.LocalEndPoints.Count)); + + foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints) + localEP.WriteTo(bW); + } + + bW.Write(_zonesApi.DefaultRecordTtl); + bW.Write(_appsApi.EnableAutomaticUpdate); bW.Write(_dnsServer.PreferIPv6); + bW.Write(_dnsServer.UdpPayloadSize); + bW.Write(_dnsServer.DnssecValidation); - bW.Write(_dnsServer.QueryLogManager != null); //logQueries - bW.Write(_dnsServer.StatsManager.MaxStatFileDays); + bW.Write(_dnsServer.QpmLimitRequests); + bW.Write(_dnsServer.QpmLimitErrors); + bW.Write(_dnsServer.QpmLimitSampleMinutes); + bW.Write(_dnsServer.QpmLimitIPv4PrefixLength); + bW.Write(_dnsServer.QpmLimitIPv6PrefixLength); + bW.Write(_dnsServer.ClientTimeout); + bW.Write(_dnsServer.TcpSendTimeout); + bW.Write(_dnsServer.TcpReceiveTimeout); + + //optional protocols + bW.Write(_dnsServer.EnableDnsOverHttp); + bW.Write(_dnsServer.EnableDnsOverTls); + bW.Write(_dnsServer.EnableDnsOverHttps); + + if (_dnsTlsCertificatePath == null) + bW.WriteShortString(string.Empty); + else + bW.WriteShortString(_dnsTlsCertificatePath); + + if (_dnsTlsCertificatePassword == null) + bW.WriteShortString(string.Empty); + else + bW.WriteShortString(_dnsTlsCertificatePassword); + + //tsig + if (_dnsServer.TsigKeys is null) + { + bW.Write((byte)0); + } + else + { + bW.Write(Convert.ToByte(_dnsServer.TsigKeys.Count)); + + foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) + { + bW.WriteShortString(tsigKey.Key); + bW.WriteShortString(tsigKey.Value.SharedSecret); + bW.Write((byte)tsigKey.Value.Algorithm); + } + } + + //recursion bW.Write((byte)_dnsServer.Recursion); if (_dnsServer.RecursionDeniedNetworks is null) @@ -4198,21 +4927,57 @@ namespace DnsServerCore bW.Write(_dnsServer.RandomizeName); bW.Write(_dnsServer.QnameMinimization); + bW.Write(_dnsServer.NsRevalidation); - bW.Write(_dnsServer.QpmLimitRequests); - bW.Write(_dnsServer.QpmLimitErrors); - bW.Write(_dnsServer.QpmLimitSampleMinutes); - bW.Write(_dnsServer.QpmLimitIPv4PrefixLength); - bW.Write(_dnsServer.QpmLimitIPv6PrefixLength); + bW.Write(_dnsServer.ResolverRetries); + bW.Write(_dnsServer.ResolverTimeout); + bW.Write(_dnsServer.ResolverMaxStackCount); + //cache bW.Write(_dnsServer.ServeStale); bW.Write(_dnsServer.CacheZoneManager.ServeStaleTtl); + bW.Write(_dnsServer.CacheZoneManager.MaximumEntries); + bW.Write(_dnsServer.CacheZoneManager.MinimumRecordTtl); + bW.Write(_dnsServer.CacheZoneManager.MaximumRecordTtl); + bW.Write(_dnsServer.CacheZoneManager.NegativeRecordTtl); + bW.Write(_dnsServer.CacheZoneManager.FailureRecordTtl); + bW.Write(_dnsServer.CachePrefetchEligibility); bW.Write(_dnsServer.CachePrefetchTrigger); bW.Write(_dnsServer.CachePrefetchSampleIntervalInMinutes); bW.Write(_dnsServer.CachePrefetchSampleEligibilityHitsPerHour); + //blocking + bW.Write(_dnsServer.EnableBlocking); + bW.Write(_dnsServer.AllowTxtBlockingReport); + + bW.Write((byte)_dnsServer.BlockingType); + + { + bW.Write(Convert.ToByte(_dnsServer.CustomBlockingARecords.Count + _dnsServer.CustomBlockingAAAARecords.Count)); + + foreach (DnsARecordData record in _dnsServer.CustomBlockingARecords) + record.Address.WriteTo(bW); + + foreach (DnsAAAARecordData record in _dnsServer.CustomBlockingAAAARecords) + record.Address.WriteTo(bW); + } + + { + bW.Write(Convert.ToByte(_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)); + + foreach (Uri allowListUrl in _dnsServer.BlockListZoneManager.AllowListUrls) + bW.WriteShortString("!" + allowListUrl.AbsoluteUri); + + foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls) + bW.WriteShortString(blockListUrl.AbsoluteUri); + + bW.Write(_blockListUpdateIntervalHours); + bW.Write(_blockListLastUpdatedOn); + } + + //proxy & forwarders if (_dnsServer.Proxy == null) { bW.Write((byte)NetProxyType.None); @@ -4257,117 +5022,14 @@ namespace DnsServerCore forwarder.WriteTo(bW); } - { - bW.Write(Convert.ToByte(_credentials.Count)); - - foreach (KeyValuePair credential in _credentials) - { - bW.WriteShortString(credential.Key); - bW.WriteShortString(credential.Value); - } - } - - //block list - bW.Write(_dnsServer.EnableBlocking); - bW.Write((byte)_dnsServer.BlockingType); - - { - bW.Write(Convert.ToByte(_dnsServer.CustomBlockingARecords.Count + _dnsServer.CustomBlockingAAAARecords.Count)); - - foreach (DnsARecordData record in _dnsServer.CustomBlockingARecords) - record.Address.WriteTo(bW); - - foreach (DnsAAAARecordData record in _dnsServer.CustomBlockingAAAARecords) - record.Address.WriteTo(bW); - } - - { - bW.Write(Convert.ToByte(_dnsServer.BlockListZoneManager.AllowListUrls.Count + _dnsServer.BlockListZoneManager.BlockListUrls.Count)); - - foreach (Uri allowListUrl in _dnsServer.BlockListZoneManager.AllowListUrls) - bW.WriteShortString("!" + allowListUrl.AbsoluteUri); - - foreach (Uri blockListUrl in _dnsServer.BlockListZoneManager.BlockListUrls) - bW.WriteShortString(blockListUrl.AbsoluteUri); - - bW.Write(_blockListLastUpdatedOn); - bW.Write(_blockListUpdateIntervalHours); - } - - - { - bW.Write(Convert.ToByte(_dnsServer.LocalEndPoints.Count)); - - foreach (IPEndPoint localEP in _dnsServer.LocalEndPoints) - localEP.WriteTo(bW); - } - - bW.Write(_dnsServer.EnableDnsOverHttp); - bW.Write(_dnsServer.EnableDnsOverTls); - bW.Write(_dnsServer.EnableDnsOverHttps); - - if (_dnsTlsCertificatePath == null) - bW.WriteShortString(string.Empty); - else - bW.WriteShortString(_dnsTlsCertificatePath); - - if (_dnsTlsCertificatePassword == null) - bW.WriteShortString(string.Empty); - else - bW.WriteShortString(_dnsTlsCertificatePassword); - - bW.Write(_dnsServer.CacheZoneManager.MinimumRecordTtl); - bW.Write(_dnsServer.CacheZoneManager.MaximumRecordTtl); - bW.Write(_dnsServer.CacheZoneManager.NegativeRecordTtl); - bW.Write(_dnsServer.CacheZoneManager.FailureRecordTtl); - - if (_dnsServer.TsigKeys is null) - { - bW.Write((byte)0); - } - else - { - bW.Write(Convert.ToByte(_dnsServer.TsigKeys.Count)); - - foreach (KeyValuePair tsigKey in _dnsServer.TsigKeys) - { - bW.WriteShortString(tsigKey.Key); - bW.WriteShortString(tsigKey.Value.SharedSecret); - bW.Write((byte)tsigKey.Value.Algorithm); - } - } - - bW.Write(_dnsServer.NsRevalidation); - bW.Write(_dnsServer.AllowTxtBlockingReport); - bW.Write(_zonesApi.DefaultRecordTtl); - bW.Write(_webServiceUseSelfSignedTlsCertificate); - bW.Write(_dnsServer.UdpPayloadSize); - bW.Write(_dnsServer.DnssecValidation); - - bW.Write(_dnsServer.ResolverRetries); - bW.Write(_dnsServer.ResolverTimeout); - bW.Write(_dnsServer.ResolverMaxStackCount); - bW.Write(_dnsServer.ForwarderRetries); bW.Write(_dnsServer.ForwarderTimeout); bW.Write(_dnsServer.ForwarderConcurrency); - bW.Write(_dnsServer.ClientTimeout); - bW.Write(_dnsServer.TcpSendTimeout); - bW.Write(_dnsServer.TcpReceiveTimeout); - - bW.Write(_dnsServer.CacheZoneManager.MaximumEntries); - - //write config - mS.Position = 0; - - using (FileStream fS = new FileStream(configFile, FileMode.Create, FileAccess.Write)) - { - mS.CopyTo(fS); - } + //logging + bW.Write(_dnsServer.QueryLogManager is not null); //log all queries + bW.Write(_dnsServer.StatsManager.MaxStatFileDays); } - - _log.Write("DNS Server config file was saved: " + configFile); } #endregion @@ -4548,6 +5210,10 @@ namespace DnsServerCore //init dhcp server _dhcpServer = new DhcpServer(Path.Combine(_configFolder, "scopes"), _log); _dhcpServer.AuthZoneManager = _dnsServer.AuthZoneManager; + _dhcpServer.AuthManager = _authManager; + + //load auth config + _authManager.LoadConfigFile(); //load config LoadConfigFile(); @@ -4557,6 +5223,7 @@ namespace DnsServerCore //load all zones files _dnsServer.AuthZoneManager.LoadAllZoneFiles(); + InspectAndFixZonePermissions(); //disable zones from old config format if (_configDisabledZones != null) @@ -4577,7 +5244,7 @@ namespace DnsServerCore _dnsServer.BlockedZoneManager.LoadBlockedZoneFile(); //load block list zone async - if (_dnsServer.BlockListZoneManager.BlockListUrls.Count > 0) + if ((_blockListUpdateIntervalHours > 0) && (_dnsServer.BlockListZoneManager.BlockListUrls.Count > 0)) { ThreadPool.QueueUserWorkItem(delegate (object state) { @@ -4648,12 +5315,21 @@ namespace DnsServerCore internal LogManager Log { get { return _log; } } + internal AuthManager AuthManager + { get { return _authManager; } } + + internal WebServiceZonesApi ZonesApi + { get { return _zonesApi; } } + internal DnsServer DnsServer { get { return _dnsServer; } } internal DhcpServer DhcpServer { get { return _dhcpServer; } } + internal Version ServerVersion + { get { return _currentVersion; } } + public string ConfigFolder { get { return _configFolder; } } diff --git a/DnsServerCore/UserSession.cs b/DnsServerCore/UserSession.cs deleted file mode 100644 index 961baa01..00000000 --- a/DnsServerCore/UserSession.cs +++ /dev/null @@ -1,66 +0,0 @@ -/* -Technitium DNS Server -Copyright (C) 2019 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 System; - -namespace DnsServerCore -{ - class UserSession - { - #region variables - - const int SESSION_TIMEOUT = 30 * 60 * 1000; //30 mins - - readonly string _username; - DateTime _lastSeen; - - #endregion - - #region constructor - - public UserSession(string username) - { - _username = username; - _lastSeen = DateTime.UtcNow; - } - - #endregion - - #region public - - public void UpdateLastSeen() - { - _lastSeen = DateTime.UtcNow; - } - - public bool HasExpired() - { - return _lastSeen.AddMilliseconds(SESSION_TIMEOUT) < DateTime.UtcNow; - } - - #endregion - - #region properties - - public string Username - { get { return _username; } } - - #endregion - } -} diff --git a/DnsServerCore/WebServiceAppsApi.cs b/DnsServerCore/WebServiceAppsApi.cs index 18fa6de6..e234ca6d 100644 --- a/DnsServerCore/WebServiceAppsApi.cs +++ b/DnsServerCore/WebServiceAppsApi.cs @@ -25,13 +25,14 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using TechnitiumLibrary; using TechnitiumLibrary.IO; namespace DnsServerCore { - class WebServiceAppsApi + sealed class WebServiceAppsApi : IDisposable { #region variables @@ -42,6 +43,10 @@ namespace DnsServerCore DateTime _storeAppsJsonDataUpdatedOn; const int STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS = 300; + Timer _appUpdateTimer; + const int APP_UPDATE_TIMER_INITIAL_INTERVAL = 10000; + const int APP_UPDATE_TIMER_PERIODIC_INTERVAL = 86400000; + #endregion #region constructor @@ -54,14 +59,113 @@ namespace DnsServerCore #endregion + #region IDisposable + + bool _disposed; + + public void Dispose() + { + if (_disposed) + return; + + if (_appUpdateTimer is not null) + _appUpdateTimer.Dispose(); + + _disposed = true; + } + + #endregion + #region private + private void StartAutomaticUpdate() + { + if (_appUpdateTimer is null) + { + _appUpdateTimer = new Timer(async delegate (object state) + { + try + { + if (_dnsWebService.DnsServer.DnsApplicationManager.Applications.Count < 1) + return; + + string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000); + dynamic jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData); + + foreach (DnsApplication application in _dnsWebService.DnsServer.DnsApplicationManager.Applications.Values) + { + foreach (dynamic jsonStoreApp in jsonStoreAppsArray) + { + string name = jsonStoreApp.name.Value; + if (name.Equals(application.Name)) + { + string url = null; + Version storeAppVersion = null; + Version lastServerVersion = null; + + foreach (dynamic jsonVersion in jsonStoreApp.versions) + { + string strServerVersion = jsonVersion.serverVersion.Value; + Version requiredServerVersion = new Version(strServerVersion); + + if (_dnsWebService.ServerVersion < requiredServerVersion) + continue; + + if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion)) + continue; + + string version = jsonVersion.version.Value; + url = jsonVersion.url.Value; + + storeAppVersion = new Version(version); + lastServerVersion = requiredServerVersion; + } + + if ((storeAppVersion is not null) && (storeAppVersion > application.Version)) + { + try + { + await DownloadAndUpdateAppAsync(application.Name, url); + + _dnsWebService.Log.Write("DNS application '" + application.Name + "' was automatically updated successfully from: " + url); + } + catch (Exception ex) + { + _dnsWebService.Log.Write("Failed to automatically download and update DNS application '" + application.Name + "': " + ex.ToString()); + } + } + + break; + } + } + } + } + catch (Exception ex) + { + _dnsWebService.Log.Write(ex); + } + }); + + _appUpdateTimer.Change(APP_UPDATE_TIMER_INITIAL_INTERVAL, APP_UPDATE_TIMER_PERIODIC_INTERVAL); + } + } + + private void StopAutomaticUpdate() + { + if (_appUpdateTimer is not null) + { + _appUpdateTimer.Dispose(); + _appUpdateTimer = null; + } + } + private async Task GetStoreAppsJsonData() { if ((_storeAppsJsonData == null) || (DateTime.UtcNow > _storeAppsJsonDataUpdatedOn.AddSeconds(STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS))) { SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsWebService.DnsServer.Proxy; + handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) @@ -74,6 +178,45 @@ namespace DnsServerCore return _storeAppsJsonData; } + private async Task DownloadAndUpdateAppAsync(string applicationName, string url) + { + string tmpFile = Path.GetTempFileName(); + try + { + using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) + { + //download to temp file + SocketsHttpHandler handler = new SocketsHttpHandler(); + handler.Proxy = _dnsWebService.DnsServer.Proxy; + handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null; + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + + using (HttpClient http = new HttpClient(handler)) + { + using (Stream httpStream = await http.GetStreamAsync(url)) + { + await httpStream.CopyToAsync(fS); + } + } + + //update app + fS.Position = 0; + await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(applicationName, fS); + } + } + finally + { + try + { + File.Delete(tmpFile); + } + catch (Exception ex) + { + _dnsWebService.Log.Write(ex); + } + } + } + #endregion #region public @@ -81,7 +224,6 @@ namespace DnsServerCore public async Task ListInstalledAppsAsync(JsonTextWriter jsonWriter) { List apps = new List(_dnsWebService.DnsServer.DnsApplicationManager.Applications.Keys); - apps.Sort(); dynamic jsonStoreAppsArray = null; @@ -119,8 +261,31 @@ namespace DnsServerCore string name = jsonStoreApp.name.Value; if (name.Equals(application.Name)) { - string version = jsonStoreApp.version.Value; - string url = jsonStoreApp.url.Value; + string version = null; + string url = null; + Version storeAppVersion = null; + Version lastServerVersion = null; + + foreach (dynamic jsonVersion in jsonStoreApp.versions) + { + string strServerVersion = jsonVersion.serverVersion.Value; + Version requiredServerVersion = new Version(strServerVersion); + + if (_dnsWebService.ServerVersion < requiredServerVersion) + continue; + + if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion)) + continue; + + version = jsonVersion.version.Value; + url = jsonVersion.url.Value; + + storeAppVersion = new Version(version); + lastServerVersion = requiredServerVersion; + } + + if (storeAppVersion is null) + break; //no compatible update available jsonWriter.WritePropertyName("updateVersion"); jsonWriter.WriteValue(version); @@ -129,7 +294,7 @@ namespace DnsServerCore jsonWriter.WriteValue(url); jsonWriter.WritePropertyName("updateAvailable"); - jsonWriter.WriteValue(new Version(version) > application.Version); + jsonWriter.WriteValue(storeAppVersion > application.Version); break; } } @@ -196,22 +361,46 @@ namespace DnsServerCore foreach (dynamic jsonStoreApp in jsonStoreAppsArray) { string name = jsonStoreApp.name.Value; - string version = jsonStoreApp.version.Value; string description = jsonStoreApp.description.Value; - string url = jsonStoreApp.url.Value; - string size = jsonStoreApp.size.Value; + string version = null; + string url = null; + string size = null; + Version storeAppVersion = null; + Version lastServerVersion = null; + + foreach (dynamic jsonVersion in jsonStoreApp.versions) + { + string strServerVersion = jsonVersion.serverVersion.Value; + Version requiredServerVersion = new Version(strServerVersion); + + if (_dnsWebService.ServerVersion < requiredServerVersion) + continue; + + if ((lastServerVersion is not null) && (lastServerVersion > requiredServerVersion)) + continue; + + version = jsonVersion.version.Value; + url = jsonVersion.url.Value; + size = jsonVersion.size.Value; + + storeAppVersion = new Version(version); + lastServerVersion = requiredServerVersion; + } + + if (storeAppVersion is null) + continue; //app is not compatible jsonWriter.WriteStartObject(); jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(name); - jsonWriter.WritePropertyName("version"); - jsonWriter.WriteValue(version); - jsonWriter.WritePropertyName("description"); jsonWriter.WriteValue(description); + jsonWriter.WritePropertyName("version"); + jsonWriter.WriteValue(version); + jsonWriter.WritePropertyName("url"); jsonWriter.WriteValue(url); @@ -229,7 +418,7 @@ namespace DnsServerCore jsonWriter.WriteValue(DnsWebService.GetCleanVersion(installedApp.Version)); jsonWriter.WritePropertyName("updateAvailable"); - jsonWriter.WriteValue(new Version(version) > installedApp.Version); + jsonWriter.WriteValue(storeAppVersion > installedApp.Version); } jsonWriter.WriteEndObject(); @@ -261,6 +450,7 @@ namespace DnsServerCore //download to temp file SocketsHttpHandler handler = new SocketsHttpHandler(); handler.Proxy = _dnsWebService.DnsServer.Proxy; + handler.UseProxy = _dnsWebService.DnsServer.Proxy is not null; handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; using (HttpClient http = new HttpClient(handler)) @@ -275,7 +465,7 @@ namespace DnsServerCore fS.Position = 0; await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was installed successfully from: " + url); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was installed successfully from: " + url); } } finally @@ -306,42 +496,9 @@ namespace DnsServerCore if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'."); - string tmpFile = Path.GetTempFileName(); - try - { - using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite)) - { - //download to temp file - SocketsHttpHandler handler = new SocketsHttpHandler(); - handler.Proxy = _dnsWebService.DnsServer.Proxy; - handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + await DownloadAndUpdateAppAsync(name, url); - using (HttpClient http = new HttpClient(handler)) - { - using (Stream httpStream = await http.GetStreamAsync(url)) - { - await httpStream.CopyToAsync(fS); - } - } - - //update app - fS.Position = 0; - await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS); - - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was updated successfully from: " + url); - } - } - finally - { - try - { - File.Delete(tmpFile); - } - catch (Exception ex) - { - _dnsWebService.Log.Write(ex); - } - } + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was updated successfully from: " + url); } public async Task InstallAppAsync(HttpListenerRequest request) @@ -387,7 +544,7 @@ namespace DnsServerCore fS.Position = 0; await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was installed successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was installed successfully."); } } finally @@ -446,7 +603,7 @@ namespace DnsServerCore fS.Position = 0; await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was updated successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was updated successfully."); } } finally @@ -471,7 +628,7 @@ namespace DnsServerCore name = name.Trim(); _dnsWebService.DnsServer.DnsApplicationManager.UninstallApplication(name); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was uninstalled successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' was uninstalled successfully."); } public async Task GetAppConfigAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) @@ -514,14 +671,14 @@ namespace DnsServerCore { if (formPart.StartsWith("config=")) { - string config = formPart.Substring(7); + string config = Uri.UnescapeDataString(formPart.Substring(7)); if (config.Length == 0) config = null; await application.SetConfigAsync(config); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' app config was saved successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DNS application '" + name + "' app config was saved successfully."); return; } } @@ -530,5 +687,21 @@ namespace DnsServerCore } #endregion + + #region properties + + public bool EnableAutomaticUpdate + { + get { return _appUpdateTimer is not null; } + set + { + if (value) + StartAutomaticUpdate(); + else + StopAutomaticUpdate(); + } + } + + #endregion } } diff --git a/DnsServerCore/WebServiceAuthApi.cs b/DnsServerCore/WebServiceAuthApi.cs new file mode 100644 index 00000000..869e4548 --- /dev/null +++ b/DnsServerCore/WebServiceAuthApi.cs @@ -0,0 +1,1086 @@ +/* +Technitium DNS Server +Copyright (C) 2022 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.Auth; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +namespace DnsServerCore +{ + sealed class WebServiceAuthApi + { + #region variables + + readonly DnsWebService _dnsWebService; + + #endregion + + #region constructor + + public WebServiceAuthApi(DnsWebService dnsWebService) + { + _dnsWebService = dnsWebService; + } + + #endregion + + #region private + + private void WriteCurrentSessionDetails(JsonTextWriter jsonWriter, UserSession currentSession, bool includeInfo) + { + if (currentSession.Type == UserSessionType.ApiToken) + { + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(currentSession.User.Username); + + jsonWriter.WritePropertyName("tokenName"); + jsonWriter.WriteValue(currentSession.TokenName); + + jsonWriter.WritePropertyName("token"); + jsonWriter.WriteValue(currentSession.Token); + } + else + { + jsonWriter.WritePropertyName("displayName"); + jsonWriter.WriteValue(currentSession.User.DisplayName); + + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(currentSession.User.Username); + + jsonWriter.WritePropertyName("token"); + jsonWriter.WriteValue(currentSession.Token); + } + + if (includeInfo) + { + jsonWriter.WritePropertyName("info"); + jsonWriter.WriteStartObject(); + + jsonWriter.WritePropertyName("version"); + jsonWriter.WriteValue(_dnsWebService.GetServerVersion()); + + jsonWriter.WritePropertyName("dnsServerDomain"); + jsonWriter.WriteValue(_dnsWebService.DnsServer.ServerDomain); + + jsonWriter.WritePropertyName("defaultRecordTtl"); + jsonWriter.WriteValue(_dnsWebService.ZonesApi.DefaultRecordTtl); + + jsonWriter.WritePropertyName("permissions"); + jsonWriter.WriteStartObject(); + + for (int i = 1; i <= 11; i++) + { + PermissionSection section = (PermissionSection)i; + + jsonWriter.WritePropertyName(section.ToString()); + jsonWriter.WriteStartObject(); + + jsonWriter.WritePropertyName("canView"); + jsonWriter.WriteValue(_dnsWebService.AuthManager.IsPermitted(section, currentSession.User, PermissionFlag.View)); + + jsonWriter.WritePropertyName("canModify"); + jsonWriter.WriteValue(_dnsWebService.AuthManager.IsPermitted(section, currentSession.User, PermissionFlag.Modify)); + + jsonWriter.WritePropertyName("canDelete"); + jsonWriter.WriteValue(_dnsWebService.AuthManager.IsPermitted(section, currentSession.User, PermissionFlag.Delete)); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndObject(); + + jsonWriter.WriteEndObject(); + } + } + + private void WriteUserDetails(JsonTextWriter jsonWriter, User user, UserSession currentSession, bool includeMoreDetails, bool includeGroups) + { + jsonWriter.WritePropertyName("displayName"); + jsonWriter.WriteValue(user.DisplayName); + + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(user.Username); + + jsonWriter.WritePropertyName("disabled"); + jsonWriter.WriteValue(user.Disabled); + + jsonWriter.WritePropertyName("previousSessionLoggedOn"); + jsonWriter.WriteValue(user.PreviousSessionLoggedOn); + + jsonWriter.WritePropertyName("previousSessionRemoteAddress"); + jsonWriter.WriteValue(user.PreviousSessionRemoteAddress.ToString()); + + jsonWriter.WritePropertyName("recentSessionLoggedOn"); + jsonWriter.WriteValue(user.RecentSessionLoggedOn); + + jsonWriter.WritePropertyName("recentSessionRemoteAddress"); + jsonWriter.WriteValue(user.RecentSessionRemoteAddress.ToString()); + + if (includeMoreDetails) + { + jsonWriter.WritePropertyName("sessionTimeoutSeconds"); + jsonWriter.WriteValue(user.SessionTimeoutSeconds); + + jsonWriter.WritePropertyName("memberOfGroups"); + jsonWriter.WriteStartArray(); + + List memberOfGroups = new List(user.MemberOfGroups); + memberOfGroups.Sort(); + + foreach (Group group in memberOfGroups) + { + if (group.Name.Equals("Everyone", StringComparison.OrdinalIgnoreCase)) + continue; + + jsonWriter.WriteValue(group.Name); + } + + jsonWriter.WriteEndArray(); + + jsonWriter.WritePropertyName("sessions"); + jsonWriter.WriteStartArray(); + + List sessions = _dnsWebService.AuthManager.GetSessions(user); + sessions.Sort(); + + foreach (UserSession session in sessions) + WriteUserSessionDetails(jsonWriter, session, currentSession); + + jsonWriter.WriteEndArray(); + } + + if (includeGroups) + { + List groups = new List(_dnsWebService.AuthManager.Groups); + groups.Sort(); + + jsonWriter.WritePropertyName("groups"); + jsonWriter.WriteStartArray(); + + foreach (Group group in groups) + { + if (group.Name.Equals("Everyone", StringComparison.OrdinalIgnoreCase)) + continue; + + jsonWriter.WriteValue(group.Name); + } + + jsonWriter.WriteEndArray(); + } + } + + private static void WriteUserSessionDetails(JsonTextWriter jsonWriter, UserSession session, UserSession currentSession) + { + jsonWriter.WriteStartObject(); + + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(session.User.Username); + + jsonWriter.WritePropertyName("isCurrentSession"); + jsonWriter.WriteValue(session.Equals(currentSession)); + + jsonWriter.WritePropertyName("partialToken"); + jsonWriter.WriteValue(session.Token.Substring(0, 16)); + + jsonWriter.WritePropertyName("type"); + jsonWriter.WriteValue(session.Type.ToString()); + + jsonWriter.WritePropertyName("tokenName"); + jsonWriter.WriteValue(session.TokenName); + + jsonWriter.WritePropertyName("lastSeen"); + jsonWriter.WriteValue(session.LastSeen); + + jsonWriter.WritePropertyName("lastSeenRemoteAddress"); + jsonWriter.WriteValue(session.LastSeenRemoteAddress.ToString()); + + jsonWriter.WritePropertyName("lastSeenUserAgent"); + jsonWriter.WriteValue(session.LastSeenUserAgent); + + jsonWriter.WriteEndObject(); + } + + private void WriteGroupDetails(JsonTextWriter jsonWriter, Group group, bool includeMembers, bool includeUsers) + { + jsonWriter.WritePropertyName("name"); + jsonWriter.WriteValue(group.Name); + + jsonWriter.WritePropertyName("description"); + jsonWriter.WriteValue(group.Description); + + if (includeMembers) + { + jsonWriter.WritePropertyName("members"); + jsonWriter.WriteStartArray(); + + List members = _dnsWebService.AuthManager.GetGroupMembers(group); + members.Sort(); + + foreach (User user in members) + jsonWriter.WriteValue(user.Username); + + jsonWriter.WriteEndArray(); + } + + if (includeUsers) + { + List users = new List(_dnsWebService.AuthManager.Users); + users.Sort(); + + jsonWriter.WritePropertyName("users"); + jsonWriter.WriteStartArray(); + + foreach (User user in users) + jsonWriter.WriteValue(user.Username); + + jsonWriter.WriteEndArray(); + } + } + + private void WritePermissionDetails(JsonTextWriter jsonWriter, Permission permission, string subItem, bool includeUsersAndGroups) + { + jsonWriter.WritePropertyName("section"); + jsonWriter.WriteValue(permission.Section.ToString()); + + if (subItem is not null) + { + jsonWriter.WritePropertyName("subItem"); + jsonWriter.WriteValue(subItem.Length == 0 ? "." : subItem); + } + + jsonWriter.WritePropertyName("userPermissions"); + jsonWriter.WriteStartArray(); + + List> userPermissions = new List>(permission.UserPermissions); + + userPermissions.Sort(delegate (KeyValuePair x, KeyValuePair y) + { + return x.Key.Username.CompareTo(y.Key.Username); + }); + + foreach (KeyValuePair userPermission in userPermissions) + { + jsonWriter.WriteStartObject(); + + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(userPermission.Key.Username); + + jsonWriter.WritePropertyName("canView"); + jsonWriter.WriteValue(userPermission.Value.HasFlag(PermissionFlag.View)); + + jsonWriter.WritePropertyName("canModify"); + jsonWriter.WriteValue(userPermission.Value.HasFlag(PermissionFlag.Modify)); + + jsonWriter.WritePropertyName("canDelete"); + jsonWriter.WriteValue(userPermission.Value.HasFlag(PermissionFlag.Delete)); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + + jsonWriter.WritePropertyName("groupPermissions"); + jsonWriter.WriteStartArray(); + + List> groupPermissions = new List>(permission.GroupPermissions); + + groupPermissions.Sort(delegate (KeyValuePair x, KeyValuePair y) + { + return x.Key.Name.CompareTo(y.Key.Name); + }); + + foreach (KeyValuePair groupPermission in groupPermissions) + { + jsonWriter.WriteStartObject(); + + jsonWriter.WritePropertyName("name"); + jsonWriter.WriteValue(groupPermission.Key.Name); + + jsonWriter.WritePropertyName("canView"); + jsonWriter.WriteValue(groupPermission.Value.HasFlag(PermissionFlag.View)); + + jsonWriter.WritePropertyName("canModify"); + jsonWriter.WriteValue(groupPermission.Value.HasFlag(PermissionFlag.Modify)); + + jsonWriter.WritePropertyName("canDelete"); + jsonWriter.WriteValue(groupPermission.Value.HasFlag(PermissionFlag.Delete)); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + + if (includeUsersAndGroups) + { + List users = new List(_dnsWebService.AuthManager.Users); + users.Sort(); + + List groups = new List(_dnsWebService.AuthManager.Groups); + groups.Sort(); + + jsonWriter.WritePropertyName("users"); + jsonWriter.WriteStartArray(); + + foreach (User user in users) + jsonWriter.WriteValue(user.Username); + + jsonWriter.WriteEndArray(); + + jsonWriter.WritePropertyName("groups"); + jsonWriter.WriteStartArray(); + + foreach (Group group in groups) + jsonWriter.WriteValue(group.Name); + + jsonWriter.WriteEndArray(); + } + } + + #endregion + + #region public + + public async Task LoginAsync(HttpListenerRequest request, JsonTextWriter jsonWriter, UserSessionType sessionType) + { + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + string strPassword = request.QueryString["pass"]; + if (string.IsNullOrEmpty(strPassword)) + throw new DnsWebServiceException("Parameter 'pass' missing."); + + string strTokenName = null; + + if (sessionType == UserSessionType.ApiToken) + { + strTokenName = request.QueryString["tokenName"]; + if (string.IsNullOrEmpty(strTokenName)) + throw new DnsWebServiceException("Parameter 'tokenName' missing."); + } + + bool includeInfo; + string strIncludeInfo = request.QueryString["includeInfo"]; + if (string.IsNullOrEmpty(strIncludeInfo)) + includeInfo = false; + else + includeInfo = bool.Parse(strIncludeInfo); + + IPEndPoint remoteEP = DnsWebService.GetRequestRemoteEndPoint(request); + + UserSession session = await _dnsWebService.AuthManager.CreateSessionAsync(sessionType, strTokenName, strUsername, strPassword, remoteEP.Address, request.UserAgent); + + _dnsWebService.Log.Write(remoteEP, "[" + session.User.Username + "] User logged in."); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteCurrentSessionDetails(jsonWriter, session, includeInfo); + } + + public void Logout(HttpListenerRequest request) + { + string strToken = request.QueryString["token"]; + if (string.IsNullOrEmpty(strToken)) + throw new DnsWebServiceException("Parameter 'token' missing."); + + UserSession session = _dnsWebService.AuthManager.DeleteSession(strToken); + if (session is not null) + { + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User logged out."); + + _dnsWebService.AuthManager.SaveConfigFile(); + } + } + + public void GetCurrentSessionDetails(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + if (!_dnsWebService.TryGetSession(request, out UserSession session)) + throw new InvalidTokenWebServiceException("Invalid token or session expired."); + + WriteCurrentSessionDetails(jsonWriter, session, true); + } + + public void ChangePassword(HttpListenerRequest request) + { + UserSession session = _dnsWebService.GetSession(request); + + if (session.Type != UserSessionType.Standard) + throw new DnsWebServiceException("Access was denied."); + + string strPassword = request.QueryString["pass"]; + if (string.IsNullOrEmpty(strPassword)) + throw new DnsWebServiceException("Parameter 'pass' missing."); + + session.User.ChangePassword(strPassword); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Password was changed successfully."); + + _dnsWebService.AuthManager.SaveConfigFile(); + } + + public void GetProfile(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + UserSession session = _dnsWebService.GetSession(request); + + WriteUserDetails(jsonWriter, session.User, session, true, false); + } + + public void SetProfile(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + UserSession session = _dnsWebService.GetSession(request); + + if (session.Type != UserSessionType.Standard) + throw new DnsWebServiceException("Access was denied."); + + string strDisplayName = request.QueryString["displayName"]; + if (!string.IsNullOrEmpty(strDisplayName)) + session.User.DisplayName = strDisplayName; + + string strSessionTimeoutSeconds = request.QueryString["sessionTimeoutSeconds"]; + if (!string.IsNullOrEmpty(strSessionTimeoutSeconds)) + session.User.SessionTimeoutSeconds = int.Parse(strSessionTimeoutSeconds); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User profile was updated successfully."); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteUserDetails(jsonWriter, session.User, session, true, false); + } + + public void ListSessions(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + UserSession session = _dnsWebService.GetSession(request); + + jsonWriter.WritePropertyName("sessions"); + jsonWriter.WriteStartArray(); + + List sessions = new List(_dnsWebService.AuthManager.Sessions); + sessions.Sort(); + + foreach (UserSession activeSession in sessions) + { + if (!activeSession.HasExpired()) + WriteUserSessionDetails(jsonWriter, activeSession, session); + } + + jsonWriter.WriteEndArray(); + } + + public void CreateApiToken(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + string strTokenName = request.QueryString["tokenName"]; + if (string.IsNullOrEmpty(strTokenName)) + throw new DnsWebServiceException("Parameter 'tokenName' missing."); + + IPEndPoint remoteEP = DnsWebService.GetRequestRemoteEndPoint(request); + + UserSession session = _dnsWebService.AuthManager.CreateApiToken(strTokenName, strUsername, remoteEP.Address, request.UserAgent); + + _dnsWebService.Log.Write(remoteEP, "[" + session.User.Username + "] API token [" + strTokenName + "] was created successfully for user: " + strUsername); + + _dnsWebService.AuthManager.SaveConfigFile(); + + jsonWriter.WritePropertyName("username"); + jsonWriter.WriteValue(session.User.Username); + + jsonWriter.WritePropertyName("tokenName"); + jsonWriter.WriteValue(session.TokenName); + + jsonWriter.WritePropertyName("token"); + jsonWriter.WriteValue(session.Token); + } + + public void DeleteSession(HttpListenerRequest request, bool isAdminContext) + { + string strPartialToken = request.QueryString["partialToken"]; + if (string.IsNullOrEmpty(strPartialToken)) + throw new DnsWebServiceException("Parameter 'partialToken' missing."); + + UserSession session = _dnsWebService.GetSession(request); + + if (session.Token.StartsWith(strPartialToken)) + throw new InvalidOperationException("Invalid operation: cannot delete current session."); + + string token = null; + + foreach (UserSession activeSession in _dnsWebService.AuthManager.Sessions) + { + if (activeSession.Token.StartsWith(strPartialToken)) + { + token = activeSession.Token; + break; + } + } + + if (token is null) + throw new DnsWebServiceException("No such active session was found for partial token: " + strPartialToken); + + if (!isAdminContext) + { + UserSession sessionToDelete = _dnsWebService.AuthManager.GetSession(token); + if (sessionToDelete.User != session.User) + throw new DnsWebServiceException("Access was denied."); + } + + UserSession deletedSession = _dnsWebService.AuthManager.DeleteSession(token); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User session [" + strPartialToken + "] was deleted successfully for user: " + deletedSession.User.Username); + + _dnsWebService.AuthManager.SaveConfigFile(); + } + + public void ListUsers(JsonTextWriter jsonWriter) + { + List users = new List(_dnsWebService.AuthManager.Users); + users.Sort(); + + jsonWriter.WritePropertyName("users"); + jsonWriter.WriteStartArray(); + + foreach (User user in users) + { + jsonWriter.WriteStartObject(); + + WriteUserDetails(jsonWriter, user, null, false, false); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + } + + public void CreateUser(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strDisplayName = request.QueryString["displayName"]; + + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + string strPassword = request.QueryString["pass"]; + if (string.IsNullOrEmpty(strPassword)) + throw new DnsWebServiceException("Parameter 'pass' missing."); + + User user = _dnsWebService.AuthManager.CreateUser(strDisplayName, strUsername, strPassword); + + UserSession session = _dnsWebService.GetSession(request); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User account was created successfully with username: " + user.Username); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteUserDetails(jsonWriter, user, null, false, false); + } + + public void GetUserDetails(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + bool includeGroups; + string strIncludeGroups = request.QueryString["includeGroups"]; + if (string.IsNullOrEmpty(strIncludeGroups)) + includeGroups = false; + else + includeGroups = bool.Parse(strIncludeGroups); + + User user = _dnsWebService.AuthManager.GetUser(strUsername); + if (user is null) + throw new DnsWebServiceException("No such user exists: " + strUsername); + + WriteUserDetails(jsonWriter, user, null, true, includeGroups); + } + + public void SetUserDetails(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + User user = _dnsWebService.AuthManager.GetUser(strUsername); + if (user is null) + throw new DnsWebServiceException("No such user exists: " + strUsername); + + string strDisplayName = request.QueryString["displayName"]; + if (!string.IsNullOrEmpty(strDisplayName)) + user.DisplayName = strDisplayName; + + string strNewUsername = request.QueryString["newUser"]; + if (!string.IsNullOrEmpty(strNewUsername)) + _dnsWebService.AuthManager.ChangeUsername(user, strNewUsername); + + UserSession session = _dnsWebService.GetSession(request); + + string strDisabled = request.QueryString["disabled"]; + if (!string.IsNullOrEmpty(strDisabled) && (session.User != user)) //to avoid self lockout + { + user.Disabled = bool.Parse(strDisabled); + + if (user.Disabled) + { + foreach (UserSession userSession in _dnsWebService.AuthManager.Sessions) + { + if (userSession.Type == UserSessionType.ApiToken) + continue; + + if (userSession.User == user) + _dnsWebService.AuthManager.DeleteSession(userSession.Token); + } + } + } + + string strSessionTimeoutSeconds = request.QueryString["sessionTimeoutSeconds"]; + if (!string.IsNullOrEmpty(strSessionTimeoutSeconds)) + user.SessionTimeoutSeconds = int.Parse(strSessionTimeoutSeconds); + + string strNewPassword = request.QueryString["newPass"]; + if (!string.IsNullOrWhiteSpace(strNewPassword)) + { + int iterations; + string strIterations = request.QueryString["iterations"]; + if (string.IsNullOrEmpty(strIterations)) + iterations = User.DEFAULT_ITERATIONS; + else + iterations = int.Parse(strIterations); + + user.ChangePassword(strNewPassword, iterations); + } + + string strMemberOfGroups = request.QueryString["memberOfGroups"]; + if (strMemberOfGroups is not null) + { + string[] parts = strMemberOfGroups.Split(','); + Dictionary groups = new Dictionary(parts.Length); + + foreach (string part in parts) + { + if (part.Length == 0) + continue; + + Group group = _dnsWebService.AuthManager.GetGroup(part); + if (group is null) + throw new DnsWebServiceException("No such group exists: " + part); + + groups.Add(group.Name.ToLower(), group); + } + + //ensure user is member of everyone group + Group everyone = _dnsWebService.AuthManager.GetGroup(Group.EVERYONE); + groups[everyone.Name.ToLower()] = everyone; + + if (session.User == user) + { + //ensure current admin user is member of administrators group to avoid self lockout + Group admins = _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS); + groups[admins.Name.ToLower()] = admins; + } + + user.SyncGroups(groups); + } + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User account details were updated successfully for user: " + strUsername); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteUserDetails(jsonWriter, user, null, true, false); + } + + public void DeleteUser(HttpListenerRequest request) + { + string strUsername = request.QueryString["user"]; + if (string.IsNullOrEmpty(strUsername)) + throw new DnsWebServiceException("Parameter 'user' missing."); + + UserSession session = _dnsWebService.GetSession(request); + + if (session.User.Username.Equals(strUsername, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException("Invalid operation: cannot delete current user."); + + if (!_dnsWebService.AuthManager.DeleteUser(strUsername)) + throw new DnsWebServiceException("Failed to delete user: " + strUsername); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] User account was deleted successfully with username: " + strUsername); + + _dnsWebService.AuthManager.SaveConfigFile(); + } + + public void ListGroups(JsonTextWriter jsonWriter) + { + List groups = new List(_dnsWebService.AuthManager.Groups); + groups.Sort(); + + jsonWriter.WritePropertyName("groups"); + jsonWriter.WriteStartArray(); + + foreach (Group group in groups) + { + if (group.Name.Equals("Everyone", StringComparison.OrdinalIgnoreCase)) + continue; + + jsonWriter.WriteStartObject(); + + WriteGroupDetails(jsonWriter, group, false, false); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + } + + public void CreateGroup(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strGroup = request.QueryString["group"]; + if (string.IsNullOrEmpty(strGroup)) + throw new DnsWebServiceException("Parameter 'group' missing."); + + string strDescription = request.QueryString["description"]; + if (string.IsNullOrEmpty(strDescription)) + strDescription = ""; + + Group group = _dnsWebService.AuthManager.CreateGroup(strGroup, strDescription); + + UserSession session = _dnsWebService.GetSession(request); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Group was created successfully with name: " + group.Name); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteGroupDetails(jsonWriter, group, false, false); + } + + public void GetGroupDetails(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strGroup = request.QueryString["group"]; + if (string.IsNullOrEmpty(strGroup)) + throw new DnsWebServiceException("Parameter 'group' missing."); + + bool includeUsers; + string strIncludeGroups = request.QueryString["includeUsers"]; + if (string.IsNullOrEmpty(strIncludeGroups)) + includeUsers = false; + else + includeUsers = bool.Parse(strIncludeGroups); + + Group group = _dnsWebService.AuthManager.GetGroup(strGroup); + if (group is null) + throw new DnsWebServiceException("No such group exists: " + strGroup); + + WriteGroupDetails(jsonWriter, group, true, includeUsers); + } + + public void SetGroupDetails(HttpListenerRequest request, JsonTextWriter jsonWriter) + { + string strGroup = request.QueryString["group"]; + if (string.IsNullOrEmpty(strGroup)) + throw new DnsWebServiceException("Parameter 'group' missing."); + + Group group = _dnsWebService.AuthManager.GetGroup(strGroup); + if (group is null) + throw new DnsWebServiceException("No such group exists: " + strGroup); + + string strNewGroup = request.QueryString["newGroup"]; + if (!string.IsNullOrEmpty(strNewGroup)) + _dnsWebService.AuthManager.RenameGroup(group, strNewGroup); + + string strDescription = request.QueryString["description"]; + if (!string.IsNullOrEmpty(strDescription)) + group.Description = strDescription; + + UserSession session = _dnsWebService.GetSession(request); + + string strMembers = request.QueryString["members"]; + if (strMembers is not null) + { + string[] parts = strMembers.Split(','); + Dictionary users = new Dictionary(); + + foreach (string part in parts) + { + if (part.Length == 0) + continue; + + User user = _dnsWebService.AuthManager.GetUser(part); + if (user is null) + throw new DnsWebServiceException("No such user exists: " + part); + + users.Add(user.Username, user); + } + + if (group.Name.Equals("administrators", StringComparison.OrdinalIgnoreCase)) + users[session.User.Username] = session.User; //ensure current admin user is member of administrators group to avoid self lockout + + _dnsWebService.AuthManager.SyncGroupMembers(group, users); + } + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Group details were updated successfully for group: " + strGroup); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WriteGroupDetails(jsonWriter, group, true, false); + } + + public void DeleteGroup(HttpListenerRequest request) + { + string strGroup = request.QueryString["group"]; + if (string.IsNullOrEmpty(strGroup)) + throw new DnsWebServiceException("Parameter 'group' missing."); + + if (!_dnsWebService.AuthManager.DeleteGroup(strGroup)) + throw new DnsWebServiceException("Failed to delete group: " + strGroup); + + UserSession session = _dnsWebService.GetSession(request); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Group was deleted successfully with name: " + strGroup); + + _dnsWebService.AuthManager.SaveConfigFile(); + } + + public void ListPermissions(JsonTextWriter jsonWriter) + { + List permissions = new List(_dnsWebService.AuthManager.Permissions); + permissions.Sort(); + + jsonWriter.WritePropertyName("permissions"); + jsonWriter.WriteStartArray(); + + foreach (Permission permission in permissions) + { + jsonWriter.WriteStartObject(); + + WritePermissionDetails(jsonWriter, permission, null, false); + + jsonWriter.WriteEndObject(); + } + + jsonWriter.WriteEndArray(); + } + + public void GetPermissionDetails(HttpListenerRequest request, JsonTextWriter jsonWriter, PermissionSection section) + { + if (section == PermissionSection.Unknown) + { + string strSection = request.QueryString["section"]; + if (string.IsNullOrEmpty(strSection)) + throw new DnsWebServiceException("Parameter 'section' missing."); + + if (!Enum.TryParse(strSection, true, out section)) + throw new DnsWebServiceException("No such permission section exists: " + strSection); + } + + string strSubItem; + + switch (section) + { + case PermissionSection.Zones: + strSubItem = request.QueryString["zone"]; + + if (strSubItem is not null) + strSubItem = strSubItem.TrimEnd('.'); + + break; + + default: + strSubItem = null; + break; + } + + bool includeUsersAndGroups; + string strIncludeUsersAndGroups = request.QueryString["includeUsersAndGroups"]; + if (string.IsNullOrEmpty(strIncludeUsersAndGroups)) + includeUsersAndGroups = false; + else + includeUsersAndGroups = bool.Parse(strIncludeUsersAndGroups); + + if (strSubItem is not null) + { + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(section, strSubItem, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + } + + Permission permission; + + if (strSubItem is null) + permission = _dnsWebService.AuthManager.GetPermission(section); + else + permission = _dnsWebService.AuthManager.GetPermission(section, strSubItem); + + if (permission is null) + throw new DnsWebServiceException("No permissions exists for section: " + section.ToString() + (strSubItem is null ? "" : "/" + strSubItem)); + + WritePermissionDetails(jsonWriter, permission, strSubItem, includeUsersAndGroups); + } + + public void SetPermissionsDetails(HttpListenerRequest request, JsonTextWriter jsonWriter, PermissionSection section) + { + if (section == PermissionSection.Unknown) + { + string strSection = request.QueryString["section"]; + if (string.IsNullOrEmpty(strSection)) + throw new DnsWebServiceException("Parameter 'section' missing."); + + if (!Enum.TryParse(strSection, true, out section)) + throw new DnsWebServiceException("No such permission section exists: " + strSection); + } + + string strSubItem; + + switch (section) + { + case PermissionSection.Zones: + strSubItem = request.QueryString["zone"]; + + if (strSubItem is not null) + strSubItem = strSubItem.TrimEnd('.'); + + break; + + default: + strSubItem = null; + break; + } + + UserSession session = _dnsWebService.GetSession(request); + + if (strSubItem is not null) + { + if (!_dnsWebService.AuthManager.IsPermitted(section, strSubItem, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + } + + Permission permission; + + if (strSubItem is null) + permission = _dnsWebService.AuthManager.GetPermission(section); + else + permission = _dnsWebService.AuthManager.GetPermission(section, strSubItem); + + if (permission is null) + throw new DnsWebServiceException("No permissions exists for section: " + section.ToString() + (strSubItem is null ? "" : "/" + strSubItem)); + + string strUserPermissions = request.QueryString["userPermissions"]; + if (strUserPermissions is not null) + { + string[] parts = strUserPermissions.Split('|'); + Dictionary userPermissions = new Dictionary(); + + for (int i = 0; i < parts.Length; i += 4) + { + if (parts[i].Length == 0) + continue; + + User user = _dnsWebService.AuthManager.GetUser(parts[i]); + bool canView = bool.Parse(parts[i + 1]); + bool canModify = bool.Parse(parts[i + 2]); + bool canDelete = bool.Parse(parts[i + 3]); + + if (user is not null) + { + PermissionFlag permissionFlag = PermissionFlag.None; + + if (canView) + permissionFlag |= PermissionFlag.View; + + if (canModify) + permissionFlag |= PermissionFlag.Modify; + + if (canDelete) + permissionFlag |= PermissionFlag.Delete; + + userPermissions[user] = permissionFlag; + } + } + + permission.SyncPermissions(userPermissions); + } + + string strGroupPermissions = request.QueryString["groupPermissions"]; + if (strGroupPermissions is not null) + { + string[] parts = strGroupPermissions.Split('|'); + Dictionary groupPermissions = new Dictionary(); + + for (int i = 0; i < parts.Length; i += 4) + { + if (parts[i].Length == 0) + continue; + + Group group = _dnsWebService.AuthManager.GetGroup(parts[i]); + bool canView = bool.Parse(parts[i + 1]); + bool canModify = bool.Parse(parts[i + 2]); + bool canDelete = bool.Parse(parts[i + 3]); + + if (group is not null) + { + PermissionFlag permissionFlag = PermissionFlag.None; + + if (canView) + permissionFlag |= PermissionFlag.View; + + if (canModify) + permissionFlag |= PermissionFlag.Modify; + + if (canDelete) + permissionFlag |= PermissionFlag.Delete; + + groupPermissions[group] = permissionFlag; + } + } + + //ensure administrators group always has all permissions + Group admins = _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS); + groupPermissions[admins] = PermissionFlag.ViewModifyDelete; + + switch (section) + { + case PermissionSection.Zones: + //ensure DNS administrators group always has all permissions + Group dnsAdmins = _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS); + groupPermissions[dnsAdmins] = PermissionFlag.ViewModifyDelete; + break; + + case PermissionSection.DhcpServer: + //ensure DHCP administrators group always has all permissions + Group dhcpAdmins = _dnsWebService.AuthManager.GetGroup(Group.DHCP_ADMINISTRATORS); + groupPermissions[dhcpAdmins] = PermissionFlag.ViewModifyDelete; + break; + } + + permission.SyncPermissions(groupPermissions); + } + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Permissions were updated successfully for section: " + section.ToString() + (string.IsNullOrEmpty(strSubItem) ? "" : "/" + strSubItem)); + + _dnsWebService.AuthManager.SaveConfigFile(); + + WritePermissionDetails(jsonWriter, permission, strSubItem, false); + } + + #endregion + } +} diff --git a/DnsServerCore/WebServiceDhcpApi.cs b/DnsServerCore/WebServiceDhcpApi.cs index eeaac0c9..1f582f63 100644 --- a/DnsServerCore/WebServiceDhcpApi.cs +++ b/DnsServerCore/WebServiceDhcpApi.cs @@ -638,13 +638,13 @@ namespace DnsServerCore { _dnsWebService.DhcpServer.SaveScope(scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was updated successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was updated successfully: " + scopeName); } else { await _dnsWebService.DhcpServer.AddScopeAsync(scope); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was added successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was added successfully: " + scopeName); } string newName = request.QueryString["newName"]; @@ -652,10 +652,64 @@ namespace DnsServerCore { _dnsWebService.DhcpServer.RenameScope(scopeName, newName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was renamed successfully: '" + scopeName + "' to '" + newName + "'"); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was renamed successfully: '" + scopeName + "' to '" + newName + "'"); } } + public void AddReservedLease(HttpListenerRequest request) + { + string scopeName = request.QueryString["name"]; + if (string.IsNullOrEmpty(scopeName)) + throw new DnsWebServiceException("Parameter 'name' missing."); + + Scope scope = _dnsWebService.DhcpServer.GetScope(scopeName); + if (scope is null) + throw new DnsWebServiceException("No such scope exists: " + scopeName); + + string hostName = request.QueryString["hostName"]; + + string hardwareAddress = request.QueryString["hardwareAddress"]; + if (string.IsNullOrEmpty(hardwareAddress)) + throw new DnsWebServiceException("Parameter 'hardwareAddress' missing."); + + string strIpAddress = request.QueryString["ipAddress"]; + if (string.IsNullOrEmpty(strIpAddress)) + throw new DnsWebServiceException("Parameter 'ipAddress' missing."); + + string comments = request.QueryString["comments"]; + + Lease reservedLease = new Lease(LeaseType.Reserved, hostName, DhcpMessageHardwareAddressType.Ethernet, hardwareAddress, IPAddress.Parse(strIpAddress), comments); + + if (!scope.AddReservedLease(reservedLease)) + throw new DnsWebServiceException("Failed to add reserved lease for scope: " + scopeName); + + _dnsWebService.DhcpServer.SaveScope(scopeName); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope reserved lease was added successfully: " + scopeName); + } + + public void RemoveReservedLease(HttpListenerRequest request) + { + string scopeName = request.QueryString["name"]; + if (string.IsNullOrEmpty(scopeName)) + throw new DnsWebServiceException("Parameter 'name' missing."); + + Scope scope = _dnsWebService.DhcpServer.GetScope(scopeName); + if (scope is null) + throw new DnsWebServiceException("No such scope exists: " + scopeName); + + string hardwareAddress = request.QueryString["hardwareAddress"]; + if (string.IsNullOrEmpty(hardwareAddress)) + throw new DnsWebServiceException("Parameter 'hardwareAddress' missing."); + + if (!scope.RemoveReservedLease(hardwareAddress)) + throw new DnsWebServiceException("Failed to remove reserved lease for scope: " + scopeName); + + _dnsWebService.DhcpServer.SaveScope(scopeName); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope reserved lease was removed successfully: " + scopeName); + } + public async Task EnableDhcpScopeAsync(HttpListenerRequest request) { string scopeName = request.QueryString["name"]; @@ -665,7 +719,7 @@ namespace DnsServerCore if (!await _dnsWebService.DhcpServer.EnableScopeAsync(scopeName)) throw new DnsWebServiceException("Failed to enable DHCP scope, please check logs for details: " + scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was enabled successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was enabled successfully: " + scopeName); } public void DisableDhcpScope(HttpListenerRequest request) @@ -677,7 +731,7 @@ namespace DnsServerCore if (!_dnsWebService.DhcpServer.DisableScope(scopeName)) throw new DnsWebServiceException("Failed to disable DHCP scope, please check logs for details: " + scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was disabled successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was disabled successfully: " + scopeName); } public void DeleteDhcpScope(HttpListenerRequest request) @@ -688,7 +742,7 @@ namespace DnsServerCore _dnsWebService.DhcpServer.DeleteScope(scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope was deleted successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope was deleted successfully: " + scopeName); } public void RemoveDhcpLease(HttpListenerRequest request) @@ -713,7 +767,7 @@ namespace DnsServerCore _dnsWebService.DhcpServer.SaveScope(scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope's lease was removed successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope's lease was removed successfully: " + scopeName); } public void ConvertToReservedLease(HttpListenerRequest request) @@ -738,7 +792,7 @@ namespace DnsServerCore _dnsWebService.DhcpServer.SaveScope(scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope's lease was reserved successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope's lease was reserved successfully: " + scopeName); } public void ConvertToDynamicLease(HttpListenerRequest request) @@ -763,7 +817,7 @@ namespace DnsServerCore _dnsWebService.DhcpServer.SaveScope(scopeName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DHCP scope's lease was unreserved successfully: " + scopeName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] DHCP scope's lease was unreserved successfully: " + scopeName); } #endregion diff --git a/DnsServerCore/WebServiceLogsApi.cs b/DnsServerCore/WebServiceLogsApi.cs index a5b67fd4..dbbc33b3 100644 --- a/DnsServerCore/WebServiceLogsApi.cs +++ b/DnsServerCore/WebServiceLogsApi.cs @@ -1,6 +1,6 @@ /* Technitium DNS Server -Copyright (C) 2021 Shreyas Zare (shreyas@technitium.com) +Copyright (C) 2022 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 @@ -76,6 +76,22 @@ namespace DnsServerCore jsonWriter.WriteEndArray(); } + public Task DownloadLogAsync(HttpListenerRequest request, HttpListenerResponse response) + { + string strFileName = request.QueryString["fileName"]; + if (string.IsNullOrEmpty(strFileName)) + throw new DnsWebServiceException("Parameter 'fileName' missing."); + + int limit; + string strLimit = request.QueryString["limit"]; + if (string.IsNullOrEmpty(strLimit)) + limit = 0; + else + limit = int.Parse(strLimit); + + return _dnsWebService.Log.DownloadLogAsync(request, response, strFileName, limit * 1024 * 1024); + } + public void DeleteLog(HttpListenerRequest request) { string log = request.QueryString["log"]; @@ -84,21 +100,21 @@ namespace DnsServerCore _dnsWebService.Log.DeleteLog(log); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Log file was deleted: " + log); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Log file was deleted: " + log); } public void DeleteAllLogs(HttpListenerRequest request) { _dnsWebService.Log.DeleteAllLogs(); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] All log files were deleted."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] All log files were deleted."); } public void DeleteAllStats(HttpListenerRequest request) { _dnsWebService.DnsServer.StatsManager.DeleteAllStats(); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] All stats files were deleted."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] All stats files were deleted."); } public async Task QueryLogsAsync(HttpListenerRequest request, JsonTextWriter jsonWriter) diff --git a/DnsServerCore/WebServiceOtherZonesApi.cs b/DnsServerCore/WebServiceOtherZonesApi.cs index 42fc336b..115743c8 100644 --- a/DnsServerCore/WebServiceOtherZonesApi.cs +++ b/DnsServerCore/WebServiceOtherZonesApi.cs @@ -19,6 +19,7 @@ along with this program. If not, see . using DnsServerCore.Dns.Zones; using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.IO; using System.Net; @@ -53,7 +54,7 @@ namespace DnsServerCore { _dnsWebService.DnsServer.CacheZoneManager.Flush(); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Cache was flushed."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Cache was flushed."); } public void ListCachedZones(HttpListenerRequest request, JsonTextWriter jsonWriter) @@ -128,7 +129,7 @@ namespace DnsServerCore throw new DnsWebServiceException("Parameter 'domain' missing."); if (_dnsWebService.DnsServer.CacheZoneManager.DeleteZone(domain)) - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Cached zone was deleted: " + domain); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Cached zone was deleted: " + domain); } #endregion @@ -217,7 +218,8 @@ namespace DnsServerCore { if (formPart.StartsWith("allowedZones=")) { - string[] allowedZones = formPart.Substring(13).Split(','); + string value = Uri.UnescapeDataString(formPart.Substring(13)); + string[] allowedZones = value.Split(','); bool added = false; foreach (string allowedZone in allowedZones) @@ -228,7 +230,7 @@ namespace DnsServerCore if (added) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Total " + allowedZones.Length + " zones were imported into allowed zone successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Total " + allowedZones.Length + " zones were imported into allowed zone successfully."); _dnsWebService.DnsServer.AllowedZoneManager.SaveZoneFile(); } @@ -261,7 +263,7 @@ namespace DnsServerCore if (_dnsWebService.DnsServer.AllowedZoneManager.DeleteZone(domain)) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Allowed zone was deleted: " + domain); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Allowed zone was deleted: " + domain); _dnsWebService.DnsServer.AllowedZoneManager.SaveZoneFile(); } } @@ -270,7 +272,7 @@ namespace DnsServerCore { _dnsWebService.DnsServer.AllowedZoneManager.Flush(); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Allowed zone was flushed successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Allowed zone was flushed successfully."); _dnsWebService.DnsServer.AllowedZoneManager.SaveZoneFile(); } @@ -285,7 +287,7 @@ namespace DnsServerCore if (_dnsWebService.DnsServer.AllowedZoneManager.AllowZone(domain)) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Zone was allowed: " + domain); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Zone was allowed: " + domain); _dnsWebService.DnsServer.AllowedZoneManager.SaveZoneFile(); } } @@ -376,7 +378,8 @@ namespace DnsServerCore { if (formPart.StartsWith("blockedZones=")) { - string[] blockedZones = formPart.Substring(13).Split(','); + string value = Uri.UnescapeDataString(formPart.Substring(13)); + string[] blockedZones = value.Split(','); bool added = false; foreach (string blockedZone in blockedZones) @@ -387,7 +390,7 @@ namespace DnsServerCore if (added) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Total " + blockedZones.Length + " zones were imported into blocked zone successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Total " + blockedZones.Length + " zones were imported into blocked zone successfully."); _dnsWebService.DnsServer.BlockedZoneManager.SaveZoneFile(); } @@ -420,7 +423,7 @@ namespace DnsServerCore if (_dnsWebService.DnsServer.BlockedZoneManager.DeleteZone(domain)) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Blocked zone was deleted: " + domain); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Blocked zone was deleted: " + domain); _dnsWebService.DnsServer.BlockedZoneManager.SaveZoneFile(); } } @@ -429,7 +432,7 @@ namespace DnsServerCore { _dnsWebService.DnsServer.BlockedZoneManager.Flush(); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Blocked zone was flushed successfully."); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Blocked zone was flushed successfully."); _dnsWebService.DnsServer.BlockedZoneManager.SaveZoneFile(); } @@ -444,7 +447,7 @@ namespace DnsServerCore if (_dnsWebService.DnsServer.BlockedZoneManager.BlockZone(domain)) { - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Domain was added to blocked zone: " + domain); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).User.Username + "] Domain was added to blocked zone: " + domain); _dnsWebService.DnsServer.BlockedZoneManager.SaveZoneFile(); } } diff --git a/DnsServerCore/WebServiceZonesApi.cs b/DnsServerCore/WebServiceZonesApi.cs index 473940a2..2645ce7a 100644 --- a/DnsServerCore/WebServiceZonesApi.cs +++ b/DnsServerCore/WebServiceZonesApi.cs @@ -17,6 +17,7 @@ along with this program. If not, see . */ +using DnsServerCore.Auth; using DnsServerCore.Dns; using DnsServerCore.Dns.Dnssec; using DnsServerCore.Dns.ResourceRecords; @@ -859,17 +860,23 @@ namespace DnsServerCore #region public - public void ListZones(JsonTextWriter jsonWriter) + public void ListZones(HttpListenerRequest request, JsonTextWriter jsonWriter) { List zones = _dnsWebService.DnsServer.AuthZoneManager.ListZones(); - zones.Sort(); + UserSession session = _dnsWebService.GetSession(request); + jsonWriter.WritePropertyName("zones"); jsonWriter.WriteStartArray(); foreach (AuthZoneInfo zone in zones) + { + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zone.Name, session.User, PermissionFlag.View)) + continue; + WriteZoneInfoAsJson(zone, jsonWriter); + } jsonWriter.WriteEndArray(); } @@ -906,14 +913,27 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(strType)) type = Enum.Parse(strType, true); + AuthZoneInfo zoneInfo; + switch (type) { case AuthZoneType.Primary: - if (_dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(zoneName, _dnsWebService.DnsServer.ServerDomain, false) is null) - throw new DnsWebServiceException("Zone already exists: " + zoneName); + { + zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(zoneName, _dnsWebService.DnsServer.ServerDomain, false); + if (zoneInfo is null) + throw new DnsWebServiceException("Zone already exists: " + zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Authoritative primary zone was created: " + zoneName); - _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); + UserSession session = _dnsWebService.GetSession(request); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Authoritative primary zone was created: " + zoneName); + _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); + } break; case AuthZoneType.Secondary: @@ -934,11 +954,20 @@ namespace DnsServerCore if (string.IsNullOrEmpty(tsigKeyName)) tsigKeyName = null; - if (await _dnsWebService.DnsServer.AuthZoneManager.CreateSecondaryZoneAsync(zoneName, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName) is null) + zoneInfo = await _dnsWebService.DnsServer.AuthZoneManager.CreateSecondaryZoneAsync(zoneName, primaryNameServerAddresses, zoneTransferProtocol, tsigKeyName); + if (zoneInfo is null) throw new DnsWebServiceException("Zone already exists: " + zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Authoritative secondary zone was created: " + zoneName); - _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); + UserSession session = _dnsWebService.GetSession(request); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Authoritative secondary zone was created: " + zoneName); + _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } break; @@ -948,11 +977,20 @@ namespace DnsServerCore if (string.IsNullOrEmpty(strPrimaryNameServerAddresses)) strPrimaryNameServerAddresses = null; - if (await _dnsWebService.DnsServer.AuthZoneManager.CreateStubZoneAsync(zoneName, strPrimaryNameServerAddresses) is null) + zoneInfo = await _dnsWebService.DnsServer.AuthZoneManager.CreateStubZoneAsync(zoneName, strPrimaryNameServerAddresses); + if (zoneInfo is null) throw new DnsWebServiceException("Zone already exists: " + zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Stub zone was created: " + zoneName); - _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); + UserSession session = _dnsWebService.GetSession(request); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Stub zone was created: " + zoneName); + _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } break; @@ -997,11 +1035,20 @@ namespace DnsServerCore proxyPassword = request.QueryString["proxyPassword"]; } - if (_dnsWebService.DnsServer.AuthZoneManager.CreateForwarderZone(zoneName, forwarderProtocol, strForwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, null) is null) + zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreateForwarderZone(zoneName, forwarderProtocol, strForwarder, dnssecValidation, proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword, null); + if (zoneInfo is null) throw new DnsWebServiceException("Zone already exists: " + zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Forwarder zone was created: " + zoneName); - _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); + UserSession session = _dnsWebService.GetSession(request); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, zoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Forwarder zone was created: " + zoneName); + _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } break; @@ -1010,10 +1057,10 @@ namespace DnsServerCore } //delete cache for this zone to allow rebuilding cache data as needed by stub or forwarder zones - _dnsWebService.DnsServer.CacheZoneManager.DeleteZone(zoneName); + _dnsWebService.DnsServer.CacheZoneManager.DeleteZone(zoneInfo.Name); jsonWriter.WritePropertyName("domain"); - jsonWriter.WriteValue(string.IsNullOrEmpty(zoneName) ? "." : zoneName); + jsonWriter.WriteValue(string.IsNullOrEmpty(zoneInfo.Name) ? "." : zoneInfo.Name); } public void SignPrimaryZone(HttpListenerRequest request) @@ -1024,6 +1071,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string algorithm = request.QueryString["algorithm"]; if (string.IsNullOrEmpty(algorithm)) throw new DnsWebServiceException("Parameter 'algorithm' missing."); @@ -1116,7 +1168,7 @@ namespace DnsServerCore throw new NotSupportedException("Algorithm is not supported: " + algorithm); } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone was signed successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone was signed successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1129,9 +1181,14 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + _dnsWebService.DnsServer.AuthZoneManager.UnsignPrimaryZone(zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone was unsigned successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone was unsigned successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1154,6 +1211,11 @@ namespace DnsServerCore if (zoneInfo.Type != AuthZoneType.Primary) throw new DnsWebServiceException("The zone must be a primary zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(zoneInfo.Name); @@ -1254,9 +1316,14 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + _dnsWebService.DnsServer.AuthZoneManager.ConvertPrimaryZoneToNSEC(zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone was converted to NSEC successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone was converted to NSEC successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1269,6 +1336,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + ushort iterations = 0; string strIterations = request.QueryString["iterations"]; if (!string.IsNullOrEmpty(strIterations)) @@ -1281,7 +1353,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.ConvertPrimaryZoneToNSEC3(zoneName, iterations, saltLength); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone was converted to NSEC3 successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone was converted to NSEC3 successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1294,6 +1366,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + ushort iterations = 0; string strIterations = request.QueryString["iterations"]; if (!string.IsNullOrEmpty(strIterations)) @@ -1306,7 +1383,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.UpdatePrimaryZoneNSEC3Parameters(zoneName, iterations, saltLength); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone NSEC3 parameters were updated successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone NSEC3 parameters were updated successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1319,6 +1396,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strDnsKeyTtl = request.QueryString["ttl"]; if (string.IsNullOrEmpty(strDnsKeyTtl)) throw new DnsWebServiceException("Parameter 'ttl' missing."); @@ -1327,7 +1409,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.UpdatePrimaryZoneDnsKeyTtl(zoneName, dnsKeyTtl); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone DNSKEY TTL was updated successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone DNSKEY TTL was updated successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1340,6 +1422,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strKeyType = request.QueryString["keyType"]; if (string.IsNullOrEmpty(strKeyType)) throw new DnsWebServiceException("Parameter 'keyType' missing."); @@ -1385,7 +1472,7 @@ namespace DnsServerCore throw new NotSupportedException("Algorithm is not supported: " + algorithm); } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNSSEC private key was generated and added to the primary zone successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] DNSSEC private key was generated and added to the primary zone successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1398,6 +1485,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strKeyTag = request.QueryString["keyTag"]; if (string.IsNullOrEmpty(strKeyTag)) throw new DnsWebServiceException("Parameter 'keyTag' missing."); @@ -1412,7 +1504,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.UpdatePrimaryZoneDnssecPrivateKey(zoneName, keyTag, rolloverDays); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Primary zone DNSSEC private key config was updated successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Primary zone DNSSEC private key config was updated successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1425,6 +1517,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strKeyTag = request.QueryString["keyTag"]; if (string.IsNullOrEmpty(strKeyTag)) throw new DnsWebServiceException("Parameter 'keyTag' missing."); @@ -1433,7 +1530,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.DeletePrimaryZoneDnssecPrivateKey(zoneName, keyTag); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNSSEC private key was deleted from primary zone successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] DNSSEC private key was deleted from primary zone successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1446,9 +1543,14 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + _dnsWebService.DnsServer.AuthZoneManager.PublishAllGeneratedPrimaryZoneDnssecPrivateKeys(zoneName); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] All DNSSEC private keys from the primary zone were published successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] All DNSSEC private keys from the primary zone were published successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1461,6 +1563,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strKeyTag = request.QueryString["keyTag"]; if (string.IsNullOrEmpty(strKeyTag)) throw new DnsWebServiceException("Parameter 'keyTag' missing."); @@ -1469,7 +1576,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.RolloverPrimaryZoneDnsKey(zoneName, keyTag); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] The DNSKEY (" + keyTag + ") from the primary zone was rolled over successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] The DNSKEY (" + keyTag + ") from the primary zone was rolled over successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1482,6 +1589,11 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneName, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strKeyTag = request.QueryString["keyTag"]; if (string.IsNullOrEmpty(strKeyTag)) throw new DnsWebServiceException("Parameter 'keyTag' missing."); @@ -1490,7 +1602,7 @@ namespace DnsServerCore _dnsWebService.DnsServer.AuthZoneManager.RetirePrimaryZoneDnsKey(zoneName, keyTag); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] The DNSKEY (" + keyTag + ") from the primary zone was retired successfully: " + zoneName); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] The DNSKEY (" + keyTag + ") from the primary zone was retired successfully: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneName); } @@ -1513,11 +1625,18 @@ namespace DnsServerCore if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); - if (!_dnsWebService.DnsServer.AuthZoneManager.DeleteZone(zoneName)) - throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneName); + UserSession session = _dnsWebService.GetSession(request); - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was deleted: " + zoneName); + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + if (!_dnsWebService.DnsServer.AuthZoneManager.DeleteZone(zoneInfo.Name)) + throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneInfo.Name); + + _dnsWebService.AuthManager.RemoveAllPermissions(PermissionSection.Zones, zoneInfo.Name); + _dnsWebService.AuthManager.SaveConfigFile(); + + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + zoneInfo.Type.ToString() + " zone was deleted: " + zoneName); _dnsWebService.DnsServer.AuthZoneManager.DeleteZoneFile(zoneInfo.Name); } @@ -1533,15 +1652,20 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName); - if (zoneInfo == null) + if (zoneInfo is null) throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneName); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + zoneInfo.Disabled = false; - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was enabled: " + zoneInfo.Name); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + zoneInfo.Type.ToString() + " zone was enabled: " + zoneInfo.Name); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); @@ -1561,15 +1685,20 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName); - if (zoneInfo == null) + if (zoneInfo is null) throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneName); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + zoneInfo.Disabled = true; - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone was disabled: " + zoneInfo.Name); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + zoneInfo.Type.ToString() + " zone was disabled: " + zoneInfo.Name); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } @@ -1592,6 +1721,11 @@ namespace DnsServerCore if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + jsonWriter.WritePropertyName("name"); jsonWriter.WriteValue(zoneInfo.Name); @@ -1688,12 +1822,17 @@ namespace DnsServerCore zoneName = zoneName.TrimEnd('.'); AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName); - if (zoneInfo == null) + if (zoneInfo is null) throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneName); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); + string strDisabled = request.QueryString["disabled"]; if (!string.IsNullOrEmpty(strDisabled)) zoneInfo.Disabled = bool.Parse(strDisabled); @@ -1763,26 +1902,34 @@ namespace DnsServerCore } } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] " + zoneInfo.Type.ToString() + " zone options were updated successfully: " + zoneInfo.Name); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] " + zoneInfo.Type.ToString() + " zone options were updated successfully: " + zoneInfo.Name); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } public void ResyncZone(HttpListenerRequest request) { - string domain = request.QueryString["domain"]; - if (string.IsNullOrEmpty(domain)) - throw new DnsWebServiceException("Parameter 'domain' missing."); + string zoneName = request.QueryString["zone"]; + if (string.IsNullOrEmpty(zoneName)) + zoneName = request.QueryString["domain"]; - domain = domain.TrimEnd('.'); + if (string.IsNullOrEmpty(zoneName)) + throw new DnsWebServiceException("Parameter 'zone' missing."); - AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(domain); - if (zoneInfo == null) - throw new DnsWebServiceException("No authoritative zone was not found for domain: " + domain); + zoneName = zoneName.TrimEnd('.'); + + AuthZoneInfo zoneInfo = _dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(zoneName); + if (zoneInfo is null) + throw new DnsWebServiceException("No authoritative zone was not found for domain: " + zoneName); if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); + switch (zoneInfo.Type) { case AuthZoneType.Secondary: @@ -1814,8 +1961,10 @@ namespace DnsServerCore if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); - if (string.IsNullOrEmpty(zoneName)) - zoneName = zoneInfo.Name; + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) @@ -1872,7 +2021,7 @@ namespace DnsServerCore string ptrDomain = Zone.GetReverseZone(ipAddress, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo reverseZoneInfo = _dnsWebService.DnsServer.AuthZoneManager.FindAuthZoneInfo(ptrDomain); - if (reverseZoneInfo == null) + if (reverseZoneInfo is null) { bool createPtrZone = false; string strCreatePtrZone = request.QueryString["createPtrZone"]; @@ -1887,6 +2036,12 @@ namespace DnsServerCore reverseZoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(ptrZone, _dnsWebService.DnsServer.ServerDomain, false); if (reverseZoneInfo == null) throw new DnsServerException("Failed to create reverse zone to add PTR record: " + ptrZone); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); } if (reverseZoneInfo.Internal) @@ -1908,9 +2063,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -1938,9 +2093,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -1948,7 +2103,7 @@ namespace DnsServerCore { if (!overwrite) { - IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneName, domain, type); + IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneInfo.Name, domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } @@ -1967,7 +2122,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); } break; @@ -1988,9 +2143,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2015,9 +2170,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2038,9 +2193,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2073,9 +2228,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2083,7 +2238,7 @@ namespace DnsServerCore { if (!overwrite) { - IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneName, domain, type); + IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneInfo.Name, domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } @@ -2102,7 +2257,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); } break; @@ -2135,9 +2290,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2160,9 +2315,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2183,9 +2338,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2244,9 +2399,9 @@ namespace DnsServerCore newRecord.SetComments(comments); if (overwrite) - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); else - _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.AddRecord(zoneInfo.Name, newRecord); } break; @@ -2271,7 +2426,7 @@ namespace DnsServerCore if (!overwrite) { - IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneName, domain, type); + IReadOnlyList existingRecords = _dnsWebService.DnsServer.AuthZoneManager.GetRecords(zoneInfo.Name, domain, type); if (existingRecords.Count > 0) throw new DnsWebServiceException("Record already exists. Use overwrite option if you wish to overwrite existing records."); } @@ -2281,7 +2436,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newRecord); } break; @@ -2289,7 +2444,7 @@ namespace DnsServerCore throw new DnsWebServiceException("Type not supported for AddRecords()."); } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] New record was added to authoritative zone {domain: " + domain + "; type: " + type + "; value: " + value + "; ttl: " + ttl + ";}"); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] New record was added to authoritative zone {domain: " + domain + "; type: " + type + "; value: " + value + "; ttl: " + ttl + ";}"); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); @@ -2312,6 +2467,11 @@ namespace DnsServerCore if (zoneInfo is null) throw new DnsWebServiceException("No authoritative zone was not found for domain: " + domain); + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.View)) + throw new DnsWebServiceException("Access was denied."); + jsonWriter.WritePropertyName("zone"); WriteZoneInfoAsJson(zoneInfo, jsonWriter); @@ -2340,8 +2500,10 @@ namespace DnsServerCore if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); - if (string.IsNullOrEmpty(zoneName)) - zoneName = zoneInfo.Name; + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Delete)) + throw new DnsWebServiceException("Access was denied."); string strType = request.QueryString["type"]; if (string.IsNullOrEmpty(strType)) @@ -2368,9 +2530,9 @@ namespace DnsServerCore IPAddress ipAddress = IPAddress.Parse(strIPAddress); if (type == DnsResourceRecordType.A) - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsARecordData(ipAddress)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsARecordData(ipAddress)); else - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsAAAARecordData(ipAddress)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsAAAARecordData(ipAddress)); string ptrDomain = Zone.GetReverseZone(ipAddress, type == DnsResourceRecordType.A ? 32 : 128); AuthZoneInfo reverseZoneInfo = _dnsWebService.DnsServer.AuthZoneManager.FindAuthZoneInfo(ptrDomain); @@ -2405,12 +2567,12 @@ namespace DnsServerCore nameServer = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsNSRecordData(nameServer)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsNSRecordData(nameServer)); } break; case DnsResourceRecordType.CNAME: - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneName, domain, type); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneInfo.Name, domain, type); break; case DnsResourceRecordType.PTR: @@ -2424,7 +2586,7 @@ namespace DnsServerCore ptrName = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsPTRRecordData(ptrName)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsPTRRecordData(ptrName)); } break; @@ -2443,7 +2605,7 @@ namespace DnsServerCore exchange = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsMXRecordData(ushort.Parse(preference), exchange)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsMXRecordData(ushort.Parse(preference), exchange)); } break; @@ -2458,7 +2620,7 @@ namespace DnsServerCore text = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsTXTRecordData(text)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsTXTRecordData(text)); } break; @@ -2485,12 +2647,12 @@ namespace DnsServerCore target = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsSRVRecordData(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), target)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsSRVRecordData(ushort.Parse(priority), ushort.Parse(weight), ushort.Parse(port), target)); } break; case DnsResourceRecordType.DNAME: - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneName, domain, type); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneInfo.Name, domain, type); break; case DnsResourceRecordType.DS: @@ -2516,7 +2678,7 @@ namespace DnsServerCore digest = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsDSRecordData(ushort.Parse(strKeyTag), Enum.Parse(strAlgorithm, true), Enum.Parse(strDigestType, true), Convert.FromHexString(digest))); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsDSRecordData(ushort.Parse(strKeyTag), Enum.Parse(strAlgorithm, true), Enum.Parse(strDigestType, true), Convert.FromHexString(digest))); } break; @@ -2533,7 +2695,7 @@ namespace DnsServerCore if (string.IsNullOrEmpty(value)) throw new DnsWebServiceException("Parameter 'value' missing."); - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsCAARecordData(byte.Parse(flags), tag, value)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsCAARecordData(byte.Parse(flags), tag, value)); } break; @@ -2548,7 +2710,7 @@ namespace DnsServerCore aname = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsANAMERecordData(aname)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsANAMERecordData(aname)); } break; @@ -2567,19 +2729,19 @@ namespace DnsServerCore forwarder = value; } - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneName, domain, type, new DnsForwarderRecordData(Enum.Parse(strProtocol, true), forwarder)); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecord(zoneInfo.Name, domain, type, new DnsForwarderRecordData(Enum.Parse(strProtocol, true), forwarder)); } break; case DnsResourceRecordType.APP: - _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneName, domain, type); + _dnsWebService.DnsServer.AuthZoneManager.DeleteRecords(zoneInfo.Name, domain, type); break; default: throw new DnsWebServiceException("Type not supported for DeleteRecord()."); } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Record was deleted from authoritative zone {domain: " + domain + "; type: " + type + ";}"); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Record was deleted from authoritative zone {domain: " + domain + "; type: " + type + ";}"); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); } @@ -2609,8 +2771,10 @@ namespace DnsServerCore if (zoneInfo.Internal) throw new DnsWebServiceException("Access was denied to manage internal DNS Server zone."); - if (string.IsNullOrEmpty(zoneName)) - zoneName = zoneInfo.Name; + UserSession session = _dnsWebService.GetSession(request); + + if (!_dnsWebService.AuthManager.IsPermitted(PermissionSection.Zones, zoneInfo.Name, session.User, PermissionFlag.Modify)) + throw new DnsWebServiceException("Access was denied."); string newDomain = request.QueryString["newDomain"]; if (string.IsNullOrEmpty(newDomain)) @@ -2686,8 +2850,14 @@ namespace DnsServerCore string ptrZone = Zone.GetReverseZone(newIpAddress, type == DnsResourceRecordType.A ? 24 : 64); reverseZoneInfo = _dnsWebService.DnsServer.AuthZoneManager.CreatePrimaryZone(ptrZone, _dnsWebService.DnsServer.ServerDomain, false); - if (reverseZoneInfo == null) + if (reverseZoneInfo is null) throw new DnsServerException("Failed to create reverse zone to add PTR record: " + ptrZone); + + //set permissions + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, session.User, PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SetPermission(PermissionSection.Zones, reverseZoneInfo.Name, _dnsWebService.AuthManager.GetGroup(Group.DNS_ADMINISTRATORS), PermissionFlag.ViewModifyDelete); + _dnsWebService.AuthManager.SaveConfigFile(); } if (reverseZoneInfo.Internal) @@ -2730,7 +2900,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -2767,7 +2937,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(glueAddresses)) newRecord.SetGlueRecords(glueAddresses); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -2791,7 +2961,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -2856,7 +3026,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newSOARecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneName, newSOARecord); + _dnsWebService.DnsServer.AuthZoneManager.SetRecord(zoneInfo.Name, newSOARecord); newRecord = zoneInfo.GetRecords(DnsResourceRecordType.SOA)[0]; } @@ -2891,7 +3061,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -2932,7 +3102,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -2965,7 +3135,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3022,7 +3192,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3046,7 +3216,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3103,7 +3273,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3140,7 +3310,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3173,7 +3343,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3249,7 +3419,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3281,7 +3451,7 @@ namespace DnsServerCore if (!string.IsNullOrEmpty(comments)) newRecord.SetComments(comments); - _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneName, oldRecord, newRecord); + _dnsWebService.DnsServer.AuthZoneManager.UpdateRecord(zoneInfo.Name, oldRecord, newRecord); } break; @@ -3289,7 +3459,7 @@ namespace DnsServerCore throw new DnsWebServiceException("Type not supported for UpdateRecords()."); } - _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] Record was updated for authoritative zone {oldDomain: " + domain + "; domain: " + newDomain + "; type: " + type + "; oldValue: " + value + "; value: " + newValue + "; ttl: " + ttl + "; disabled: " + disable + ";}"); + _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + session.User.Username + "] Record was updated for authoritative zone {oldDomain: " + domain + "; domain: " + newDomain + "; type: " + type + "; oldValue: " + value + "; value: " + newValue + "; ttl: " + ttl + "; disabled: " + disable + ";}"); _dnsWebService.DnsServer.AuthZoneManager.SaveZoneFile(zoneInfo.Name); diff --git a/DnsServerCore/named.root b/DnsServerCore/named.root index 151630ac..6a8a4fd9 100644 --- a/DnsServerCore/named.root +++ b/DnsServerCore/named.root @@ -9,8 +9,8 @@ ; on server FTP.INTERNIC.NET ; -OR- RS.INTERNIC.NET ; -; last update: March 16, 2022 -; related version of root zone: 2022031601 +; last update: August 31, 2022 +; related version of root zone: 2022083101 ; ; FORMERLY NS.INTERNIC.NET ; diff --git a/DnsServerCore/www/img/loader-small.gif b/DnsServerCore/www/img/loader-small.gif new file mode 100644 index 00000000..a5ff00ff Binary files /dev/null and b/DnsServerCore/www/img/loader-small.gif differ diff --git a/DnsServerCore/www/index.html b/DnsServerCore/www/index.html index aa43d5ad..63fab79a 100644 --- a/DnsServerCore/www/index.html +++ b/DnsServerCore/www/index.html @@ -25,6 +25,7 @@ + @@ -42,7 +43,10 @@ @@ -109,6 +113,7 @@ + @@ -347,7 +352,7 @@ DNSSEC Status Expiry - + @@ -364,7 +369,7 @@
-

example.com

+

example.com

Primary DNSSEC @@ -373,21 +378,22 @@
- - - - - + + + + + +
@@ -738,6 +744,18 @@
The default TTL value to use if not specified when adding or updating records in a Zone.
+ +
+ +
+
+ +
+
DNS server will check for DNS Apps update every day and will automatically download and install the updates.
+
+
@@ -1008,7 +1026,7 @@ Key Name Shared Secret Algorithm - + @@ -1350,7 +1368,7 @@
- hours (default 24, valid range 1-168) + hours (valid range 0-168; default 24; set 0 to disable)
The interval in hours to automatically download and update the block lists.
@@ -1667,7 +1685,7 @@ Host Name Lease Obtained Lease Expires - + @@ -1917,7 +1935,7 @@ Vendor Class Identifier Vendor Specific Information - + @@ -1936,7 +1954,7 @@ Starting Address Ending Address - + @@ -1986,6 +2004,130 @@ +
+ + + +
+ +
+
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + +
UsernameSessionLast SeenRemote AddressUser Agent
+
+
+ +
+
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + +
UsernameDisplay NameStatusPrevious LoginRecent Login
+
+
+ +
+
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + +
NameDescription
+
+
+ +
+
+ +
+ + + + + + + + + + + + + + +
SectionUser PermissionsGroup Permissions
+
+
+ +
+ +
+
@@ -3402,7 +3704,7 @@ ns1.example.com ([2001:db8::])
- +
@@ -3521,7 +3823,7 @@ ns1.example.com ([2001:db8::]) (valid range 0-50, recommended 0)
- The number of iterations used by NSEC3 for hashing the domain names. It is recommended to use 0 iterations since more iterations will increase computational costs for both the DNS server and resolver while not providing much value against "zone walking" [draft-ietf-dnsop-nsec3-guidance]. + The number of iterations used by NSEC3 for hashing the domain names. It is recommended to use 0 iterations since more iterations will increase computational costs for both the DNS server and resolver while not providing much value against "zone walking" [RFC 9276].
@@ -3532,7 +3834,7 @@ ns1.example.com ([2001:db8::]) (valid range 0-32, recommended 0)
- The number of bytes of random salt to generate to be used with the NSEC3 hash computation. It is recommended to not use salt by setting the length to 0 [draft-ietf-dnsop-nsec3-guidance]. + The number of bytes of random salt to generate to be used with the NSEC3 hash computation. It is recommended to not use salt by setting the length to 0 [RFC 9276].
@@ -3775,6 +4077,12 @@ ns1.example.com ([2001:db8::])
+
+ +
+