Compare commits
67 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
79f283bdcb | ||
![]() |
75ebf21481 | ||
![]() |
fe8e330cc3 | ||
![]() |
a7a383f109 | ||
![]() |
1c5abafcbc | ||
![]() |
63c0835625 | ||
![]() |
74226bb2e0 | ||
![]() |
b55e907e1b | ||
![]() |
c018918d8f | ||
![]() |
d0164f3116 | ||
![]() |
ccfb9721a9 | ||
![]() |
d4729052a5 | ||
![]() |
cd6667c001 | ||
![]() |
16ccf617aa | ||
![]() |
9a62da39fe | ||
![]() |
b7d6d176cd | ||
![]() |
97b02fd6d4 | ||
![]() |
5dd2ead831 | ||
![]() |
687f08dd55 | ||
![]() |
19cd0b903a | ||
![]() |
977848d443 | ||
![]() |
d990a3c7c4 | ||
![]() |
884ff4e597 | ||
![]() |
a591e8d74f | ||
![]() |
0b3f9b3c79 | ||
![]() |
9b8106163d | ||
![]() |
ab2e9ecb11 | ||
![]() |
c6a7169eb6 | ||
![]() |
87cb572011 | ||
![]() |
bfbc7f7b0f | ||
![]() |
173cad4358 | ||
![]() |
abe8dc5601 | ||
![]() |
a7223ddebc | ||
![]() |
b2f8f11c75 | ||
![]() |
084bd4641b | ||
![]() |
c4168adf55 | ||
![]() |
0fbef750c5 | ||
![]() |
c5faafc01b | ||
![]() |
30e9a71060 | ||
![]() |
1729a193cc | ||
![]() |
d42505d38a | ||
![]() |
da090d1de5 | ||
![]() |
d05b1647c8 | ||
![]() |
8d3f3b6610 | ||
![]() |
c46fbc2735 | ||
![]() |
2a6159ea4e | ||
![]() |
981a92c226 | ||
![]() |
9b420f3a8a | ||
![]() |
bab98aa927 | ||
![]() |
a889cede10 | ||
![]() |
c5717a10d4 | ||
![]() |
b89b2bfe44 | ||
![]() |
cbb18e8146 | ||
![]() |
10498390c8 | ||
![]() |
c0247d7b1a | ||
![]() |
c32cbf2c7a | ||
![]() |
5c13dc09f9 | ||
![]() |
0dccc44ad4 | ||
![]() |
ccc264ed12 | ||
![]() |
474e9a1549 | ||
![]() |
2564cd90e1 | ||
![]() |
661d139bfc | ||
![]() |
442295c2cf | ||
![]() |
625eec3456 | ||
![]() |
03f8863e9e | ||
![]() |
ee057b7fc9 | ||
![]() |
67d50c5e7e |
28 changed files with 819 additions and 166 deletions
15
README.md
15
README.md
|
@ -7,10 +7,11 @@ DNS admin panel, designed to operate via nsupdate, for all kinds of RFC complian
|
|||
* Different servers for querying zone info (transfer) and for update, useful for load balancing
|
||||
* Audit logs
|
||||
* Support LDAP / Active Directory authentication
|
||||
* Web API
|
||||
* Individual user and LDAP group permissions to edit zones via roles (read/write)
|
||||
|
||||
# How does it work
|
||||
DNS PHP admin is a very simple GUI utility that helps sysadmins manage their DNS records and also provides easy to use interface for end users, which is more idiot friendly than low level command line tools that are typically used to manage BIND9 servers.
|
||||
DNS PHP admin is a very simple GUI utility that helps sysadmins manage their DNS records and also provides easy to use interface for end users, which is more user friendly than low level command line tools that are typically used to manage BIND9 servers.
|
||||
|
||||
It also makes it possible to centralize management of multiple separate DNS servers, so that you can edit multiple zones on multiple different DNS servers.
|
||||
|
||||
|
@ -23,10 +24,10 @@ Then, download release tarball into any folder which is configured a http root o
|
|||
|
||||
```
|
||||
cd /tmp
|
||||
wget https://github.com/benapetr/dnsphpadmin/releases/download/1.7.0/dnsphpadmin_1.7.0.tar.gz
|
||||
wget https://github.com/benapetr/dnsphpadmin/releases/download/1.10.0/dnsphpadmin_1.10.0.tar.gz
|
||||
cd /var/www/html
|
||||
tar -xf /tmp/dnsphpadmin_1.7.0.tar.gz
|
||||
mv dnsphpadmin_1.7.0 dnsphpadmin
|
||||
tar -xf /tmp/dnsphpadmin_1.10.0.tar.gz
|
||||
mv dnsphpadmin_1.10.0 dnsphpadmin
|
||||
cd dnsphpadmin
|
||||
|
||||
# Now copy the default config file
|
||||
|
@ -37,4 +38,10 @@ vi config.php
|
|||
|
||||
Now update `$g_domains` so that it contains information about zones you want to manage. Web server must have nsupdate and dig Linux commands installed in paths that are in config.php and it also needs to have firewall access to perform zone transfer and to perform nsupdate updates.
|
||||
|
||||
## Docker image
|
||||
There is also a docker image maintained by Eugene Taylashev
|
||||
|
||||
* GitHub: https://github.com/eugene-taylashev/docker-dnsphpadmin
|
||||
* Docker Hub: https://hub.docker.com/repository/docker/etaylashev/dnsphpadmin
|
||||
|
||||
**IMPORTANT:** DNS tool doesn't use any authentication by default, so everyone with access to web server will have access to DNS tool. If this is just a simple setup for 1 or 2 admins who should have unlimited access to everything, you should setup login via htaccess or similar see https://httpd.apache.org/docs/2.4/howto/auth.html for apache. If you have LDAP (active directory is also LDAP), you can configure this tool to use LDAP authentication as well.
|
||||
|
|
230
api.php
230
api.php
|
@ -17,9 +17,11 @@ require("config.default.php");
|
|||
require("config.php");
|
||||
require_once("psf/psf.php");
|
||||
require_once("includes/common.php");
|
||||
require_once("includes/debug.php");
|
||||
require_once("includes/fatal_api.php");
|
||||
require_once("includes/record_list.php");
|
||||
require_once("includes/modify.php");
|
||||
require_once("includes/notifications.php");
|
||||
require_once("includes/login.php");
|
||||
require_once("includes/validator.php");
|
||||
require_once("includes/zones.php");
|
||||
|
@ -34,8 +36,19 @@ date_default_timezone_set($g_timezone);
|
|||
|
||||
function print_result($result)
|
||||
{
|
||||
global $api;
|
||||
$api->PrintObj([ 'result' => $result ]);
|
||||
global $api, $g_api_warnings, $g_api_errors;
|
||||
$json = [ 'result' => $result ];
|
||||
if (!empty($g_api_warnings))
|
||||
$json['warnings'] = $g_api_warnings;
|
||||
if (!empty($g_api_errors))
|
||||
$json['errors'] = $g_api_errors;
|
||||
$api->PrintObj($json);
|
||||
}
|
||||
|
||||
function api_warning($text)
|
||||
{
|
||||
global $g_api_warnings;
|
||||
$g_api_warnings[] = $text;
|
||||
}
|
||||
|
||||
function print_success()
|
||||
|
@ -204,6 +217,19 @@ function get_zone_for_fqdn_or_throw($fqdn)
|
|||
return $zone;
|
||||
}
|
||||
|
||||
function validate_type_or_throw($type)
|
||||
{
|
||||
global $api;
|
||||
|
||||
if (!IsValidRecordType($type))
|
||||
{
|
||||
$api->ThrowError('Invalid type', "Type $type is not a valid DNS record type");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function api_call_create_record($source)
|
||||
{
|
||||
global $api, $g_domains;
|
||||
|
@ -213,6 +239,7 @@ function api_call_create_record($source)
|
|||
$type = get_required_post_get_parameter('type');
|
||||
$value = get_required_post_get_parameter('value');
|
||||
$comment = get_optional_post_get_parameter('comment');
|
||||
$ptr = IsTrue(get_optional_post_get_parameter('ptr'));
|
||||
$merge_record = true;
|
||||
|
||||
if ($zone === NULL)
|
||||
|
@ -224,11 +251,8 @@ function api_call_create_record($source)
|
|||
if (!check_zone_access($zone))
|
||||
return false;
|
||||
|
||||
if (!IsValidRecordType($type))
|
||||
{
|
||||
$api->ThrowError('Invalid type', "Type $type is not a valid DNS record type");
|
||||
if (!validate_type_or_throw($type))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_numeric($ttl))
|
||||
{
|
||||
|
@ -236,19 +260,129 @@ function api_call_create_record($source)
|
|||
return false;
|
||||
}
|
||||
|
||||
$record = SanitizeHostname($record);
|
||||
if (!IsValidHostName($record))
|
||||
{
|
||||
$api->ThrowError('Invalid hostname', "Hostname is containing invalid characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
$n = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
$merged_record = NULL;
|
||||
if ($merge_record)
|
||||
{
|
||||
$n .= ProcessInsertFromPOST($zone, $record, $value, $type, $ttl);
|
||||
else
|
||||
$merged_record = $record . "." . $zone;
|
||||
} else
|
||||
{
|
||||
$n .= ProcessInsertFromPOST("" , $record, $value, $type, $ttl);
|
||||
$merged_record = $record;
|
||||
}
|
||||
$n .= "send\nquit\n";
|
||||
|
||||
ProcessNSUpdateForDomain($n, $zone);
|
||||
WriteToAuditFile("create", $merged_record . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
|
||||
if ($merge_record)
|
||||
WriteToAuditFile("create", $record . "." . $zone . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
else
|
||||
WriteToAuditFile("create", $record . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
if ($ptr == true)
|
||||
{
|
||||
Debug('PTR record was requested for ' . $merged_record . ' creating one');
|
||||
if ($type != 'A')
|
||||
{
|
||||
api_warning('Requested PTR record was not created: PTR record can be only created when you are inserting A record, you created ' . $type . ' record instead');
|
||||
} else
|
||||
{
|
||||
DNS_InsertPTRForARecord($value, $merged_record, $ttl, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
print_success();
|
||||
return true;
|
||||
}
|
||||
|
||||
function api_call_replace_record($source)
|
||||
{
|
||||
global $api, $g_domains;
|
||||
$zone = get_optional_post_get_parameter('zone');
|
||||
$record = get_required_post_get_parameter('record');
|
||||
$ttl = get_required_post_get_parameter('ttl');
|
||||
$type = get_required_post_get_parameter('type');
|
||||
$new_value = get_required_post_get_parameter('new_value');
|
||||
$value = get_optional_post_get_parameter('value');
|
||||
$comment = get_optional_post_get_parameter('comment');
|
||||
$new_record = get_optional_post_get_parameter('new_record');
|
||||
$new_type = get_optional_post_get_parameter('new_type');
|
||||
$ptr = IsTrue(get_optional_post_get_parameter('ptr'));
|
||||
$merge_record = true;
|
||||
|
||||
// Auto-fill optional
|
||||
if ($new_type === NULL)
|
||||
$new_type = $type;
|
||||
|
||||
if ($new_record === NULL)
|
||||
$new_record = $record;
|
||||
|
||||
if ($zone === NULL)
|
||||
{
|
||||
$merge_record = false;
|
||||
$zone = get_zone_for_fqdn_or_throw($record);
|
||||
}
|
||||
|
||||
if (!check_zone_access($zone))
|
||||
return false;
|
||||
|
||||
if (!validate_type_or_throw($type))
|
||||
return false;
|
||||
|
||||
if (!validate_type_or_throw($new_type))
|
||||
return false;
|
||||
|
||||
if (!is_numeric($ttl))
|
||||
{
|
||||
$api->ThrowError('Invalid ttl', "TTL must be a number");
|
||||
return false;
|
||||
}
|
||||
|
||||
$old = NULL;
|
||||
$old_record = NULL;
|
||||
$merged_record = NULL;
|
||||
if (!$merge_record)
|
||||
{
|
||||
$old = $record . ' 0 ' . $type;
|
||||
$old_record = $record;
|
||||
$merged_record = $new_record;
|
||||
} else
|
||||
{
|
||||
$old = $record . '.' . $zone . ' 0 ' . $type;
|
||||
$old_record = $record . '.' . $zone;
|
||||
$merged_record = $new_record . '.' . $zone;
|
||||
}
|
||||
|
||||
if ($value !== NULL)
|
||||
$old .= ' ' . $value;
|
||||
|
||||
DNS_ModifyRecord($zone, $new_record, $new_value, $new_type, $ttl, $comment, $old, !$merge_record);
|
||||
|
||||
if ($ptr)
|
||||
{
|
||||
if ($type != 'A' && $new_type != 'A')
|
||||
{
|
||||
api_warning("You requested to modify underlying PTR record, but neither new or old record type is A record, ignoring PTR update request");
|
||||
} else
|
||||
{
|
||||
// PTR update was requested, if old type was A, delete it. If new type is A, create it
|
||||
if ($type == 'A')
|
||||
{
|
||||
if ($value === NULL)
|
||||
api_warning("Old PTR record was not deleted, because parameter value was not provided - so we don't know what to delete");
|
||||
else
|
||||
DNS_DeletePTRForARecord($value, $old_record, $comment);
|
||||
}
|
||||
if (($new_type === NULL && $type == 'A') || $new_type == 'A')
|
||||
{
|
||||
DNS_InsertPTRForARecord($new_value, $merged_record, $ttl, $comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_success();
|
||||
return true;
|
||||
|
@ -259,10 +393,11 @@ function api_call_delete_record($source)
|
|||
global $api, $g_domains;
|
||||
$zone = get_optional_post_get_parameter('zone');
|
||||
$record = get_required_post_get_parameter('record');
|
||||
$ttl = get_required_post_get_parameter('ttl');
|
||||
$ttl = 0;
|
||||
$type = get_required_post_get_parameter('type');
|
||||
$value = get_optional_post_get_parameter('value');
|
||||
$comment = get_optional_post_get_parameter('comment');
|
||||
$ptr = get_optional_post_get_parameter('ptr');
|
||||
$merge_record = true;
|
||||
|
||||
if ($zone === NULL)
|
||||
|
@ -274,36 +409,51 @@ function api_call_delete_record($source)
|
|||
if (!check_zone_access($zone))
|
||||
return false;
|
||||
|
||||
if (!IsValidRecordType($type))
|
||||
if (!validate_type_or_throw($type))
|
||||
return false;
|
||||
|
||||
$record = SanitizeHostname($record);
|
||||
if (!IsValidHostName($record))
|
||||
{
|
||||
$api->ThrowError('Invalid type', "Type $type is not a valid DNS record type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_numeric($ttl))
|
||||
{
|
||||
$api->ThrowError('Invalid ttl', "TTL must be a number");
|
||||
$api->ThrowError('Invalid hostname', "Hostname is containing invalid characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Value is optional, so in order to make nsupdate call more simple, we prefix it with space
|
||||
$original_value = $value;
|
||||
if (!psf_string_is_null_or_empty($value))
|
||||
$value = " " . $value;
|
||||
else
|
||||
$value = "";
|
||||
|
||||
$n = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
|
||||
$merged_record = "";
|
||||
if ($merge_record)
|
||||
$n .= "update delete " . $record . "." . $zone . " " . $ttl . " " . $type . $value . "\n";
|
||||
else
|
||||
$n .= "update delete " . $record . " " . $ttl . " " . $type . $value . "\n";
|
||||
{
|
||||
$n .= "update delete " . $record . "." . $zone . " 0 " . $type . $value . "\n";
|
||||
$merged_record = $record . "." . $zone;
|
||||
} else
|
||||
{
|
||||
$n .= "update delete " . $record . " 0 " . $type . $value . "\n";
|
||||
$merged_record = $record;
|
||||
}
|
||||
$n .= "send\nquit\n";
|
||||
|
||||
ProcessNSUpdateForDomain($n, $zone);
|
||||
WriteToAuditFile("delete", $merged_record . " 0 " . $type . $value, $comment);
|
||||
|
||||
if ($merge_record)
|
||||
WriteToAuditFile("delete", $record . "." . $zone . " " . $ttl . " " . $type . $value, $comment);
|
||||
else
|
||||
WriteToAuditFile("delete", $record . " " . $ttl . " " . $type . $value, $comment);
|
||||
if ($ptr == true)
|
||||
{
|
||||
Debug('PTR record deletion was requested for ' . $merged_record);
|
||||
if ($type != 'A')
|
||||
{
|
||||
api_warning('Requested PTR record was not deleted: PTR record can be only deleted when you are changing A record, you deleted ' . $type . ' record instead');
|
||||
} else
|
||||
{
|
||||
DNS_DeletePTRForARecord($original_value, $merged_record, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
print_success();
|
||||
return true;
|
||||
|
@ -324,6 +474,7 @@ function api_call_get_record($source)
|
|||
{
|
||||
global $api;
|
||||
$record = get_required_post_get_parameter('record');
|
||||
$record = SanitizeHostname($record);
|
||||
if (!IsValidHostName($record))
|
||||
{
|
||||
$api->ThrowError('Invalid hostname', "Hostname $record is not a valid hostname");
|
||||
|
@ -423,7 +574,7 @@ register_api("list_zones", "List all existing zones that you have access to", "L
|
|||
register_api('list_records', "List all existing records for a specified zone", "List all existing records for a specified zone", "api_call_list_records", true,
|
||||
[ new PsfApiParameter("zone", PsfApiParameterType::String, "Zone to list records for") ],
|
||||
[], '?action=list_records&zone=domain.org');
|
||||
register_api('create_record', 'Create a new DNS record in specified zone', 'Creates a new DNS record in specific zone. Please mind that domain name / zone is appended to record name automatically, ' .
|
||||
register_api('create_record', 'Creates a new DNS record in specified zone', 'Creates a new DNS record in specific zone. Please mind that domain name / zone is appended to record name automatically, ' .
|
||||
'so if you want to add test.domain.org, name of key is only test.', 'api_call_create_record', true,
|
||||
// Required parameters
|
||||
[ new PsfApiParameter("record", PsfApiParameterType::String, "Record name, if you don't provide zone name explicitly, this should be FQDN"),
|
||||
|
@ -431,19 +582,38 @@ register_api('create_record', 'Create a new DNS record in specified zone', 'Crea
|
|||
new PsfApiParameter("value", PsfApiParameterType::String, "Value of record") ],
|
||||
// Optional parameters
|
||||
[ new PsfApiParameter("zone", PsfApiParameterType::String, "Zone to modify, if not specified and record is fully qualified, it's automatically looked up from config file"),
|
||||
new PsfApiParameter("ptr", PsfApiParameterType::Boolean, "Optionally create PTR record, works only when you are adding A records"),
|
||||
new PsfApiParameter("comment", PsfApiParameterType::String, "Optional comment for audit logs") ],
|
||||
// Example call
|
||||
'?action=create_record&zone=domain.org&record=test&ttl=3600&type=A&value=0.0.0.0');
|
||||
register_api('delete_record', 'Deletes DNS record(s) in specified zone', 'Deletes DNS record(s) in specific zone. If you don\'t provide value, all records of given type will be deleted.', 'api_call_delete_record', true,
|
||||
// Required parameters
|
||||
[ new PsfApiParameter("record", PsfApiParameterType::String, "Record name, if you don't provide zone name explicitly, this should be FQDN"),
|
||||
new PsfApiParameter("ttl", PsfApiParameterType::Number, "Time to live (seconds)"), new PsfApiParameter("type", PsfApiParameterType::String, "Record type") ],
|
||||
new PsfApiParameter("type", PsfApiParameterType::String, "Record type") ],
|
||||
// Optional parameters
|
||||
[ new PsfApiParameter("zone", PsfApiParameterType::String, "Zone to modify, if not specified and record is fully qualified, it's automatically looked up from config file"),
|
||||
[ new PsfApiParameter("ttl", PsfApiParameterType::Number, "Time to live (seconds). Please note that nsupdate ignores TTL in delete requests. This parameter exists only for compatiblity reasons and is silently ignored."),
|
||||
new PsfApiParameter("zone", PsfApiParameterType::String, "Zone to modify, if not specified and record is fully qualified, it's automatically looked up from config file"),
|
||||
new PsfApiParameter("value", PsfApiParameterType::String, "Value of record. If not provided, all records with given type will be removed."),
|
||||
new PsfApiParameter("ptr", PsfApiParameterType::Boolean, "Optionally delete PTR record, works only when you are deleting A records"),
|
||||
new PsfApiParameter("comment", PsfApiParameterType::String, "Optional comment for audit logs") ],
|
||||
// Example call
|
||||
'?action=delete_record&zone=domain.org&record=test&ttl=3600&type=A&value=0.0.0.0');
|
||||
register_api('replace_record', 'Removes old and create a new DNS record in single nsupdate transaction', 'Replaces specific record. Both records must be within same zone, but may be of different type. Note that due to nature of nsupdate, if record you want to replace ' .
|
||||
'doesn\'t exist, it will not fail. So replace_record on non-existent record will still create a new record.', 'api_call_replace_record', true,
|
||||
// Required parameters
|
||||
[ new PsfApiParameter("record", PsfApiParameterType::String, "Name of existing record you want to replace, if you don't provide zone name explicitly, this should be FQDN"),
|
||||
new PsfApiParameter("type", PsfApiParameterType::String, "Type of current record that you want to replace"),
|
||||
new PsfApiParameter("ttl", PsfApiParameterType::Number, "Time to live (seconds)"),
|
||||
new PsfApiParameter("new_value", PsfApiParameterType::String, "Value of new record")],
|
||||
// Optional parameters
|
||||
[ new PsfApiParameter("zone", PsfApiParameterType::String, "Zone to modify, if not specified and record is fully qualified, it's automatically looked up from config file"),
|
||||
new PsfApiParameter("value", PsfApiParameterType::String, "Value of record. If not provided, all records with given type will be removed and replaced with a single new record"),
|
||||
new PsfApiParameter("new_record", PsfApiParameterType::String, "New record name, if you are not changing name of key, this can be omitted. If you don't provide zone name explicitly, this should be FQDN"),
|
||||
new PsfApiParameter("new_type", PsfApiParameterType::String, "Type of record, if you are not changing type, this can be omitted."),
|
||||
new PsfApiParameter("ptr", PsfApiParameterType::Boolean, "Optionally replace associated PTR record, works only when either new, old or both records are A records"),
|
||||
new PsfApiParameter("comment", PsfApiParameterType::String, "Optional comment for audit logs") ],
|
||||
// Example call
|
||||
'?action=replace_record&record=test.zone.org&ttl=3600&type=A&value=0.0.0.0&new_value=2.2.2.2&ptr=true');
|
||||
register_api('get_zone_for_fqdn', 'Returns zone name for given FQDN', 'Attempts to look up zone name for given FQDN using configuration file of php dns admin using auto-lookup function',
|
||||
'api_call_get_zone_for_fqdn', false, [ new PsfApiParameter("fqdn", PsfApiParameterType::String, "FQDN") ], [], '?action=get_zone_for_fqdn&fqdn=test.example.org');
|
||||
register_api('get_record', 'Return single record with specified FQDN', 'Lookup single record from master server responsible for zone that hosts this record', 'api_call_get_record', true,
|
||||
|
|
|
@ -30,8 +30,10 @@ $g_domains = [ 'example.domain' => [ 'transfer_server' => 'localhost', 'update_s
|
|||
// 'read_only' => false, // by default false, if true domain will be read only
|
||||
// 'in_transfer' => false, // if true domain will be marked as "in transfer" which means it's being transfered from one DNS master to another, so the records may not reflect truth
|
||||
// 'maintenance_note' => 'This domain is being configured now', // maintenance note to display for this domain
|
||||
// 'note' => 'This zone is very important', // generic note to display for this domain
|
||||
// 'tsig' => true,
|
||||
// 'tsig_key' => 'some_key' ] ];
|
||||
// 'tsig_key' => 'some_key',
|
||||
// 'ttl' => 3600 ] ]; // Overrides default global TTL for new records
|
||||
|
||||
// List of record types that can be edited
|
||||
// https://en.wikipedia.org/wiki/List_of_DNS_record_types
|
||||
|
@ -41,7 +43,7 @@ $g_editable = [ 'A', 'AAAA', 'CNAME', 'DNAME', 'DS', 'NS', 'PTR', 'SRV', 'SSHFP'
|
|||
// API is not affected by this
|
||||
$g_hidden_record_types = [ 'NSEC', 'RRSIG' ];
|
||||
|
||||
// Default TTL for new DNS records
|
||||
// Default TTL for new DNS records (can be also specified per zone using ttl key)
|
||||
$g_default_ttl = 3600;
|
||||
|
||||
// Path to executable of dig, you can also use this to specify some dig options for example:
|
||||
|
@ -51,6 +53,9 @@ $g_dig = '/usr/bin/dig';
|
|||
// Path to executable of nsupdate
|
||||
$g_nsupdate = '/usr/bin/nsupdate';
|
||||
|
||||
// If enabled, it will not be possible to work with garbage hostnames not conforming to standards
|
||||
$g_strict_hostname_checks = true;
|
||||
|
||||
// If set to value higher than 0, dig will be retried for N times, this is useful on broken networks with heavy packet loss
|
||||
$g_retry_on_error = 2;
|
||||
|
||||
|
@ -225,3 +230,6 @@ $g_caching_memcached_expiry = 0;
|
|||
|
||||
// You can optionally enable in-cache statistics that can be exported for use with monitoring, such as prometheus, to obtain usage metrics
|
||||
$g_caching_stats_enabled = false;
|
||||
|
||||
// Per-user configuration location
|
||||
$g_user_config_prefix = null;
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
||||
die("Not a valid entry point");
|
||||
|
||||
define('G_DNSTOOL_VERSION', '1.8.0');
|
||||
define('G_DNSTOOL_VERSION', '1.12.0');
|
||||
define('G_API_ELOGIN', 2);
|
||||
define('G_HEADER', 'DNS management tool');
|
||||
|
|
4
examples/ansible/README
Normal file
4
examples/ansible/README
Normal file
|
@ -0,0 +1,4 @@
|
|||
This folder contains some example ansible playbooks that modify DNS via API of dns tool using URI module
|
||||
|
||||
create_record.yml - very simple implementation of login, record creation and logout
|
||||
delete_record.yml - removes the same record, contains extra checks that result is success
|
46
examples/ansible/create_record.yml
Normal file
46
examples/ansible/create_record.yml
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- include_vars: vars.yml
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: login
|
||||
loginUsername: "{{ dns_tool_username }}"
|
||||
loginPassword: "{{ dns_tool_password }}"
|
||||
name: 'Login to DNS tool'
|
||||
no_log: True
|
||||
register: login
|
||||
delegate_to: localhost
|
||||
failed_when: "login.json.result != 'success'"
|
||||
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: create_record
|
||||
record: "petr.bena.cz.preprod"
|
||||
ttl: 3600
|
||||
type: A
|
||||
value: 1.1.2.2
|
||||
headers:
|
||||
Cookie: "{{ login.set_cookie }}"
|
||||
delegate_to: localhost
|
||||
name: 'Create a new record'
|
||||
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: logout
|
||||
headers:
|
||||
Cookie: "{{ login.set_cookie }}"
|
||||
delegate_to: localhost
|
||||
name: 'Logout from DNS tool'
|
49
examples/ansible/delete_record.yml
Normal file
49
examples/ansible/delete_record.yml
Normal file
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- include_vars: vars.yml
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: login
|
||||
loginUsername: "{{ dns_tool_username }}"
|
||||
loginPassword: "{{ dns_tool_password }}"
|
||||
name: 'Login to DNS tool'
|
||||
register: login
|
||||
nolog: True
|
||||
delegate_to: localhost
|
||||
failed_when: "login.json.result != 'success'"
|
||||
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: delete_record
|
||||
record: "petr.bena.cz.preprod"
|
||||
type: A
|
||||
value: 1.1.2.2
|
||||
headers:
|
||||
Cookie: "{{ login.set_cookie }}"
|
||||
name: 'Delete record'
|
||||
delegate_to: localhost
|
||||
register: this
|
||||
failed_when: "this.json.result != 'success'"
|
||||
|
||||
- uri:
|
||||
url: "{{ dns_tool }}/api.php"
|
||||
validate_certs: no
|
||||
method: POST
|
||||
body_format: form-urlencoded
|
||||
body:
|
||||
action: logout
|
||||
headers:
|
||||
Cookie: "{{ login.set_cookie }}"
|
||||
name: 'Logout from DNS tool'
|
||||
delegate_to: localhost
|
||||
register: this
|
||||
failed_when: "this.json.result != 'success'"
|
3
examples/ansible/vars.yml
Normal file
3
examples/ansible/vars.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
dns_tool: "https://dnstool.org/dns"
|
||||
dns_tool_username: ""
|
||||
dns_tool_password: ""
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -151,3 +151,20 @@ function GetCurrentUserName()
|
|||
return "unknown user";
|
||||
return $_SERVER['REMOTE_USER'];
|
||||
}
|
||||
|
||||
//! Required to handle various non-standard boolean interpretations, mostly for strings from API requests
|
||||
function IsTrue($bool)
|
||||
{
|
||||
if ($bool === true)
|
||||
return true;
|
||||
|
||||
// Check string version
|
||||
if ($bool == "true" || $bool == "t")
|
||||
return true;
|
||||
|
||||
// Check int version
|
||||
if (is_numeric($bool) && $bool != 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
||||
die("Not a valid entry point");
|
||||
|
||||
require_once("notifications.php");
|
||||
|
||||
function ShowError($form, $txt)
|
||||
{
|
||||
$msg = new BS_Alert('FATAL: ' . $txt, 'danger', $form);
|
||||
|
@ -29,15 +31,9 @@ function CheckEmpty($form, $label, $name)
|
|||
return true;
|
||||
}
|
||||
|
||||
//! Insert a warning message to warning message container, right now this works only with UI, it does nothing when used with API calls
|
||||
function DisplayWarning($text)
|
||||
{
|
||||
if (G_DNSTOOL_ENTRY_POINT === "api.php")
|
||||
return;
|
||||
global $g_warning_container;
|
||||
$warning_box = new BS_Alert('<b>WARNING:</b> ' . htmlspecialchars($text), 'warning');
|
||||
$warning_box->EscapeHTML = false;
|
||||
$g_warning_container->AppendObject($warning_box);
|
||||
Notifications::DisplayWarning($text);
|
||||
}
|
||||
|
||||
function GetSwitcher($parent)
|
||||
|
@ -63,4 +59,4 @@ function GetSwitcher($parent)
|
|||
'window.open("index.php?action=manage&domain=" + switcher[0].value, "_self");' .
|
||||
"}\n";
|
||||
return $switcher;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ function GetLoginInfo()
|
|||
{
|
||||
$role_info = ' (' . psf_string_auto_trim(implode (', ', $g_auth_roles_map[$_SESSION['user']]), 80, '...') . ')';
|
||||
}
|
||||
return '<div class="login_info"><span class="glyphicon glyphicon-user"></span>' . $_SESSION["user"] . $role_info . ' <a href="?logout">logout</a></div>';
|
||||
return '<div class="login_info"><span class="glyphicon glyphicon-user"></span>' . $_SESSION["user"] . $role_info . ' <a href="?logout"><span class="glyphicon glyphicon-log-out" title="logout"></span></a></div>';
|
||||
}
|
||||
|
||||
function ProcessLogin_Error($reason)
|
||||
|
|
|
@ -19,6 +19,7 @@ require_once("audit.php");
|
|||
require_once("common.php");
|
||||
require_once("debug.php");
|
||||
require_once("nsupdate.php");
|
||||
require_once("validator.php");
|
||||
require_once("zones.php");
|
||||
|
||||
//! Wrapper around nsupdate from nsupdate.php that checks if there are custom TSIG overrides for given domain
|
||||
|
@ -42,34 +43,6 @@ function ProcessNSUpdateForDomain($input, $domain)
|
|||
return nsupdate($input, $tsig, $tsig_key, $zone_name);
|
||||
}
|
||||
|
||||
function ProcessDelete($well)
|
||||
{
|
||||
global $g_domains, $g_selected_domain;
|
||||
if (!isset($_GET["delete"]))
|
||||
return;
|
||||
|
||||
if (strlen($g_selected_domain) == 0)
|
||||
Error("No domain");
|
||||
|
||||
if (!Zones::IsEditable($g_selected_domain))
|
||||
Error("Domain $g_selected_domain is not writeable");
|
||||
|
||||
if (!IsAuthorizedToWrite($g_selected_domain))
|
||||
Error("You are not authorized to edit $g_selected_domain");
|
||||
|
||||
$record = $_GET["delete"];
|
||||
|
||||
if (psf_string_contains($record, "\n"))
|
||||
Error("Invalid delete string");
|
||||
|
||||
$input = "server " . $g_domains[$g_selected_domain]["update_server"] . "\n";
|
||||
$input .= "update delete " . $record . "\nsend\nquit\n";
|
||||
ProcessNSUpdateForDomain($input, $g_selected_domain);
|
||||
WriteToAuditFile("delete", $record);
|
||||
IncrementStat('delete');
|
||||
$well->AppendObject(new BS_Alert("Successfully deleted record " . $record));
|
||||
}
|
||||
|
||||
function ProcessInsertFromPOST($zone, $record, $value, $type, $ttl)
|
||||
{
|
||||
if (psf_string_is_null_or_empty($record) && psf_string_is_null_or_empty($zone))
|
||||
|
@ -87,3 +60,161 @@ function ProcessInsertFromPOST($zone, $record, $value, $type, $ttl)
|
|||
|
||||
return "update add " . $fqdn . " " . $ttl . " " . $type . " " . $value . "\n";
|
||||
}
|
||||
|
||||
//! Create a new record in given zone, returns false on error - however, some errors may cancel execution
|
||||
//! This function may crash the app without returning
|
||||
function DNS_CreateRecord($zone, $record, $value, $type, $ttl, $comment)
|
||||
{
|
||||
global $g_domains;
|
||||
$input = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
$input .= ProcessInsertFromPOST($zone, $record, $value, $type, $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('create', $record . "." . $zone . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
IncrementStat('create');
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Replace record - atomic, returns true on success
|
||||
function DNS_ModifyRecord($zone, $record, $value, $type, $ttl, $comment, $old, $is_fqdn = false)
|
||||
{
|
||||
global $g_domains;
|
||||
if (!NSupdateEscapeCheck($old))
|
||||
Error('Invalid data for old record: ' . $old);
|
||||
$input = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
// First delete the existing record
|
||||
$input .= "update delete " . $old . "\n";
|
||||
if ($is_fqdn == false)
|
||||
$input .= ProcessInsertFromPOST($zone, $record, $value, $type, $ttl);
|
||||
else
|
||||
$input .= ProcessInsertFromPOST(NULL, $record, $value, $type, $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('replace_delete', $old, $comment);
|
||||
IncrementStat('replace_delete');
|
||||
WriteToAuditFile('replace_create', $record . "." . $zone . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
IncrementStat('replace_create');
|
||||
return true;
|
||||
}
|
||||
|
||||
function DNS_DeleteRecord($zone, $record)
|
||||
{
|
||||
global $g_domains;
|
||||
|
||||
if (strlen($zone) == 0)
|
||||
Error("No domain");
|
||||
|
||||
if (!Zones::IsEditable($zone))
|
||||
Error("Domain $zone is not writeable");
|
||||
|
||||
if (!IsAuthorizedToWrite($zone))
|
||||
Error("You are not authorized to edit $zone");
|
||||
|
||||
if (!NSupdateEscapeCheck($record))
|
||||
Error("Invalid delete string: " . $record);
|
||||
|
||||
$input = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
$input .= "update delete " . $record . "\nsend\nquit\n";
|
||||
ProcessNSUpdateForDomain($input, $zone);
|
||||
WriteToAuditFile("delete", $record);
|
||||
IncrementStat('delete');
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Try to insert a PTR record for given IP, on failure, warning is emitted and false returned, true returned on success
|
||||
//! this function is designed as a helper function that is used together with creation of A record, so it's never fatal
|
||||
function DNS_InsertPTRForARecord($ip, $fqdn, $ttl, $comment)
|
||||
{
|
||||
global $g_domains;
|
||||
Debug('PTR record was requested, checking zone name');
|
||||
$ip_parts = explode('.', $ip);
|
||||
if (count($ip_parts) != 4)
|
||||
{
|
||||
DisplayWarning('PTR record was not created: record '. $ip .' is not a valid IPv4 quad');
|
||||
return false;
|
||||
}
|
||||
$arpa = $ip_parts[3] . '.' . $ip_parts[2] . '.' . $ip_parts[1] . '.' . $ip_parts[0] . '.in-addr.arpa';
|
||||
$arpa_zone = Zones::GetZoneForFQDN($arpa);
|
||||
if ($arpa_zone === NULL)
|
||||
{
|
||||
DisplayWarning('PTR record was not created: there is no PTR zone for record '. $ip);
|
||||
return false;
|
||||
}
|
||||
if (!Zones::IsEditable($arpa_zone))
|
||||
{
|
||||
DisplayWarning("PTR record was not created for $ip: zone " . $arpa_zone . ' is read only');
|
||||
return false;
|
||||
}
|
||||
if (!IsAuthorizedToWrite($arpa_zone))
|
||||
{
|
||||
DisplayWarning("PTR record was not created: you don't have write access to zone " . $arpa_zone);
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug('Found PTR useable zone: ' . $arpa_zone);
|
||||
|
||||
if (!psf_string_endsWith($fqdn, '.'))
|
||||
$fqdn = $fqdn . '.';
|
||||
|
||||
// Let's insert this record
|
||||
$input = "server " . $g_domains[$arpa_zone]['update_server'] . "\n";
|
||||
$input .= ProcessInsertFromPOST(NULL, $arpa, $fqdn, 'PTR', $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $arpa_zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('create', $arpa . " " . $ttl . " PTR " . $fqdn, $comment);
|
||||
IncrementStat('create');
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Try to delete a PTR record for a given IP, on failure, warning is emitted and false returned, true returned on success
|
||||
//! this function is designed as a helper function that is used together with modifications of A record, so it's never fatal
|
||||
function DNS_DeletePTRForARecord($ip, $fqdn, $comment)
|
||||
{
|
||||
global $g_domains;
|
||||
Debug('PTR record removal was requested, checking zone name');
|
||||
$ip_parts = explode('.', $ip);
|
||||
if (count($ip_parts) != 4)
|
||||
{
|
||||
DisplayWarning('PTR record was not deleted: record '. $ip .' is not a valid IPv4 quad');
|
||||
return false;
|
||||
}
|
||||
$arpa = $ip_parts[3] . '.' . $ip_parts[2] . '.' . $ip_parts[1] . '.' . $ip_parts[0] . '.in-addr.arpa';
|
||||
$arpa_zone = Zones::GetZoneForFQDN($arpa);
|
||||
if ($arpa_zone === NULL)
|
||||
{
|
||||
DisplayWarning('PTR record was not deleted: there is no PTR zone for record '. $ip);
|
||||
return false;
|
||||
}
|
||||
if (!Zones::IsEditable($arpa_zone))
|
||||
{
|
||||
DisplayWarning("PTR record was not deleted for $ip: zone " . $arpa_zone . ' is read only');
|
||||
return false;
|
||||
}
|
||||
if (!IsAuthorizedToWrite($arpa_zone))
|
||||
{
|
||||
DisplayWarning("PTR record was not deleted: you don't have write access to zone " . $arpa_zone);
|
||||
return false;
|
||||
}
|
||||
|
||||
Debug('Found PTR useable zone: ' . $arpa_zone);
|
||||
|
||||
if (!psf_string_endsWith($fqdn, '.'))
|
||||
$fqdn = $fqdn . '.';
|
||||
|
||||
// Let's insert this record
|
||||
$input = "server " . $g_domains[$arpa_zone]['update_server'] . "\n";
|
||||
$input .= "update delete " . $arpa . " 0 PTR " . $fqdn . "\n";
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $arpa_zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('delete', $arpa . " 0 PTR " . $fqdn, $comment);
|
||||
IncrementStat('delete');
|
||||
return true;
|
||||
}
|
|
@ -18,20 +18,36 @@ if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
|||
$g_error_container = new BS_FluidContainer();
|
||||
$g_warning_container = new BS_FluidContainer();
|
||||
|
||||
//! Buffer that contains list of API warnings
|
||||
$g_api_warnings = [];
|
||||
$g_api_errors = [];
|
||||
|
||||
//! Provides interface to all notification functions that hold and display errors, warnings, etc.
|
||||
class Notifications
|
||||
{
|
||||
public static function DisplayError($text)
|
||||
public static function DisplayWarning($text)
|
||||
{
|
||||
global $g_warning_container;
|
||||
global $g_warning_container, $g_api_warnings;
|
||||
if (G_DNSTOOL_ENTRY_POINT === "api.php")
|
||||
{
|
||||
// API have separate container as we don't work with HTML there
|
||||
$g_api_warnings[] = $text;
|
||||
return;
|
||||
}
|
||||
$warning_box = new BS_Alert('<b>WARNING:</b> ' . htmlspecialchars($text), 'warning');
|
||||
$warning_box->EscapeHTML = false;
|
||||
$g_warning_container->AppendObject($warning_box);
|
||||
}
|
||||
|
||||
public static function DisplayWarning($text)
|
||||
public static function DisplayError($text)
|
||||
{
|
||||
global $g_error_container;
|
||||
global $g_error_container, $g_api_errors;
|
||||
if (G_DNSTOOL_ENTRY_POINT === "api.php")
|
||||
{
|
||||
// API have separate container as we don't work with HTML there
|
||||
$g_api_errors[] = $text;
|
||||
return;
|
||||
}
|
||||
$fatal_box = new BS_Alert('<b>ERROR:</b> ' . $text, 'danger');
|
||||
$fatal_box->EscapeHTML = false;
|
||||
$g_error_container->AppendObject($fatal_box);
|
||||
|
|
|
@ -26,6 +26,10 @@ $g_show_hidden_types = false;
|
|||
// when rendering UI so that we know if "show / hide" button should be even present or not
|
||||
$g_hidden_types_present = false;
|
||||
|
||||
// Counts of visible items
|
||||
$g_hidden_records_count = 0;
|
||||
$g_total_records_count = 0;
|
||||
|
||||
function GetStatusOfZoneAsNote($domain)
|
||||
{
|
||||
global $g_domains;
|
||||
|
@ -64,6 +68,11 @@ function GetStatusOfZoneAsNote($domain)
|
|||
$is_ok = false;
|
||||
$status->Text .= '<span class="glyphicon glyphicon-alert"></span> <b>Maintenance note:</b> ' .$domain_info['maintenance_note'];
|
||||
}
|
||||
if (array_key_exists('note', $domain_info))
|
||||
{
|
||||
$is_ok = false;
|
||||
$status->Text .= '<span class="glyphicon glyphicon-info"></span> <b>Note:</b> ' .$domain_info['note'];
|
||||
}
|
||||
|
||||
if ($is_ok)
|
||||
return NULL;
|
||||
|
@ -198,7 +207,7 @@ function GetRecordList($zone)
|
|||
|
||||
function GetRecordListTable($parent, $domain)
|
||||
{
|
||||
global $g_editable, $g_show_hidden_types, $g_hidden_record_types, $g_hidden_types_present;
|
||||
global $g_editable, $g_show_hidden_types, $g_hidden_record_types, $g_hidden_types_present, $g_total_records_count, $g_hidden_records_count;
|
||||
$table = new BS_Table($parent);
|
||||
$table->Condensed = true;
|
||||
$table->Headers = [ "Record", "TTL", "Scope", "Type", "Value", "Options" ];
|
||||
|
@ -207,30 +216,47 @@ function GetRecordListTable($parent, $domain)
|
|||
$table->SetColumnWidth(5, '80px'); // Options
|
||||
$records = GetRecordList($domain);
|
||||
$is_editable = Zones::IsEditable($domain) && IsAuthorizedToWrite($domain);
|
||||
$has_ptr = Zones::HasPTRZones();
|
||||
foreach ($records as $record)
|
||||
{
|
||||
$g_total_records_count++;
|
||||
if (in_array($record[3], $g_hidden_record_types))
|
||||
{
|
||||
$g_hidden_types_present = true;
|
||||
if (!$g_show_hidden_types)
|
||||
{
|
||||
$g_hidden_records_count++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$is_editable || !in_array($record[3], $g_editable))
|
||||
{
|
||||
$record[] = '';
|
||||
} else
|
||||
{
|
||||
$record[] = '<a href="index.php?action=manage&domain=' . $domain . '&delete=' .
|
||||
urlencode($record[0] . " " . $record[1] . " " . $record[3] . " " . $record[4]) .
|
||||
'" onclick="return confirm(\'Are you sure?\')"><span class="glyphicon glyphicon-trash" title="Delete"></span></a> ' .
|
||||
'<a href="index.php?action=edit&domain=' . $domain . '&key=' .
|
||||
$delete_record = '<a href="index.php?action=manage&domain=' . $domain . '&delete=' . urlencode($record[0] . " " . $record[1] . " " . $record[3] . " " . $record[4]) .
|
||||
'" onclick="return confirm(\'Are you sure you want to delete ' . $record[0] . '?\')"><span class="glyphicon glyphicon-trash" title="Delete"></span></a>';
|
||||
$delete_record_with_ptr = '';
|
||||
if ($has_ptr && $record[3] == 'A')
|
||||
{
|
||||
// Optional button to delete record together with PTR record, show only if there are PTR zones in cfg
|
||||
$delete_record_with_ptr = '<a href="index.php?action=manage&ptr=true&key=' . urlencode($record[0]) . '&value=' . urlencode($record[4]) . '&type=' . $record[3] . '&domain=' . $domain .
|
||||
'&delete=' . urlencode($record[0] . ' ' . $record[1] . " " . $record[3] . " " . $record[4]) .
|
||||
'" onclick="return confirm(\'Are you sure you want to delete ' . $record[0] . '?\')"><span style="color: #ff0000;" class="glyphicon glyphicon-trash" title="Delete together with associated PTR record (if any exist)"></span></a>';
|
||||
}
|
||||
$large_space = ' ';
|
||||
$record[] = $delete_record . $large_space . '<a href="index.php?action=edit&domain=' . $domain . '&key=' .
|
||||
urlencode($record[0]) . "&ttl=" . $record[1] . "&type=" . $record[3] . "&value=" . urlencode($record[4]) .
|
||||
"&old=" . urlencode($record[0] . " " . $record[1] . " " . $record[3] . " " . $record[4]) .
|
||||
'"><span title="Edit" class="glyphicon glyphicon-pencil"></span></a>';
|
||||
'"><span title="Edit" class="glyphicon glyphicon-pencil"></span></a>' . $large_space . $delete_record_with_ptr;
|
||||
}
|
||||
$record[4] = '<span class="value">' . $record[4] . '</span>';
|
||||
$table->AppendRow($record);
|
||||
}
|
||||
if ($is_editable) {
|
||||
$add = '<a href="index.php?action=new&domain=' . $domain . '"><span title="Add New" class="glyphicon glyphicon-plus"></span></a>';
|
||||
$table->AppendRow(['', '', '', '', '', $add]);
|
||||
}
|
||||
return $table;
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class TabBatch
|
|||
$form->Method = FormMethod::Post;
|
||||
$layout = new HtmlTable($form);
|
||||
$layout->BorderSize = 0;
|
||||
$dl = new ComboBox("zone", $layout);
|
||||
$dl = new BS_ComboBox("zone", $layout);
|
||||
foreach ($g_domains as $key => $info)
|
||||
{
|
||||
if (!IsAuthorizedToWrite($key))
|
||||
|
|
|
@ -15,6 +15,7 @@ if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
|||
die("Not a valid entry point");
|
||||
|
||||
require_once("common_ui.php");
|
||||
require_once("debug.php");
|
||||
require_once("validator.php");
|
||||
require_once("modify.php");
|
||||
require_once("zones.php");
|
||||
|
@ -57,43 +58,47 @@ class TabEdit
|
|||
if (!is_numeric($ttl))
|
||||
Error('TTL must be a number');
|
||||
|
||||
// Sanitize input from user
|
||||
$record = SanitizeHostname($record);
|
||||
|
||||
if (!IsValidHostName($record))
|
||||
Error('Invalid hostname: ' . $record);
|
||||
|
||||
$input = "server " . $g_domains[$zone]['update_server'] . "\n";
|
||||
$comment = NULL;
|
||||
if (isset($_POST["comment"]))
|
||||
$comment = $_POST["comment"];
|
||||
|
||||
if ($_POST['submit'] == 'Create')
|
||||
{
|
||||
$input .= ProcessInsertFromPOST($zone, $record, $value, $type, $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('create', $record . "." . $zone . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
IncrementStat('create');
|
||||
$form->AppendObject(new BS_Alert("Successfully inserted record " . $record . "." . $zone));
|
||||
if (DNS_CreateRecord($zone, $record, $value, $type, $ttl, $comment))
|
||||
$form->AppendObject(new BS_Alert("Successfully inserted record " . $record . "." . $zone));
|
||||
} else if ($_POST["submit"] == "Edit")
|
||||
{
|
||||
if (!isset($_POST["old"]))
|
||||
Error("Missing old record necessary for update");
|
||||
if (!NSupdateEscapeCheck($_POST["old"]))
|
||||
Error('Invalid data for old record: ' . $_POST["old"]);
|
||||
// First delete the existing record
|
||||
$input .= "update delete " . $_POST["old"] . "\n";
|
||||
$input .= ProcessInsertFromPOST($zone, $record, $value, $type, $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('replace_delete', $_POST["old"], $comment);
|
||||
IncrementStat('replace_delete');
|
||||
WriteToAuditFile('replace_create', $record . "." . $zone . " " . $ttl . " " . $type . " " . $value, $comment);
|
||||
IncrementStat('replace_create');
|
||||
$form->AppendObject(new BS_Alert("Successfully replaced " . $_POST["old"] . " with " . $record . "." . $zone . " " .
|
||||
$ttl . " " . $type . " " . $value));
|
||||
if (DNS_ModifyRecord($zone, $record, $value, $type, $ttl, $comment, $_POST["old"]))
|
||||
{
|
||||
$form->AppendObject(new BS_Alert("Successfully replaced " . $_POST["old"] . " with " . $record . "." . $zone . " " .
|
||||
$ttl . " " . $type . " " . $value));
|
||||
}
|
||||
// Delete PTR if wanted
|
||||
if (isset($_POST['ptr']) && $_POST['ptr'] === "true")
|
||||
{
|
||||
if (isset($_POST["old_type"]) && $_POST["old_type"] == "A")
|
||||
{
|
||||
// Check if all necessary values are present
|
||||
if (!isset($_POST["old_value"]) || !isset($_POST["old_record"]))
|
||||
{
|
||||
DisplayWarning("PTR record was not deleted, because old_record or old_value was missing");
|
||||
} else
|
||||
{
|
||||
DNS_DeletePTRForARecord($_POST["old_value"], $_POST["old_record"], $comment);
|
||||
}
|
||||
} else
|
||||
{
|
||||
Debug("Not removing PTR, original type was " . $_POST["old_type"]);
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
Error("Unknown modify mode");
|
||||
|
@ -102,53 +107,18 @@ class TabEdit
|
|||
// Create PTR if wanted
|
||||
if (isset($_POST['ptr']) && $_POST['ptr'] === "true")
|
||||
{
|
||||
Debug('PTR record was requested, checking zone name');
|
||||
if ($type !== "A")
|
||||
{
|
||||
DisplayWarning('PTR record was not created: PTR record can be only created when you are inserting A record, you created ' . $type . ' record instead');
|
||||
return;
|
||||
}
|
||||
$ip_parts = explode('.', $value);
|
||||
if (count($ip_parts) != 4)
|
||||
{
|
||||
DisplayWarning('PTR record was not created: record '. $value .' is not a valid IPv4 quad');
|
||||
return;
|
||||
}
|
||||
$arpa = $ip_parts[3] . '.' . $ip_parts[2] . '.' . $ip_parts[1] . '.' . $ip_parts[0] . '.in-addr.arpa';
|
||||
$arpa_zone = Zones::GetZoneForFQDN($arpa);
|
||||
if ($arpa_zone === NULL)
|
||||
{
|
||||
DisplayWarning('PTR record was not created: there is no PTR zone for record '. $value);
|
||||
return;
|
||||
}
|
||||
if (!Zones::IsEditable($arpa_zone))
|
||||
{
|
||||
DisplayWarning('PTR record was not created: zone ' . $arpa_zone . ' is read only');
|
||||
return;
|
||||
}
|
||||
if (!IsAuthorizedToWrite($arpa_zone))
|
||||
{
|
||||
DisplayWarning("PTR record was not created: you don't have write access to zone " . $arpa_zone);
|
||||
return;
|
||||
}
|
||||
Debug('Found PTR useable zone: ' . $arpa_zone);
|
||||
$arpa_value = $record . '.' . $zone . '.';
|
||||
|
||||
// Let's insert this record
|
||||
$input = "server " . $g_domains[$arpa_zone]['update_server'] . "\n";
|
||||
$input .= ProcessInsertFromPOST(NULL, $arpa, $arpa_value, 'PTR', $ttl);
|
||||
$input .= "send\nquit\n";
|
||||
$result = ProcessNSUpdateForDomain($input, $arpa_zone);
|
||||
if (strlen($result) > 0)
|
||||
Debug("result: " . $result);
|
||||
WriteToAuditFile('create', $arpa . " " . $ttl . " PTR " . $arpa_value, $comment);
|
||||
IncrementStat('create');
|
||||
DNS_InsertPTRForARecord($value, $record . '.' . $zone, $ttl, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetInsertForm($parent, $edit_mode = false, $default_key = "", $default_ttl = NULL, $default_type = "A", $default_value = "", $default_comment = "")
|
||||
{
|
||||
global $g_audit, $g_selected_domain, $g_domains, $g_editable, $g_default_ttl;
|
||||
global $g_audit, $g_selected_domain, $g_domains, $g_editable;
|
||||
|
||||
// In case we are returning to insert form from previous insert, make default type the one we used before
|
||||
if (isset($_POST['type']))
|
||||
|
@ -162,20 +132,24 @@ class TabEdit
|
|||
if (isset($_POST['comment']))
|
||||
$default_comment = $_POST['comment'];
|
||||
|
||||
if (isset($_POST['ttl']))
|
||||
$default_ttl = $_POST['ttl'];
|
||||
|
||||
// If ttl is not specified use default one from config file
|
||||
if ($default_ttl === NULL)
|
||||
$default_ttl = strval($g_default_ttl);
|
||||
$default_ttl = strval(Zones::GetDefaultTTL($g_selected_domain));
|
||||
|
||||
$form = new Form("index.php?action=new", $parent);
|
||||
$form->Method = FormMethod::Post;
|
||||
$layout = new HtmlTable($form);
|
||||
$layout->BorderSize = 0;
|
||||
$layout->ColWidth[4] = '40%';
|
||||
$layout->Headers = [ "Record", "Zone", "TTL", "Type", "Value" ];
|
||||
if ($g_audit)
|
||||
$layout->Headers[] = 'Comment';
|
||||
$form_items = [];
|
||||
$form_items[] = new BS_TextBox("record", $default_key, NULL, $layout);
|
||||
$dl = new ComboBox("zone", $layout);
|
||||
$dl = new BS_ComboBox("zone", $layout);
|
||||
if ($edit_mode)
|
||||
{
|
||||
if ($g_selected_domain === NULL)
|
||||
|
@ -184,6 +158,8 @@ class TabEdit
|
|||
}
|
||||
$dl->AddDefaultValue($g_selected_domain, "." . $g_selected_domain);
|
||||
$dl->Enabled = false;
|
||||
// we must add a hidden element that preserves the zone because disabled HTML elements do not submit form data
|
||||
$form->AppendObject(new Hidden("zone", $g_selected_domain));
|
||||
} else
|
||||
{
|
||||
foreach ($g_domains as $key => $info)
|
||||
|
@ -198,7 +174,7 @@ class TabEdit
|
|||
}
|
||||
$form_items[] = $dl;
|
||||
$form_items[] = new BS_TextBox("ttl", $default_ttl, NULL, $layout);
|
||||
$tl = new ComboBox("type", $layout);
|
||||
$tl = new BS_ComboBox("type", $layout);
|
||||
$types = $g_editable;
|
||||
foreach ($types as $type)
|
||||
{
|
||||
|
@ -219,17 +195,67 @@ class TabEdit
|
|||
$form_items[] = $comment;
|
||||
}
|
||||
$layout->AppendRow($form_items);
|
||||
if (!$edit_mode && Zones::HasPTRZones())
|
||||
$form->AppendObject(new BS_CheckBox("ptr", "true", false, NULL, $form, "Create PTR record for this IP (works only with A records)"));
|
||||
if (Zones::HasPTRZones())
|
||||
{
|
||||
if (!$edit_mode)
|
||||
$form->AppendObject(new BS_CheckBox("ptr", "true", false, NULL, $form, "Create PTR record for this IP (works only with A records)"));
|
||||
else
|
||||
$form->AppendObject(new BS_CheckBox("ptr", "true", false, NULL, $form, "Modify underlying PTR records (works only if original, new or both values are A records)"));
|
||||
}
|
||||
if (isset($_GET["old"]))
|
||||
$form->AppendObject(new Hidden("old", htmlspecialchars($_GET["old"])));
|
||||
$form->AppendObject(new Hidden("old", htmlspecialchars($_GET["old"])));
|
||||
|
||||
if ($edit_mode)
|
||||
{
|
||||
// Preserve old values, we need to work with them when modifying PTR records
|
||||
$form->AppendObject(new Hidden("old_record", htmlspecialchars($_GET["key"])));
|
||||
$form->AppendObject(new Hidden("old_ttl", htmlspecialchars($default_ttl)));
|
||||
$form->AppendObject(new Hidden("old_type", htmlspecialchars($default_type)));
|
||||
$form->AppendObject(new Hidden("old_value", htmlspecialchars($default_value)));
|
||||
|
||||
$form->AppendObject(new BS_Button("submit", "Edit"));
|
||||
else
|
||||
} else
|
||||
{
|
||||
$form->AppendObject(new BS_Button("submit", "Create"));
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
public static function GetHelp()
|
||||
{
|
||||
$help = new DivContainer();
|
||||
$help->AppendLine();
|
||||
$help->AppendHtmlLine('<a data-toggle="collapse" href="#collapseHelp">Display help</a>');
|
||||
$c = new DivContainer($help);
|
||||
$c->ClassName = "collapse";
|
||||
$c->ID = "collapseHelp";
|
||||
$c->AppendHeader("Record", 3);
|
||||
$c->AppendHtmlLine('Name of the key you want to add. If you want to create DNS record <code>test.domain.org</code> in zone domain.org, then value of field record will be just <code>test</code>. <b>Do not append zone name to record name, this is done automatically</b>. Record can be also left blank if you want to add a record for zone apex (zone itself), such as MX records.');
|
||||
$c->AppendHeader("Zone", 3);
|
||||
$c->AppendHtmlLine('Name of zone you want to create record in. In case that subzone exist (for example you want to add record <code>subzone.test.domain.org</code> but subzone <code>test.domain.org</code> exists in dropdown menu), you must create the record within the subzone, not in the parent zone, otherwise it will not be visible in domain name system. If no subzone exists, then you can create a record <code>subzone.test</code> inside of <code>domain.org</code>.');
|
||||
$c->AppendHeader("TTL", 3);
|
||||
$c->AppendHtmlLine('Time to live tells caching name servers for how long can this record be cached for. Too low TTL may lead to performance issues as the request to resolve such record will be forwarded to authoritative name server most of the time. Too long TTL can make it complicated to change the value of record, because caching name servers will hold the cached value for too long. If you are not sure which value to pick, leave the default value.');
|
||||
$c->AppendHeader("Type", 3);
|
||||
$c->AppendHtmlLine('Type of DNS record, following record types are most common:');
|
||||
$record_types = new BS_Table($c);
|
||||
$record_types->Headers = [ 'Type', 'Description' ];
|
||||
$record_types->AppendRow( [ 'A', 'IPv4 record, value of this record is IPv4 address, for example 1.2.3.4' ]);
|
||||
$record_types->AppendRow( [ 'AAAA', 'IPv6 record, value of this record is IPv6 address, for example ::1' ]);
|
||||
$record_types->AppendRow( [ 'TXT', 'Text record, must be max 255 characters in length, otherwise you need to split it to multiple parts within quotes ("), each part max. 255 characters in size' ]);
|
||||
$record_types->AppendRow( [ 'MX', 'Mail server record, value consist of two parts, priority and hostname of mail server, for example: <code>10 mail.domain.org</code>']);
|
||||
$record_types->AppendRow( [ 'NS', 'Delegates a record to another name server. If used on zone apex it defines authoritative name servers for a zone.']);
|
||||
$record_types->AppendRow( [ 'SSHFP', 'SSH fingerprint, used by SSH client when verifying that target server has authentic fingerprint']);
|
||||
$record_types->AppendRow( [ 'CNAME', 'Redirect record to another domain name, this will redirect all record types for given record name and therefore can\'t be used on zone apex']);
|
||||
$record_types->AppendRow( [ 'SOA', 'Start of authority record - this record exists only for apex of zone and denotes existence of a zone, it includes administrative data for zone, this record is returned twice in zone transfer, as first and last record']);
|
||||
$c->AppendHtmlLine('See <a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types" target="_blank">https://en.wikipedia.org/wiki/List_of_DNS_record_types</a> for a more complete and detailed list');
|
||||
$c->AppendHeader("Value", 3);
|
||||
$c->AppendHtmlLine('Value of record, format depends on record type');
|
||||
$c->AppendHeader("Comment", 3);
|
||||
$c->AppendHtmlLine('Optional comment for audit log of DNS tool, this has no effect on DNS server itself. This field is available only if audit subsystem is enabled.');
|
||||
//$c->AppendObject(new BS_List);
|
||||
return $help;
|
||||
}
|
||||
|
||||
public static function GetEditForm($parent)
|
||||
{
|
||||
global $g_selected_domain;
|
||||
|
|
|
@ -14,13 +14,49 @@
|
|||
if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
||||
die("Not a valid entry point");
|
||||
|
||||
require_once("common.php");
|
||||
require_once("common_ui.php");
|
||||
require_once("debug.php");
|
||||
require_once("modify.php");
|
||||
|
||||
class TabManage
|
||||
{
|
||||
//! Called from index.php this function is supposed to process deleting if requested in UI
|
||||
public static function ProcessDelete($well)
|
||||
{
|
||||
global $g_domains, $g_selected_domain;
|
||||
if (!isset($_GET["delete"]))
|
||||
return;
|
||||
|
||||
$record = $_GET["delete"];
|
||||
|
||||
if (DNS_DeleteRecord($g_selected_domain, $record))
|
||||
$well->AppendObject(new BS_Alert("Successfully deleted record " . $record));
|
||||
|
||||
if (isset($_GET["ptr"]) && $_GET["ptr"] == true)
|
||||
{
|
||||
Debug('PTR record deletion was requested for ' . $record);
|
||||
if (!isset($_GET['key']) || !isset($_GET['value']) || !isset($_GET['type']))
|
||||
{
|
||||
Warning('PTR record was not removed because either key, value or type was not specified');
|
||||
return;
|
||||
}
|
||||
$key = $_GET['key'];
|
||||
$type = $_GET['type'];
|
||||
$value = $_GET['value'];
|
||||
if ($type != 'A')
|
||||
{
|
||||
Warning('Requested PTR record was not deleted: PTR record can be only deleted when you are changing A record, you deleted ' . $type . ' record instead');
|
||||
} else
|
||||
{
|
||||
DNS_DeletePTRForARecord($value, $key, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetContents($fc)
|
||||
{
|
||||
global $g_auth_session_name, $g_domains, $g_selected_domain, $g_show_hidden_types, $g_hidden_types_present;
|
||||
global $g_auth_session_name, $g_domains, $g_selected_domain, $g_total_records_count, $g_hidden_records_count, $g_show_hidden_types, $g_hidden_types_present;
|
||||
|
||||
// Check toggle for hidden
|
||||
if (isset($_GET['hidden_types']))
|
||||
|
@ -46,12 +82,20 @@ class TabManage
|
|||
reset($g_domains);
|
||||
$g_selected_domain = key($g_domains);
|
||||
}
|
||||
// First get the record list - this function will fill up g_hidden_types_present variable as well as global counters
|
||||
$record_list = GetRecordListTable(NULL, $g_selected_domain);
|
||||
$record_count = "";
|
||||
if ($g_total_records_count > 0)
|
||||
{
|
||||
if ($g_hidden_records_count == 0)
|
||||
$record_count = " ($g_total_records_count records)";
|
||||
else
|
||||
$record_count = " ($g_total_records_count records, $g_hidden_records_count hidden)";
|
||||
}
|
||||
$fc->AppendObject(GetSwitcher($fc));
|
||||
$fc->AppendHeader($g_selected_domain, 2);
|
||||
$fc->AppendHeader($g_selected_domain . $record_count, 2);
|
||||
$fc->AppendHtml('<div class="export_csv"><a href="?action=csv&domain=' . $g_selected_domain . '">Export as CSV</a></div>');
|
||||
$fc->AppendObject(GetStatusOfZoneAsNote($g_selected_domain));
|
||||
// First get the record list - this function will fill up g_hidden_types_present variable
|
||||
$record_list = GetRecordListTable(NULL, $g_selected_domain);
|
||||
if ($g_hidden_types_present === true)
|
||||
{
|
||||
// This zone contains some hidden record types, show toggle for user
|
||||
|
|
|
@ -45,6 +45,10 @@ class TabOverview
|
|||
$is_ok = false;
|
||||
$status .= '<span class="glyphicon glyphicon-alert" title="' . $domain_info['maintenance_note'] . '"></span> ';
|
||||
}
|
||||
if (array_key_exists('note', $domain_info))
|
||||
{
|
||||
$status .= ' <span class="glyphicon glyphicon-comment" title="' . $domain_info['note'] . '"></span> ';
|
||||
}
|
||||
|
||||
if ($is_ok)
|
||||
return '<span class="glyphicon glyphicon-ok" title="OK"></span>' . $status;
|
||||
|
|
|
@ -16,6 +16,7 @@ if (!defined('G_DNSTOOL_ENTRY_POINT'))
|
|||
|
||||
function IsValidHostName($fqdn)
|
||||
{
|
||||
global $g_strict_hostname_checks;
|
||||
// Few extra checks to prevent shell escaping
|
||||
if (!ShellEscapeCheck($fqdn))
|
||||
return false;
|
||||
|
@ -29,11 +30,22 @@ function IsValidHostName($fqdn)
|
|||
return false;
|
||||
if (psf_string_contains($fqdn, "\n"))
|
||||
return false;
|
||||
// security fix + and - are switches used by dig so we need to make sure they aren't first symbol even if strict checking is not enabled
|
||||
if (psf_string_startsWith($fqdn, "+"))
|
||||
return false;
|
||||
if (psf_string_startsWith($fqdn, "-"))
|
||||
return false;
|
||||
if ($g_strict_hostname_checks && preg_match('/[^0-9\*a-zA-Z_\-\.]/', $fqdn))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function SanitizeHostname($hostname)
|
||||
{
|
||||
// Right now we do only trim, but maybe in future we will do more, so let's keep it in here
|
||||
return trim($hostname);
|
||||
}
|
||||
|
||||
function NSupdateEscapeCheck($string)
|
||||
{
|
||||
if (psf_string_contains($string, "\n"))
|
||||
|
|
|
@ -30,6 +30,9 @@ class Zones
|
|||
if (isset($properties['in_transfer']))
|
||||
$result[$domain]['in_transfer'] = $properties['in_transfer'];
|
||||
|
||||
if (isset($properties['note']))
|
||||
$result[$domain]['note'] = $properties['note'];
|
||||
|
||||
if (isset($properties['maintenance_note']))
|
||||
$result[$domain]['maintenance_note'] = $properties['maintenance_note'];
|
||||
|
||||
|
@ -78,5 +81,23 @@ class Zones
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function GetDefaultTTL($domain)
|
||||
{
|
||||
global $g_default_ttl, $g_domains;
|
||||
|
||||
if ($domain === NULL)
|
||||
return $g_default_ttl;
|
||||
|
||||
if (!array_key_exists($domain, $g_domains))
|
||||
die("No such zone: $domain");
|
||||
|
||||
$domain_info = $g_domains[$domain];
|
||||
|
||||
if (array_key_exists('ttl', $domain_info))
|
||||
return $domain_info['ttl'];
|
||||
|
||||
return $g_default_ttl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
16
index.php
16
index.php
|
@ -42,6 +42,9 @@ if ($g_use_local_bootstrap)
|
|||
// Start up the program, initialize all sorts of resources, syslog, session data etc.
|
||||
Initialize();
|
||||
|
||||
if ($g_user_config_prefix !== null)
|
||||
include($g_user_config_prefix.GetCurrentUserName().".php");
|
||||
|
||||
// Save us some coding
|
||||
$psf_containers_auto_insert_child = true;
|
||||
|
||||
|
@ -112,7 +115,10 @@ if (RequireLogin())
|
|||
$fc->AppendObject(GetLogin());
|
||||
} else
|
||||
{
|
||||
$fc->AppendHeader(G_HEADER);
|
||||
$header = new DivContainer($fc);
|
||||
$header->ClassName = 'header';
|
||||
$header->AppendObject(new Image("favicon.png", "DNS"));
|
||||
$header->AppendHeader(G_HEADER);
|
||||
if ($g_logged_in)
|
||||
$fc->AppendHtml(GetLoginInfo());
|
||||
|
||||
|
@ -128,7 +134,7 @@ if (RequireLogin())
|
|||
$fc->AppendObject(TabOverview::GetSelectForm($fc));
|
||||
} else if ($g_action == "manage")
|
||||
{
|
||||
ProcessDelete($fc);
|
||||
TabManage::ProcessDelete($fc);
|
||||
TabManage::GetContents($fc);
|
||||
} else if ($g_action == 'csv')
|
||||
{
|
||||
|
@ -144,11 +150,13 @@ if (RequireLogin())
|
|||
// Process previous inserting call (via submit) in case there was some
|
||||
TabEdit::Process($fc);
|
||||
$fc->AppendObject(TabEdit::GetInsertForm($fc));
|
||||
$fc->AppendObject(TabEdit::GetHelp());
|
||||
} else if ($g_action == 'edit')
|
||||
{
|
||||
// Process previous edit call (via submit) in case there was some
|
||||
TabEdit::Process($fc);
|
||||
$fc->AppendObject(TabEdit::GetEditForm($fc));
|
||||
$fc->AppendObject(TabEdit::GetHelp());
|
||||
} else if ($g_action == 'batch')
|
||||
{
|
||||
// Process any previous pending batch operation
|
||||
|
@ -160,8 +168,8 @@ if (RequireLogin())
|
|||
// Bug workaround - the footer seems to take up some space
|
||||
$website->AppendHtml("<br><br><br>");
|
||||
|
||||
$website->AppendHtmlLine("<footer class='footer'><div class='container'>Created by Petr Bena [petr@bena.rocks] (c) 2018 - 2020, source code at ".
|
||||
"<a href='http://github.com/benapetr/dnsphpadmin'>http://github.com/benapetr/dnsphpadmin</a> Version: " . G_DNSTOOL_VERSION . "</div></footer>");
|
||||
$website->AppendHtmlLine("<footer class='footer'><div class='container'>Created by Petr Bena [petr@bena.rocks] (c) 2018 - 2023, source code at ".
|
||||
"<a href='https://github.com/benapetr/dnsphpadmin'>https://github.com/benapetr/dnsphpadmin</a> Version: " . G_DNSTOOL_VERSION . "</div></footer>");
|
||||
|
||||
$website->PrintHtml();
|
||||
|
||||
|
|
2
psf
2
psf
|
@ -1 +1 @@
|
|||
Subproject commit 1f8fde4565a25e5605736e37631de5be572dddd3
|
||||
Subproject commit 776af33fed083797593e1da7bb5ec902f7758322
|
10
style.css
10
style.css
|
@ -7,6 +7,16 @@ body {
|
|||
margin-bottom: 60px; /* Margin bottom by footer height */
|
||||
}
|
||||
|
||||
.header img {
|
||||
float: left;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
position: relative;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
version=`git tag | tail -1`
|
||||
#version=`git tag | tail -1`
|
||||
version=`git for-each-ref --sort=creatordate --format '%(refname) %(creatordate)' refs/tags | tail -1 | sed 's/ .*//' | sed 's/..........//'`
|
||||
|
||||
if [ ! -f 'index.php' ]; then
|
||||
echo "You have to run this from root folder of project"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "/tmp/dnsphpadmin_$version.tar.gz" ];then
|
||||
echo "/tmp/dnsphpadmin_$version.tar.gz already exist"
|
||||
|
@ -17,6 +23,7 @@ rm -rf "/tmp/dnsphpadmin_$version/.git"
|
|||
rm -f "/tmp/dnsphpadmin_$version/.gitignore"
|
||||
rm -f "/tmp/dnsphpadmin_$version/.gitmodules"
|
||||
rm -rf "/tmp/dnsphpadmin_$version/util"
|
||||
rm -rf "/tmp/dnsphpadmin_$version/examples"
|
||||
rm -f "/tmp/dnsphpadmin_$version/.travis.yml"
|
||||
|
||||
cd /tmp || exit 1
|
||||
|
|
|
@ -38,11 +38,15 @@ function CheckZone($data)
|
|||
|
||||
$ut = new UnitTest();
|
||||
|
||||
$g_default_ttl = 3600;
|
||||
|
||||
$ut->Evaluate('Check for non-existence of PTR zones (none) in empty list', Zones::HasPTRZones() === false);
|
||||
$g_domains['168.192.in-addr.arpa'] = [ ];
|
||||
$g_domains['192.in-addr.arpa'] = [ ];
|
||||
$g_domains['192.in-addr.arpa'] = [ 'ttl' => 200 ];
|
||||
$ut->Evaluate('Check for non-existence of PTR zones (none) in empty list', Zones::HasPTRZones() === true);
|
||||
$ut->Evaluate('Get zone for FQDN', Zones::GetZoneForFQDN('0.0.168.192.in-addr.arpa') == '168.192.in-addr.arpa');
|
||||
$ut->Evaluate('Test GetDefaultTTL()', Zones::GetDefaultTTL('168.192.in-addr.arpa') == 3600);
|
||||
$ut->Evaluate('Test GetDefaultTTL()', Zones::GetDefaultTTL('192.in-addr.arpa') == 200);
|
||||
|
||||
$dz1 = raw_zone_to_array(file_get_contents(dirname(__FILE__) . '/testdata/valid.zone1'));
|
||||
$dz2 = raw_zone_to_array(file_get_contents(dirname(__FILE__) . '/testdata/invalid.zone'));
|
||||
|
@ -58,6 +62,9 @@ $ut->Evaluate('Parser test - zone 2', CheckZone($dz3));
|
|||
$ut->Evaluate('Validator - valid #1', IsValidHostName('insw.cz') === true);
|
||||
$ut->Evaluate('Validator - valid #2', IsValidHostName('te-st1.petr.bena.rocks') === true);
|
||||
$ut->Evaluate('Validator - valid #3', IsValidHostName('*.petr.bena.rocks') === true);
|
||||
$ut->Evaluate('Validator - valid #4', IsValidHostName('_spf.petr.bena.rocks') === true);
|
||||
$ut->Evaluate('Validator - valid #5', IsValidHostName('wqdcsrv331') === true);
|
||||
$ut->Evaluate('Validator - valid #6', IsValidHostName('2.168.192.in-addr.arpa') === true);
|
||||
$ut->Evaluate('Validator - invalid #1', IsValidHostName('-invalid') === false);
|
||||
$ut->Evaluate('Validator - invalid #2', IsValidHostName('---') === false);
|
||||
$ut->Evaluate('Validator - invalid #3', IsValidHostName('google domain') === false);
|
||||
|
@ -66,8 +73,10 @@ $ut->Evaluate('Validator - invalid #5', IsValidHostName("google.com\ntest") ===
|
|||
$ut->Evaluate('Validator - invalid #6', IsValidHostName("google.com\ttest") === false);
|
||||
$ut->Evaluate('Validator - invalid #7', IsValidHostName("'google.com") === false);
|
||||
$ut->Evaluate('Validator - invalid #8', IsValidHostName("\"google.com") === false);
|
||||
$ut->Evaluate('Validator - invalid #9', IsValidHostName('$test.org') === false);
|
||||
$ut->Evaluate('Validator - invalid #10', IsValidHostName('/x.test.org') === false);
|
||||
|
||||
echo ("\n");
|
||||
$ut->PrintResults();
|
||||
|
||||
$ut->Exit();
|
||||
$ut->ExitTest();
|
||||
|
|
|
@ -15,6 +15,8 @@ function api($action)
|
|||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $action);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
return json_decode(curl_exec($ch), true);
|
||||
}
|
||||
|
||||
|
@ -27,11 +29,48 @@ $ut->Evaluate("get_version", $version['version'] == G_DNSTOOL_VERSION);
|
|||
$logged = api("action=is_logged");
|
||||
$ut->Evaluate("is_logged", $logged['is_logged']);
|
||||
|
||||
$list = api("action=list_zones");
|
||||
//var_export($list);
|
||||
$ut->Evaluate("list_zones contains local test", array_key_exists('test.local', $list));
|
||||
$ut->Evaluate("list_zones contains update server localhost", $list['test.local']['update_server'] == 'localhost');
|
||||
$ut->Evaluate("list_zones contains transfer server localhost", $list['test.local']['transfer_server'] == 'localhost');
|
||||
|
||||
$zone_transfer = api("action=list_records&zone=test.local");
|
||||
$ut->Evaluate("list_records contains SOA for local", $zone_transfer[0][3] == "SOA");
|
||||
//var_export($zone_transfer);
|
||||
|
||||
$output = api('action=create_record&record=test1.test.local&ttl=10&type=A&value=10.2.2.8');
|
||||
$ut->Evaluate('create_record test A', $output['result'] == 'success');
|
||||
|
||||
$record = api('action=get_record&record=test1.test.local');
|
||||
$ut->Evaluate('get_record TTL', $record[0][1] == '10');
|
||||
$ut->Evaluate('get_record value', $record[0][4] == '10.2.2.8');
|
||||
$ut->Evaluate('get_record name', $record[0][0] == 'test1.test.local.');
|
||||
|
||||
$output = api('action=delete_record&record=test1.test.local&ttl=10&type=A&value=10.2.2.8');
|
||||
$ut->Evaluate('delete_record test A', $output['result'] == 'success');
|
||||
|
||||
$output = api('action=delete_record&record=test.local&ttl=10&type=SOA&value=');
|
||||
$ut->Evaluate("delete_record that isn't allowed", isset($output['error']));
|
||||
|
||||
$output = api('action=create_record&record=-.test.local&ttl=10&type=A&value=10.2.2.8');
|
||||
$ut->Evaluate('create invalid record #1', isset($output['error']));
|
||||
|
||||
$output = api('action=create_record&record=$.test.local&ttl=10&type=A&value=10.2.2.8');
|
||||
$ut->Evaluate('create invalid record #2', isset($output['error']));
|
||||
|
||||
$fqdn = api('action=get_zone_for_fqdn&fqdn=meep.test.local');
|
||||
$ut->Evaluate('get zone for FQDN (test.local name)', $fqdn['zone'] == 'test.local');
|
||||
|
||||
$fqdn = api('action=get_zone_for_fqdn&fqdn=local');
|
||||
$ut->Evaluate('get zone for nonexistent FQDN', $fqdn['error'] != '');
|
||||
|
||||
$login = api("action=logout");
|
||||
$ut->Evaluate("logout", $login['result'] == 'success');
|
||||
|
||||
|
||||
|
||||
echo ("\n\n\n");
|
||||
$ut->PrintResults();
|
||||
|
||||
$ut->Exit();
|
||||
$ut->ExitTest();
|
||||
|
|
Loading…
Add table
Reference in a new issue