Compare commits

...

185 commits
v0.9 ... master

Author SHA1 Message Date
Tuxis B.V.
7a18c00a24
Merge pull request from bartvanhalder/master
Add support to load templates from json files
2025-03-11 09:17:49 +01:00
Tuxis B.V.
2ea8bff800
Merge pull request from PowerDNS/docker-bookworm
update Dockerfile to Debian bookworm
2025-03-11 09:16:24 +01:00
Peter van Dijk
12d3d33c36 update Dockerfile to Debian bookworm 2025-03-10 15:20:29 +01:00
Tuxis B.V.
6c565719d9
Merge pull request from stecklars/master
INCEPTION-INCREMENT is not a valid option for soa_edit_api
2024-08-19 14:41:51 +02:00
Tuxis B.V.
fe4120e2a7
Merge pull request from deividasraila/feat/default_login_credentials
Default login crediantials in config
2024-08-19 14:40:55 +02:00
Bart van Halder
8e1d0035d3
[.gitignore] ignore json files in templates.d/ 2024-07-25 15:38:00 +02:00
Bart van Halder
a3ba9ed45e
Add a way to load templates from json files 2024-07-25 15:32:03 +02:00
Deividas Raila
3b69fcf770 Default login crediantials in config 2024-05-07 18:11:07 +03:00
Tuxis B.V
65d58cfd92
Merge pull request from WilliamDEdwards/fix/ipv6-api-ip
Place brackets around IPv6 address
2024-04-29 21:39:17 +02:00
William Edwards
c8f28c5c3e Place brackets around IPv6 address
Without this, using an IPv6 address as $apiip is not possible, because cURL requires brackets.
2024-04-29 20:48:04 +02:00
Tuxis B.V
39050cd015
Merge pull request from WilliamDEdwards/fix/make-clone-admin-only
Make cloning admin-only, check zones returned by formzonelist
2024-04-17 12:21:46 +02:00
William Edwards
e8d028ba75 Make cloning admin-only, check zones returned by formzonelist
Cloning was meant to be an admin-only functionality. However, this was not fully implemented: when `allowzoneadd = true`, the user could clone zones, even when not an admin. This is not necessarily a problem. But in this case, it is. Because the endpoint that is used to get zones to clone (`formzonelist`), did not check whether those zones belong to the current user. In other words: when `allowzoneadd = true` and the user is not an admin, that user is able to see *all zones* under 'Clone a zone' button -> 'Source domain' dropdown.

This commit fixes that, by letting `formzonelist` return only zones belonging to the user, and showing the 'Clone a zone' button only when the user is an admin.
2024-04-17 12:18:06 +02:00
Tuxis B.V
a4aa3c35cf
Merge pull request from WilliamDEdwards/fix/add-missing-return
Add missing return to Comment::export
2024-01-23 17:13:29 +01:00
William Edwards
e5a121f24b Add missing return to Comment::export
Without this return, RRSet::exportComments returns an array of `null`, which causes PowerDNS to return `Key 'content' not present or not a String`.
2024-01-23 16:21:18 +01:00
Tuxis B.V
76d70251bb
Merge pull request from zydronium/master
Deprecated: Creation of dynamic properties is deprecated in PHP8.2
2023-06-20 15:46:30 +02:00
Jelle Luteijn
b7ef64db18
Update Zone.php 2023-06-20 14:24:17 +02:00
Jelle Luteijn
758a021ca2
Update Zone.php 2023-06-20 14:22:36 +02:00
Jelle Luteijn
cae4e4ff93
Update Zone.php 2023-06-20 14:14:45 +02:00
Jelle Luteijn
7c50964633
Update ApiHandler.php 2023-06-20 14:13:08 +02:00
Jelle Luteijn
d59da92f37
Update PdnsApi.php
Creation of dynamic property PdnsAPI::$http is deprecated
2023-06-20 14:12:30 +02:00
Jelle Luteijn
607ecbd9f2
forgot $ 2023-06-20 14:06:52 +02:00
Jelle Luteijn
c327e23859
Update session.inc.php
Passing null to parameter  ($value) of type string is deprecated
2023-06-20 14:03:20 +02:00
Jelle Luteijn
e38fa1e120
Update ApiHandler.php
Creation of dynamic property ApiHandler::$headers is deprecated
2023-06-20 13:57:08 +02:00
Jelle Luteijn
b8e3261180
Update session.inc.php
Using ${var} in strings is deprecated, use {$var} instead
2023-06-20 13:45:10 +02:00
Tuxis B.V
947ec4a67d
Merge pull request from tuxis-ie/check-authdb-docroot
Simply check if authdb is in the docroot.
2020-12-23 13:14:48 +01:00
Mark Schouten
d414f239a8 Simply check if authdb is in the docroot. If so, just blocklogin with a nice message. 2020-12-23 13:11:40 +01:00
Tuxis B.V
8efcfbd597
Merge pull request from tuxis-ie/template-ns-records
If a template contains NS-records, do not try to add them again. They were already added while creating the zone.
2020-12-23 11:48:42 +01:00
Mark Schouten
5c6c9e1847 If a template contains NS-records, do not try to add them again. They were already added while creating the zone. 2020-12-23 11:45:58 +01:00
Tuxis B.V
888e7bad42
Merge pull request from margau/master
AuthDB Download Check improvements
2020-11-26 09:47:28 +01:00
Marvin Gaube
82db64595c authdb check: Use relative, not absolute path 2020-03-01 20:18:15 +01:00
Marvin Gaube
6ba23a85b8 Only run authdb check when user is logged in 2020-03-01 20:15:14 +01:00
Tuxis B.V
059c679193
Merge pull request from jbrunemann/master
 only replace basename when there is actually something to replace
2020-01-22 11:43:33 +01:00
Jan Brunemann
8586816c47 only replace basename when there is actually something to replace 2020-01-22 11:33:27 +01:00
Lars-Sören Steck
de278c9ad7
INCEPTION-INCREMENT is not a valid option for soa_edit_api
INCEPTION-INCREMENT is not a valid option for soa_edit_api, see:
https://doc.powerdns.com/authoritative/domainmetadata.html#soa-edit-api

-> "These rules are the same as the SOA-EDIT-DNSUPDATE rules."
--> https://doc.powerdns.com/authoritative/dnsupdate.html#dnsupdate-soa-serial-updates
---> There is no "INCEPTION-INCREMENT" Setting.

Since 4.2 this is more strictly checked by PowerDNS:
636301b9f1 (diff-fcc782f1cdc22f79be390c5d65da9050)

Before this, it would simply use the DEFAULT option, now it logs an error and doesn't increase the Serial.
2019-07-28 02:57:29 +02:00
Mark Schouten
371eb41787 Content must be canonical 2018-08-22 17:12:01 +02:00
Tuxis Internet Engineering V.O.F
c63dbba617
Merge pull request from tuxis-ie/nice-authdb-error
Show a clear message instead of ERROR 500
2018-08-22 16:51:28 +02:00
Mark Schouten
9407c92a6a Show a clear message instead of ERROR 500 2018-08-22 16:51:07 +02:00
Tuxis Internet Engineering V.O.F
23b9fe2c54
Merge pull request from tuxis-ie/margau-patch-1
Margau patch 1
2018-08-22 16:45:32 +02:00
Mark Schouten
523fc1849d Test if we can download the from the browser. Alert, annoyingly, that the user should fix this 2018-08-22 16:43:19 +02:00
Mark Schouten
3448ccf653 Merge branch 'patch-1' of https://github.com/margau/nsedit into margau-patch-1 2018-08-22 16:41:41 +02:00
Tuxis Internet Engineering V.O.F
e80aa0dca7
Merge pull request from tuxis-ie/support-SMIMEA
Support SMIMEA fields, fixes and closes 
2018-08-22 16:06:40 +02:00
Mark Schouten
92290bdf05 Support SMIMEA fields, fixes and closes 2018-08-22 16:05:48 +02:00
Tuxis Internet Engineering V.O.F
3facd3271f
Merge pull request from tuxis-ie/fix-api-calls
Fix api calls
2018-08-22 15:54:32 +02:00
Mark Schouten
be647cc26a Do not insert 'localhost', we are autodetecting the API url 2018-08-22 15:39:11 +02:00
Mark Schouten
bf513b9ffd Assume that we have Pdns > 4.x 2018-08-22 15:21:26 +02:00
Tuxis Internet Engineering V.O.F
880508f585
Merge pull request from margau/master
API issue with PDNS 4.1.1
2018-06-05 10:30:05 +02:00
margau
39cf7138ef
Update README.md 2018-04-17 19:41:33 +02:00
margau
e403e396bc
Update README.md 2018-04-17 19:37:38 +02:00
margau
96c67a8e8f
Added notice (see ) 2018-04-17 19:33:58 +02:00
margau
805176648d Fixed from paulgiordanozethcon suggestion in https://github.com/tuxis-ie/nsedit/issues/162 2018-04-17 18:53:31 +02:00
Tuxis Internet Engineering V.O.F
ebd12ebeb2
Merge pull request from zydronium/patch-1
Fixing Undefined index
2018-03-28 09:07:39 +02:00
Tuxis Internet Engineering V.O.F
1dedc3ae3f
Merge pull request from hutchinsonnetworks/master
Docker: Move configuration to runtime rather than build time
2018-03-28 09:06:55 +02:00
Tuxis Internet Engineering V.O.F
77d7c50110
Merge pull request from tuxis-ie/revert-166-master
Revert "notify pdns after zone update"
2018-03-28 09:05:28 +02:00
Tuxis Internet Engineering V.O.F
635441dbe0
Revert "notify pdns after zone update" 2018-03-28 09:05:09 +02:00
Tuxis Internet Engineering V.O.F
ed27b5e7d1
Merge pull request from Rico29/master
notify pdns slaves after zone update
2018-03-28 09:03:11 +02:00
Harry Reeder
a2d7c21636 Move configuration to runtime rather than build time 2018-03-19 16:00:38 +00:00
Jelle Luteijn
4f013d4081
Fixing Undefined index
PHP Notice:  Undefined index: label in /var/www/nsedit/wwwroot/zones.php on line 325
2018-02-08 23:41:07 +01:00
root
d44b1a011f wrong syntax correction 2018-02-05 09:58:06 +01:00
root
6dc6df497e typo correction 2018-02-02 15:12:11 +01:00
root
cb234599b7 typo correction 2018-02-02 15:10:54 +01:00
root
03d9d88026 add "notifyafterupdate" flag 2018-02-02 14:58:10 +01:00
Tuxis Internet Engineering V.O.F
1dfd47ae70
Merge pull request from tuxis-ie/fix-issue-160
Fix issue 160
2017-11-20 16:04:42 +01:00
Mark Schouten
c1c680217d Set zonekind of the new zone 2017-11-20 15:58:15 +01:00
Mark Schouten
374e03aa43 Add zone to database and fix ownership 2017-11-20 15:56:28 +01:00
Tuxis Internet Engineering V.O.F
877d433b92
Merge pull request from jsoref/spelling
Spelling
2017-11-20 10:35:50 +01:00
Josh Soref
ac5304badb spelling: whether 2017-11-19 00:58:36 +00:00
Josh Soref
fd2a7cb7a5 spelling: nameserver 2017-11-17 09:23:49 +00:00
Josh Soref
c3bd9da355 spelling: configuration 2017-11-17 09:11:20 +00:00
Tuxis Internet Engineering V.O.F
0befe9e5ab Merge pull request from maltris/master
Separated RUN-commands, changed a sed-command because the matching did not work
2017-10-05 11:29:53 +02:00
maltris
0c1183e716 Separated RUN-commands, changed a sed-command because the matching did not work 2017-08-14 10:22:04 +02:00
Tuxis Internet Engineering V.O.F
4b060c6430 Merge pull request from webvanced/master
Added support for CNAME's to zone in templates
2017-08-07 10:32:40 +02:00
Mark Schouten
18fa97373e Add other ways of installation to the README 2017-08-07 10:30:52 +02:00
Daniel Eiland
1f2225cf6d Added support for CNAME's to zone in templates 2017-07-11 09:24:44 +02:00
Mark Schouten
382ca51db1 Allow ALIAS records. Closes 2017-04-28 13:37:22 +02:00
Mark Schouten
6be5f2f29c Fix updating the password, and store if we have local auth, we can't change passwords if we don't have local auth 2017-04-28 13:09:31 +02:00
Mark Schouten
f67fa04d85 This fixes the issues with the newer pdns, which suddenly includes the whole API url in the returned json.
Closes . In response to 4d4e536d52
2017-04-28 12:32:01 +02:00
Mark Schouten
9d27a140d7 Fix proto in logo url 2017-04-20 11:46:55 +02:00
Mark Schouten
4d4e536d52 Deduplicate the api-url 2017-04-20 11:45:33 +02:00
Tuxis Internet Engineering V.O.F
6fbd049941 Merge pull request from tuxis-ie/caa-support
Implement CAA-records. Please note that pdns requires quotes around t…
2017-04-03 09:48:15 +02:00
Mark Schouten
0e63757d19 Implement CAA-records. Please note that pdns requires quotes around the third field: https://github.com/PowerDNS/pdns/issues/4937. Closes 2017-04-03 09:47:38 +02:00
Mark Schouten
5787659b07 Add this missing line, this may have broken editing of SPF/TXT records. Closes 2017-01-25 09:46:05 +01:00
Tuxis Internet Engineering V.O.F
b7d61f5778 Merge pull request from krombel/master
fix switching of Views for non-admin-users (fix )
2016-11-21 10:15:03 +01:00
Krombel
a3affccacd fix switching of Views for non-admin-users () 2016-11-19 02:50:09 +01:00
Mark Schouten
93c88cc196 If we get here, there is a value without dots... So add it 2016-11-18 17:11:22 +01:00
Mark Schouten
5c5f9f7abd Fix sorting 2016-11-18 17:07:49 +01:00
Mark Schouten
6737aa9b83 Fix matching on zonename, we were missing the . 2016-11-18 17:01:48 +01:00
Mark Schouten
ae00aa8ed9 Fix quoting of TXT and SPF records 2016-11-18 17:00:18 +01:00
Mark Schouten
42b247d5c0 Add missing types. Closes 2016-11-03 10:11:24 +01:00
Mark Schouten
41801a73f6 Try to set soa_edit_api, if it is not yet set 2016-10-25 12:14:07 +02:00
Mark Schouten
94e0d22bf2 If we don't have a soa_edit_api for this zone yet, set it to our default 2016-10-25 11:10:22 +02:00
Mark Schouten
77192d84b1 Don't close the database connection and make it global. Also, honour the account that is set in pdns, unless its empty 2016-10-19 17:28:16 +02:00
Mark Schouten
92ac4576ab An empty name is possible 2016-10-14 14:20:07 +02:00
Mark Schouten
b5d7fa8183 Don't forget the dot in between 2016-10-14 14:16:48 +02:00
Mark Schouten
83e8a0eda6 TRAILING DOTSS!!!11eleven!!11!&$W*&@*@!@#& 2016-10-14 14:14:17 +02:00
Mark Schouten
32f0456f21 Fix TXT-record quoting 2016-10-10 14:22:31 +02:00
Tuxis Internet Engineering V.O.F
b34d7ee2f1 Merge pull request from richard-underwood/issue-122
Modified users jtable to use id & fixed user deletion.
2016-09-26 16:45:18 +02:00
Richard Underwood
083cb9429c Modified users jtable to use id & fixed user deletion. 2016-09-20 10:10:54 +01:00
Tuxis Internet Engineering V.O.F
e7713615a5 Merge pull request from bjoe2k4/master 2016-09-19 12:04:51 +02:00
bjoe2k4
5d1f23c814 Update .gitignore 2016-09-17 15:27:29 +02:00
Tuxis Internet Engineering V.O.F
b16af25052 Merge pull request from richard-underwood/issue-107
Ability to rotate and search logs
2016-09-13 11:10:22 +02:00
Richard Underwood
badebb9965 Clarified wording of rotation warning. 2016-08-26 11:59:49 +01:00
Richard Underwood
8d6e8ddf55 Removed delete button from logs table as the action wasn't implemented and would not be possible on rotated logs. 2016-08-26 11:45:30 +01:00
Richard Underwood
669c78a1db Merge remote-tracking branch 'origin/master' into issue-107 2016-08-26 10:54:04 +01:00
Tuxis Internet Engineering V.O.F
531f8a2609 Merge pull request from richard-underwood/issue-109
PHP <= 5.4 fix for curl_reset
2016-08-26 11:50:48 +02:00
Richard Underwood
9d59441dd0 Check around curl_reset to prevent errors with PHP <=5.4 2016-08-26 10:11:52 +01:00
Richard Underwood
7c767b7769 Merge remote-tracking branch 'origin/master' into issue-107
Add test for pre-PHP 5.4 for pretty-printing logs

Conflicts:
	includes/misc.inc.php
2016-08-26 10:00:51 +01:00
Tuxis Internet Engineering V.O.F
6833f59400 Merge pull request from tuxis-ie/fix-bug-104
Fix logging in cases we don't have a username yet. Also, log more stu…
2016-08-26 10:38:24 +02:00
Richard Underwood
2cb95a6959 UNRELATED CHANGE - put test around curl_reset to allow testing on PHP 5.4 2016-08-26 09:30:56 +01:00
Richard Underwood
befb891174 Changed Download logs to download the logs currently being shown, not always the current logs - note, doesn't filter first.
Removed "delete" case in logs.php
Moved logging check out of case statements to avoid duplication.
Changed wording of clear logs warning.
Pretty-print the JSON on log export - requires PHP 5.4.
2016-08-25 10:23:31 +01:00
Richard Underwood
ff8df5e5b2 Merge remote-tracking branch 'origin/master' into issue-107 2016-08-24 14:59:23 +01:00
Mark Schouten
43e4d53611 Fix CSS issues to eliminate whitespace with vertical menubar 2016-08-24 15:49:39 +02:00
Richard Underwood
9d8d909c18 Remove jtable_respond from the CLI script. 2016-08-24 14:38:03 +01:00
Richard Underwood
f081d96b0c Allow viewing of past logs.
Add a command-line PHP script for rotation in cron.
2016-08-24 14:19:52 +01:00
Richard Underwood
56c1789b30 Changed "Save logs" to "Download logs" for clarity.
Removed the rotate logs icon.
Updated warning text for clearing logs, if rotation is allowed.
2016-08-24 11:42:22 +01:00
Richard Underwood
d1b817443c Initial implementation of log rotation. 2016-08-24 11:32:43 +01:00
Tuxis Internet Engineering V.O.F
a8abca1121 Merge pull request from richard-underwood/issue-90
Implemented logs and zone searching.
Closes: 
2016-08-23 17:24:33 +02:00
Richard Underwood
708327ecd2 Match capitalisation on toolbar 2016-08-23 16:23:10 +01:00
Richard Underwood
dde58c798c Implemented logs and zone searching. 2016-08-23 15:56:41 +01:00
Mark Schouten
b91317046b Fix logging in cases we don't have a username yet. Also, log more stuff. Closes 2016-08-23 12:30:27 +02:00
Mark Schouten
4b5d4b02c9 Fix bug in Exception() and clearify the error message. Closes 2016-08-09 22:59:29 +02:00
Mark Schouten
b04b4dd864 Set ttl for the whole rrset if we update a record within that rrset. Might update other records as well, but that's as designed. Should fix 2016-08-08 19:13:47 +02:00
Tuxis Internet Engineering V.O.F
8cb9df7597 Merge pull request from tuxis-ie/fix-add-user
emailaddress is a key, but we want to be able to use it upon create. …
2016-08-08 09:20:50 +02:00
Mark Schouten
b850510e5a emailaddress is a key, but we want to be able to use it upon create. Closes 2016-08-08 09:20:21 +02:00
Tuxis Internet Engineering V.O.F
d04a7ac8fe Merge pull request from abcdmitry/patch-1 2016-08-05 23:33:32 +02:00
Dmitry Lukashin
44561faea4 Add notes about configuring PowerDNS 2016-08-06 00:30:54 +03:00
Tuxis Internet Engineering V.O.F
854c067e1f Merge pull request from tuxis-ie/pdns-40
Create a version 1.0, to manage Pdns 4.0.0 and greater
2016-08-05 13:49:47 +02:00
Mark Schouten
e05bff5fa0 Fix readme 2016-08-05 13:48:56 +02:00
Mark Schouten
396d2af907 Update documentation 2016-08-05 13:14:53 +02:00
Mark Schouten
58fab332dc Implement new jquery-ui and fix creation of new zones 2016-08-05 13:08:48 +02:00
Mark Schouten
0115f4500d Merge commit '7f7c9b378f0bb34a7414736c047ee6616a383fe8' as 'jquery-ui' 2016-08-05 12:57:52 +02:00
Mark Schouten
7f7c9b378f Squashed 'jquery-ui/' content from commit 77ff328
git-subtree-dir: jquery-ui
git-subtree-split: 77ff328cbe8a662f52178020d202dc1bfb560fd8
2016-08-05 12:57:52 +02:00
Mark Schouten
d3488a963e Remove broken jquery-ui 2016-08-05 12:56:15 +02:00
Mark Schouten
39aa35f2aa Merge commit '5500cf75c004255bd2d4ede59c4a6534b1a5acb1' as 'jtable' 2016-08-05 12:52:54 +02:00
Mark Schouten
5500cf75c0 Squashed 'jtable/' content from commit 1a6e6ab
git-subtree-dir: jtable
git-subtree-split: 1a6e6ab44e57f1cea35a1b7168a8c8ef319d5a81
2016-08-05 12:52:54 +02:00
Mark Schouten
0fb23b7b32 Remove incorrect jtable 2016-08-05 12:52:39 +02:00
Mark Schouten
b23131fdc2 Merge commit 'd32092c1f0f621405fc67d992c345ce46589f6df' as 'jquery-ui' 2016-08-05 12:47:57 +02:00
Mark Schouten
d32092c1f0 Squashed 'jquery-ui/' content from commit 3dd8a09
git-subtree-dir: jquery-ui
git-subtree-split: 3dd8a09b441d65445f2b6a7c73e72af65445d5da
2016-08-05 12:47:57 +02:00
Mark Schouten
41bad3c240 Merge commit 'fad871ffb123ef9eb2d617d748b35e555fd5f89b' as 'jtable' 2016-08-05 12:46:32 +02:00
Mark Schouten
fad871ffb1 Squashed 'jtable/' content from commit 25686e8
git-subtree-dir: jtable
git-subtree-split: 25686e8ec19b1fee953c72971ffdb9a1abec166d
2016-08-05 12:46:32 +02:00
Mark Schouten
c907a35457 Remove submodules 2016-08-05 12:45:44 +02:00
Mark Schouten
190a5be54b Merge commit 'c854a8c29bf55e07cc1a2412c568a245f4e7ed74' as 'js/addclear' 2016-08-05 12:42:31 +02:00
Mark Schouten
c854a8c29b Squashed 'js/addclear/' content from commit 25686e8
git-subtree-dir: js/addclear
git-subtree-split: 25686e8ec19b1fee953c72971ffdb9a1abec166d
2016-08-05 12:42:31 +02:00
Mark Schouten
93f37931a4 Update documentation [WIP] and remove submodule 2016-08-05 12:42:12 +02:00
Mark Schouten
3fd525748f Do not do any logging if it's disabled 2016-08-05 12:20:19 +02:00
Mark Schouten
a91d91baa4 Add default value for setptr 2016-08-05 12:05:36 +02:00
Mark Schouten
8d9e41ffed We need this file too... :) 2016-08-05 11:59:12 +02:00
Mark Schouten
cbea4778ef Implement logging. Closes 2016-08-05 11:57:04 +02:00
Mark Schouten
e429005134 Delete users based on username, not id 2016-08-05 10:35:51 +02:00
Mark Schouten
5b4bcc34cc Don't show a 'records' button anymore. Open the zone by clicking it. Closes 2016-08-05 10:05:09 +02:00
Mark Schouten
749478c36a Implement record sorting. Closes 2016-08-05 09:15:19 +02:00
Mark Schouten
d103c7b04f Implement set-ptr. Closes . Make 'true/false' prettier 2016-08-04 16:55:03 +02:00
Mark Schouten
5cd225cb43 Implement cloning of zones, closes 2016-08-04 16:18:45 +02:00
Mark Schouten
30f43e98a5 Improve readability of function names 2016-08-04 15:31:17 +02:00
Mark Schouten
623fcefc5c Replace 'owner' with account to be consistent with Pdns. Set account upon creating a zone. 2016-08-04 15:01:39 +02:00
Mark Schouten
ef7f47e3f4 Fix creating records without the zonename. Sort the zones. Fix creation of disabled records 2016-08-04 14:46:32 +02:00
Mark Schouten
82819bf33b Remove leftover from copy/paste. Do not replace and check if ['serial'] exists.. 2016-08-04 13:44:39 +02:00
Mark Schouten
3f3c18c21b Missing ; 2016-08-04 13:37:54 +02:00
Mark Schouten
65d6b9afe0 We don't need all these functions anymore 2016-08-04 13:35:49 +02:00
Mark Schouten
8a63698b9f Fix templating 2016-08-04 13:32:18 +02:00
Mark Schouten
337d4ed363 We now have all functionality! 2016-08-04 12:10:19 +02:00
Mark Schouten
c7b36d7d7d Implement deleteRecord and fix some small errors 2016-08-04 12:09:56 +02:00
Mark Schouten
475bbca87b Switch from zone.name to zone.id 2016-08-04 12:07:45 +02:00
Mark Schouten
d94d1da445 Implement exportzone 2016-08-04 12:07:14 +02:00
Mark Schouten
5161f7102b Add record and catch Exceptions 2016-08-03 16:57:25 +02:00
Mark Schouten
e2034c5861 * Implement getrrset() and use it where we can
* addrecord() should just add the record, even if the rrset does not yet exist
* Make addrecord() return the created record
* Implement getrecord()
2016-08-03 16:54:58 +02:00
Mark Schouten
be9683ef83 Allow saving of zones 2016-08-03 16:08:57 +02:00
Mark Schouten
4d6ecb612e Fix a erasemasters() for a zone 2016-08-03 16:08:40 +02:00
Mark Schouten
cf754d0135 Check if serial is an integer, so we know if it's a new zone. Also, return the new zone upon saving it. 2016-08-03 16:07:48 +02:00
Mark Schouten
2cc92e6842 We can now import, create AND delete zones 2016-08-03 15:14:27 +02:00
Mark Schouten
c8774fc05e Implement DELETE 2016-08-03 14:42:49 +02:00
Mark Schouten
4ac1e6e767 We can now view zones. Yay! 2016-08-03 14:29:35 +02:00
Mark Schouten
a8ccbeb10a Switch from zoneurl to zoneid 2016-08-03 14:25:25 +02:00
Mark Schouten
1c3e5cfc99 Cannot use object of type RRSet as array 2016-08-03 14:21:56 +02:00
Mark Schouten
d5ef95e711 Make functions public 2016-08-03 14:21:01 +02:00
Mark Schouten
a7d3e6e6eb Fix typo and move function to correct class 2016-08-03 14:19:31 +02:00
Mark Schouten
6fd7087dc9 Rebuild listrecords and fake records by merging them with rrsets in Zone.php 2016-08-03 14:16:07 +02:00
Mark Schouten
a94d0e0b31 Fix all kind of development and trying code. 2016-08-03 14:08:33 +02:00
Mark Schouten
ccf90cab61 Include these files 2016-08-03 13:48:30 +02:00
Mark Schouten
db0c7e0b85 Cleanup stuff in zones.php and implement the first part, 'list' 2016-08-03 13:38:21 +02:00
Mark Schouten
fcdba74c14 Also export keyinfo 2016-08-03 13:37:22 +02:00
Mark Schouten
d02889a918 Allow setting a string 'keyinfo' 2016-08-03 13:35:54 +02:00
Mark Schouten
f61f52a14d Add getzonekeys() to PdnsApi 2016-08-03 13:33:44 +02:00
Mark Schouten
367dde6f19 Add new classes to handle zones and pdns-api v4.0 2016-08-03 13:16:30 +02:00
Mark Schouten
22502ae521 Implement an ApiHandler class 2016-08-02 14:16:34 +02:00
Mark Schouten
574db00d94 Cleanup. We don't need this stuff for v4 anymore 2016-08-02 14:15:57 +02:00
19 changed files with 1918 additions and 684 deletions

3
.gitignore vendored
View file

@ -1 +1,4 @@
includes/config.inc.php includes/config.inc.php
nsedit.sublime*
etc
templates.d/*.json

View file

@ -1,38 +1,28 @@
FROM debian:jessie FROM debian:bookworm
MAINTAINER Yury Evtikhov <yury@evtikhov.info> MAINTAINER Yury Evtikhov <yury@evtikhov.info>
# #
# This Dockerfile is intended only for test/development use. # This Dockerfile is intended only for test/development use.
# It will be a really BAD idea to use it for production or public services. # It will be a really BAD idea to use it for production or public services.
# #
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
#
# Please set the following variables before building:
#
ENV PDNSAPIPWD mypowerdnsapipassword
ENV PDNSAPIIP 192.168.1.2
ENV PDNSAPIPORT 8081
# Update and Upgrade system # Update and Upgrade system
RUN apt-get -y update && \ RUN apt-get -y update && \
apt-get -y install curl git-core php5-cli php5-curl php5-json php5-sqlite && \ apt-get -y install curl git-core php8.2-cli php8.2-curl php8.2-sqlite3 && \
mkdir /app && \ rm -rf /var/lib/apt/lists/*
git clone --recursive https://github.com/tuxis-ie/nsedit.git /app/nsedit && \ RUN mkdir /app
cp /app/nsedit/includes/config.inc.php-dist /app/nsedit/includes/config.inc.php && \ RUN git clone --recursive https://github.com/tuxis-ie/nsedit.git /app/nsedit
sed "s/\$apipass = ''/\$apipass = '$PDNSAPIPWD'/" -i /app/nsedit/includes/config.inc.php && \ RUN cp /app/nsedit/includes/config.inc.php-dist /app/nsedit/includes/config.inc.php
sed "s/\$apiip = ''/\$apiip = '$PDNSAPIIP'/" -i /app/nsedit/includes/config.inc.php && \ COPY docker-entrypoint.sh /app/nsedit/docker-entrypoint.sh
sed "s/\$apiport = ''/\$apiport = '$PDNSAPIPORT'/" -i /app/nsedit/includes/config.inc.php && \ RUN chmod +x /app/nsedit/docker-entrypoint.sh
sed "s/\$authdb = \"\.\.\/etc\/pdns\.users\.sqlite3\"/\$authdb = \"\/app\/pdns\.users\.sqlite3\"/" -i /app/nsedit/includes/config.inc.php
# Define working directory. # Define working directory.
VOLUME /app/nsedit VOLUME /app/nsedit
WORKDIR /app/nsedit WORKDIR /app/nsedit
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["/usr/bin/php", "-S", "0.0.0.0:8080"] CMD ["sh", "-c", "/app/nsedit/docker-entrypoint.sh"]
# #
# Usage: # Usage:

View file

@ -10,14 +10,16 @@ Features
======== ========
* Import BIND- or AXFR-style dumps of your existing zones * Import BIND- or AXFR-style dumps of your existing zones
* Add/remove zones and records * Add/remove zones and records
* Clone zones
* Show the DNSsec details of a zone * Show the DNSsec details of a zone
* Multiple user support * Multiple user support
* Allow logging of all actions in NSEdit, including exporting the log in JSON-format
* [experimental] nsedit API, to create zones from another system * [experimental] nsedit API, to create zones from another system
User support User support
============ ============
Multiple users are supported. A user can be an admin or a normal user. You can Multiple users are supported. A user can be an admin or a normal user. You can
configure wheter or not a normal user is allowed to add new zones. configure whether or not a normal user is allowed to add new zones.
WeFact Login support WeFact Login support
==================== ====================
@ -32,7 +34,7 @@ Requirements
* php sqlite3 * php sqlite3
* php curl * php curl
* php with openssl support * php with openssl support
* PowerDNS with the experimental JSON-api enabled (3.4.0 should do. For Pdns > 4.0.0 you ***NEED*** v1.0 of NSEdit) * PowerDNS with the JSON-api enabled. Version 4.0.0 or greater
Installing Installing
========== ==========
@ -41,19 +43,42 @@ Installing
- Run git clone in the directory where you want to run nsedit from - Run git clone in the directory where you want to run nsedit from
: ```git clone https://github.com/tuxis-ie/nsedit.git``` : ```git clone https://github.com/tuxis-ie/nsedit.git```
- Select tag v0.9 or skip this if you want to run from master - Select tag v1.0 or skip this if you want to run from master
: ```git checkout tags/v0.9``` : ```git checkout tags/v1.0```
* Via releases * Via releases
- Download the zip-file from [Releases](https://github.com/tuxis-ie/nsedit/releases) - Download the zip-file from [Releases](https://github.com/tuxis-ie/nsedit/releases)
* Copy ```includes/config.inc.php-dist``` to ```includes/config.inc.php``` and edit config.inc.php to your needs. * Copy ```includes/config.inc.php-dist``` to ```includes/config.inc.php``` and edit config.inc.php to your needs.
* By default, nsedit writes its user database to ../etc/pdns.users.sqlite3. Be sure that your webserver can create that directory and write to it. * By default, nsedit writes its user database to ../etc/pdns.users.sqlite3. Be sure that your webserver can create that directory and write to it. **Make sure the Webserver doesn't serve this file/folder to the public!**
* Visit http(s)://<url>/nsedit/ and login with admin/admin (Don't forget to update your password!) * Visit http(s)://<url>/nsedit/ and login with admin/admin (Don't forget to update your password!)
Have fun ;) Have fun ;)
Other methods of installation (Unsupported)
===========================================
* Baji Zsolt created a Suse image: https://susestudio.com/a/vvnMqa/powerdns-with-nsedit
* Yury Evtikhov created the Docker file: https://github.com/tuxis-ie/nsedit/blob/master/Dockerfile
Configuring PowerDNS
====================
Minimal configuration of PowerDNS for supporting nsedit has to include 3 directives:
```
webserver=yes
api=yes
api-key=SomeRandomString
```
Special note for Ubuntu Xenial Xerus 16.04 users:
Default `pdns` package included in Ubuntu repositories has the version of 4.0.0-alpha2 and *nsedit v1.0* doesn't work with it due to API incompatibility.
If your PowerDNS version is not the latest one, please consider adding PowerDNS repository to your system.
Detailed instructions for adding repository are available at http://repo.powerdns.com/
Screenshots Screenshots
=========== ===========

View file

@ -15,6 +15,7 @@ body, html {
border: 1px solid #DDD; border: 1px solid #DDD;
font-family: 'Segoe UI Semilight','Open Sans',Verdana,Arial,Helvetica,sans-serif; font-family: 'Segoe UI Semilight','Open Sans',Verdana,Arial,Helvetica,sans-serif;
font-weight: 300; font-weight: 300;
float: left;
font-size: 14px; font-size: 14px;
line-height: 1.3; line-height: 1.3;
width: 10%; width: 10%;

13
docker-entrypoint.sh Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
[ -z "$PDNSAPIIP" ] && echo "Set PDNSAPIIP to your PowerDNS API IP/Hostname" && exit 1;
[ -z "$PDNSAPIPWD" ] && echo "Set PDNSAPIPWD to your PowerDNS API Password" && exit 1;
sed "s/\$apipass = ''/\$apipass = '$PDNSAPIPWD'/" -i /app/nsedit/includes/config.inc.php
sed "s/\$apiip = ''/\$apiip = '$PDNSAPIIP'/" -i /app/nsedit/includes/config.inc.php
if [[ $PDNSAPIPORT && ${PDNSAPIPORT-x} ]]
then
sed "s/\$apiport = '8081'/\$apiport = '$PDNSAPIPORT'/" -i /app/nsedit/includes/config.inc.php
fi
sed "s/\$authdb = \"\.\.\/etc\/pdns\.users\.sqlite3\"/\$authdb = \"\/app\/pdns\.users\.sqlite3\"/" -i /app/nsedit/includes/config.inc.php
exec /usr/bin/php -S 0.0.0.0:8080

BIN
img/delete.png Normal file

Binary file not shown.

After

(image error) Size: 3.1 KiB

BIN
img/delete_inverted.png Normal file

Binary file not shown.

After

(image error) Size: 278 B

View file

@ -0,0 +1,135 @@
<?php
include_once('includes/config.inc.php');
class ApiHandler {
public $headers;
public $hostname;
public $port;
public $auth;
public $proto;
public $sslverify;
public $curlh;
public $method;
public $content;
public $apiurl;
public $url;
public $json;
public function __construct() {
global $apiip, $apiport, $apipass, $apiproto, $apisslverify;
$this->headers = Array();
$this->hostname = $apiip;
$this->port = $apiport;
$this->auth = $apipass;
$this->proto = $apiproto;
$this->sslverify = $apisslverify;
$this->curlh = curl_init();
$this->method = 'GET';
$this->content = FALSE;
$this->apiurl = '';
}
public function addheader($field, $content) {
$this->headers[$field] = $content;
}
private function authheaders() {
$this->addheader('X-API-Key', $this->auth);
}
private function apiurl() {
$tmp = new ApiHandler();
$tmp->url = '/api/v1/servers/localhost';
$tmp->go();
$this->apiurl = $tmp->json["url"];
}
private function curlopts() {
$this->authheaders();
$this->addheader('Accept', 'application/json');
if(defined('curl_reset')) {
curl_reset($this->curlh);
} else {
$this->curlh = curl_init();
}
curl_setopt($this->curlh, CURLOPT_HTTPHEADER, Array());
curl_setopt($this->curlh, CURLOPT_RETURNTRANSFER, 1);
if (strcasecmp($this->proto, 'https')) {
curl_setopt($this->curlh, CURLOPT_SSL_VERIFYPEER, $this->sslverify);
}
$setheaders = Array();
foreach ($this->headers as $k => $v) {
array_push($setheaders, join(": ", Array($k, $v)));
}
curl_setopt($this->curlh, CURLOPT_HTTPHEADER, $setheaders);
}
private function baseurl() {
$ip = $this->hostname;
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$ip = sprintf('[%s]', $ip); // curl needs brackets for IPv6
}
return $this->proto.'://'.$ip.':'.$this->port.$this->apiurl;
}
private function go() {
$this->curlopts();
if ($this->content) {
$this->addheader('Content-Type', 'application/json');
curl_setopt($this->curlh, CURLOPT_POST, 1);
curl_setopt($this->curlh, CURLOPT_POSTFIELDS, $this->content);
}
switch ($this->method) {
case 'POST':
curl_setopt($this->curlh, CURLOPT_POST, 1);
break;
case 'GET':
curl_setopt($this->curlh, CURLOPT_POST, 0);
break;
case 'DELETE':
case 'PATCH':
case 'PUT':
curl_setopt($this->curlh, CURLOPT_CUSTOMREQUEST, $this->method);
break;
}
curl_setopt($this->curlh, CURLOPT_URL, $this->baseurl().$this->url);
$return = curl_exec($this->curlh);
$code = curl_getinfo($this->curlh, CURLINFO_HTTP_CODE);
$json = json_decode($return, 1);
if (isset($json['error'])) {
throw new Exception("API Error $code: ".$json['error']);
} elseif ($code < 200 || $code >= 300) {
if ($code == 401) {
throw new Exception("Authentication failed. Have you configured your authmethod correct?");
}
throw new Exception("Curl Error: $code ".curl_error($this->curlh));
}
$this->json = $json;
}
public function call() {
if (substr($this->url, 0, 1) != '/') {
$this->url = '/'.$this->url;
}
$this->apiurl();
$this->url = str_replace($this->apiurl, '', $this->url);
$this->go();
}
}

128
includes/class/PdnsApi.php Normal file
View file

@ -0,0 +1,128 @@
<?php
include_once('ApiHandler.php');
class PdnsAPI {
public $http;
public function __construct() {
$this->http = new ApiHandler();
}
public function listzones($q = FALSE) {
$api = clone $this->http;
$api->method = 'GET';
if ($q) {
$api->url = "/search-data?q=*".$q."*&max=25";
$api->call();
$ret = Array();
$seen = Array();
foreach ($api->json as $result) {
if (isset($seen[$result['zone_id']])) {
continue;
}
$zone = $this->loadzone($result['zone_id']);
unset($zone['rrsets']);
array_push($ret, $zone);
$seen[$result['zone_id']] = 1;
}
return $ret;
}
$api->url = "/zones";
$api->call();
return $api->json;
}
public function loadzone($zoneid) {
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/zones/$zoneid";
$api->call();
return $api->json;
}
public function exportzone($zoneid) {
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/zones/$zoneid/export";
$api->call();
return $api->json;
}
public function savezone($zone) {
$api = clone $this->http;
// We have to split up RRSets and Zoneinfo.
// First, update the zone
$zonedata = $zone;
unset($zonedata['id']);
unset($zonedata['url']);
unset($zonedata['rrsets']);
if (!isset($zone['serial']) or gettype($zone['serial']) != 'integer') {
$api->method = 'POST';
$api->url = '/zones';
$api->content = json_encode($zonedata);
$api->call();
return $api->json;
}
$api->method = 'PUT';
$api->url = $zone['url'];
$api->content = json_encode($zonedata);
$api->call();
// Then, update the rrsets
if (count($zone['rrsets']) > 0) {
$api->method = 'PATCH';
$api->content = json_encode(Array('rrsets' => $zone['rrsets']));
$api->call();
}
return $this->loadzone($zone['id']);
}
public function deletezone($zoneid) {
$api = clone $this->http;
$api->method = 'DELETE';
$api->url = "/zones/$zoneid";
$api->call();
return $api->json;
}
public function getzonekeys($zoneid) {
$ret = array();
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/zones/$zoneid/cryptokeys";
$api->call();
foreach ($api->json as $key) {
if (!isset($key['active']))
continue;
$key['dstxt'] = $zoneid . ' IN DNSKEY '.$key['dnskey']."\n\n";
if (isset($key['ds'])) {
foreach ($key['ds'] as $ds) {
$key['dstxt'] .= $zoneid . ' IN DS '.$ds."\n";
}
unset($key['ds']);
}
array_push($ret, $key);
}
return $ret;
}
}
?>

376
includes/class/Zone.php Normal file
View file

@ -0,0 +1,376 @@
<?php
class Zone {
public $id;
public $name;
public $kind;
public $url;
public $serial;
public $dnssec;
public $soa_edit;
public $soa_edit_api;
public $keyinfo;
public $account;
public $zone;
public $nameservers;
public $rrsets;
public $masters;
public function __construct() {
$this->id = '';
$this->name = '';
$this->kind = '';
$this->url = '';
$this->serial = '';
$this->dnssec = '';
$this->soa_edit = '';
$this->soa_edit_api = '';
$this->keyinfo = '';
$this->account = '';
$this->zone = FALSE;
$this->nameservers = Array();
$this->rrsets = Array();
$this->masters = Array();
}
public function parse($data) {
$this->setId($data['id']);
$this->setName($data['name']);
$this->setKind($data['kind']);
$this->setDnssec($data['dnssec']);
$this->setAccount($data['account']);
$this->setSerial($data['serial']);
$this->url = $data['url'];
if (isset($data['soa_edit']) && $data['soa_edit'] != "")
$this->setSoaEdit($data['soa_edit']);
if (isset($data['soa_edit_api']) && $data['soa_edit_api'] != "")
$this->setSoaEditApi($data['soa_edit_api'], True);
foreach ($data['masters'] as $master) {
$this->addMaster($master);
}
if (isset($data['rrsets'])) {
foreach ($data['rrsets'] as $rrset) {
$toadd = new RRSet($rrset['name'], $rrset['type']);
foreach ($rrset['comments'] as $comment) {
$toadd->addComment($comment['content'], $comment['account'], $comment['modified_at']);
}
foreach ($rrset['records'] as $record) {
$toadd->addRecord($record['content'], $record['disabled']);
}
$toadd->setTtl($rrset['ttl']);
array_push($this->rrsets, $toadd);
}
}
}
public function importData($data) {
$this->zone = $data;
}
public function setKeyinfo($info) {
$this->keyinfo = $info;
}
public function addNameserver($nameserver) {
foreach ($this->nameservers as $ns) {
if ($nameserver == $ns) {
throw new Exception("We already have this as a nameserver");
}
}
array_push($this->nameservers, $nameserver);
}
public function setSerial($serial) {
$this->serial = $serial;
}
public function setSoaEdit($soaedit) {
$this->soa_edit = $soaedit;
}
public function setSoaEditApi($soaeditapi, $overwrite=False) {
if (isset($this->soa_edit_api) and $this->soa_edit_api != "") {
if ($overwrite === False) {
return False;
}
}
$this->soa_edit_api = $soaeditapi;
}
public function setName($name) {
$this->name = $name;
}
public function setKind($kind) {
$this->kind = $kind;
}
public function setAccount($account) {
$this->account = $account;
}
public function setDnssec($dnssec) {
$this->dnssec = $dnssec;
}
public function setId($id) {
$this->id = $id;
}
public function addMaster($ip) {
foreach ($this->masters as $master) {
if ($ip == $master) {
throw new Exception("We already have this as a master");
}
}
array_push($this->masters, $ip);
}
public function eraseMasters() {
$this->masters = Array();
}
public function addRRSet($name, $type, $content, $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
if ($this->getRRSet($name, $type) !== FALSE) {
throw new Exception("This rrset already exists.");
}
$rrset = new RRSet($name, $type, $content, $disabled, $ttl, $setptr);
array_push($this->rrsets, $rrset);
}
public function addRecord($name, $type, $content, $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
$rrset = $this->getRRSet($name, $type);
if ($rrset) {
$rrset->addRecord($content, $disabled, $setptr);
$rrset->setTtl($ttl);
} else {
$this->addRRSet($name, $type, $content, $disabled, $ttl, $setptr);
}
return $this->getRecord($name, $type, $content);
}
public function getRecord($name, $type, $content) {
$rrset = $this->getRRSet($name, $type);
foreach ($rrset->exportRecords() as $record) {
if ($record['content'] == $content) {
$record['name'] = $rrset->name;
$record['ttl'] = $rrset->ttl;
$record['type'] = $rrset->type;
$id = json_encode($record);
$record['id'] = $id;
return $record;
}
}
}
public function getRRSet($name, $type) {
foreach ($this->rrsets as $rrset) {
if ($rrset->name == $name and $rrset->type == $type) {
return $rrset;
}
}
return FALSE;
}
public function rrsets2records() {
$ret = Array();
foreach ($this->rrsets as $rrset) {
foreach ($rrset->exportRecords() as $record) {
$record['name'] = $rrset->name;
$record['ttl'] = $rrset->ttl;
$record['type'] = $rrset->type;
$id = json_encode($record);
$record['id'] = $id;
array_push($ret, $record);
}
}
return $ret;
}
public function export() {
$ret = Array();
$ret['account'] = $this->account;
$ret['nameservers'] = $this->nameservers;
$ret['kind'] = $this->kind;
$ret['name'] = $this->name;
if (isset($this->soa_edit) && $this->soa_edit != "") {
$ret['soa_edit'] = $this->soa_edit;
}
if (isset($this->soa_edit_api) && $this->soa_edit_api != "") {
$ret['soa_edit_api'] = $this->soa_edit_api;
}
if ($this->zone) {
$ret['zone'] = $this->zone;
return $ret;
}
$ret['dnssec'] = $this->dnssec;
if ($this->dnssec) {
$ret['keyinfo'] = $this->keyinfo;
}
$ret['id'] = $this->id;
$ret['masters'] = $this->masters;
$ret['rrsets'] = $this->exportRRSets();
$ret['serial'] = $this->serial;
$ret['url'] = $this->url;
return $ret;
}
private function exportRRSets() {
$ret = Array();
foreach ($this->rrsets as $rrset) {
array_push($ret, $rrset->export());
}
return $ret;
}
}
class RRSet {
public $name;
public $type;
public $ttl;
public $changetype;
public $records;
public $comments;
public function __construct($name = '', $type = '', $content = '', $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
$this->name = $name;
$this->type = $type;
$this->ttl = $ttl;
$this->changetype = 'REPLACE';
$this->records = Array();
$this->comments = Array();
if (isset($content) and $content != '') {
$this->addRecord($content, $disabled, $setptr);
}
}
public function delete() {
$this->changetype = 'DELETE';
}
public function setTtl($ttl) {
$this->ttl = $ttl;
}
public function setName($name) {
$this->name = $name;
}
public function addRecord($content, $disabled = FALSE, $setptr = FALSE) {
foreach ($this->records as $record) {
if ($record->content == $content) {
throw new Exception($this->name."/".$this->type." has duplicate records.");
}
}
$record = new Record($content, $disabled, $setptr);
array_push($this->records, $record);
}
public function deleteRecord($content) {
foreach ($this->records as $idx => $record) {
if ($record->content == $content) {
unset($this->records[$idx]);
}
}
}
public function addComment($content, $account, $modified_at = FALSE) {
$comment = new Comment($content, $account, $modified_at);
array_push($this->comments, $comment);
}
public function export() {
$ret = Array();
$ret['comments'] = $this->exportComments();
$ret['name'] = $this->name;
$ret['records'] = $this->exportRecords();
if ($this->changetype != 'DELETE') {
$ret['ttl'] = $this->ttl;
}
$ret['type'] = $this->type;
$ret['changetype'] = $this->changetype;
return $ret;
}
public function exportRecords() {
$ret = Array();
foreach ($this->records as $record) {
if ($this->type != "A" and $this->type != "AAAA") {
$record->setptr = FALSE;
}
array_push($ret, $record->export());
}
return $ret;
}
public function exportComments() {
$ret = Array();
foreach ($this->comments as $comment) {
array_push($ret, $comment->export());
}
return $ret;
}
}
class Record {
public $content;
public $disabled;
public $setptr;
public function __construct($content, $disabled = FALSE, $setptr = FALSE) {
$this->content = $content;
$this->disabled = $disabled;
$this->setptr = $setptr;
}
public function export() {
$ret;
$ret['content'] = $this->content;
$ret['disabled'] = ( bool ) $this->disabled;
if ($this->setptr) {
$ret['set-ptr'] = ( bool ) TRUE;
}
return $ret;
}
}
class Comment {
public $content;
public $account;
public $modified_at;
public function __construct($content, $account, $modified_at) {
$this->content = $content;
$this->account = $account;
$this->modified_at = $modified_at;
}
public function export() {
$ret;
$ret['content'] = $this->content;
$ret['account'] = $this->account;
$ret['modified_at'] = $this->modified_at;
return $ret;
}
}
?>

View file

@ -1,23 +1,18 @@
<?php <?php
$apiuser = ''; # The PowerDNS API username. Leave empty for authmethod='xapikey' (see AUTHENTICATION) $apipass = ''; # The PowerDNS API-key
$apipass = ''; # The PowerDNS API-user password or the PowerDNS-API key (see AUTHENTICATION)
$apiip = ''; # The IP of the PowerDNS API $apiip = ''; # The IP of the PowerDNS API
$apiport = '8081'; # The port of the PowerDNS API $apiport = '8081'; # The port of the PowerDNS API
$apivers = 0; # The version of the PowerDNS API. 0 == experimental, 1 = v1 (pdns 4.0)
$apisid = 'localhost'; # PowerDNS's :server_id
$apiproto = 'http'; # http | https $apiproto = 'http'; # http | https
$apisslverify = FALSE; # Verify SSL Certificate if using https for apiproto $apisslverify = FALSE; # Verify SSL Certificate if using https for apiproto
$allowzoneadd = FALSE; # Allow normal users to add zones $allowzoneadd = FALSE; # Allow normal users to add zones
$logging = TRUE;
$allowclearlogs = TRUE; # Allow clearing of log entries
$allowrotatelogs = FALSE;# Allow rotation to text file on server
# Log directory - if allowrotatelogs is set, this is where the logs will
### AUTHENTICATION ### # be written. It must be writeable by the web server user.
# The first versions of the PowerDNS API used the standard webserver password $logsdirectory = "../etc";
# for authentication, newer versions use an X-API-Key mechanism. NSEdit tries
# to autodetect the method you should use, but that does affect performance.
# For optimal performance, configure the right method.
# (Should be 'auto', 'xapikey' or 'userpass')
$authmethod = 'auto';
# If you configure this, nsedit will try to authenticate via WeFact too. # If you configure this, nsedit will try to authenticate via WeFact too.
# Debtors will be added to the sqlitedatabase with their crypted password. # Debtors will be added to the sqlitedatabase with their crypted password.
@ -29,8 +24,13 @@ $authmethod = 'auto';
#$adminapiips = array(); #$adminapiips = array();
#$adminapikey = 'thisshouldbequitealongstring,youknow'; #$adminapikey = 'thisshouldbequitealongstring,youknow';
# Location of user-database. Make sure its writeable and not served by the webserver!
$authdb = "../etc/pdns.users.sqlite3"; $authdb = "../etc/pdns.users.sqlite3";
# Admin login and password at first start-up
$default_admin_username = "admin";
$default_admin_password = "admin";
# Set a random generated secret to enable auto-login and long living csrf tokens # Set a random generated secret to enable auto-login and long living csrf tokens
// $secret = '...'; // $secret = '...';
@ -43,16 +43,24 @@ $templates[] = array(
array( array(
'name' => '', 'name' => '',
'type' => 'MX', 'type' => 'MX',
'content' => '200 mx2.tuxis.nl') 'content' => '200 mx2.tuxis.nl.'),
array(
'name' => '',
'type' => 'A',
'content' => '1.2.3.4'),
array(
'name' => 'www',
'type' => 'CNAME',
'content' => '[zonename]')
) )
); );
*/ */
$defaults['soa_edit'] = 'INCEPTION-INCREMENT'; $defaults['soa_edit'] = 'INCEPTION-INCREMENT';
$defaults['soa_edit_api'] = 'INCEPTION-INCREMENT'; $defaults['soa_edit_api'] = 'DEFAULT';
$defaults['defaulttype'] = 'Master'; # Choose between 'Native' or 'Master' $defaults['defaulttype'] = 'Master'; # Choose between 'Native' or 'Master'
$defaults['ns'][0] = 'unconfigured.primaryns'; # The value of the first NS-record $defaults['ns'][0] = 'unconfigured.primaryns.'; # The value of the first NS-record
$defaults['ns'][1] = 'unconfigured.secondaryns'; # The value of the second NS-record $defaults['ns'][1] = 'unconfigured.secondaryns.'; # The value of the second NS-record
$defaults['ttl'] = 3600; # Default TTL for records $defaults['ttl'] = 3600; # Default TTL for records
$defaults['disabled'] = false; # Default disabled state $defaults['disabled'] = false; # Default disabled state

View file

@ -4,21 +4,11 @@ include('config.inc.php');
$blocklogin = FALSE; $blocklogin = FALSE;
if ((!isset($apipass) or empty($apipass)) or (!isset($apiip) or empty($apiip)) or (!isset($apiport) or empty($apiport)) or (!isset($apivers) or is_null($apivers))) { if ((!isset($apipass) or empty($apipass)) or (!isset($apiip) or empty($apiip)) or (!isset($apiport) or empty($apiport))) {
$errormsg = 'You need to configure your settings for the PowerDNS API. See <a href="doc/apiconf.txt">doc/apiconf.txt</a>'; $errormsg = 'You need to configure your settings for the PowerDNS API. See <a href="doc/apiconf.txt">doc/apiconf.txt</a>';
$blocklogin = TRUE; $blocklogin = TRUE;
} }
if (!preg_match('/^[01]$/', $apivers)) {
$errormsg = "The value for \$apivers is incorrect your config";
$blocklogin = TRUE;
}
if (!isset($authmethod) or !preg_match('/^(xapikey|userpass|auto)$/', $authmethod)) {
$errormsg = "The value for \$authmethod is incorrect in your config";
$blocklogin = TRUE;
}
if (!isset($apiproto) or !preg_match('/^http(s)?$/', $apiproto)) { if (!isset($apiproto) or !preg_match('/^http(s)?$/', $apiproto)) {
$errormsg = "The value for \$apiproto is incorrect in your config. Did you configure it?"; $errormsg = "The value for \$apiproto is incorrect in your config. Did you configure it?";
$blocklogin = TRUE; $blocklogin = TRUE;
@ -45,18 +35,12 @@ if (isset($defaults['primaryns'])) {
} }
if (!isset($logo) or empty($logo)) { if (!isset($logo) or empty($logo)) {
$logo = 'http://www.tuxis.nl/uploads/images/nsedit.png'; $logo = 'https://www.tuxis.nl/uploads/images/nsedit.png';
} }
/* No need to change stuf below */ /* No need to change stuf below */
if ($apivers == 0) {
$apipath = "";
} elseif ($apivers == 1) {
$apipath = "/api/v1";
}
if (function_exists('curl_init') === FALSE) { if (function_exists('curl_init') === FALSE) {
$errormsg = "You need PHP Curl to run nsedit"; $errormsg = "You need PHP Curl to run nsedit";
$blocklogin = TRUE; $blocklogin = TRUE;
@ -75,13 +59,20 @@ if (function_exists('openssl_random_pseudo_bytes') === FALSE) {
$defaults['defaulttype'] = ucfirst(strtolower($defaults['defaulttype'])); $defaults['defaulttype'] = ucfirst(strtolower($defaults['defaulttype']));
try {
if (isset($authdb) && !file_exists($authdb) && class_exists('SQLite3')) { if (isset($authdb) && !file_exists($authdb) && class_exists('SQLite3')) {
is_dir(dirname($authdb)) || mkdir(dirname($authdb)); is_dir(dirname($authdb)) || mkdir(dirname($authdb));
$db = new SQLite3($authdb, SQLITE3_OPEN_CREATE|SQLITE3_OPEN_READWRITE); $db = new SQLite3($authdb, SQLITE3_OPEN_CREATE|SQLITE3_OPEN_READWRITE);
$createsql = file_get_contents('includes/scheme.sql'); $createsql = file_get_contents('includes/scheme.sql');
$db->exec($createsql); $db->exec($createsql);
$salt = bin2hex(openssl_random_pseudo_bytes(16)); $salt = bin2hex(openssl_random_pseudo_bytes(16));
$db->exec("INSERT INTO users (emailaddress, password, isadmin) VALUES ('admin', '".crypt("admin", '$6$'.$salt)."', 1)"); $default_admin_username = $default_admin_username ?? "admin";
$default_admin_password = $default_admin_password ?? "admin";
$db->exec("INSERT INTO users (emailaddress, password, isadmin) VALUES ('".$default_admin_username."', '".crypt($default_admin_password, '$6$'.$salt)."', 1)");
}
} catch (Exception $e) {
print("We have issues getting the authdb working: $e");
$blocklogin = TRUE;
} }
function string_starts_with($string, $prefix) function string_starts_with($string, $prefix)
@ -101,10 +92,12 @@ function string_ends_with($string, $suffix)
} }
function get_db() { function get_db() {
global $authdb; global $authdb, $db;
if (!isset($db)) {
$db = new SQLite3($authdb, SQLITE3_OPEN_READWRITE); $db = new SQLite3($authdb, SQLITE3_OPEN_READWRITE);
$db->exec('PRAGMA foreign_keys = 1'); $db->exec('PRAGMA foreign_keys = 1');
}
return $db; return $db;
} }
@ -126,7 +119,6 @@ function get_user_info($u) {
$q->bindValue(1, $u); $q->bindValue(1, $u);
$result = $q->execute(); $result = $q->execute();
$userinfo = $result->fetchArray(SQLITE3_ASSOC); $userinfo = $result->fetchArray(SQLITE3_ASSOC);
$db->close();
return $userinfo; return $userinfo;
} }
@ -141,7 +133,6 @@ function do_db_auth($u, $p) {
$q->bindValue(1, $u); $q->bindValue(1, $u);
$result = $q->execute(); $result = $q->execute();
$userinfo = $result->fetchArray(SQLITE3_ASSOC); $userinfo = $result->fetchArray(SQLITE3_ASSOC);
$db->close();
if ($userinfo and $userinfo['password'] and (crypt($p, $userinfo['password']) === $userinfo['password'])) { if ($userinfo and $userinfo['password'] and (crypt($p, $userinfo['password']) === $userinfo['password'])) {
return TRUE; return TRUE;
@ -165,12 +156,16 @@ function add_user($username, $isadmin = FALSE, $password = '') {
$q->bindValue(2, $password, SQLITE3_TEXT); $q->bindValue(2, $password, SQLITE3_TEXT);
$q->bindValue(3, (int)(bool) $isadmin, SQLITE3_INTEGER); $q->bindValue(3, (int)(bool) $isadmin, SQLITE3_INTEGER);
$ret = $q->execute(); $ret = $q->execute();
$db->close();
if ($isadmin) {
writelog("Added user $username as admin.");
} else {
writelog("Added user $username.");
}
return $ret; return $ret;
} }
function update_user($username, $isadmin, $password) { function update_user($id, $isadmin, $password) {
if ($password && !preg_match('/\$6\$/', $password)) { if ($password && !preg_match('/\$6\$/', $password)) {
$salt = bin2hex(openssl_random_pseudo_bytes(16)); $salt = bin2hex(openssl_random_pseudo_bytes(16));
$password = crypt($password, '$6$'.$salt); $password = crypt($password, '$6$'.$salt);
@ -178,30 +173,49 @@ function update_user($username, $isadmin, $password) {
$db = get_db(); $db = get_db();
$q = $db->prepare('SELECT * FROM users WHERE id = ?');
$q->bindValue(1, $id, SQLITE3_INTEGER);
$result = $q->execute();
$userinfo = $result->fetchArray(SQLITE3_ASSOC);
$q->close();
$username = $userinfo['emailaddress'];
if ($password) { if ($password) {
$q = $db->prepare('UPDATE users SET isadmin = ?, password = ? WHERE emailaddress = ?'); $q = $db->prepare('UPDATE users SET isadmin = ?, password = ? WHERE id = ?');
$q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER); $q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER);
$q->bindValue(2, $password, SQLITE3_TEXT); $q->bindValue(2, $password, SQLITE3_TEXT);
$q->bindValue(3, $username, SQLITE3_TEXT); $q->bindValue(3, $id, SQLITE3_INTEGER);
writelog("Updating password and/or settings for $username. Admin: ".(int)(bool)$isadmin);
} else { } else {
$q = $db->prepare('UPDATE users SET isadmin = ? WHERE emailaddress = ?'); $q = $db->prepare('UPDATE users SET isadmin = ? WHERE id = ?');
$q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER); $q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER);
$q->bindValue(2, $username, SQLITE3_TEXT); $q->bindValue(2, $id, SQLITE3_INTEGER);
writelog("Updating settings for $username. Admin: ".(int)(bool)$isadmin);
} }
$ret = $q->execute(); $ret = $q->execute();
$db->close();
return $ret; return $ret;
} }
function delete_user($id) { function delete_user($id) {
$db = get_db(); $db = get_db();
$q = $db->prepare('SELECT * FROM users WHERE id = ?');
$q->bindValue(1, $id, SQLITE3_INTEGER);
$result = $q->execute();
$userinfo = $result->fetchArray(SQLITE3_ASSOC);
$q->close();
if($userinfo) {
$q = $db->prepare('DELETE FROM users WHERE id = ?'); $q = $db->prepare('DELETE FROM users WHERE id = ?');
$q->bindValue(1, $id, SQLITE3_INTEGER); $q->bindValue(1, $id, SQLITE3_INTEGER);
$ret = $q->execute(); $ret = $q->execute();
$db->close();
writelog("Deleted user " . $userinfo['emailaddress'] . ".");
return $ret; return $ret;
} else {
return false;
}
} }
function valid_user($name) { function valid_user($name) {
@ -231,6 +245,8 @@ function jtable_respond($records, $method = 'multiple', $msg = 'Undefined errorm
$jTableResult['RecordCount'] = count($records); $jTableResult['RecordCount'] = count($records);
} }
$db = get_db();
$db->close();
header('Content-Type: application/json'); header('Content-Type: application/json');
print json_encode($jTableResult); print json_encode($jTableResult);
exit(0); exit(0);
@ -239,6 +255,27 @@ function jtable_respond($records, $method = 'multiple', $msg = 'Undefined errorm
function user_template_list() { function user_template_list() {
global $templates; global $templates;
if (is_dir("templates.d")) {
if ($templdir=opendir("templates.d")) {
while ($entry = readdir($templdir)) {
if (!str_ends_with($entry, ".json")) {
continue;
}
$f=file_get_contents("templates.d/$entry");
if ($f === false) {
error_log("Error reading file templates.d/$entry", 0);
continue;
}
$t = json_decode($f, true);
if ($t === null) {
error_log("Error decoding templates.d/$entry", 0);
continue;
}
array_push($templates, $t);
}
}
}
$templatelist = array(); $templatelist = array();
foreach ($templates as $template) { foreach ($templates as $template) {
if (is_adminuser() if (is_adminuser()
@ -258,7 +295,109 @@ function user_template_names() {
return $templatenames; return $templatenames;
} }
function getlogs() {
global $logging;
if ($logging !== TRUE)
return;
$db = get_db();
$r = $db->query('SELECT * FROM logs ORDER BY timestamp DESC');
$ret = array();
while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
array_push($ret, $row);
}
return $ret;
}
function clearlogs() {
global $logging;
if ($logging !== TRUE)
return;
$db = get_db();
$q = $db->query('DELETE FROM logs;');
writelog("Logtable truncated.");
}
function rotatelogs() {
global $logging, $logsdirectory;
if ($logging !== TRUE)
return FALSE;
if(!is_dir($logsdirectory) || !is_writable($logsdirectory)) {
writelog("Logs directory cannot be written to.");
return FALSE;
}
date_default_timezone_set('UTC');
$filename = date("Y-m-d-His") . ".json";
$file = fopen($logsdirectory . "/" . $filename, "x");
if($file === FALSE) {
writelog("Can't create file for log rotation.");
return FALSE;
}
if(fwrite($file,json_encode(getlogs())) === FALSE) {
writelog("Can't write to file for log rotation.");
fclose($file);
return FALSE;
} else {
fclose($file);
clearlogs();
return $filename;
}
}
function listrotatedlogs() {
global $logging, $logsdirectory;
if ($logging !== TRUE)
return FALSE;
$list = scandir($logsdirectory,SCANDIR_SORT_DESCENDING);
if($list === FALSE) {
writelog("Logs directory cannot read.");
return FALSE;
}
$list=array_filter($list,
function ($val) {
return(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$val) == 1);
}
);
return $list;
}
function writelog($line, $user=False) {
global $logging;
if ($logging !== TRUE)
return;
if ($user === False) {
$user = get_sess_user();
}
try {
$db = get_db();
$q = $db->prepare('CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
user TEXT NOT NULL,
log TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP);');
$ret = $q->execute();
$q = $db->prepare('INSERT INTO logs (user, log) VALUES (:user, :log)');
$q->bindValue(':user', $user, SQLITE3_TEXT);
$q->bindValue(':log', $line, SQLITE3_TEXT);
$q->execute();
} catch (Exception $e) {
return jtable_respond(null, 'error', $e->getMessage());
}
}
/* This function was taken from https://gist.github.com/rsky/5104756 to make /* This function was taken from https://gist.github.com/rsky/5104756 to make
it available on older php versions. Thanks! */ it available on older php versions. Thanks! */

View file

@ -9,11 +9,13 @@ global $current_user;
$current_user = false; $current_user = false;
// session startup // session startup
function _set_current_user($username, $is_admin = false, $has_csrf_token = false, $is_api = false) { function _set_current_user($username, $userid, $localauth = true, $is_admin = false, $has_csrf_token = false, $is_api = false) {
global $current_user; global $current_user;
$current_user = array( $current_user = array(
'username' => $username, 'username' => $username,
'id' => $userid,
'localauth' => $localauth,
'is_admin' => $is_admin, 'is_admin' => $is_admin,
'has_csrf_token' => $has_csrf_token, 'has_csrf_token' => $has_csrf_token,
'is_api' => $is_api, 'is_api' => $is_api,
@ -50,7 +52,7 @@ function _check_csrf_token($user) {
} }
define('CSRF_TOKEN', $csrf_token); define('CSRF_TOKEN', $csrf_token);
header("X-CSRF-Token: ${csrf_token}"); header("X-CSRF-Token: {$csrf_token}");
} }
function enc_secret($message) { function enc_secret($message) {
@ -120,13 +122,13 @@ function dec_secret($code) {
function _unset_cookie($name) { function _unset_cookie($name) {
$is_ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off'; $is_ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
setcookie($name, null, -1, null, null, $is_ssl); setcookie($name, "", -1, "", "", $is_ssl);
} }
function _store_auto_login($value) { function _store_auto_login($value) {
$is_ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off'; $is_ssl = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off';
// set for 30 days // set for 30 days
setcookie('NSEDIT_AUTOLOGIN', $value, time()+60*60*24*30, null, null, $is_ssl); setcookie('NSEDIT_AUTOLOGIN', $value, time()+60*60*24*30, "", "", $is_ssl);
} }
function try_login() { function try_login() {
@ -150,6 +152,7 @@ function _try_login($username, $password) {
global $wefactapiurl, $wefactapikey; global $wefactapiurl, $wefactapikey;
if (!valid_user($username)) { if (!valid_user($username)) {
writelog("Illegal username at login!", $username);
return false; return false;
} }
@ -158,6 +161,7 @@ function _try_login($username, $password) {
if (isset($wefactapiurl) && isset($wefactapikey)) { if (isset($wefactapiurl) && isset($wefactapikey)) {
$wefact = do_wefact_auth($username, $password); $wefact = do_wefact_auth($username, $password);
if (false === $wefact ) { if (false === $wefact ) {
writelog("Failed Wefact login!", $username);
return false; return false;
} }
if (-1 !== $wefact) { if (-1 !== $wefact) {
@ -166,14 +170,16 @@ function _try_login($username, $password) {
} }
if ($do_local_auth && !do_db_auth($username, $password)) { if ($do_local_auth && !do_db_auth($username, $password)) {
writelog("Failed login!", $username);
return false; return false;
} }
$user = get_user_info($username); $user = get_user_info($username);
if (!$user) { if (!$user) {
writelog("Failed to find user!", $username);
return false; return false;
} else { } else {
_set_current_user($username, (bool) $user['isadmin']); _set_current_user($username, $user['id'], (bool) $do_local_auth, (bool) $user['isadmin']);
if (session_id()) { if (session_id()) {
session_unset(); session_unset();
@ -183,6 +189,8 @@ function _try_login($username, $password) {
session_regenerate_id(true) or die('session failure: regenerated id failed'); session_regenerate_id(true) or die('session failure: regenerated id failed');
session_unset(); session_unset();
$_SESSION['username'] = $username; $_SESSION['username'] = $username;
$_SESSION['localauth'] = $do_local_auth;
$_SESSION['userid'] = $user['id'];
# requires session: # requires session:
_check_csrf_token($user); _check_csrf_token($user);
@ -202,7 +210,7 @@ function _check_session() {
and $_POST['adminapikey'] === $adminapikey) and $_POST['adminapikey'] === $adminapikey)
{ {
# Allow this request, fake that we're logged in as user. # Allow this request, fake that we're logged in as user.
return _set_current_user('admin', true, true, true); return _set_current_user('admin', 1, false, true, true, true);
} }
else else
{ {
@ -218,7 +226,7 @@ function _check_session() {
session_destroy(); session_destroy();
session_unset(); session_unset();
} else { } else {
_set_current_user($_SESSION['username'], (bool) $user['isadmin']); _set_current_user($_SESSION['username'], $_SESSION['userid'], (bool) $_SESSION['localauth'], (bool) $user['isadmin']);
_check_csrf_token($user); _check_csrf_token($user);
return; return;
} }
@ -277,6 +285,16 @@ function get_sess_user() {
return $current_user ? $current_user['username'] : null; return $current_user ? $current_user['username'] : null;
} }
function get_sess_userid() {
global $current_user;
return $current_user ? $current_user['id'] : null;
}
function has_local_auth() {
global $current_user;
return $current_user ? $current_user['localauth'] : null;
}
function logout() { function logout() {
@session_destroy(); @session_destroy();
@session_unset(); @session_unset();

696
index.php
View file

@ -6,6 +6,12 @@ include_once('includes/misc.inc.php');
global $errormsg, $blocklogin; global $errormsg, $blocklogin;
$docroot = $_SERVER['DOCUMENT_ROOT'];
if (preg_match("@$docroot@", $authdb) == 1) {
$blocklogin = TRUE;
$errormsg = "You authdb is in your document root and probably downloadable. Please move it to a safe location!";
}
if (isset($_GET['logout']) or isset($_POST['logout'])) { if (isset($_GET['logout']) or isset($_POST['logout'])) {
logout(); logout();
header("Location: index.php"); header("Location: index.php");
@ -20,7 +26,7 @@ if (!is_logged_in() and isset($_POST['formname']) and $_POST['formname'] === "lo
if (is_logged_in() and isset($_POST['formname']) and $_POST['formname'] === "changepwform") { if (is_logged_in() and isset($_POST['formname']) and $_POST['formname'] === "changepwform") {
if (get_sess_user() == $_POST['username']) { if (get_sess_user() == $_POST['username']) {
if (!update_user(get_sess_user(), is_adminuser(), $_POST['password'])) { if (!update_user(get_sess_userid(), is_adminuser(), $_POST['password'])) {
$errormsg = "Unable to update password!\n"; $errormsg = "Unable to update password!\n";
} }
} else { } else {
@ -113,6 +119,47 @@ if ($blocklogin === TRUE) {
<div id="wrap"> <div id="wrap">
<div id="dnssecinfo"> <div id="dnssecinfo">
</div> </div>
<div id="clearlogs" style="display: none;">
Are you sure you want to clear the current logs? Maybe download them
first<?php if($allowrotatelogs) { ?>, or use "Rotate logs" to save
them on the server<?php } ?>?
</div>
<div id="rotatelogs" style="display: none;">
Are you sure you want to rotate the current logs?
</div>
<div id="searchlogs" style="display: none; text-align: right;">
<table border="0">
<tr><td>User:</td><td><input type="text" id ="searchlogs-user"><br></td></tr>
<tr><td>Log Entry:</td><td><input type="text" id ="searchlogs-entry"></td></tr>
</table>
</div>
<div id="searchzone" style="display: none; text-align: right;">
<table border="0">
<tr><td>Label:</td><td><input type="text" id ="searchzone-label"><br></td></tr>
<tr><td>Type:</td><td style="text-align: left;"><select id="searchzone-type">
<option value=""></option>
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="CERT">CERT</option>
<option value="CNAME">CNAME</option>
<option value="ALIAS">ALIAS</option>
<option value="LOC">LOC</option>
<option value="MX">MX</option>
<option value="NAPTR">NAPTR</option>
<option value="NS">NS</option>
<option value="PTR">PTR</option>
<option value="SOA">SOA</option>
<option value="SPF">SPF</option>
<option value="SRV">SRV</option>
<option value="SSHFP">SSHFP</option>
<option value="TLSA">TLSA</option>
<option value="CAA">CAA</option>
<option value="TXT">TXT</option>
<option value="SMIMEA">SMIMEA</option>
</select><br></td></tr>
<tr><td>Content:</td><td><input type="text" id ="searchzone-content"></td></tr>
</table>
</div>
<div id="menu" class="jtable-main-container <?php if ($menutype === 'horizontal') { ?>horizontal<?php } ?>"> <div id="menu" class="jtable-main-container <?php if ($menutype === 'horizontal') { ?>horizontal<?php } ?>">
<div class="jtable-title menu-title"> <div class="jtable-title menu-title">
<div class="jtable-title-text"> <div class="jtable-title-text">
@ -123,6 +170,7 @@ if ($blocklogin === TRUE) {
<li><a href="#" id="zoneadmin">Zones</a></li> <li><a href="#" id="zoneadmin">Zones</a></li>
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
<li><a href="#" id="useradmin">Users</a></li> <li><a href="#" id="useradmin">Users</a></li>
<li><a href="#" id="logadmin">Logs</a></li>
<?php } ?> <?php } ?>
<li><a href="#" id="aboutme">About me</a></li> <li><a href="#" id="aboutme">About me</a></li>
<li><a href="index.php?logout=1">Logout</a></li> <li><a href="index.php?logout=1">Logout</a></li>
@ -133,8 +181,11 @@ if ($blocklogin === TRUE) {
} }
?> ?>
<div id="zones"> <div id="zones">
<?php if (is_adminuser() or $allowzoneadd === TRUE) { ?> <?php if ($allowzoneadd === TRUE) { ?>
<div style="visibility: hidden;" id="ImportZone"></div> <div style="display: none;" id="ImportZone"></div>
<?php } ?>
<?php if (is_adminuser()) { ?>
<div style="display: none;" id="CloneZone"></div>
<?php } ?> <?php } ?>
<div class="tables" id="MasterZones"> <div class="tables" id="MasterZones">
<div class="searchbar" id="searchbar"> <div class="searchbar" id="searchbar">
@ -147,8 +198,27 @@ if ($blocklogin === TRUE) {
<div id="users"> <div id="users">
<div class="tables" id="Users"></div> <div class="tables" id="Users"></div>
</div> </div>
<div id="logs">
<div class="tables" id="Logs"></div>
<?php if($allowrotatelogs) { ?>
<br>Log entries being viewed:
<select id="logfile">
<option value="">(Current logs)</option>
<?php
$logfiles=listrotatedlogs();
if($logfiles !== FALSE) {
foreach ($logfiles as $filename) {
echo '<option value="' . $filename . '">' . str_replace(".json","",$filename) . "</option>\n";
}
}
?></select>
<?php } else { ?>
<input type="hidden" id="logfile" value="">
<?php } ?>
</div>
<?php } ?> <?php } ?>
<?php if (has_local_auth()) { ?>
<div id="AboutMe"> <div id="AboutMe">
<div class="tables"> <div class="tables">
<p>Hi <?php echo get_sess_user(); ?>. You can change your password here.</p> <p>Hi <?php echo get_sess_user(); ?>. You can change your password here.</p>
@ -173,9 +243,11 @@ if ($blocklogin === TRUE) {
</tr> </tr>
</table> </table>
<input type="hidden" name="formname" value="changepwform"> <input type="hidden" name="formname" value="changepwform">
<input type="hidden" name="id" value="<?php echo get_sess_userid(); ?>">
</form> </form>
</div> </div>
</div> </div>
<?php } ?>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
window.csrf_token = '<?php echo CSRF_TOKEN ?>'; window.csrf_token = '<?php echo CSRF_TOKEN ?>';
@ -224,7 +296,7 @@ function displayDnssecIcon(zone) {
function displayExportIcon(zone) { function displayExportIcon(zone) {
var $img = $('<img class="list clickme" src="img/export.png" title="Export zone" />'); var $img = $('<img class="list clickme" src="img/export.png" title="Export zone" />');
$img.click(function () { $img.click(function () {
var $zexport = $.getJSON("zones.php?zone="+zone.record.name+"&action=export", function(data) { var $zexport = $.getJSON("zones.php?zoneid="+zone.record.id+"&action=export", function(data) {
blob = new Blob([data.Record.zone], { type: 'text/plain' }); blob = new Blob([data.Record.zone], { type: 'text/plain' });
var dl = document.createElement('a'); var dl = document.createElement('a');
dl.addEventListener('click', function(ev) { dl.addEventListener('click', function(ev) {
@ -251,7 +323,11 @@ function displayContent(fieldName, zone) {
var zspan = $('<span class="lightgrey">').text(zone); var zspan = $('<span class="lightgrey">').text(zone);
return lspan.add(zspan); return lspan.add(zspan);
} else { } else {
return $('<span>').text(data.record[fieldName]); var text = data.record[fieldName];
if (typeof data.record[fieldName] == 'boolean') {
text == false ? text = 'No' : text = 'Yes';
}
return $('<span>').text(text);
} }
} }
} }
@ -305,16 +381,16 @@ $(document).ready(function () {
listClass: 'dnssec' listClass: 'dnssec'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
width: '8%', width: '8%',
display: displayContent('owner'), display: displayContent('account'),
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner', inputClass: 'account',
listClass: 'owner' listClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -363,7 +439,7 @@ $(document).ready(function () {
title: 'Records in ' + zone.record.name, title: 'Records in ' + zone.record.name,
openChildAsAccordion: true, openChildAsAccordion: true,
actions: { actions: {
listAction: 'zones.php?action=listrecords&zoneurl=' + zone.record.url listAction: 'zones.php?action=listrecords&zoneid=' + zone.record.id
}, },
fields: { fields: {
name: { name: {
@ -428,17 +504,227 @@ $(document).ready(function () {
hoverAnimation: true, hoverAnimation: true,
hoverAnimationDuration: 60, hoverAnimationDuration: 60,
hoverAnimationEasing: undefined, hoverAnimationEasing: undefined,
items: [{ items: [
<?php if (is_adminuser() or $allowzoneadd === TRUE) { ?> <?php if ($allowzoneadd === TRUE) { ?>
{
icon: 'jtable/lib/themes/metro/add.png', icon: 'jtable/lib/themes/metro/add.png',
text: 'Import a new zone', text: 'Import a new zone',
click: function() { click: function() {
$('#ImportZone').jtable('showCreateForm'); $('#ImportZone').jtable('showCreateForm');
} }
},
<?php } ?> <?php } ?>
}], <?php if (is_adminuser()) { ?>
{
icon: 'jtable/lib/themes/metro/add.png',
text: 'Clone a zone',
click: function() {
$('#CloneZone').jtable('showCreateForm');
}
},
<?php } ?>
],
}, },
sorting: false, sorting: false,
selecting: true,
selectOnRowClick: true,
selectionChanged: function (data) {
var $selectedRows = $('#MasterZones').jtable('selectedRows');
$selectedRows.each(function () {
var zone = $(this).data('record');
$('#MasterZones').jtable('openChildTable',
$(this).closest('tr'), {
title: 'Records in ' + zone.name,
messages: {
addNewRecord: 'Add to ' + zone.name,
noDataAvailable: 'No records for ' + zone.name
},
toolbar: {
items: [
{
text: 'Search zone',
click: function() {
$("#searchzone").dialog({
modal: true,
title: "Search zone for ...",
width: 'auto',
buttons: {
Search: function() {
$( this ).dialog( 'close' );
opentable.find('.jtable-title-text').text(opentableTitle + " (filtered)");
opentable.jtable('load', {
label: $('#searchzone-label').val(),
type: $('#searchzone-type').val(),
content: $('#searchzone-content').val()
});
},
Reset: function() {
$('#searchzone-label').val('');
$('#searchzone-type').val('');
$('#searchzone-content').val('');
$( this ).dialog( 'close' );
opentable.find('.jtable-title-text').text(opentableTitle);
opentable.jtable('load');
return false;
}
}
});
}
}
],
},
paging: true,
sorting: true,
pageSize: 20,
openChildAsAccordion: true,
actions: {
listAction: 'zones.php?action=listrecords&zoneid=' + zone.id,
createAction: 'zones.php?action=createrecord&zoneid=' + zone.id,
deleteAction: 'zones.php?action=deleterecord&zoneid=' + zone.id,
updateAction: 'zones.php?action=editrecord&zoneid=' + zone.id
},
fields: {
domid: {
create: true,
type: 'hidden',
defaultValue: zone.id
},
id: {
key: true,
type: 'hidden',
create: false,
edit: false,
list: false
},
domain: {
create: true,
type: 'hidden',
defaultValue: zone.name
},
name: {
title: 'Label',
width: '7%',
sorting: true,
create: true,
display: displayContent('name', zone.name),
inputClass: 'name',
listClass: 'name'
},
type: {
title: 'Type',
width: '2%',
options: function() {
zonename = new String(zone.name);
if (zonename.match(/(\.in-addr|\.ip6)\.arpa/)) {
return {
'PTR': 'PTR',
'NS': 'NS',
'MX': 'MX',
'TXT': 'TXT',
'SOA': 'SOA',
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'ALIAS': 'ALIAS',
'LOC': 'LOC',
'NAPTR': 'NAPTR',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
'CAA': 'CAA',
'DNAME': 'DNAME',
'DS': 'DS',
'SMIMEA': 'SMIMEA'
};
}
return {
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'DNAME': 'DNAME',
'ALIAS': 'ALIAS',
'DS': 'DS',
'LOC': 'LOC',
'MX': 'MX',
'NAPTR': 'NAPTR',
'NS': 'NS',
'PTR': 'PTR',
'SOA': 'SOA',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
'CAA': 'CAA',
'TXT': 'TXT',
'SMIMEA': 'SMIMEA'
};
},
display: displayContent('type'),
create: true,
inputClass: 'type',
listClass: 'type'
},
content: {
title: 'Content',
width: '30%',
create: true,
sorting: true,
display: displayContent('content'),
inputClass: 'content',
listClass: 'content'
},
ttl: {
title: 'TTL',
width: '2%',
create: true,
sorting: false,
display: displayContent('ttl'),
defaultValue: '<?php echo $defaults['ttl']; ?>',
inputClass: 'ttl',
listClass: 'ttl'
},
setptr: {
title: 'Set PTR Record',
width: '2%',
list: false,
create: true,
defaultValue: 'false',
inputClass: 'setptr',
listClass: 'setptr',
options: function() {
return {
'0': 'No',
'1': 'Yes',
};
},
},
disabled: {
title: 'Disabled',
width: '2%',
create: true,
sorting: false,
display: displayContent('disabled'),
defaultValue: '<?php echo $defaults['disabled'] ? 'No' : 'Yes'; ?>',
inputClass: 'disabled',
listClass: 'disabled',
options: function() {
return {
'0': 'No',
'1': 'Yes',
};
},
},
}
}, function (data) {
opentable=data.childTable;
opentableTitle=opentable.find('.jtable-title-text').text();
data.childTable.jtable('load');
});
});
},
openChildAsAccordion: true, openChildAsAccordion: true,
actions: { actions: {
listAction: 'zones.php?action=list', listAction: 'zones.php?action=list',
@ -472,16 +758,16 @@ $(document).ready(function () {
listClass: 'dnssec' listClass: 'dnssec'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
width: '8%', width: '8%',
display: displayContent('owner'), display: displayContent('account'),
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner', inputClass: 'account',
listClass: 'owner' listClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -533,143 +819,6 @@ $(document).ready(function () {
inputClass: 'serial', inputClass: 'serial',
listClass: 'serial' listClass: 'serial'
}, },
records: {
width: '5%',
title: 'Records',
edit: false,
create: false,
display: function (zone) {
var $img = $('<img class="list" src="img/list.png" title="Records" />');
$img.click(function () {
$('#MasterZones').jtable('openChildTable',
$img.closest('tr'), {
title: 'Records in ' + zone.record.name,
messages: {
addNewRecord: 'Add to ' + zone.record.name,
noDataAvailable: 'No records for ' + zone.record.name
},
paging: true,
pageSize: 20,
openChildAsAccordion: true,
actions: {
listAction: 'zones.php?action=listrecords&zoneurl=' + zone.record.url,
createAction: 'zones.php?action=createrecord&zoneurl=' + zone.record.url,
deleteAction: 'zones.php?action=deleterecord&zoneurl=' + zone.record.url,
updateAction: 'zones.php?action=editrecord&zoneurl=' + zone.record.url
},
fields: {
domid: {
create: true,
type: 'hidden',
defaultValue: zone.record.id
},
id: {
key: true,
type: 'hidden',
create: false,
edit: false,
list: false
},
domain: {
create: true,
type: 'hidden',
defaultValue: zone.record.name
},
name: {
title: 'Label',
width: '7%',
create: true,
display: displayContent('name', zone.record.name),
inputClass: 'name',
listClass: 'name'
},
type: {
title: 'Type',
width: '2%',
options: function() {
zonename = new String(zone.record.name);
if (zonename.match(/(\.in-addr|\.ip6)\.arpa/)) {
return {
'PTR': 'PTR',
'NS': 'NS',
'MX': 'MX',
'TXT': 'TXT',
'SOA': 'SOA',
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'NAPTR': 'NAPTR',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
};
}
return {
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'MX': 'MX',
'NAPTR': 'NAPTR',
'NS': 'NS',
'PTR': 'PTR',
'SOA': 'SOA',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
'TXT': 'TXT',
};
},
display: displayContent('type'),
create: true,
inputClass: 'type',
listClass: 'type'
},
content: {
title: 'Content',
width: '30%',
create: true,
display: displayContent('content'),
inputClass: 'content',
listClass: 'content'
},
ttl: {
title: 'TTL',
width: '2%',
create: true,
display: displayContent('ttl'),
defaultValue: '<?php echo $defaults['ttl']; ?>',
inputClass: 'ttl',
listClass: 'ttl'
},
disabled: {
title: 'Disabled',
width: '2%',
create: true,
display: displayContent('disabled'),
defaultValue: '<?php echo $defaults['disabled'] ? 'false' : 'true'; ?>',
inputClass: 'disabled',
listClass: 'disabled',
options: function() {
return {
'0': 'false',
'1': 'true',
};
},
},
}
}, function (data) {
data.childTable.jtable('load');
})
});
return $img;
}
},
exportzone: { exportzone: {
title: '', title: '',
width: '1%', width: '1%',
@ -695,13 +844,13 @@ $(document).ready(function () {
inputClass: 'domain' inputClass: 'domain'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner' inputClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -721,7 +870,7 @@ $(document).ready(function () {
type: 'checkbox', type: 'checkbox',
values: {'0': 'No', '1': 'Yes'}, values: {'0': 'No', '1': 'Yes'},
defaultValue: 1, defaultValue: 1,
inputClass: 'overwrite_namerserver' inputClass: 'overwrite_nameserver'
}, },
nameserver: { nameserver: {
title: 'Nameservers', title: 'Nameservers',
@ -742,6 +891,50 @@ $(document).ready(function () {
} }
}); });
$('#CloneZone').jtable({
title: 'Clone zone',
actions: {
createAction: 'zones.php?action=clone'
},
fields: {
id: {
key: true,
type: 'hidden'
},
sourcename: {
title: 'Source domain',
options: function(data) {
return 'zones.php?action=formzonelist&e='+$epoch;
},
inputClass: 'sourcename'
},
destname: {
title: 'Domain',
inputClass: 'destname'
},
account: {
title: 'Account',
options: function(data) {
return 'users.php?action=listoptions&e='+$epoch;
},
defaultValue: 'admin',
inputClass: 'account'
},
kind: {
title: 'Type',
options: {'Native': 'Native', 'Master': 'Master'},
defaultValue: '<?php echo $defaults['defaulttype']; ?>',
edit: false,
inputClass: 'type'
},
},
recordAdded: function() {
$("#MasterZones").jtable('load');
$("#SlaveZones").jtable('load');
}
});
$('#domsearch').addClear({ $('#domsearch').addClear({
onClear: function() { $('#MasterZones').jtable('load'); } onClear: function() { $('#MasterZones').jtable('load'); }
}); });
@ -772,26 +965,41 @@ $(document).ready(function () {
}); });
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
$('#logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#aboutme').click(function () { $('#aboutme').click(function () {
$('#logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#MasterZones').hide(); $('#MasterZones').hide();
$('#SlaveZones').hide(); $('#SlaveZones').hide();
$('#AboutMe').show(); $('#AboutMe').show();
}); });
$('#useradmin').click(function () { $('#useradmin').click(function () {
$('#Users').show(); $('#logs').hide();
$('#MasterZones').hide(); $('#MasterZones').hide();
$('#SlaveZones').hide(); $('#SlaveZones').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#Users').jtable('load');
$('#Users').show();
}); });
$('#zoneadmin').click(function () { $('#zoneadmin').click(function () {
$('#logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#MasterZones').show(); $('#MasterZones').show();
$('#SlaveZones').show(); $('#SlaveZones').show();
}); });
$('#logadmin').click(function () {
$('#Users').hide();
$('#AboutMe').hide();
$('#MasterZones').hide();
$('#SlaveZones').hide();
$('#Logs').jtable('load', {
logfile: $('#logfile').val()
});
$('#logs').show();
});
$('#Users').jtable({ $('#Users').jtable({
title: 'Users', title: 'Users',
paging: true, paging: true,
@ -816,6 +1024,7 @@ $(document).ready(function () {
title: 'User', title: 'User',
display: displayContent('emailaddress'), display: displayContent('emailaddress'),
inputClass: 'emailaddress', inputClass: 'emailaddress',
edit: false,
listClass: 'emailaddress' listClass: 'emailaddress'
}, },
password: { password: {
@ -838,7 +1047,170 @@ $(document).ready(function () {
$("#SlaveZones").jtable('reload'); $("#SlaveZones").jtable('reload');
} }
}); });
$('#Users').jtable('load');
$('#Logs').jtable({
title: 'Logs',
paging: true,
pageSize: 20,
sorting: false,
actions: {
listAction: 'logs.php?action=list'
},
messages: {
deleteConfirmation: 'This entry will be deleted. Are you sure?'
},
toolbar: {
hoverAnimation: true,
hoverAnimationDuration: 60,
hoverAnimationEasing: undefined,
items: [
{
text: 'Search logs',
click: function() {
$("#searchlogs").dialog({
modal: true,
title: "Search logs for ...",
width: 'auto',
buttons: {
Search: function() {
$( this ).dialog( 'close' );
$('#Logs').find('.jtable-title-text').text('Logs (filtered)');
$('#Logs').jtable('load', {
logfile: $('#logfile').val(),
user: $('#searchlogs-user').val(),
entry: $('#searchlogs-entry').val()
});
},
Reset: function() {
$('#searchlogs-user').val('');
$('#searchlogs-entry').val('');
$( this ).dialog( 'close' );
$('#Logs').find('.jtable-title-text').text('Logs');
$('#Logs').jtable('load', {
logfile: $('#logfile').val()
});
return false;
}
}
});
}
},
<?php if($allowrotatelogs === TRUE) { ?>
{
text: 'Rotate logs',
click: function() {
$("#rotatelogs").dialog({
modal: true,
title: "Rotate logs",
width: 'auto',
buttons: {
Ok: function() {
$.get("logs.php?action=rotate");
$( this ).dialog( "close" );
$('#logfile').val('');
$('#Logs').jtable('load');
},
Cancel: function() {
$( this ).dialog( "close" );
return false;
}
}
});
}
},
<?php } ?>
<?php if($allowclearlogs === TRUE) { ?>
{
icon: 'img/delete_inverted.png',
text: 'Clear logs',
click: function() {
$("#clearlogs").dialog({
modal: true,
title: "Clear all logs",
width: 'auto',
buttons: {
Ok: function() {
$.get("logs.php?action=clear");
$( this ).dialog( "close" );
$('#logfile').val('');
$('#Logs').jtable('load');
},
Cancel: function() {
$( this ).dialog( "close" );
return false;
}
}
});
}
},
<?php } ?>
{
icon: 'img/export.png',
text: 'Download logs',
click: function () {
var $zexport = $.get("logs.php?action=export&logfile=" + $('#logfile').val(), function(data) {
console.log(data);
blob = new Blob([data], { type: 'text/plain' });
var dl = document.createElement('a');
dl.addEventListener('click', function(ev) {
dl.href = URL.createObjectURL(blob);
dl.download = $('#logfile').val() == "" ? 'nseditlogs.txt':$('#logfile').val() + ".txt";
}, false);
if (document.createEvent) {
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
dl.dispatchEvent(event);
}
});
}
}
],
},
fields: {
id: {
title: 'key',
key: true,
type: 'hidden'
},
user: {
title: 'User',
width: '10%',
display: displayContent('user'),
},
log: {
title: 'Log',
width: '80%',
display: displayContent('log'),
},
timestamp: {
title: 'Timestamp',
width: '10%',
display: displayContent('timestamp')
}
}
});
$('#logfile').change(function () {
$('#Logs').jtable('load', {
logfile: $('#logfile').val(),
user: $('#searchlogs-user').val(),
entry: $('#searchlogs-entry').val()
});
});
<?php } else { ?>
$('#AboutMe').hide();
$('#aboutme').click(function () {
$('#MasterZones').hide();
$('#SlaveZones').hide();
$('#AboutMe').show();
});
$('#zoneadmin').click(function () {
$('#AboutMe').hide();
$('#MasterZones').show();
$('#SlaveZones').show();
});
<?php } ?> <?php } ?>
$('#MasterZones').jtable('load'); $('#MasterZones').jtable('load');
$('#SlaveZones').jtable('load'); $('#SlaveZones').jtable('load');

96
logs.php Normal file
View file

@ -0,0 +1,96 @@
<?php
include_once('includes/config.inc.php');
include_once('includes/session.inc.php');
include_once('includes/misc.inc.php');
if (!is_csrf_safe()) {
header('Status: 403');
header('Location: ./index.php');
jtable_respond(null, 'error', "Authentication required");
}
if (!is_adminuser()) {
header('Status: 403');
jtable_respond(null, 'error', "You need adminprivileges to get here");
}
if (!isset($_GET['action'])) {
header('Status: 400');
jtable_respond(null, 'error', 'No action given');
}
if ($logging !== TRUE) {
jtable_respond(null, 'error', 'Logging is disabled');
} else {
switch ($_GET['action']) {
case "list":
if(!empty($_POST['logfile'])) {
if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_POST['logfile']) == 1) {
$entries=json_decode(file_get_contents($logsdirectory . "/" . $_POST['logfile']),true);
} else {
jtable_respond(null, 'error', "Can't find log file");
break;
}
} else {
$entries=getlogs();
}
if(!empty($_POST['user'])) {
$entries=array_filter($entries,
function ($val) {
return(stripos($val['user'], $_POST['user']) !== FALSE);
}
);
}
if(!empty($_POST['entry'])) {
$entries=array_filter($entries,
function ($val) {
return(stripos($val['log'], $_POST['entry']) !== FALSE);
}
);
}
jtable_respond($entries);
break;
case "export":
if(!empty($_GET['logfile'])) {
if(preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}\.json/',$_GET['logfile']) == 1) {
$entries=json_decode(file_get_contents($logsdirectory . "/" . $_GET['logfile']),true);
} else {
jtable_respond(null, 'error', "Can't find log file");
break;
}
} else {
$entries=getlogs();
}
if(defined('JSON_PRETTY_PRINT')) {
print json_encode($entries,JSON_PRETTY_PRINT);
} else {
print json_encode($entries);
}
break;
case "clear":
if($allowclearlogs === TRUE) {
clearlogs();
} else {
jtable_respond(null, 'error', 'Invalid action');
}
break;
case "rotate":
if($allowrotatelogs === TRUE) {
rotatelogs();
} else {
jtable_respond(null, 'error', 'Invalid action');
}
break;
default:
jtable_respond(null, 'error', 'Invalid action');
break;
}
}

16
rotate-logs.php Normal file
View file

@ -0,0 +1,16 @@
<?php
include_once('includes/config.inc.php');
include_once('includes/session.inc.php');
include_once('includes/misc.inc.php');
if(php_sapi_name() !== 'cli') {
echo "This script is intended to be run from the command line";
} else {
if($allowrotatelogs === TRUE) {
$current_user['username']='<system>';
rotatelogs();
} else {
echo "Rotating logs has been disabled."
}
}

View file

@ -0,0 +1,28 @@
{
"name": "Example Template",
"owner": "public",
"records": [
{
"name": "",
"type": "NS",
"content": "ns1.example.com.",
"label": "ns1"
},
{
"name": "",
"type": "NS",
"content": "ns2.example.com.",
"label": "ns2"
},
{
"name": "example-txt",
"type": "TXT",
"content": "This is an example txt record"
},
{
"name": "localhost",
"type": "A",
"content": "127.0.0.1"
}
]
}

View file

@ -64,20 +64,13 @@ case "create":
break; break;
case "update": case "update":
$id = isset($_POST['id']) ? intval($_POST['id']) : '';
$emailaddress = isset($_POST['emailaddress']) ? $_POST['emailaddress'] : ''; $emailaddress = isset($_POST['emailaddress']) ? $_POST['emailaddress'] : '';
$isadmin = isset($_POST['isadmin']) ? $_POST['isadmin'] : '0'; $isadmin = isset($_POST['isadmin']) ? $_POST['isadmin'] : '0';
$password = isset($_POST['password']) ? $_POST['password'] : ''; $password = isset($_POST['password']) ? $_POST['password'] : '';
if (!valid_user($emailaddress)) { if ($id != '' and update_user($id, $isadmin, $password)) {
jtable_respond(null, 'error', "Please only use ^[a-z0-9@_.-]+$ for usernames"); $result = array('isadmin' => $isadmin);
}
if (!user_exists($emailaddress)) {
jtable_respond(null, 'error', 'Cannot update not existing user');
}
if (update_user($emailaddress, $isadmin, $password)) {
$result = array('emailaddress' => $emailaddress, 'isadmin' => $isadmin);
jtable_respond($result, 'single'); jtable_respond($result, 'single');
} else { } else {
jtable_respond(null, 'error', 'Could not update user'); jtable_respond(null, 'error', 'Could not update user');
@ -85,7 +78,9 @@ case "update":
break; break;
case "delete": case "delete":
if (delete_user($_POST['id']) !== FALSE) { $id = isset($_POST['id']) ? intval($_POST['id']) : '';
if ($id != '' and delete_user($id) !== FALSE) {
jtable_respond(null, 'delete'); jtable_respond(null, 'delete');
} else { } else {
jtable_respond(null, 'error', 'Could not delete user'); jtable_respond(null, 'error', 'Could not delete user');

707
zones.php
View file

@ -3,6 +3,8 @@
include_once('includes/config.inc.php'); include_once('includes/config.inc.php');
include_once('includes/session.inc.php'); include_once('includes/session.inc.php');
include_once('includes/misc.inc.php'); include_once('includes/misc.inc.php');
include_once('includes/class/PdnsApi.php');
include_once('includes/class/Zone.php');
if (!is_csrf_safe()) { if (!is_csrf_safe()) {
header('Status: 403'); header('Status: 403');
@ -10,236 +12,18 @@ if (!is_csrf_safe()) {
jtable_respond(null, 'error', "Authentication required"); jtable_respond(null, 'error', "Authentication required");
} }
function api_request($path, $opts = null, $type = null) {
global $apiproto, $apisslverify, $apisid, $apiuser, $apipass, $apiip, $apiport, $authmethod, $apipath;
$url = "$apiproto://$apiip:$apiport${apipath}${path}"; $quoteus = array('TXT', 'SPF');
if ($authmethod == "auto") {
$ad = curl_init();
if ( strcasecmp( $apiproto, 'https' ) == 0 ) {
curl_setopt($ad, CURLOPT_SSL_VERIFYPEER, $apisslverify);
}
curl_setopt($ad, CURLOPT_HTTPHEADER, array('X-API-Key: '.$apipass));
curl_setopt($ad, CURLOPT_URL, "$apiproto://$apiip:$apiport/servers/localhost/statistics");
curl_setopt($ad, CURLOPT_RETURNTRANSFER, 1);
curl_exec($ad);
if (curl_getinfo($ad, CURLINFO_HTTP_CODE) == 401) {
$authmethod = 'userpass';
} else {
$authmethod = 'xapikey';
}
}
$headers = array();
array_push($headers, 'Accept: application/json');
$ch = curl_init();
if ($authmethod == "xapikey") {
array_push($headers, 'X-API-Key: '.$apipass);
} else {
curl_setopt($ch, CURLOPT_USERPWD, "$apiuser:$apipass");
}
if ( strcasecmp( $apiproto, 'https' ) == 0 ) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $apisslverify);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if ($opts) {
if (!$type) {
$type = 'POST';
}
$postdata = json_encode($opts);
array_push($headers, 'Content-Type: application/json');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
}
switch ($type) {
case 'DELETE':
case 'PATCH':
case 'PUT':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
break;
case 'POST':
break;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$return = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$json = json_decode($return, 1);
if (isset($json['error'])) {
jtable_respond(null, 'error', "API Error $code: ".$json['error']);
} elseif ($code < 200 || $code >= 300) {
if ($code == 401) {
$code = "Authentication failed. Have you configured your authmethod correct?";
}
jtable_respond(null, 'error', "API Error: $code");
}
return $json;
}
function zones_api_request($opts = null, $type = 'POST') {
global $apisid;
return api_request("/servers/${apisid}/zones", $opts, $type);
}
function get_all_zones() {
return zones_api_request();
}
function _get_zone_by_key($key, $value) {
if ($value !== '') {
foreach (get_all_zones() as $zone) {
if ($zone[$key] === $value) {
$zone['owner'] = get_zone_owner($zone['name'], 'admin');
if (!check_owner($zone)) {
jtable_respond(null, 'error', 'Access denied');
}
return $zone;
}
}
}
header('Status: 404 Not found');
jtable_respond(null, 'error', "Zone not found");
}
function get_zone_by_url($zoneurl) {
return _get_zone_by_key('url', $zoneurl);
}
function get_zone_by_id($zoneid) {
return _get_zone_by_key('id', $zoneid);
}
function get_zone_by_name($zonename) {
return _get_zone_by_key('name', $zonename);
}
/* This function is taken from: /* This function is taken from:
http://pageconfig.com/post/how-to-validate-ascii-text-in-php and got fixed by http://pageconfig.com/post/how-to-validate-ascii-text-in-php and got fixed by
#powerdns */ #powerdns */
function is_ascii($string) { function is_ascii($string) {
return ( bool ) ! preg_match( '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x80-\\xff]/' , $string ); return ( bool ) ! preg_match( '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x80-\\xff]/' , $string );
} }
function _valid_label($name) { function _valid_label($name) {
return is_ascii($name) && ( bool ) preg_match("/^([-.a-z0-9_\/\*]+)?$/i", $name ); return is_ascii($name) && ( bool ) preg_match("/^([-.a-z0-9_\/\*]+)?.$/i", $name );
}
function make_record($zone, $input) {
global $defaults;
$name = isset($input['name']) ? $input['name'] : '';
if ('' == $name) {
$name = $zone['name'];
} elseif (string_ends_with($name, '.')) {
# "absolute" name, shouldn't append zone[name] - but check.
$name = substr($name, 0, -1);
if (!string_ends_with($name, $zone['name'])) {
jtable_respond(null, 'error', "Name $name not in zone ".$zone['name']);
}
} else if (!string_ends_with($name, $zone['name'])) {
$name = $name . '.' . $zone['name'];
}
$ttl = (int) ((isset($input['ttl']) && $input['ttl']) ? $input['ttl'] : $defaults['ttl']);
$type = isset($input['type']) ? $input['type'] : '';
$disabled = (bool) (isset($input['disabled']) && $input['disabled']);
$content = isset($input['content']) ? $input['content'] : '';
if ($type === 'TXT') {
# empty TXT records are ok, otherwise require surrounding quotes: "..."
if (strlen($content) == 1 || substr($content, 0, 1) !== '"' || substr($content, -1) !== '"') {
# fix quoting: first escape all \, then all ", then surround with quotes.
$content = '"'.str_replace('"', '\\"', str_replace('\\', '\\\\', $content)).'"';
}
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
if (!$type) {
jtable_respond(null, 'error', "Require a type");
}
if (!is_ascii($content)) {
jtable_respond(null, 'error', "Please only use ASCII-characters in your fields");
}
return array(
'disabled' => $disabled,
'type' => $type,
'name' => $name,
'ttl' => $ttl,
'content' => $content);
}
function update_records($zone, $name_and_type, $inputs) {
# need one "record" to extract name and type, in case we have no inputs
# (deletion of all records)
$name_and_type = make_record($zone, $name_and_type);
$name = $name_and_type['name'];
$type = $name_and_type['type'];
$records = array();
foreach ($inputs as $input) {
$record = make_record($zone, $input);
if ($record['name'] !== $name || $record['type'] !== $type) {
jtable_respond(null, 'error', "Records not matching");
}
array_push($records, $record);
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
$patch = array(
'rrsets' => array(array(
'name' => $name,
'type' => $type,
'changetype' => count($records) ? 'REPLACE' : 'DELETE',
'records' => $records)));
api_request($zone['url'], $patch, 'PATCH');
}
function create_record($zone, $input) {
$record = make_record($zone, $input);
$records = get_records_by_name_type($zone, $record['name'], $record['type']);
array_push($records, $record);
$patch = array(
'rrsets' => array(array(
'name' => $record['name'],
'type' => $record['type'],
'changetype' => 'REPLACE',
'records' => $records)));
api_request($zone['url'], $patch, 'PATCH');
return $record;
}
function get_records_by_name_type($zone, $name, $type) {
$zone = api_request($zone['url']);
$records = array();
foreach ($zone['records'] as $record) {
if ($record['name'] == $name and $record['type'] == $type) {
array_push($records, $record);
}
}
return $records;
} }
function decode_record_id($id) { function decode_record_id($id) {
@ -255,39 +39,6 @@ function decode_record_id($id) {
return $record; return $record;
} }
# get all records with same name and type but different id (content)
# requires records with id to be present
# SOA records match always, regardless of content.
function get_records_except($zone, $exclude) {
$is_soa = ($exclude['type'] == 'SOA');
$found = false;
$zone = api_request($zone['url']);
$records = array();
foreach ($zone['records'] as $record) {
if ($record['name'] == $exclude['name'] and $record['type'] == $exclude['type']) {
if ($is_soa) {
# SOA changes all the time (serial); we can't match it in a sane way.
# OTOH we know it is unique anyway - just pretend we found a match.
$found = true;
} elseif ($record['content'] != $exclude['content']
or $record['ttl'] != $exclude['ttl']
or $record['disabled'] != $exclude['disabled']) {
array_push($records, $record);
} else {
$found = true;
}
}
}
if (!$found) {
header("Status: 404 Not Found");
jtable_respond(null, 'error', "Didn't find record with id");
}
return $records;
}
function compareName($a, $b) { function compareName($a, $b) {
$a = array_reverse(explode('.', $a)); $a = array_reverse(explode('.', $a));
$b = array_reverse(explode('.', $b)); $b = array_reverse(explode('.', $b));
@ -321,31 +72,48 @@ function rrtype_compare($a, $b) {
} }
} }
function record_compare($a, $b) { function record_compare_default($a, $b) {
if ($cmp = compareName($a['name'], $b['name'])) return $cmp; if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp; if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp; if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
return 0; return 0;
} }
function add_db_zone($zonename, $ownername) { function record_compare_name($a, $b) {
if (valid_user($ownername) === false) { return record_compare_default($a, $b);
jtable_respond(null, 'error', "$ownername is not a valid username"); }
function record_compare_type($a, $b) {
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
return 0;
}
function record_compare_content($a, $b) {
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
return 0;
}
function add_db_zone($zonename, $accountname) {
if (valid_user($accountname) === false) {
jtable_respond(null, 'error', "$accountname is not a valid username");
} }
if (!_valid_label($zonename)) { if (!_valid_label($zonename)) {
jtable_respond(null, 'error', "$zonename is not a valid zonename"); jtable_respond(null, 'error', "$zonename is not a valid zonename");
} }
if (is_apiuser() && !user_exists($ownername)) { if (is_apiuser() && !user_exists($accountname)) {
add_user($ownername); add_user($accountname);
} }
$db = get_db(); $db = get_db();
$q = $db->prepare("INSERT OR REPLACE INTO zones (zone, owner) VALUES (?, (SELECT id FROM users WHERE emailaddress = ?))"); $q = $db->prepare("INSERT OR REPLACE INTO zones (zone, owner) VALUES (?, (SELECT id FROM users WHERE emailaddress = ?))");
$q->bindValue(1, $zonename, SQLITE3_TEXT); $q->bindValue(1, $zonename, SQLITE3_TEXT);
$q->bindValue(2, $ownername, SQLITE3_TEXT); $q->bindValue(2, $accountname, SQLITE3_TEXT);
$q->execute(); $q->execute();
$db->close();
} }
function delete_db_zone($zonename) { function delete_db_zone($zonename) {
@ -356,10 +124,9 @@ function delete_db_zone($zonename) {
$q = $db->prepare("DELETE FROM zones WHERE zone = ?"); $q = $db->prepare("DELETE FROM zones WHERE zone = ?");
$q->bindValue(1, $zonename, SQLITE3_TEXT); $q->bindValue(1, $zonename, SQLITE3_TEXT);
$q->execute(); $q->execute();
$db->close();
} }
function get_zone_owner($zonename, $default) { function get_zone_account($zonename, $default) {
if (!_valid_label($zonename)) { if (!_valid_label($zonename)) {
jtable_respond(null, 'error', "$zonename is not a valid zonename"); jtable_respond(null, 'error', "$zonename is not a valid zonename");
} }
@ -368,7 +135,6 @@ function get_zone_owner($zonename, $default) {
$q->bindValue(1, $zonename, SQLITE3_TEXT); $q->bindValue(1, $zonename, SQLITE3_TEXT);
$result = $q->execute(); $result = $q->execute();
$zoneinfo = $result->fetchArray(SQLITE3_ASSOC); $zoneinfo = $result->fetchArray(SQLITE3_ASSOC);
$db->close();
if (isset($zoneinfo['emailaddress']) && $zoneinfo['emailaddress'] != null ) { if (isset($zoneinfo['emailaddress']) && $zoneinfo['emailaddress'] != null ) {
return $zoneinfo['emailaddress']; return $zoneinfo['emailaddress'];
} }
@ -376,28 +142,18 @@ function get_zone_owner($zonename, $default) {
return $default; return $default;
} }
function get_zone_keys($zone) { function quote_content($content) {
$ret = array(); # empty TXT records are ok, otherwise require surrounding quotes: "..."
foreach (api_request($zone['url'] . "/cryptokeys") as $key) { if (strlen($content) == 1 || substr($content, 0, 1) !== '"' || substr($content, -1) !== '"') {
if (!isset($key['active'])) # fix quoting: first escape all \, then all ", then surround with quotes.
continue; $content = '"'.str_replace('"', '\\"', str_replace('\\', '\\\\', $content)).'"';
$key['dstxt'] = $zone['name'] . ' IN DNSKEY '.$key['dnskey']."\n\n";
if (isset($key['ds'])) {
foreach ($key['ds'] as $ds) {
$key['dstxt'] .= $zone['name'] . ' IN DS '.$ds."\n";
}
unset($key['ds']);
}
$ret[] = $key;
} }
return $ret; return $content;
} }
function check_owner($zone) { function check_account($zone) {
return is_adminuser() or ($zone['owner'] === get_sess_user()); return is_adminuser() or ($zone->account === get_sess_user());
} }
if (isset($_GET['action'])) { if (isset($_GET['action'])) {
@ -406,34 +162,100 @@ if (isset($_GET['action'])) {
jtable_respond(null, 'error', 'No action given'); jtable_respond(null, 'error', 'No action given');
} }
try {
$api = new PdnsAPI;
switch ($action) { switch ($action) {
case "list": case "list":
case "listslaves": case "listslaves":
$return = array(); $return = Array();
$q = isset($_POST['domsearch']) ? $_POST['domsearch'] : false; $q = isset($_POST['domsearch']) ? $_POST['domsearch'] : false;
foreach (get_all_zones() as $zone) { foreach ($api->listzones($q) as $sresult) {
$zone['owner'] = get_zone_owner($zone['name'], 'admin'); $zone = new Zone();
if (!check_owner($zone)) $zone->parse($sresult);
continue; if ($zone->account == '') {
$zone->setAccount(get_zone_account($zone->name, 'admin'));
if ($q && !preg_match("/$q/", $zone['name'])) {
continue;
} }
if ($action == "listslaves" and $zone['kind'] == "Slave") { if (!check_account($zone))
array_push($return, $zone); continue;
} elseif ($action == "list" and $zone['kind'] != "Slave") {
if ($zone['dnssec']) { if ($action == "listslaves" and $zone->kind == "Slave") {
$zone['keyinfo'] = get_zone_keys($zone); array_push($return, $zone->export());
} elseif ($action == "list" and $zone->kind != "Slave") {
if ($zone->dnssec) {
$zone->setKeyinfo($api->getzonekeys($zone->id));
} }
array_push($return, $zone); array_push($return, $zone->export());
} }
} }
usort($return, "zone_compare"); usort($return, "zone_compare");
jtable_respond($return); jtable_respond($return);
break; break;
case "listrecords":
$zonedata = $api->loadzone($_GET['zoneid']);
$zone = new Zone();
$zone->parse($zonedata);
$records = $zone->rrsets2records();
if(!empty($_POST['label'])) {
$records=array_filter($records,
function ($val) {
return(stripos($val['name'], $_POST['label']) !== FALSE);
}
);
}
if(!empty($_POST['type'])) {
$records=array_filter($records,
function ($val) {
return($val['type'] == $_POST['type']);
}
);
}
if(!empty($_POST['content'])) {
$records=array_filter($records,
function ($val) {
return(stripos($val['content'], $_POST['content']) !== FALSE);
}
);
}
if (isset($_GET['jtSorting'])) {
list($scolumn, $sorder) = preg_split("/ /", $_GET['jtSorting']);
switch ($scolumn) {
case "type":
usort($records, "record_compare_type");
break;
case "content":
usort($records, "record_compare_content");
break;
default:
usort($records, "record_compare_name");
break;
}
if ($sorder == "DESC") {
$records = array_reverse($records);
}
} else {
usort($records, "record_compare_name");
}
jtable_respond($records);
break;
case "delete":
$zone = $api->loadzone($_POST['id']);
$api->deletezone($_POST['id']);
delete_db_zone($zone['name']);
writelog("Deleted zone ".$zone['name']);
jtable_respond(null, 'delete');
break;
case "create": case "create":
$zonename = isset($_POST['name']) ? $_POST['name'] : ''; $zonename = isset($_POST['name']) ? $_POST['name'] : '';
$zonekind = isset($_POST['kind']) ? $_POST['kind'] : ''; $zonekind = isset($_POST['kind']) ? $_POST['kind'] : '';
@ -449,55 +271,50 @@ case "create":
jtable_respond(null, 'error', "Not enough data"); jtable_respond(null, 'error', "Not enough data");
} }
$createOptions = array( $zone = new Zone();
'name' => $zonename, $zone->setKind($zonekind);
'kind' => $zonekind, $zone->setName($zonename);
);
$nameservers = array();
foreach($_POST['nameserver'] as $ns) {
if (isset($ns) && !empty($ns)) {
array_push($nameservers, $ns);
}
}
if ($zonekind != "Slave") { if ($zonekind != "Slave") {
$createOptions['nameservers'] = $nameservers; if (!isset($_POST['zone']) or isset($_POST['owns'])) {
if (!isset($_POST['zone'])) { foreach ($_POST['nameserver'] as $ns) {
if (0 == count($nameservers)) { $zone->addNameserver($ns);
jtable_respond(null, 'error', "Require nameservers");
} }
} else { } else {
$createOptions['zone'] = $_POST['zone']; $zone->importData($_POST['zone']);
} }
if (isset($defaults['soa_edit_api'])) { if (isset($defaults['soa_edit_api'])) {
$createOptions['soa_edit_api'] = $defaults['soa_edit_api']; $zone->setSoaEditApi($defaults['soa_edit_api'], True);
} }
if (isset($defaults['soa_edit'])) { if (isset($defaults['soa_edit'])) {
$createOptions['soa_edit'] = $defaults['soa_edit']; $zone->setSoaEdit($defaults['soa_edit']);
} }
} else { // Slave } else { // Slave
if (isset($_POST['masters'])) { if (isset($_POST['masters'])) {
$createOptions['masters'] = preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY); foreach (preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY) as $master) {
$zone->addMaster($master);
} }
if (0 == count($createOptions['masters'])) {
jtable_respond(null, 'error', "Slave requires master servers");
} }
} }
// only admin user and original owner can "recreate" zones that are already // only admin user and original account can "recreate" zones that are already
// present in our own db but got lost in pdns. // present in our own db but got lost in pdns.
if (!is_adminuser() && get_sess_user() !== get_zone_owner($zonename, get_sess_user())) { if (!is_adminuser() && get_sess_user() !== get_zone_account($zonename, get_sess_user())) {
jtable_respond(null, 'error', 'Zone already owned by someone else'); jtable_respond(null, 'error', 'Zone already owned by someone else');
} }
$zone = zones_api_request($createOptions); $api->savezone($zone->export());
$zonename = $zone['name'];
if (is_adminuser() && isset($_POST['owner'])) { $zone = new Zone();
add_db_zone($zonename, $_POST['owner']); $zone->parse($api->loadzone($zonename));
$zonename = $zone->name;
if (is_adminuser() && isset($_POST['account'])) {
add_db_zone($zonename, $_POST['account']);
$zone->setAccount($_POST['account']);
} else { } else {
add_db_zone($zonename, get_sess_user()); add_db_zone($zonename, get_sess_user());
$zone->setAccount(get_sess_user());
} }
if (isset($_POST['template']) && $_POST['template'] != 'None') { if (isset($_POST['template']) && $_POST['template'] != 'None') {
@ -505,137 +322,192 @@ case "create":
if ($template['name'] !== $_POST['template']) continue; if ($template['name'] !== $_POST['template']) continue;
foreach ($template['records'] as $record) { foreach ($template['records'] as $record) {
if ($record['type'] == 'NS' and array_search($record['content'], $nameservers) !== FALSE) { $rrset = $zone->getRRSet($record['name'], $record['type']);
continue; if ($rrset) {
$rrset->delete();
} }
if (isset($record['label'])) {
$record['name'] = $record['label'];
unset($record['label']);
} }
create_record($zone, $record); $api->savezone($zone->export());
foreach ($template['records'] as $record) {
if ($record['type'] == 'NS') continue;
$name = $record['name'] != '' ? join(Array($record['name'],'.',$zonename)) : $zonename;
$record['content'] = str_replace("[zonename]", $zonename, $record['content']);
$zone->addRecord($name, $record['type'], $record['content']);
} }
break; break;
} }
} }
if (isset($_POST['zone']) && isset($_POST['owns']) && $_POST['owns'] && count($nameservers)) { $zone = $api->savezone($zone->export());
$records = array(); writelog("Created zone ".$zone['name']);
foreach ($nameservers as $ns) {
array_push($records, array('type' => 'NS', 'content' => $ns));
}
update_records($zone, $records[0], $records);
}
unset($zone['records']);
unset($zone['comments']);
jtable_respond($zone, 'single'); jtable_respond($zone, 'single');
break; break;
case "update": case "update":
$zone = get_zone_by_id(isset($_POST['id']) ? $_POST['id'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_POST['id']));
if ($zone->setSoaEditApi($defaults['soa_edit_api']) != False)
writelog("Set SOA-EDIT-API to ".$defaults['soa_edit_api']." for ",$zone->name);
$zoneaccount = isset($_POST['account']) ? $_POST['account'] : $zone->account;
$zoneowner = isset($_POST['owner']) ? $_POST['owner'] : $zone['owner']; if ($zone->account !== $zoneaccount) {
if ($zone['owner'] !== $zoneowner) {
if (!is_adminuser()) { if (!is_adminuser()) {
header("Status: 403 Access denied"); header("Status: 403 Access denied");
jtable_respond(null, 'error', "Can't change owner"); jtable_respond(null, 'error', "Can't change account");
} else { } else {
add_db_zone($zone['name'], $zoneowner); add_db_zone($zone->name, $zoneaccount);
$zone['owner'] = $zoneowner; $zone->setAccount($zoneaccount);
} }
} }
$update = false;
if (isset($_POST['masters'])) { if (isset($_POST['masters'])) {
$zone['masters'] = preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY); $zone->eraseMasters();
$update = true; foreach(preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY) as $master) {
$zone->addMaster($master);
}
} }
if ($update) { writelog("Updated zone ".$zone->name);
$zoneUpdate = $zone; jtable_respond($api->savezone($zone->export()), 'single');
unset($zoneUpdate['id']);
unset($zoneUpdate['url']);
unset($zoneUpdate['owner']);
$newZone = api_request($zone['url'], $zoneUpdate, 'PUT');
$newZone['owner'] = $zone['owner'];
} else {
$newZone = $zone;
}
unset($newZone['records']);
unset($newZone['comments']);
jtable_respond($newZone, 'single');
break;
case "delete":
$zone = get_zone_by_id(isset($_POST['id']) ? $_POST['id'] : '');
api_request($zone['url'], array(), 'DELETE');
delete_db_zone($zone['name']);
jtable_respond(null, 'delete');
break;
case "listrecords":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : '');
$a = api_request($zone['url']);
$records = $a['records'];
foreach ($records as &$record) {
$record['id'] = json_encode($record);
}
unset($record);
usort($records, "record_compare");
jtable_respond($records);
break; break;
case "createrecord": case "createrecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$record = create_record($zone, $_POST); $zone->parse($api->loadzone($_GET['zoneid']));
if ($zone->setSoaEditApi($defaults['soa_edit_api']) != False)
writelog("Set SOA-EDIT-API to ".$defaults['soa_edit_api']." for ",$zone->name);
$record['id'] = json_encode($record); $name = isset($_POST['name']) ? $_POST['name'] : '';
$type = $_POST['type'];
$content = $_POST['content'];
if ('' == $name) {
$name = $zone->name;
} elseif (string_ends_with($name, '.')) {
# "absolute" name, shouldn't append zone[name] - but check.
if (!string_ends_with($name, $zone->name)) {
jtable_respond(null, 'error', "Name $name not in zone ".$zone->name);
}
} else if (!string_ends_with($name.'.', $zone->name)) {
$name = $name . '.' . $zone->name;
} else {
$name = $name.'.';
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
if (!$type) {
jtable_respond(null, 'error', "Require a type");
}
if (!is_ascii($content)) {
jtable_respond(null, 'error', "Please only use ASCII-characters in your fields");
}
if (array_search($type, $quoteus) !== FALSE) {
$content = quote_content($content);
}
$record = $zone->addRecord($name, $type, $content, $_POST['disabled'], $_POST['ttl'], $_POST['setptr']);
$api->savezone($zone->export());
writelog("Created record: ".$record['id']);
jtable_respond($record, 'single'); jtable_respond($record, 'single');
break; break;
case "editrecord": case "editrecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_GET['zoneid']));
if ($zone->setSoaEditApi($defaults['soa_edit_api']) != False)
writelog("Set SOA-EDIT-API to ".$defaults['soa_edit_api']." for ",$zone->name);
$old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : ''); $old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : '');
$records = get_records_except($zone, $old_record); $rrset = $zone->getRRSet($old_record['name'], $old_record['type']);
$rrset->deleteRecord($old_record['content']);
$record = make_record($zone, $_POST); $content = $_POST['content'];
$type = $_POST['type'];
if ($record['name'] !== $old_record['name'] || $record['type'] !== $old_record['type']) { if (array_search($type, $quoteus) !== FALSE) {
# rename or retype: $content = quote_content($content);
$newRecords = get_records_by_name_type($zone, $record['name'], $record['type']);
array_push($newRecords, $record);
update_records($zone, $old_record, $records); # remove from old list
update_records($zone, $record, $newRecords); # add to new list
} else {
array_push($records, $record);
update_records($zone, $record, $records);
} }
$record['id'] = json_encode($record); $zone->addRecord($_POST['name'], $_POST['type'], $content, $_POST['disabled'], $_POST['ttl'], $_POST['setptr']);
$api->savezone($zone->export());
$record = $zone->getRecord($_POST['name'], $_POST['type'], $content);
writelog("Updated record ".$_POST['id']." to ".$record['id']);
jtable_respond($record, 'single'); jtable_respond($record, 'single');
break; break;
case "deleterecord": case "deleterecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_GET['zoneid']));
if ($zone->setSoaEditApi($defaults['soa_edit_api']) != False)
writelog("Set SOA-EDIT-API to ".$defaults['soa_edit_api']." for ",$zone->name);
$old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : ''); $old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : '');
$rrset = $zone->getRRSet($old_record['name'], $old_record['type']);
$rrset->deleteRecord($old_record['content']);
$records = get_records_except($zone, $old_record); $api->savezone($zone->export());
update_records($zone, $old_record, $records); writelog("Deleted record ".$_POST['id']);
jtable_respond(null, 'delete'); jtable_respond(null, 'delete');
break; break;
case "export": case "export":
$zone = $_GET['zone']; writelog("Exported zone ".$_GET['zoneid']);
$export = api_request("/servers/${apisid}/zones/${zone}/export"); jtable_respond($api->exportzone($_GET['zoneid']), 'single');
break;
jtable_respond($export, 'single'); case "clone":
$name = $_POST['destname'];
$src = $_POST['sourcename'];
if (!string_ends_with($name, '.')) {
$name = $name.".";
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Invalid destination zonename");
}
$srczone = new Zone();
$srczone->parse($api->loadzone($src));
if ($srczone->setSoaEditApi($defaults['soa_edit_api']) != False)
writelog("Set SOA-EDIT-API to ".$defaults['soa_edit_api']." for ",$srczone->name);
$srczone->setId('');
$srczone->setName($name);
$srczone->setSerial('');
$srczone->setKind($_POST['kind']);
$zone = $api->savezone($srczone->export());
$srczone->parse($zone);
foreach ($srczone->rrsets as $rrset) {
$newname = $rrset->name;
$newname = preg_replace('/'.$src.'$/', $name, $newname);
$rrset->setName($newname);
}
if (is_adminuser() && isset($_POST['account'])) {
add_db_zone($name, $_POST['account']);
$srczone->setAccount($_POST['account']);
} else {
add_db_zone($name, get_sess_user());
$srczone->setAccount(get_sess_user());
}
$zone = $api->savezone($srczone->export());
writelog("Cloned zone $src into $name");
jtable_respond($zone, 'single');
break; break;
case "gettemplatenameservers": case "gettemplatenameservers":
@ -669,7 +541,26 @@ case "getformnameservers":
} }
} }
break; break;
case "formzonelist":
$zones = $api->listzones();
usort($zones, "zone_compare");
$ret = array();
foreach ($zones as $zone) {
if (!check_account($zone))
continue;
if ($zone['kind'] == 'Slave')
continue;
array_push($ret, array(
'DisplayText' => $zone['name'],
'Value' => $zone['id']));
}
jtable_respond($ret, 'options');
break;
default: default:
jtable_respond(null, 'error', 'No such action'); jtable_respond(null, 'error', 'No such action');
break; break;
} }
} catch (Exception $e) {
jtable_respond(null, 'error', $e->getMessage());
}